PHP Classes

File: client/mgmicserver-client.php

Recommend this page to a friend!
  Packages of Murilo Gomes Julio   MG Mic Server   client/mgmicserver-client.php   Download  
File: client/mgmicserver-client.php
Role: Auxiliary script
Content type: text/plain
Description: Application script
Class: MG Mic Server
Send sound captured by one computer to another
Author: By
Last change:
Date: 5 months ago
Size: 3,562 bytes
 

Contents

Class file image Download
<?php
# Copyright (C) 2025 Murilo Gomes Julio
# SPDX-License-Identifier: GPL-2.0-only

# Site: https://www.mugomes.com.br

error_reporting(E_ALL);

ini_set('max_execution_time', 300);
ini_set('memory_limit', '256M');

if (!
file_exists(__DIR__ . '/config.json')) {
   
file_put_contents(__DIR__ . '/config.json', json_encode([
       
'hostname' => '',
       
'port' => '5000',
       
'showError' => false
   
], JSON_PRETTY_PRINT));
    die(
'Adicione o nome da sua máquina (servidor) no arquivo config.json gerado!' . PHP_EOL);
}

$config = json_decode(file_get_contents(__DIR__ . '/config.json'), true);

if (empty(
$config['hostname'])) {
    die(
'Adicione o nome da sua máquina (servidor) no arquivo config.json gerado!' . PHP_EOL);
}

if (empty(
$config['showError'])) {
   
ini_set('error_log', __DIR__ . '/error_log');
}

$port = $config['port'];

// Obtém o IPv4
$avahi_cmd = "avahi-resolve-host-name -4 " . escapeshellarg(sprintf('%s.local', $config['hostname'])) . " 2>/dev/null";
$avahi_out = trim(shell_exec($avahi_cmd));
$server = false;
if (
$avahi_out) {
   
$avahi_parts = preg_split('/\s+/', $avahi_out);
   
$avahi_ip = end($avahi_parts);

    if (
filter_var($avahi_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
       
$server = $avahi_ip;
    }
}

if (!
$server) {
   
error_log('Não foi possível resolver o hostname!' . PHP_EOL);
    exit;
}

echo
'Criando sink virtual PulseAudio...' . PHP_EOL;
exec('pactl unload-module module-null-sink 2>/dev/null');
exec('pactl load-module module-null-sink sink_name=MGNetworkAudio sink_properties=device.description=MGNetwork_Audio');

sleep(1);

echo
'Conectando ao servidor...' . PHP_EOL;

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!
$sock) {
   
error_log(sprintf('Erro ao criar socket: %s', socket_strerror(socket_last_error())) . PHP_EOL);
    exit;
}

if (!@
socket_connect($sock, $server, $port)) {
   
error_log(sprintf('Erro ao conectar ao servidor: %s', socket_strerror(socket_last_error($sock))) . PHP_EOL);
    exit;
}

// Abre pacat para reprodução
echo 'Enviando áudio para o dispositivo virtual...' . PHP_EOL;

$descriptorspec = [
   
0 => ["pipe", "r"], // stdin
   
1 => ["pipe", "w"], // stdout
   
2 => ["pipe", "w"] // stderr
];

$process = proc_open(
   
'pacat --playback --raw --rate=44100 --channels=1 --format=s16le --device=MGNetworkAudio',
   
$descriptorspec,
   
$pipes
);

if (!
is_resource($process)) {
   
error_log('Erro ao iniciar pacat' . PHP_EOL);
    exit;
}

// Configura STDIN para modo raw e não ecoa teclas
shell_exec('stty cbreak -echo');
stream_set_blocking(STDIN, false);
stream_set_blocking($pipes[0], true);

echo
'Recebendo áudio... pressione \'q\' para encerrar.' . PHP_EOL;

$shouldStop = false;

try {
    while (!
$shouldStop) {
       
// Verifica tecla 'q'
       
$input = fread(STDIN, 1);
        if (
$input === 'q') {
            echo
PHP_EOL . 'Sinal de encerramento recebido.' . PHP_EOL;
           
$shouldStop = true;
            break;
        }

       
// Lê dados do servidor
       
$data = socket_read($sock, 4096);
        if (
$data === false || $data === '') {
            break;
        }

       
fwrite($pipes[0], $data);
       
usleep(1000); // evita 100% CPU
   
}
} catch (
Exception $e) {
    echo
'Fluxo de áudio finalizado.' . PHP_EOL;
}

// Encerra tudo
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
socket_close($sock);

exec('pactl unload-module module-null-sink 2>/dev/null');

// Restaura o terminal
shell_exec('stty sane');

echo
'Cliente encerrado.' . PHP_EOL;