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: 11 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;