138 lines
3.6 KiB
PHP
138 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Blax\WebRtc\Media\Rust;
|
|
|
|
/**
|
|
* JSON-lines client for the Rust SFU sidecar's Unix control socket.
|
|
*
|
|
* One JSON object per line. Requests carry an `id` the reply echoes; the
|
|
* sidecar also PUSHES notices (`{"event":"peer_left",...}`) onto every control
|
|
* connection — those are buffered while waiting for a reply and retrievable
|
|
* via drainEvents().
|
|
*/
|
|
class ControlClient
|
|
{
|
|
/** @var resource|null */
|
|
private $stream = null;
|
|
|
|
private int $nextId = 1;
|
|
|
|
/** @var list<array<string,mixed>> */
|
|
private array $events = [];
|
|
|
|
public function __construct(
|
|
private readonly string $socketPath,
|
|
private readonly float $timeout = 5.0,
|
|
) {}
|
|
|
|
/**
|
|
* Build a client on an already-open stream (tests use a socket pair).
|
|
*
|
|
* @param resource $stream
|
|
*/
|
|
public static function fromStream($stream): self
|
|
{
|
|
$client = new self('');
|
|
$client->stream = $stream;
|
|
|
|
return $client;
|
|
}
|
|
|
|
/**
|
|
* Send one command and wait for its reply. Throws on transport errors and
|
|
* on `ok:false` replies.
|
|
*
|
|
* @param array<string,mixed> $params
|
|
* @return array<string,mixed> the decoded reply (includes `ok` => true)
|
|
*/
|
|
public function request(string $cmd, array $params = []): array
|
|
{
|
|
$id = $this->nextId++;
|
|
$stream = $this->stream();
|
|
|
|
$json = json_encode(['id' => $id, 'cmd' => $cmd] + $params, JSON_UNESCAPED_SLASHES);
|
|
if (@fwrite($stream, $json."\n") === false) {
|
|
$this->close();
|
|
throw new SidecarException("sidecar write failed ({$cmd})");
|
|
}
|
|
|
|
while (true) {
|
|
$line = @fgets($stream);
|
|
if ($line === false) {
|
|
$this->close();
|
|
throw new SidecarException("sidecar closed or timed out awaiting the {$cmd} reply");
|
|
}
|
|
|
|
$reply = json_decode(trim($line), true);
|
|
if (! is_array($reply)) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($reply['event'])) {
|
|
$this->events[] = $reply;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (($reply['id'] ?? null) !== $id) {
|
|
continue;
|
|
}
|
|
|
|
if (($reply['ok'] ?? false) !== true) {
|
|
throw new SidecarException((string) ($reply['error'] ?? "sidecar error ({$cmd})"));
|
|
}
|
|
|
|
return $reply;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notices (peer_connected / peer_left / …) received while waiting for
|
|
* replies. Clears the buffer.
|
|
*
|
|
* @return list<array<string,mixed>>
|
|
*/
|
|
public function drainEvents(): array
|
|
{
|
|
[$events, $this->events] = [$this->events, []];
|
|
|
|
return $events;
|
|
}
|
|
|
|
public function close(): void
|
|
{
|
|
if (is_resource($this->stream)) {
|
|
@fclose($this->stream);
|
|
}
|
|
$this->stream = null;
|
|
}
|
|
|
|
/** @return resource */
|
|
private function stream()
|
|
{
|
|
if (is_resource($this->stream)) {
|
|
return $this->stream;
|
|
}
|
|
|
|
$stream = @stream_socket_client(
|
|
'unix://'.$this->socketPath,
|
|
$errno,
|
|
$error,
|
|
$this->timeout,
|
|
);
|
|
|
|
if ($stream === false) {
|
|
throw new SidecarException(
|
|
"cannot connect to the SFU sidecar at {$this->socketPath}: ".($error ?: "errno {$errno}")
|
|
);
|
|
}
|
|
|
|
$seconds = (int) floor($this->timeout);
|
|
stream_set_timeout($stream, $seconds, (int) (($this->timeout - $seconds) * 1_000_000));
|
|
|
|
return $this->stream = $stream;
|
|
}
|
|
}
|