hls.js icon indicating copy to clipboard operation
hls.js copied to clipboard

Airplay streaming issue on ios

Open tvinko opened this issue 1 year ago • 16 comments

What version of Hls.js are you using?

v1.5.11

What browser (including version) are you using?

Safari 17.5.1

What OS (including version) are you using?

IOS

Test stream

https://hlsjs.video-dev.org/demo/?src=https%3A%2F%2Fvz-b1d0f3b4-139.flufflecdn.net%2Fb13f6bba-728f-4fd3-88b4-ad8eb276976b%2Fplaylist.m3u8&demoConfig=eyJlbmFibGVTdHJlYW1pbmciOnRydWUsImF1dG9SZWNvdmVyRXJyb3IiOnRydWUsInN0b3BPblN0YWxsIjpmYWxzZSwiZHVtcGZNUDQiOmZhbHNlLCJsZXZlbENhcHBpbmciOi0xLCJsaW1pdE1ldHJpY3MiOi0xfQ==

Configuration

{
    debug: true,
    enableWorker: true,
    lowLatencyMode: true,
    backBufferLength: 90,
    preferManagedMediaSource: false,
}

Additional player setup steps

No response

Checklist

  • [X] The issue observed is not already reported by searching on Github under https://github.com/video-dev/hls.js/issues
  • [X] The issue occurs in the stable client (latest release) on https://hlsjs.video-dev.org/demo and not just on my page
  • [X] The issue occurs in the latest client (main branch) on https://hlsjs-dev.video-dev.org/demo and not just on my page
  • [X] The stream has correct Access-Control-Allow-Origin headers (CORS)
  • [X] There are no network errors such as 404s in the browser console when trying to play the stream

Steps to reproduce

  1. Host the following html snippet locally
<!DOCTYPE html>
<html>

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.11/hls.min.js"
        integrity="sha512-Z0W3S5swMoBWjkNBP3ACbXA4mh/jdjJOTOOZfF1yIO0OdkIU373s/Tm+eaCbUQq7EEgKl9O1eji1ZgseHCUVgw=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body id="body">
    <video id="video" controls x-webkit-airplay="allow"></video>

    <script>
        var useHls = true;
        var autoplay = false;

        var urlPlaylistUrl =
            "https://vz-b1d0f3b4-139.flufflecdn.net/b13f6bba-728f-4fd3-88b4-ad8eb276976b/playlist.m3u8";

        var video = document.getElementById("video");

        video.controls = true;
        video.loop = true;
        video.muted = true;
        video.autoplay = autoplay;
        video.src = urlPlaylistUrl;

        if (useHls) {
            var hls = new Hls({
                debug: true,
                enableWorker: true,
                lowLatencyMode: true,
                backBufferLength: 90,
                preferManagedMediaSource: false,
            });

            hls.on(Hls.Events.MEDIA_ATTACHED, function () {
                console.log("video and hls.js are now bound together !");
            });

            hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
                console.log(
                    "manifest loaded, found " + data.levels.length + " quality level"
                );
            });
            hls.loadSource(urlPlaylistUrl);

            hls.attachMedia(video);

            var airPlaySrc = document.createElement("source");
            airPlaySrc.src = urlPlaylistUrl;
            video.appendChild(airPlaySrc);
            video.disableRemotePlayback = false;
        }
    </script>
</body>

</html>
  1. Connect with iPhone client
  2. Select Airplay option
  3. Select streaming destination

Expected behaviour

Airplay should be streamed to selected destination

What actually happened?

The Airplay is not streamed with a given configuration.

However, I can stream if I enable autoplay:

var useHls = true;
var autoplay = true;

or if I use native hls:

var useHls = false;
var autoplay = false;

Console output

