From b1493d6792e415d27eec073374e2b0620c7878e9 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Tue, 11 Aug 2026 10:31:52 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20prefer=20H264=20for=20browser=20(WHIP)?= =?UTF-8?q?=20publishing=20=E2=80=94=20VP8=20breaks=20HLS=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed live in mediamtx logs: the HLS muxer for a browser-published stream gets created then immediately destroyed — 'the stream doesn't contain any supported codec, which are currently AV1, VP9, H265, H264, Opus, MPEG-4 Audio, KLV'. Chrome's getUserMedia()/getDisplayMedia() default video codec for WebRTC is VP8, which isn't in that list. The WHIP publish itself succeeds (stream correctly shows live, mediamtx's own API confirms bytes arriving) so this was easy to miss — only hls/live//index.m3u8 silently 404s with 'muxer is waiting to be created' forever. Fixed with RTCRtpTransceiver.setCodecPreferences(), reordering the video codec list so H264 is offered first — falls through safely if unavailable. Matches what OBS already sends over RTMP, so recording and HLS both stay on the one already-tested codec instead of gaining a second, broken one. --- frontend/src/lib/whip.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/whip.ts b/frontend/src/lib/whip.ts index 0203843..a32905f 100644 --- a/frontend/src/lib/whip.ts +++ b/frontend/src/lib/whip.ts @@ -12,7 +12,23 @@ export async function publishWhip( ): Promise { const pc = new RTCPeerConnection(); for (const track of stream.getTracks()) { - pc.addTransceiver(track, { direction: 'sendonly' }); + const transceiver = pc.addTransceiver(track, { direction: 'sendonly' }); + // Browsers default to VP8 for getUserMedia/getDisplayMedia video, which + // MediaMTX's HLS output can't mux at all (confirmed live: "the stream + // doesn't contain any supported codec, which are currently AV1, VP9, + // H265, H264, Opus, MPEG-4 Audio, KLV" — the muxer gets created then + // immediately destroyed, so the WHIP publish itself succeeds and the + // stream shows as live, but hls/live//index.m3u8 permanently 404s + // with "muxer is waiting to be created"). Reorder codec preference so + // H264 is offered first — matches what OBS already sends over RTMP, so + // this keeps a single well-tested codec through the whole pipeline + // (HLS, recording) instead of introducing a second one. + if (track.kind === 'video' && typeof transceiver.setCodecPreferences === 'function') { + const capabilities = RTCRtpSender.getCapabilities('video'); + const h264 = capabilities?.codecs.filter((c) => c.mimeType.toLowerCase() === 'video/h264') ?? []; + const rest = capabilities?.codecs.filter((c) => c.mimeType.toLowerCase() !== 'video/h264') ?? []; + if (h264.length > 0) transceiver.setCodecPreferences([...h264, ...rest]); + } } const offer = await pc.createOffer();