fix: try VP9/AV1 too, not just H264, and fail loudly if none available

A real test with the H264-preference fix still negotiated VP8 —
RTCRtpSender.getCapabilities('video') apparently didn't list H264 on
that browser/machine (Chrome's H264 encoder is a separate downloadable
component, not guaranteed present). Confirmed via mediamtx's live path
list: still 'tracks: ["VP8"]' after the fix was deployed.

Now tries every MediaMTX-HLS-supported codec in priority order (H264,
VP9, AV1) instead of only H264, and throws a clear error instead of
silently falling back to VP8 if literally none of them are available —
better than a stream that looks live but can never produce working
HLS.
This commit is contained in:
2026-08-11 11:04:44 +00:00
parent b1493d6792
commit 9ca1cb457d
+29 -13
View File
@@ -10,24 +10,40 @@ export async function publishWhip(
bearer: string, bearer: string,
stream: MediaStream, stream: MediaStream,
): Promise<WhipSession> { ): Promise<WhipSession> {
// MediaMTX's HLS output only muxes AV1, VP9, H265, H264, Opus, MPEG-4
// Audio, or KLV (confirmed live in its logs: "the stream doesn't contain
// any supported codec" — the muxer gets created then immediately
// destroyed, so the WHIP publish itself still succeeds and the stream
// shows as live, but hls/live/<id>/index.m3u8 permanently 404s with
// "muxer is waiting to be created"). Browsers default to VP8 for
// getUserMedia/getDisplayMedia video, which isn't in that list at all.
// Reordering codec preference to H264 first didn't actually change what
// got negotiated on a real test (RTCRtpSender.getCapabilities('video')
// apparently didn't list H264 on that browser/machine — Chrome's H264
// encoder is a separate downloadable component and isn't guaranteed
// present) — so try every MediaMTX-supported codec in priority order
// instead of only H264, and fail loudly if literally none of them are
// available rather than silently falling back to the broken default.
const MEDIAMTX_HLS_VIDEO_CODECS = ['video/H264', 'video/VP9', 'video/AV1'];
const pc = new RTCPeerConnection(); const pc = new RTCPeerConnection();
for (const track of stream.getTracks()) { for (const track of stream.getTracks()) {
const transceiver = 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/<id>/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') { if (track.kind === 'video' && typeof transceiver.setCodecPreferences === 'function') {
const capabilities = RTCRtpSender.getCapabilities('video'); const capabilities = RTCRtpSender.getCapabilities('video');
const h264 = capabilities?.codecs.filter((c) => c.mimeType.toLowerCase() === 'video/h264') ?? []; const available = capabilities?.codecs ?? [];
const rest = capabilities?.codecs.filter((c) => c.mimeType.toLowerCase() !== 'video/h264') ?? []; const preferred = MEDIAMTX_HLS_VIDEO_CODECS.flatMap((mime) =>
if (h264.length > 0) transceiver.setCodecPreferences([...h264, ...rest]); available.filter((c) => c.mimeType.toLowerCase() === mime.toLowerCase()),
);
if (preferred.length === 0) {
pc.close();
throw new Error(
"This browser doesn't support any video codec MediaMTX can turn into HLS " +
`(needs one of: ${MEDIAMTX_HLS_VIDEO_CODECS.join(', ')}). Try a different browser, or use OBS instead.`,
);
}
const rest = available.filter((c) => !preferred.includes(c));
transceiver.setCodecPreferences([...preferred, ...rest]);
} }
} }