[Log] [log] > – "Debug logs enabled for \"Hls instance\" in hls.js version 1.5.11" (hls.min.js, line 1)
[Log] [log] > – "stopLoad" (hls.min.js, line 1)
[Log] [log] > – "loadSource:https://vz-b1d0f3b4-139.flufflecdn.net/b13f6bba-728f-4fd3-88b4-ad8eb276976b/playlist.m3u8" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Trigger BUFFER_RESET" (hls.min.js, line 1)
[Log] [log] > – "attachMedia" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "created media source: ManagedMediaSource" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "Media source opened" (hls.min.js, line 1)
[Log] video and hls.js are now bound together ! (test2.html, line 38)
[Log] [log] > – "[level-controller]:" – "manifest loaded, 5 level(s) found, first bitrate: 1419909" (hls.min.js, line 1)
[Log] [log] > – "setting initial bwe to 1419909" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "1 bufferCodec event(s) expected" (hls.min.js, line 1)
[Log] manifest loaded, found 5 quality level (test2.html, line 42)
[Log] [log] > – "startLoad(-1)" (hls.min.js, line 1)
[Log] [log] > – "[abr] picked start tier {\"codecSet\":\"avc1,mp4a\",\"videoRanges\":[\"SDR\"],\"preferHDR\":false,\"minFramerate\":0,\"minBitrate\":1073607}" (hls.min.js, line 1)
[Info] [info] > – "[abr] switch candidate:1->1 adjustedbw(1419909)-bitrate=0 ttfb:0.1 avgDuration:0.0 maxFetchDuration:4.0 fetchDuration:0.…" (hls.min.js, line 1)
"[abr] switch candidate:1->1 adjustedbw(1419909)-bitrate=0 ttfb:0.1 avgDuration:0.0 maxFetchDuration:4.0 fetchDuration:0.1 firstSelection:true codecSet:avc1,mp4a videoRange:SDR hls.loadLevel:-1"
[Log] [log] > – "[level-controller]:" – "Switching to level 1 (360p SDR avc1,mp4a @1419909) from level -1" (hls.min.js, line 1)
[Log] [log] > – "[level-controller]:" – "Loading level index 1 with https://vz-b1d0f3b4-139.flufflecdn.net/b13f6bba-728f-4fd3-88b4-ad8eb276976b/360p/video.m3u8" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "STOPPED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[subtitle-stream-controller]:" – "STOPPED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Level 1 loaded [0,13][part-13--1], cc [0, 0] duration:52.208333" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "Updating Media Source duration to 52.208" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 0 cc: 0 of [0-13] level: 1, target: 0" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "injecting Web Worker for \"main\"" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer-interface, main]: Starting new transmux session for sn: 0 p: -1 level: 1 id: 1↵        discontinuity: true↵        trackSwitch…" (hls.min.js, line 1)
"[transmuxer-interface, main]: Starting new transmux session for sn: 0 p: -1 level: 1 id: 1
        discontinuity: true
        trackSwitch: true
        contiguous: false
        accurateTimeOffset: true
        timeOffset: 0
        initSegmentChange: true"
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 0 of level 1" (hls.min.js, line 1)
[Log] [log] > – "Debug logs enabled for \"main\" in hls.js version 1.5.11" (5ca39030-38ae-4e1c-b4b7-59ee97a13aad, line 1)
[Log] [log] > – "[mp4-remuxer]: ISGenerated flag reset" (hls.min.js, line 1)
[Log] [log] > – "[mp4-remuxer]: initPTS & initDTS reset" (hls.min.js, line 1)
[Log] [log] > – "[mp4-remuxer]: reset next timestamp" (hls.min.js, line 1)
[Log] [log] > – "manifest codec:mp4a.40.2, ADTS type:2, samplingIndex:3" (hls.min.js, line 1)
[Log] [log] > – "parsed codec:mp4a.40.5, rate:48000, channels:2" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Init audio buffer, container:audio/mp4, codecs[selected/level/parsed]=[mp4a.40.2/mp4a.40.2/mp4a.40.5]" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Init video buffer, container:video/mp4, codecs[level/parsed]=[avc1.64001e/avc1.64001e]" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "0 bufferCodec event(s) expected audio,video" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "creating sourceBuffer(audio/mp4;codecs=mp4a.40.2)" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "creating sourceBuffer(video/mp4;codecs=avc1.64001e)" (hls.min.js, line 1)
[Log] [log] > – "[audio-stream-controller]:" – "InitPTS for cc: 0 found from main: 133500" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 0 of level 1" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 0 of level 1 (frag:[0.000-4.000] > buffer:[0.000-3.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Info] [info] > – "[abr] switch candidate:1->4 adjustedbw(23077376)-bitrate=14884866 ttfb:0.0 avgDuration:4.0 maxFetchDuration:3.9 fetchDuration:1.…" (hls.min.js, line 1)
"[abr] switch candidate:1->4 adjustedbw(23077376)-bitrate=14884866 ttfb:0.0 avgDuration:4.0 maxFetchDuration:3.9 fetchDuration:1.5 firstSelection:false codecSet:avc1,mp4a videoRange:SDR hls.loadLevel:1"
[Log] [log] > – "[stream-controller]:" – "Adapting to level 4 from level 1" (hls.min.js, line 1)
[Log] [log] > – "[level-controller]:" – "Switching to level 4 (1080p SDR avc1,mp4a @8192510) from level 1" (hls.min.js, line 1)
[Log] [log] > – "[level-controller]:" – "Loading level index 4 with https://vz-b1d0f3b4-139.flufflecdn.net/b13f6bba-728f-4fd3-88b4-ad8eb276976b/1080p/video.m3u8" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->WAITING_LEVEL" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Level 4 loaded [0,13][part-13--1], cc [0, 0] duration:52.208333" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "WAITING_LEVEL->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 1 cc: 0 of [0-13] level: 4, target: 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer-interface, main]: Starting new transmux session for sn: 1 p: -1 level: 4 id: 1↵        discontinuity: false↵      …" (hls.min.js, line 1)
"[transmuxer-interface, main]: Starting new transmux session for sn: 1 p: -1 level: 4 id: 1
        discontinuity: false
        trackSwitch: true
        contiguous: false
        accurateTimeOffset: true
        timeOffset: 4
        initSegmentChange: false"
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 1 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[mp4-remuxer]: ISGenerated flag reset" (hls.min.js, line 1)
[Log] [log] > – "[mp4-remuxer]: initPTS & initDTS reset" (hls.min.js, line 1)
[Log] [log] > – "[mp4-remuxer]: reset next timestamp" (hls.min.js, line 1)
[Log] [log] > – "manifest codec:mp4a.40.2, ADTS type:2, samplingIndex:3" (hls.min.js, line 1)
[Log] [log] > – "parsed codec:mp4a.40.5, rate:48000, channels:2" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Init audio buffer, container:audio/mp4, codecs[selected/level/parsed]=[mp4a.40.2/mp4a.40.2/mp4a.40.5]" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Init video buffer, container:video/mp4, codecs[level/parsed]=[avc1.640028/avc1.640028]" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "changing audio sourceBuffer type to audio/mp4;codecs=mp4a.40.5" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "switching codec mp4a.40.2 to mp4a.40.5" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 1 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 1 of level 4 (frag:[3.925-8.000] > buffer:[0.000-7.936])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 2 cc: 0 of [0-13] level: 4, target: 7.936" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 2 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 2 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 2 of level 4 (frag:[7.936-12.000] > buffer:[0.000-11.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 3 cc: 0 of [0-13] level: 4, target: 11.925" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 3 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 3 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 3 of level 4 (frag:[11.925-16.000] > buffer:[0.000-15.936])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 4 cc: 0 of [0-13] level: 4, target: 15.936" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 4 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 4 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 4 of level 4 (frag:[15.936-20.000] > buffer:[0.000-19.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 5 cc: 0 of [0-13] level: 4, target: 19.925" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 5 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 5 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 5 of level 4 (frag:[19.925-24.000] > buffer:[0.000-23.936])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 6 cc: 0 of [0-13] level: 4, target: 23.936" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 6 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 6 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 6 of level 4 (frag:[23.936-28.000] > buffer:[0.000-27.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 7 cc: 0 of [0-13] level: 4, target: 27.925" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 7 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 7 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 7 of level 4 (frag:[27.925-32.000] > buffer:[0.000-31.936])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 8 cc: 0 of [0-13] level: 4, target: 31.936" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 8 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 8 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 8 of level 4 (frag:[31.936-36.000] > buffer:[0.000-35.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 9 cc: 0 of [0-13] level: 4, target: 35.925" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 9 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 9 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 9 of level 4 (frag:[35.925-40.000] > buffer:[0.000-39.936])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 10 cc: 0 of [0-13] level: 4, target: 39.936" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 10 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 10 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 10 of level 4 (frag:[39.936-44.000] > buffer:[0.000-43.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 11 cc: 0 of [0-13] level: 4, target: 43.925" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 11 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 11 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 11 of level 4 (frag:[43.925-48.000] > buffer:[0.000-47.936])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 12 cc: 0 of [0-13] level: 4, target: 47.936" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 12 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 12 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 12 of level 4 (frag:[47.936-52.000] > buffer:[0.000-51.925])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loading fragment 13 cc: 0 of [0-13] level: 4, target: 51.925" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->FRAG_LOADING" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Loaded fragment 13 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "FRAG_LOADING->PARSING" (hls.min.js, line 1)
[Log] [log] > – "[transmuxer.ts]: Flushed fragment 13 of level 4" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSING->PARSED" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "Buffered main sn: 13 of level 4 (frag:[51.925-52.245] > buffer:[0.000-52.208])" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "PARSED->IDLE" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "audio sourceBuffer now EOS" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "video sourceBuffer now EOS" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "Queueing mediaSource.endOfStream()" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "IDLE->ENDED" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "Calling mediaSource.endOfStream()" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "Media source ended" (hls.min.js, line 1)
[Log] [log] > – "[buffer-controller]" – "Media source closed" (hls.min.js, line 1)
[Log] [log] > – "[stream-controller]:" – "ENDED->STOPPED" (hls.min.js, line 1)
[Log] [log] > – "[subtitle-stream-controller]:" – "IDLE->STOPPED" (hls.min.js, line 1)

