Selkies
Developer Reference

__main__

Process entry point for the Selkies streaming server.

Builds the centralized stream server, registers the WebRTC and WebSockets services, switches to the configured mode, and runs the asyncio loop until a signal or fatal error unwinds it. Signal handling routes SIGTERM/SIGHUP through main-task cancellation so a service-manager stop tears down the same way Ctrl-C does. selkies --version prints the package version and exits.

attributelogger
= logging.getLogger(__name__)
funcwait_for_app_ready(ready_file, app_wait_ready=False) -> None

Wait for the streaming app's ready signal.

Returns immediately unless app_wait_ready is set, else polls until a sidecar creates ready_file.

Source Code
async def wait_for_app_ready(ready_file: str, app_wait_ready: bool = False) -> None:
    """Wait for the streaming app's ready signal.

    Returns immediately unless `app_wait_ready` is set, else polls until a
    sidecar creates `ready_file`.
    """
    if app_wait_ready:
        logger.info(f"Waiting for streaming app ready file: {ready_file}")
    while app_wait_ready and not os.path.exists(ready_file):
        await asyncio.sleep(0.2)
paramready_filestr
paramapp_wait_readybool
= False

Returns

None
func_install_shutdown_signal_handlers() -> None

Make a service-manager stop (systemd, docker stop, kill) unwind the same way Ctrl-C does: cancelling the main task raises CancelledError through the server loop, so the streaming service is stopped, the unix socket is removed and the disconnect hooks run. Without this SIGTERM is fatal by default, and as container PID 1 it is ignored outright until SIGKILL.

The first signal wins: later ones are absorbed while the teardown runs, since cancelling the main task again would raise CancelledError at an await inside the cleanup path and leave the rest of it (listener shutdown, unix-socket removal) undone. The handlers stay installed so an impatient orchestrator's repeat SIGTERM cannot fall through to the default fatal disposition either.

Source Code
def _install_shutdown_signal_handlers() -> None:
    """Make a service-manager stop (systemd, `docker stop`, `kill`) unwind the same
    way Ctrl-C does: cancelling the main task raises CancelledError through the
    server loop, so the streaming service is stopped, the unix socket is removed and
    the disconnect hooks run. Without this SIGTERM is fatal by default, and as
    container PID 1 it is ignored outright until SIGKILL.

    The first signal wins: later ones are absorbed while the teardown runs, since
    cancelling the main task again would raise CancelledError at an await inside
    the cleanup path and leave the rest of it (listener shutdown, unix-socket
    removal) undone. The handlers stay installed so an impatient orchestrator's
    repeat SIGTERM cannot fall through to the default fatal disposition either.
    """
    loop = asyncio.get_running_loop()
    main_task = asyncio.current_task()
    if main_task is None:
        return
    shutting_down = False

    def _request_shutdown(signal_name: str) -> None:
        nonlocal shutting_down
        if shutting_down:
            logger.info("Ignoring %s: shutdown already in progress", signal_name)
            return
        shutting_down = True
        logger.info("Received %s, shutting down", signal_name)
        main_task.cancel()

    for signal_name in ("SIGTERM", "SIGHUP"):
        sig = getattr(signal, signal_name, None)
        if sig is None:
            continue
        try:
            loop.add_signal_handler(sig, _request_shutdown, signal_name)
        except (NotImplementedError, RuntimeError, ValueError):
            logger.debug("Cannot install a %s handler on this platform", signal_name)

Returns

None
funcrun() -> None

Build the stream server, register its services, and run until cancelled.

Publishes the resolved gamepad and webcam socket directories to the environment first, so the LD_PRELOAD interposers in app processes (which read SELKIES_JS_SOCKET_PATH and SELKIES_WEBCAM_SOCKET_PATH) use the same directories selkies does however the settings were supplied. The virtual webcam outlives mode switches (applications hold /dev/videoN open across them), so it is released only when the server exits.

Source Code
async def run() -> None:
    """Build the stream server, register its services, and run until cancelled.

    Publishes the resolved gamepad and webcam socket directories to the
    environment first, so the LD_PRELOAD interposers in app processes (which
    read `SELKIES_JS_SOCKET_PATH` and `SELKIES_WEBCAM_SOCKET_PATH`) use the
    same directories selkies does however the settings were supplied. The
    virtual webcam outlives mode switches (applications hold `/dev/videoN`
    open across them), so it is released only when the server exits.
    """
    _install_shutdown_signal_handlers()

    os.environ["SELKIES_JS_SOCKET_PATH"] = settings.js_socket_path
    os.environ["SELKIES_WEBCAM_SOCKET_PATH"] = settings.webcam_socket_path

    if settings.computer_use_bind:
        try:
            from pixelflux import start_computer_use
            start_computer_use(settings.computer_use_bind)
        except Exception as e:
            logger.warning(f"Computer-Use server not started: {e}")

    await wait_for_app_ready(settings.app_ready_file, settings.app_wait_ready[0])

    server = CentralizedStreamServer(settings)

    server.register_service("webrtc", WebRTCService(server))
    server.register_service("websockets", DataStreamingServer(server))

    logger.info(f"Initiating server with {settings.mode} mode")
    await server.switch_to_mode(settings.mode)

    try:
        await server.run()
    finally:
        await stop_shared_webcam()

Returns

None
funcmain() -> None

Entry point for command-line execution.

Runs under uvloop when installed, else the stock loop: uvloop makes the whole loop (timers, callbacks, socket I/O) markedly faster, which lifts the pure-Python WebRTC SCTP data-channel throughput and keeps large transfers from stalling input. uvloop.run owns how its loop is installed per interpreter, so no event-loop policy API (removed in Python 3.16) is touched here.

Source Code
def main() -> None:
    """Entry point for command-line execution.

    Runs under uvloop when installed, else the stock loop: uvloop makes the
    whole loop (timers, callbacks, socket I/O) markedly faster, which lifts
    the pure-Python WebRTC SCTP data-channel throughput and keeps large
    transfers from stalling input. `uvloop.run` owns how its loop is
    installed per interpreter, so no event-loop policy API (removed in
    Python 3.16) is touched here.
    """
    try:
        import uvloop
        runner = uvloop.run
    except ImportError:
        runner = asyncio.run
    try:
        runner(run())
    except KeyboardInterrupt:
        logger.info("Server stopped by user")
    except asyncio.CancelledError:
        logger.info("Server stopped by signal")
    except Exception as e:
        logger.error(f"Error in main: {e}", exc_info=True)
        sys.exit(1)

Returns

None

On this page

No Headings
Edit on GitHub