diff --git a/dist/roverd b/dist/roverd
index 72484550..aebc4960 100755
Binary files a/dist/roverd and b/dist/roverd differ
diff --git a/dist/servoverifier b/dist/servoverifier
index 1f965abb..c79a79a9 100755
Binary files a/dist/servoverifier and b/dist/servoverifier differ
diff --git a/pi/bin/video-publisher.sh b/pi/bin/video-publisher.sh
index 6d3f4b2e..37387b63 100644
--- a/pi/bin/video-publisher.sh
+++ b/pi/bin/video-publisher.sh
@@ -38,6 +38,9 @@ else
AUDIO_RATE=8000
AUDIO_BITRATE=64000
fi
+
+# Disable audio streaming for now; only video will be published.
+AUDIO_ENABLE=0
# Flip the camera 180deg (supported by rpicam-vid/libcamera-vid)
FLIP_ARGS=(--rotation 180)
diff --git a/pi/install_roverd.sh b/pi/install_roverd.sh
index a3e3d1b2..3becfb3b 100755
--- a/pi/install_roverd.sh
+++ b/pi/install_roverd.sh
@@ -205,10 +205,7 @@ VIDEO_HEIGHT=720
VIDEO_FPS=30
VIDEO_BITRATE=3000000
AUDIO_ENABLE=1
-AUDIO_DEVICE=plughw:0,0
-AUDIO_RATE=48000
-AUDIO_CHANNELS=1
-AUDIO_BITRATE=24000
+AUDIO_ENABLE=0
ENV
chown roverd:roverd /var/lib/roverd/video.env
chmod 0640 /var/lib/roverd/video.env
diff --git a/pi/roverd/config.go b/pi/roverd/config.go
index 032f558f..0bfb44ff 100644
--- a/pi/roverd/config.go
+++ b/pi/roverd/config.go
@@ -141,7 +141,7 @@ func LoadConfig(path string) (*Config, error) {
NudgeDegrees: 2,
},
Audio: AudioConfig{
- CaptureEnabled: true,
+ CaptureEnabled: false,
CaptureDevice: "plughw:0,0",
SampleRate: 48000,
Channels: 1,
diff --git a/pi/roverd/media_env.go b/pi/roverd/media_env.go
index 556a1cf0..8aef312e 100644
--- a/pi/roverd/media_env.go
+++ b/pi/roverd/media_env.go
@@ -25,11 +25,8 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
fmt.Fprintf(&buf, "VIDEO_HEIGHT=%d\n", media.VideoHeight)
fmt.Fprintf(&buf, "VIDEO_FPS=%d\n", media.VideoFPS)
fmt.Fprintf(&buf, "VIDEO_BITRATE=%d\n", media.VideoBitrate)
- fmt.Fprintf(&buf, "AUDIO_ENABLE=%d\n", boolToInt(audio.CaptureEnabled))
- fmt.Fprintf(&buf, "AUDIO_DEVICE=%s\n", audio.CaptureDevice)
- fmt.Fprintf(&buf, "AUDIO_RATE=%d\n", audio.SampleRate)
- fmt.Fprintf(&buf, "AUDIO_CHANNELS=%d\n", audio.Channels)
- fmt.Fprintf(&buf, "AUDIO_BITRATE=%d\n", audio.Bitrate)
+ // Disable mic streaming; audio is handled locally for TTS only.
+ fmt.Fprintf(&buf, "AUDIO_ENABLE=0\n")
if err := os.WriteFile(publisherEnvPath, buf.Bytes(), 0o640); err != nil {
return err
}
diff --git a/pi/roverd/roverd.sample.yaml b/pi/roverd/roverd.sample.yaml
index 151e2e0d..c5341d3f 100644
--- a/pi/roverd/roverd.sample.yaml
+++ b/pi/roverd/roverd.sample.yaml
@@ -38,7 +38,7 @@ cameraServo:
nudgeDegrees: 2
allowRawPulse: false
audio:
- captureEnabled: true
+ captureEnabled: false
captureDevice: plughw:0,0
sampleRate: 48000
channels: 1
diff --git a/server/config.example.yaml b/server/config.example.yaml
index 1f9939aa..14977741 100644
--- a/server/config.example.yaml
+++ b/server/config.example.yaml
@@ -12,8 +12,6 @@ media:
# http:////whep
# Example: http://192.168.0.86:8889/video
whepBaseUrl: "http://192.168.0.86:8889/video"
- # Set audioBridge to false to disable server-side audio transcoding from -raw ->
- # audioBridge: true
homeAssistant:
url: "http://homeassistant.local:8123"
diff --git a/server/index.js b/server/index.js
index 439902bc..f2be3274 100644
--- a/server/index.js
+++ b/server/index.js
@@ -26,4 +26,3 @@ require('./src/services/sessionService');
require('./src/services/batteryManager');
require('./src/services/discordBotService');
require('./src/services/httpServer');
-require('./src/services/audioBridgeService');
diff --git a/server/src/services/audioBridgeService.js b/server/src/services/audioBridgeService.js
deleted file mode 100644
index 46af4720..00000000
--- a/server/src/services/audioBridgeService.js
+++ /dev/null
@@ -1,145 +0,0 @@
-const { spawn } = require('child_process');
-const logger = require('../globals/logger').child('audioBridge');
-const { loadConfig } = require('../helpers/configLoader');
-
-const config = loadConfig();
-
-const MEDIAMTX_API =
- process.env.MEDIAMTX_API || 'http://127.0.0.1:9997/v3/paths/list';
-const SRT_HOST = process.env.MEDIAMTX_SRT_HOST || '127.0.0.1';
-const POLL_MS = 5000;
-
-// Track active ffmpeg bridges keyed by raw path name (e.g., rover-raw).
-const bridges = new Map();
-
-function stopBridge(rawName) {
- const proc = bridges.get(rawName);
- if (!proc) return;
- bridges.delete(rawName);
- proc.kill('SIGTERM');
- logger.info('stopped bridge', { rawName });
-}
-
-function startBridge(rawName, baseName) {
- if (bridges.has(rawName)) return;
-
- // Use explicit credentials so mediamtx auth will allow the bridge to pull.
- const inputUrl = `srt://${SRT_HOST}:9000?streamid=#!::r=${rawName},m=request&mode=caller&transtype=live&latency=20&u=bridge&p=bridge`;
- const outputUrl = `srt://${SRT_HOST}:9000?streamid=#!::r=${baseName},m=publish&mode=caller&transtype=live&pkt_size=1316`;
-
- const args = [
- '-hide_banner',
- '-loglevel',
- 'warning',
- '-fflags',
- 'nobuffer',
- '-thread_queue_size',
- '512',
- '-i',
- inputUrl,
- '-map',
- '0:v',
- '-map',
- '0:a?',
- '-c:v',
- 'copy',
- '-c:a',
- 'libopus',
- '-b:a',
- '24000',
- '-ac',
- '1',
- '-ar',
- '16000',
- '-application',
- 'voip',
- '-frame_duration',
- '60',
- '-af',
- 'pan=1c|c0=c0,volume=12dB',
- '-flush_packets',
- '1',
- '-f',
- 'mpegts',
- outputUrl,
- ];
-
- const proc = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'pipe'] });
- bridges.set(rawName, proc);
- logger.info('started bridge', { rawName, baseName });
-
- proc.stdout.on('data', (data) => {
- logger.info(data.toString().trim());
- });
- proc.stderr.on('data', (data) => {
- logger.info(data.toString().trim());
- });
- proc.on('exit', (code, signal) => {
- bridges.delete(rawName);
- logger.info('bridge exited', { rawName, code, signal });
- });
-}
-
-async function fetchPaths() {
- try {
- const res = await fetch(MEDIAMTX_API, { signal: AbortSignal.timeout(3000) });
- if (!res.ok) {
- throw new Error(`mediamtx api ${res.status}`);
- }
- return res.json();
- } catch (err) {
- logger.warn('path fetch failed: %s', err.message);
- return null;
- }
-}
-
-function pickRawPairs(items) {
- const names = new Set(items.map((i) => i.name));
- return items
- .filter((i) => i.name.endsWith('-raw'))
- .map((raw) => {
- const baseName = raw.name.slice(0, -4);
- return { rawName: raw.name, baseName };
- })
- .filter(({ baseName }) => !names.has(baseName));
-}
-
-async function reconcile() {
- const data = await fetchPaths();
- if (!data?.items) {
- // On failure, stop all bridges so we don't run blind.
- Array.from(bridges.keys()).forEach(stopBridge);
- return;
- }
-
- const activeRaw = new Set();
- pickRawPairs(data.items).forEach(({ rawName, baseName }) => {
- activeRaw.add(rawName);
- startBridge(rawName, baseName);
- });
-
- // Stop bridges whose raw path disappeared.
- Array.from(bridges.keys()).forEach((rawName) => {
- if (!activeRaw.has(rawName)) {
- stopBridge(rawName);
- }
- });
-}
-
-function start() {
- // Only enable when explicit media config allows it, or by default.
- const enabled = config.media?.audioBridge !== false;
- if (!enabled) {
- logger.info('audio bridge disabled by config');
- return;
- }
- logger.info('audio bridge enabled; watching for *-raw streams');
- reconcile();
- setInterval(reconcile, POLL_MS);
-}
-
-start();
-
-module.exports = {
- start,
-};
diff --git a/server/src/services/videoAuthService.js b/server/src/services/videoAuthService.js
index 54a22587..ca9ca4ba 100644
--- a/server/src/services/videoAuthService.js
+++ b/server/src/services/videoAuthService.js
@@ -71,23 +71,9 @@ function canView(socket) {
app.post('/mediamtx/auth', (req, res) => {
const body = req.body || {};
const path = (body.path || '').replace(/^\//, '');
- const password = body.pass || body.password || '';
- const action = body.action || '';
const sessionId = body.user;
const streamInfo = extractStreamInfo(path);
- // Allow internal bridge (server-side audio transcode) to pull/push without a websocket session.
- if (body.user === 'bridge' && password === 'bridge') {
- return res.status(200).end();
- }
- // Also allow localhost reads of *-raw paths for the bridge even if creds are missing.
- if (
- streamInfo?.id?.endsWith('-raw') &&
- (req.ip === '127.0.0.1' || req.ip === '::1')
- ) {
- return res.status(200).end();
- }
-
logger.info('video auth request', { path: body.path, sessionId, stream: streamInfo });
if (!sessionId || !streamInfo?.id) {