Chrome media internals output

No response

tvinko avatar Jun 06 '24 11:06 tvinko

Adding preferManagedMediaSource: false, instructs HLS.js to not use ManagedMediaMediaSource, and to assign the media source (blob url) to the src attribute rather than a source child element. HLS.js only appends source children which is required by Safari for AirPlay with MSE sources when ManagedMediaMediaSource is in use.

Removing preferManagedMediaSource: false and adding crossorigin="anonymous" to the media element (depending on the source CORS policy) resolves the issue for me.

If you'd like to use AirPlay with ManagedMediaMediaSource disabled, consider contributing a PR that makes the buffer-controller's appendSource flag configurable.

robwalch avatar Jun 06 '24 17:06 robwalch

hi @robwalch , sorry I made a mistake when I was reporting the issue. I was testing various options with preferManagedMediaSource and left it disabled when copying the code. Otherwise I was testing it also without.

However, even with adding crossorigin="anonymous" I cannot make it streamable.

I published two code samples without preferManagedMediaSource and with crossorigin="anonymous"

Using native hls: https://js-tests.b-cdn.net/native_hls.html

And hls.js: https://js-tests.b-cdn.net/hls.html

but I still get same results. I cannot stream it with hlsjs and can with native hls.

I'm testing streaming from iPhone to Macbook M1 Pro

