The clip embed (clips.twitch.tv/embed) always ends up muted with the “click to unmute” overlay, even when passing autoplay=true&muted=false in an environment that explicitly allows unmuted autoplay (an OBS Studio browser source, or Chrome started with --autoplay-policy=no-user-gesture-required).
Under the exact same environment and flags, both live streams and VODs start with audio without any issues (e.g. player.twitch.tv/?video=2887917739 or a live channel embed). It only happens with the clip player.
This is not the browser autoplay policy discussed in How to unmute clips on load? (the browser allows audio here, see below), nor the mute/muted parameter confusion from #973.
How to reproduce
Start Chrome with unmuted autoplay allowed and a separate user data directory so it doesn’t join an existing browser session: chrome.exe --autoplay-policy=no-user-gesture-required --user-data-dir="%TEMP%\twitch-test"
(or just add an OBS Studio browser source pointing to the page below)
Serve a test page on http://localhost with the standard clip embed:
Result: the clip plays, but it’s muted and displays the unmute overlay.
Expected behavior
The clip plays with sound, since muted=false is set and the environment allows unmuted autoplay, the same way the VOD and live embeds do in this setup.
Clip embed a few seconds after load: “Click to unmute”, muted icon, 360p.
Additional context or questions
The rejection doesn’t show up in the DevTools console, because the player catches it. To see it, I wrapped HTMLMediaElement.prototype.play and the muted/src setters inside the embed iframe before the player loads (a script injected on new document through the Chrome DevTools Protocol). This is the log from a clean run, with times relative to the first event:
+ 473 ms muted = false
+ 520 ms src = .../landscape/avc/720/index.mp4
+ 560 ms play() (muted = false)
+ 563 ms src = .../landscape/avc/360/index.mp4
+ 564 ms play() rejected: AbortError: The play() request was interrupted by a new load request.
+ 564 ms muted = true
+ 564 ms play() (muted = true)
+ 879 ms play() resolved (muted = true)
What seems to be happening is a race condition during the initial quality switch:
The player sets video.muted = false and calls play().
It starts loading the 720p rendition (.../landscape/avc/720/index.mp4), but 3 ms after play() it switches video.src to a lower rendition (360p in my test; the clip also ends up at 480p in OBS).
Because the src changes while the first play() call is still pending, the promise rejects with: AbortError: The play() request was interrupted by a new load request.
The player appears to treat this rejection like an autoplay permission error (NotAllowedError). It falls back to video.muted = true, calls play() again (which succeeds), and displays the unmute overlay.
To verify that the browser wasn’t the one blocking audio, I ran document.querySelector('video').muted = false followed by .play() in the iframe’s context in that same environment once the clip had loaded (DevTools console with the clips.twitch.tv frame selected): play() resolves with the video unmuted and no errors. So the browser allows audio; the fallback seems to be triggered only because the player’s own rendition switch aborts the first play() promise. The VOD and live embeds don’t seem to reload video.src right after calling play(), which would explain why they don’t hit this.
Why this matters: browser sources in OBS used for overlays, alerts or shoutout widgets run live on stream. Streamers would have to open OBS’s “Interact” window and click on every clip while live, so clips in overlays are effectively silent.
Possible fixes:
Don’t treat an AbortError as an autoplay block (e.g. only fall back to muted playback on NotAllowedError).
Or pick the target rendition before the first play() call, so src doesn’t get swapped mid-flight.
Yes, it’s the same report as [GitHub issue link]. I cross-posted it here for visibility, sorry for the duplicate.
Clip downloads don’t fit this case: it’s a shoutout (!so) overlay that plays clips from other channels, so the streamer is neither the broadcaster nor an editor of the clip’s channel.
I tested it in OBS 33.0.0 beta 4 as you suggested, and the result is the same: the clip still starts muted with the “click to unmute” overlay. It also reproduces in desktop Chrome 154.0.8037.92 with --autoplay-policy=no-user-gesture-required, so it doesn’t look CEF-specific.
The player itself seems to trigger it: after play() with muted=false, the ABR switches the source (720p → 360p), that new load aborts the pending play() with AbortError, and the player then treats that rejection as an autoplay block and falls back to muted=true. The fix would be to only fall back to muted on NotAllowedError, not on AbortError. Full timeline and repro steps are in the original post.