125 lines
6.3 KiB
Bash
Executable File
125 lines
6.3 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ── Clean up stale sockets from previous run ──────────────────────────────────
|
|
rm -f /tmp/.X99-lock
|
|
rm -f /tmp/.X11-unix/X99
|
|
rm -rf /tmp/weston-runtime
|
|
|
|
# ── XDG runtime dir (required by Weston and PipeWire) ────────────────────────
|
|
export XDG_RUNTIME_DIR=/tmp/weston-runtime
|
|
mkdir -p "$XDG_RUNTIME_DIR"
|
|
chmod 700 "$XDG_RUNTIME_DIR"
|
|
|
|
# ── D-Bus session bus (required by PipeWire / wireplumber) ───────────────────
|
|
eval $(dbus-launch --sh-syntax)
|
|
export DBUS_SESSION_BUS_ADDRESS
|
|
|
|
# ── Wine virtual desktop matching Weston size ────────────────────────────────
|
|
wine reg add "HKCU\\Software\\Wine\\Explorer" /v "Desktop" /t REG_SZ /d "Default" /f
|
|
wine reg add "HKCU\\Software\\Wine\\Explorer\\Desktops" /v "Default" /t REG_SZ /d "800x600" /f
|
|
wine reg add "HKCU\\Software\\Valve\\Source\\cstrike\\Settings" /v ScreenWidth /t REG_DWORD /d 800 /f
|
|
wine reg add "HKCU\\Software\\Valve\\Source\\cstrike\\Settings" /v ScreenHeight /t REG_DWORD /d 600 /f
|
|
wine reg add "HKCU\\Software\\Wine\\WineDbg" /v ShowCrashDialog /t REG_DWORD /d 0 /f
|
|
export DXVK_CUSTOM_DISPLAY_MODES="800x600@60"
|
|
|
|
# ── Start Weston (headless Wayland compositor) ────────────────────────────────
|
|
weston --backend=headless-backend.so --width=800 --height=600 --no-config &
|
|
sleep 2
|
|
|
|
export WAYLAND_DISPLAY=$(ls "$XDG_RUNTIME_DIR/" | grep -o 'wayland-[0-9]*' | head -1)
|
|
echo "Using WAYLAND_DISPLAY=$WAYLAND_DISPLAY"
|
|
|
|
# ── Start Xwayland (X11 compatibility layer over Wayland) ────────────────────
|
|
# -geometry fixes the virtual framebuffer at exactly 800x600 from the start,
|
|
# matching Weston's headless output — no xrandr mode-adding needed.
|
|
Xwayland :99 -glamor off -shm -geometry 800x600 &
|
|
|
|
for i in $(seq 1 20); do
|
|
DISPLAY=:99 xdpyinfo > /dev/null 2>&1 && echo "Xwayland ready" && break
|
|
echo "Waiting for Xwayland... attempt $i"
|
|
sleep 1
|
|
done
|
|
export DISPLAY=:99
|
|
echo "xrandr result: $(DISPLAY=:99 xrandr | grep '*')"
|
|
|
|
# ── Start PipeWire stack ──────────────────────────────────────────────────────
|
|
export PULSE_RUNTIME_PATH="$XDG_RUNTIME_DIR/pulse"
|
|
|
|
pipewire &
|
|
sleep 1
|
|
wireplumber &
|
|
sleep 1
|
|
# Remove the pulse socket directory that pipewire may have pre-created, then
|
|
# immediately recreate it empty and start pipewire-pulse with no gap in
|
|
# between — otherwise pipewire can recreate it first and pipewire-pulse
|
|
# fails with "File exists" on the socket.
|
|
rm -rf "$PULSE_RUNTIME_PATH"
|
|
mkdir -p "$PULSE_RUNTIME_PATH"
|
|
pipewire-pulse &
|
|
|
|
# Wait for the PulseAudio-compatible socket
|
|
for i in $(seq 1 30); do
|
|
pactl info > /dev/null 2>&1 && echo "PipeWire-Pulse ready" && break
|
|
echo "Waiting for PipeWire-Pulse... attempt $i"
|
|
sleep 1
|
|
done
|
|
|
|
export PULSE_SERVER="unix:${PULSE_RUNTIME_PATH}/native"
|
|
|
|
# Raise the PipeWire scheduling quantum (buffer size). This container runs
|
|
# llvmpipe software rendering + vp8enc + opusenc all competing for CPU at
|
|
# ~800% on 8 cores. A larger quantum gives the audio graph more buffer
|
|
# headroom to absorb brief scheduling jitter, trading a few ms of latency
|
|
# (irrelevant for one-way streaming) for fewer audible underruns/crackling.
|
|
pw-metadata -n settings 0 clock.force-quantum 4096 || true
|
|
|
|
# ── Audio sinks ───────────────────────────────────────────────────────────────
|
|
# game_output: CSS's own audio output (game sounds, music) — set as CSS's
|
|
# default playback sink.
|
|
# mic_sink: browser mic input ONLY — pacat writes here, nothing else.
|
|
#
|
|
# Without this split, CSS's own audio output was also landing in mic_sink
|
|
# (the only sink that existed), which fed back into virtual_mic — CSS's own
|
|
# game audio was drowning out / interfering with the actual browser mic
|
|
# signal, so other players never heard a clean voice transmission.
|
|
pactl load-module module-null-sink sink_name=game_output sink_properties=device.description=game_output || true
|
|
pactl load-module module-null-sink sink_name=mic_sink sink_properties=device.description=mic_sink rate=48000 channels=1 channel_map=mono || true
|
|
pactl load-module module-remap-source source_name=virtual_mic master=mic_sink.monitor || true
|
|
|
|
pactl set-default-sink game_output || true
|
|
pactl set-default-source virtual_mic || true
|
|
|
|
# ── Launch CSS ────────────────────────────────────────────────────────────────
|
|
wine ~/Counter-Strike\ Source/revLoader.exe &
|
|
|
|
for i in $(seq 1 60); do
|
|
WIN=$(DISPLAY=:99 xdotool search --name "" 2>/dev/null | head -1)
|
|
[ -n "$WIN" ] && echo "CSS window found: $WIN" && break
|
|
echo "Waiting for CSS window... attempt $i"
|
|
sleep 1
|
|
done
|
|
sleep 3
|
|
|
|
# gst_agent.py targets game_output explicitly via pipewiresrc target-object,
|
|
# so no extra env var is needed here for game audio capture.
|
|
|
|
# ── Mic audio: socat → pacat (on-demand, per TCP connection) ─────────────────
|
|
# signaling-server.js connects a TCP socket to port 12345 only while the user
|
|
# holds PTT. socat spawns a fresh pacat per connection and kills it when the
|
|
# connection closes — pacat only runs while actively transmitting, which also
|
|
# keeps mic_sink idle (not RUNNING) the rest of the time.
|
|
socat TCP-LISTEN:12345,reuseaddr,fork \
|
|
EXEC:"pacat --playback --device=mic_sink --format=s16le --rate=48000 --channels=1 --latency-msec=20" &
|
|
echo "socat mic listener started on TCP :12345"
|
|
|
|
# ── Start streaming agent (with auto-restart) ─────────────────────────────────
|
|
# gst_agent exits with code 1 on ICE failure or other fatal errors.
|
|
# Without a restart loop, the container sits dead until manually restarted.
|
|
# 3 second delay before each restart to avoid a tight crash loop.
|
|
while true; do
|
|
python3 ~/streaming-agent/gst_agent.py
|
|
echo "[gst_agent] exited with code $?, restarting in 3s..."
|
|
sleep 3
|
|
done
|