I appreciate your fast responses

tvinko avatar Jun 06 '24 20:06 tvinko

I was testing Desktop Safari to LG C2. I'll take another look on iPhone.

robwalch avatar Jun 07 '24 13:06 robwalch

This looks like an iOS issue. Please file a bug report with Feedback Assistant.

Make sure to update your page so that you are only setting video.src when not using HLS.js. Otherwise, HLS.js has to remove the attribute and there is some potential for loading and unloading of resources that you do not intend to use:

if (useHls) {
  // source children will be used when ManagedMediaSource is used
  // video.src should not be set prior to this, otherwise it must be unloaded
  // <hls.js setup with fallback source>

  if (video.webkitCurrentPlaybackTargetIsWireless) {
    hls.stopLoad();
  }
  video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', function (event) {
    if (video.webkitCurrentPlaybackTargetIsWireless) {
      hls.stopLoad();
    } else {
      hls.startLoad();
    }
  });
} else {
  video.src = urlPlaylistUrl;
}

Some other points:

  1. Provide a valid asset URL that the receiver can play. To be sure this issue is not related to your HLS asset, try a sample HLS asset from another host (like https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8).
  2. Use the "webkitcurrentplaybacktargetiswirelesschanged" event and video.webkitCurrentPlaybackTargetIsWireless to call stopLoad() and startLoad() on HLS.js when AirPlay starts and stops (shared example above)
  3. Adding playsinline to the video element allows playback in the page without entering fullscreen on play
  4. Source elements should have a type attribute. airPlaySrc.type = 'application/x-mpegURL'; will let Safari know up front that this is an HLS asset.
  5. Include the type of receiver (smartTV, AppleTV, or Macbook as you mentioned above) in your bug report
  6. Describe in detail how AirPlay failed, including what you saw on each device (sender and receiver).

I also experienced difficulty initiating a successful AirPlay session from iOS when compared to the same attempt from desktop Safari which worked every time. At first the receiver did not play anything, after a couple reconnects (to WebOS, then to AppleTV) it began to show the music last played from my phone, and then (back to WebOS) finally I got audio-only playback for the AV asset. Only after enabling autoplay and initiating the session after seeking or starting playback was I able to get audio and video to play on the receiver, but it was only for that one session. Additional attempts to reconnect used the receiver as a speaker (no video), or launched the session with the song "Now Playing" on the device. The one way I am most successful at starting a session from iOS is to start playback first, and while playback is active, begin the session. I think the sample above that also stops HLS.js loading (and MMS appends) may also be a factor, but it's the reason that sessions start with the active control center content or audio-only.

Share the feedback ID here so that I can help route the bug report appropriately. Thanks for working with me to identify and resolve this issue.

robwalch avatar Jun 10 '24 22:06 robwalch

stream provided give a 404 error

Cannot load https://vz-b1d0f3b4-139.flufflecdn.net/b13f6bba-728f-4fd3-88b4-ad8eb276976b/playlist.m3u8HTTP response code:404 

jyavenard avatar Jun 11 '24 06:06 jyavenard

Loading other URL, the

If you want airplay to work, you need an alternative source that is AirPlay compatible.

jyavenard avatar Jun 11 '24 06:06 jyavenard

This looks like an iOS issue. Please file a bug report with Feedback Assistant.

Make sure to update your page so that you are only setting video.src when not using HLS.js. Otherwise, HLS.js has to remove the attribute and there is some potential for loading and unloading of resources that you do not intend to use:

if (useHls) {
  // source children will be used when ManagedMediaSource is used
  // video.src should not be set prior to this, otherwise it must be unloaded
  // <hls.js setup with fallback source>

  if (video.webkitCurrentPlaybackTargetIsWireless) {
    hls.stopLoad();
  }
  video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', function (event) {
    if (video.webkitCurrentPlaybackTargetIsWireless) {
      hls.stopLoad();
    } else {
      hls.startLoad();
    }
  });
} else {
  video.src = urlPlaylistUrl;
}

Some other points:

  1. Provide a valid asset URL that the receiver can play. To be sure this issue is not related to your HLS asset, try a sample HLS asset from another host (like https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8).
  2. Use the "webkitcurrentplaybacktargetiswirelesschanged" event and video.webkitCurrentPlaybackTargetIsWireless to call stopLoad() and startLoad() on HLS.js when AirPlay starts and stops (shared example above)
  3. Adding playsinline to the video element allows playback in the page without entering fullscreen on play
  4. Source elements should have a type attribute. airPlaySrc.type = 'application/x-mpegURL'; will let Safari know up front that this is an HLS asset.
  5. Include the type of receiver (smartTV, AppleTV, or Macbook as you mentioned above) in your bug report
  6. Describe in detail how AirPlay failed, including what you saw on each device (sender and receiver).

I also experienced difficulty initiating a successful AirPlay session from iOS when compared to the same attempt from desktop Safari which worked every time. At first the receiver did not play anything, after a couple reconnects (to WebOS, then to AppleTV) it began to show the music last played from my phone, and then (back to WebOS) finally I got audio-only playback for the AV asset. Only after enabling autoplay and initiating the session after seeking or starting playback was I able to get audio and video to play on the receiver, but it was only for that one session. Additional attempts to reconnect used the receiver as a speaker (no video), or launched the session with the song "Now Playing" on the device. The one way I am most successful at starting a session from iOS is to start playback first, and while playback is active, begin the session. I think the sample above that also stops HLS.js loading (and MMS appends) may also be a factor, but it's the reason that sessions start with the active control center content or audio-only.

Share the feedback ID here so that I can help route the bug report appropriately. Thanks for working with me to identify and resolve this issue.

Hey @robwalch , I'll report the issue and share the feedback ID here

tvinko avatar Jun 14 '24 07:06 tvinko

3. playsinline

This looks like an iOS issue. Please file a bug report with Feedback Assistant.

Make sure to update your page so that you are only setting video.src when not using HLS.js. Otherwise, HLS.js has to remove the attribute and there is some potential for loading and unloading of resources that you do not intend to use:

if (useHls) {
  // source children will be used when ManagedMediaSource is used
  // video.src should not be set prior to this, otherwise it must be unloaded
  // <hls.js setup with fallback source>

  if (video.webkitCurrentPlaybackTargetIsWireless) {
    hls.stopLoad();
  }
  video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', function (event) {
    if (video.webkitCurrentPlaybackTargetIsWireless) {
      hls.stopLoad();
    } else {
      hls.startLoad();
    }
  });
} else {
  video.src = urlPlaylistUrl;
}

Some other points:

  1. Provide a valid asset URL that the receiver can play. To be sure this issue is not related to your HLS asset, try a sample HLS asset from another host (like https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8).
  2. Use the "webkitcurrentplaybacktargetiswirelesschanged" event and video.webkitCurrentPlaybackTargetIsWireless to call stopLoad() and startLoad() on HLS.js when AirPlay starts and stops (shared example above)
  3. Adding playsinline to the video element allows playback in the page without entering fullscreen on play
  4. Source elements should have a type attribute. airPlaySrc.type = 'application/x-mpegURL'; will let Safari know up front that this is an HLS asset.
  5. Include the type of receiver (smartTV, AppleTV, or Macbook as you mentioned above) in your bug report
  6. Describe in detail how AirPlay failed, including what you saw on each device (sender and receiver).

I also experienced difficulty initiating a successful AirPlay session from iOS when compared to the same attempt from desktop Safari which worked every time. At first the receiver did not play anything, after a couple reconnects (to WebOS, then to AppleTV) it began to show the music last played from my phone, and then (back to WebOS) finally I got audio-only playback for the AV asset. Only after enabling autoplay and initiating the session after seeking or starting playback was I able to get audio and video to play on the receiver, but it was only for that one session. Additional attempts to reconnect used the receiver as a speaker (no video), or launched the session with the song "Now Playing" on the device. The one way I am most successful at starting a session from iOS is to start playback first, and while playback is active, begin the session. I think the sample above that also stops HLS.js loading (and MMS appends) may also be a factor, but it's the reason that sessions start with the active control center content or audio-only.

Share the feedback ID here so that I can help route the bug report appropriately. Thanks for working with me to identify and resolve this issue.

@robwalch can you please confirm if this is proper example of sample code

<!DOCTYPE html>
<html>

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.11/hls.min.js"
        integrity="sha512-Z0W3S5swMoBWjkNBP3ACbXA4mh/jdjJOTOOZfF1yIO0OdkIU373s/Tm+eaCbUQq7EEgKl9O1eji1ZgseHCUVgw=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body id="body">
    <video id="video" playsinline crossorigin="anonymous" controls x-webkit-airplay="allow"></video>

    <script>
        var useHls = true;
        var autoplay = false;

        var urlPlaylistUrl =
            "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8";

        var video = document.getElementById("video");

        video.controls = true;
        video.loop = true;
        video.autoplay = autoplay;

        if (useHls) {
            var hls = new Hls({
                debug: true,
                enableWorker: true,
                lowLatencyMode: true,
                backBufferLength: 90
            });


            hls.loadSource(urlPlaylistUrl);

            hls.attachMedia(video);

            if (video.webkitCurrentPlaybackTargetIsWireless) {
                hls.stopLoad();
            }
            video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', function (event) {
                if (video.webkitCurrentPlaybackTargetIsWireless) {
                    hls.stopLoad();
                } else {
                    hls.startLoad();
                }
            });

            var airPlaySrc = document.createElement("source");
            airPlaySrc.src = urlPlaylistUrl;
            airPlaySrc.type = 'application/x-mpegURL';
            video.appendChild(airPlaySrc);
            video.disableRemotePlayback = false;

        } else {
            video.src = urlPlaylistUrl;
        }
    </script>
</body>

</html>

tvinko avatar Jun 14 '24 12:06 tvinko

  video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', function (event) {
    if (video.webkitCurrentPlaybackTargetIsWireless) {
      hls.stopLoad();
    } else {
      hls.startLoad();
    }
  });

This seems wrong to me and was needed before there was any support of AirPlay when using MSE prior iOS 17. If <source> are to be set, the src attribute must be not be set ever.

There is nothing to unload/stop load. Safari/WebKit will automatically select the AirPlay compatible alternative when the user select an AirPlay target.

You don't want to set the source alternative after the fact once you receive the message. In particular in recent iOS there won't ever be an AirPlay icon for user to select which would trigger the message. Icon will be displayed if there are multiple sources children.

jyavenard avatar Jun 14 '24 13:06 jyavenard

stopLoad() tells HLS.js to stop loading segments and playlists for the MSE playback asset initialized with loadSource(m3u8). @jyavenard - The start and stop load methods do not relate to or call video.load(), unload(), nor set src. They are similar to the action we take when MMS emits start and stop streaming events, which I did not observe firing when an AP session begins or ends.

We don't want the sender loading files and attempting to append them to a MediaSource while the session is active. It's an optimization missing from prior examples. If it's not required with ManagedMediaSource, then it can be removed.

robwalch avatar Jun 14 '24 14:06 robwalch

@robwalch feedback id: FB13939256

tvinko avatar Jun 17 '24 07:06 tvinko

I see that you have enableWorker: true, configuration set. Does it still occur if this is set to false?

Thanks

jyavenard avatar Jun 27 '24 23:06 jyavenard

I see that you have enableWorker: true, configuration set. Does it still occur if this is set to false?

Thanks

I tried with false option but it didn't have an impact

tvinko avatar Jul 02 '24 06:07 tvinko

For us AirPlay works correctly when initiated from iOS to macOS if after hls.attachMedia append source, enable remote playback, and enable autoplay:

hls.attachMedia(video);

const airPlaySrc = document.createElement("source");
airPlaySrc.src = hlsUrl;
airPlaySrc.type = "application/x-mpegURL";
video.appendChild(airPlaySrc);

video.disableRemotePlayback = false;

video.autoplay = true;

Without autoplay when initiated from iOS to macOS we hear only first second of audio and video never appears.

From macOS to macOS autoplay is not required.

DimaDemchenko avatar Jan 10 '25 13:01 DimaDemchenko

I'm testing on Safari Version 18.5 (20621.2.5.11.8) OSX 15.5 (24F74) and airplaying to an Apple TV.

The technique mentioned above does get things working. However, I've noticed in my testing that when you return to HLS.js playback on Mac Safari, the manual quality controls of HLS.js do not work, e.g.

hls.currentLevel = 0;

will not actually do an immediate quality switch, flush the buffer, or start requesting new segments at the given level. Furthermore, using the getter of hls.currentLevel will reveal that the switch never actually took place.

EDIT: As an addendum, I noticed that if HLS.js throws a MediaError when you leave airplay, the recoverMediaError() function will get things back in a working state. However, it was not 100% guarantee in my testing that HLS.js would throw any sort of error when resuming playback after airplay.

For us AirPlay works correctly when initiated from iOS to macOS if after hls.attachMedia append source, enable remote playback, and enable autoplay:

hls.attachMedia(video);

const airPlaySrc = document.createElement("source"); airPlaySrc.src = hlsUrl; airPlaySrc.type = "application/x-mpegURL"; video.appendChild(airPlaySrc);

video.disableRemotePlayback = false;

video.autoplay = true; Without autoplay when initiated from iOS to macOS we hear only first second of audio and video never appears.

From macOS to macOS autoplay is not required.

lpommers avatar May 28 '25 14:05 lpommers

when you return to HLS.js playback on Mac Safari, the manual quality controls of HLS.js do not work

It sounds like Safari HLS playback is being used rather than HLS.js. Safari doesn't know how to restore HLS.js/MSE playback after the remote session has ended. Your app should detach HLS.js during remote playback and re-attach after disconnecting to restore MSE based playback.

recoverMediaError() just detaches and attached the media element. Rather than looking for an error, see if the MediaSource was closed. If it has then the media element must be reattached to HLS.js to create a new MediaSource.

robwalch avatar May 28 '25 16:05 robwalch