From bfc17b4570c50f3f6d13318f17406be9cf8f2f6e Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 9 Oct 2024 12:58:59 -0400 Subject: [PATCH 1/4] feat: Add Airplay support when overriding native HLS in Safari/iOS (#1543) --- package.json | 2 +- src/videojs-http-streaming.js | 14 ++++++++++- test/videojs-http-streaming.test.js | 39 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f3670db03..b5e514bb1 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "video.js": "^7 || ^8" }, "peerDependencies": { - "video.js": "^8.14.0" + "video.js": "^8.19.0" }, "devDependencies": { "@babel/cli": "^7.21.0", diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js index 1a6b75306..404b5927a 100644 --- a/src/videojs-http-streaming.js +++ b/src/videojs-http-streaming.js @@ -1079,7 +1079,19 @@ class VhsHandler extends Component { this.mediaSourceUrl_ = window.URL.createObjectURL(this.playlistController_.mediaSource); - this.tech_.src(this.mediaSourceUrl_); + // If we are playing HLS with MSE in Safari, add source elements for both the blob and manifest URLs. + // The latter will enable Airplay playback on receiver devices. + if (( + videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS) && + this.options_.overrideNative && + this.options_.sourceType === 'hls' && + typeof this.tech_.addSourceElement === 'function' + ) { + this.tech_.addSourceElement(this.mediaSourceUrl_); + this.tech_.addSourceElement(this.source_.src); + } else { + this.tech_.src(this.mediaSourceUrl_); + } } createKeySessions_() { diff --git a/test/videojs-http-streaming.test.js b/test/videojs-http-streaming.test.js index 929dc00b6..027ca75b1 100644 --- a/test/videojs-http-streaming.test.js +++ b/test/videojs-http-streaming.test.js @@ -3121,6 +3121,45 @@ QUnit.test( } ); +QUnit.test('uses source elements when overriding native HLS in Safari/iOS', function(assert) { + const origIsAnySafari = videojs.browser.IS_ANY_SAFARI; + const addSourceElementCalls = []; + let srcCalls = 0; + + videojs.browser.IS_ANY_SAFARI = true; + + const player = createPlayer({ html5: { vhs: { overrideNative: true } } }); + + player.tech_.addSourceElement = function(url) { + addSourceElementCalls.push(url); + }; + + player.tech_.src = function() { + srcCalls++; + }; + + player.src({ + src: 'http://example.com/manifest/main.m3u8', + type: 'application/x-mpegURL' + }); + + this.clock.tick(1); + + assert.equal(addSourceElementCalls.length, 2, '2 source elements added'); + assert.equal(srcCalls, 0, 'tech.src() not called'); + + const blobUrl = addSourceElementCalls[0]; + const manifestUrl = addSourceElementCalls[1]; + + assert.ok(blobUrl.startsWith('blob:'), 'First source element is a blob URL'); + assert.equal(manifestUrl, 'http://example.com/manifest/main.m3u8', 'Second source element is the manifest URL'); + + // Clean up and restore original flags + player.dispose(); + + videojs.browser.IS_ANY_SAFARI = origIsAnySafari; +}); + QUnit.test('re-emits mediachange events', function(assert) { let mediaChanges = 0; From ae1ae707e3a207610ee16f9a842ba6817a42646e Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 9 Oct 2024 15:25:13 -0400 Subject: [PATCH 2/4] feat: Add support for ManagedMediaSource 'startstreaming' and 'endstream' event handling (#1542) --- src/playlist-controller.js | 22 +++++++++++++++++++ test/playlist-controller.test.js | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index c37425f52..6f4f97130 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -215,6 +215,7 @@ export class PlaylistController extends videojs.EventTarget { // Airplay source not yet implemented. Remote playback must be disabled. this.tech_.el_.disableRemotePlayback = true; this.mediaSource = new window.ManagedMediaSource(); + videojs.log('Using ManagedMediaSource'); } else if (window.MediaSource) { this.mediaSource = new window.MediaSource(); @@ -223,12 +224,16 @@ export class PlaylistController extends videojs.EventTarget { this.handleDurationChange_ = this.handleDurationChange_.bind(this); this.handleSourceOpen_ = this.handleSourceOpen_.bind(this); this.handleSourceEnded_ = this.handleSourceEnded_.bind(this); + this.load = this.load.bind(this); + this.pause = this.pause.bind(this); this.mediaSource.addEventListener('durationchange', this.handleDurationChange_); // load the media source into the player this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_); this.mediaSource.addEventListener('sourceended', this.handleSourceEnded_); + this.mediaSource.addEventListener('startstreaming', this.load); + this.mediaSource.addEventListener('endstreaming', this.pause); // we don't have to handle sourceclose since dispose will handle termination of // everything, and the MediaSource should not be detached without a proper disposal @@ -1056,14 +1061,31 @@ export class PlaylistController extends videojs.EventTarget { */ load() { this.mainSegmentLoader_.load(); + if (this.mediaTypes_.AUDIO.activePlaylistLoader) { this.audioSegmentLoader_.load(); } + if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { this.subtitleSegmentLoader_.load(); } } + /** + * Call pause on our SegmentLoaders + */ + pause() { + this.mainSegmentLoader_.pause(); + + if (this.mediaTypes_.AUDIO.activePlaylistLoader) { + this.audioSegmentLoader_.pause(); + } + + if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { + this.subtitleSegmentLoader_.pause(); + } + } + /** * Re-tune playback quality level for the current player * conditions. This method will perform destructive actions like removing diff --git a/test/playlist-controller.test.js b/test/playlist-controller.test.js index 9cbe9391c..0f555d556 100644 --- a/test/playlist-controller.test.js +++ b/test/playlist-controller.test.js @@ -7693,3 +7693,39 @@ QUnit.test('uses ManagedMediaSource only when opted in', function(assert) { mmsSpy.restore(); mms.restore(); }); + +QUnit.test('ManagedMediaSource startstreaming and endstreaming events start and pause segment loading respectively', function(assert) { + const mms = useFakeManagedMediaSource(); + const options = { + src: 'test.m3u8', + tech: this.player.tech_, + player_: this.player, + experimentalUseMMS: true + }; + + const controller = new PlaylistController(options); + + controller.mediaTypes_.AUDIO.activePlaylistLoader = {}; + controller.mediaTypes_.SUBTITLES.activePlaylistLoader = {}; + + const mainLoadSpy = sinon.spy(controller.mainSegmentLoader_, 'load'); + const audioLoadSpy = sinon.spy(controller.audioSegmentLoader_, 'load'); + const subtitleLoadSpy = sinon.spy(controller.subtitleSegmentLoader_, 'load'); + const mainPauseSpy = sinon.spy(controller.mainSegmentLoader_, 'pause'); + const audioPauseSpy = sinon.spy(controller.audioSegmentLoader_, 'pause'); + const subtitlePauseSpy = sinon.spy(controller.subtitleSegmentLoader_, 'pause'); + + controller.mediaSource.trigger('startstreaming'); + + assert.ok(mainLoadSpy.calledOnce, 'Segment loading started on startstreaming event'); + assert.ok(audioLoadSpy.calledOnce, 'Audio segment loading started on startstreaming event'); + assert.ok(subtitleLoadSpy.calledOnce, 'Subtitle segment loading started on startstreaming event'); + + controller.mediaSource.trigger('endstreaming'); + + assert.ok(mainPauseSpy.calledOnce, 'Main segment loading paused on endstreaming event'); + assert.ok(audioPauseSpy.calledOnce, 'Audio segment loading paused on endstreaming event'); + assert.ok(subtitlePauseSpy.calledOnce, 'Subtitle segment loading paused on endstreaming event'); + + mms.restore(); +}); From a9dd79039ae0828ff10436811f81a1880dd365c8 Mon Sep 17 00:00:00 2001 From: Walter Seymour Date: Wed, 9 Oct 2024 14:51:59 -0500 Subject: [PATCH 3/4] chore: update mpd-parser to v1.3.1 (#1544) --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f73b4050..6e27e37d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1863,9 +1863,9 @@ } }, "@xmldom/xmldom": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.7.tgz", - "integrity": "sha512-sI1Ly2cODlWStkINzqGrZ8K6n+MTSbAeQnAipGyL+KZCXuHaRlj2gyyy8B/9MvsFFqN7XHryQnB2QwhzvJXovg==" + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==" }, "JSONStream": { "version": "1.3.5", @@ -6896,9 +6896,9 @@ "dev": true }, "mpd-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.0.tgz", - "integrity": "sha512-WgeIwxAqkmb9uTn4ClicXpEQYCEduDqRKfmUdp4X8vmghKfBNXZLYpREn9eqrDx/Tf5LhzRcJLSpi4ohfV742Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz", + "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==", "requires": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^4.0.0", diff --git a/package.json b/package.json index b5e514bb1..34bc21d03 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "aes-decrypter": "^4.0.2", "global": "^4.4.0", "m3u8-parser": "^7.2.0", - "mpd-parser": "^1.3.0", + "mpd-parser": "^1.3.1", "mux.js": "7.0.3", "video.js": "^7 || ^8" }, From 8456cb3c1c8af207156c0330a9cc2050592e8880 Mon Sep 17 00:00:00 2001 From: wseymour Date: Thu, 10 Oct 2024 09:25:12 -0500 Subject: [PATCH 4/4] 3.15.0 --- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16432a760..aabd583be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + +# [3.15.0](https://github.com/videojs/http-streaming/compare/v3.14.2...v3.15.0) (2024-10-10) + +### Features + +* Add Airplay support when overriding native HLS in Safari/iOS ([#1543](https://github.com/videojs/http-streaming/issues/1543)) ([bfc17b4](https://github.com/videojs/http-streaming/commit/bfc17b4)) +* Add support for ManagedMediaSource 'startstreaming' and 'endstream' event handling ([#1542](https://github.com/videojs/http-streaming/issues/1542)) ([ae1ae70](https://github.com/videojs/http-streaming/commit/ae1ae70)) + +### Chores + +* update mpd-parser to v1.3.1 ([#1544](https://github.com/videojs/http-streaming/issues/1544)) ([a9dd790](https://github.com/videojs/http-streaming/commit/a9dd790)) + ## [3.14.2](https://github.com/videojs/http-streaming/compare/v3.14.1...v3.14.2) (2024-09-17) diff --git a/package-lock.json b/package-lock.json index 6e27e37d8..f9f5c97a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.14.2", + "version": "3.15.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 34bc21d03..db74d5eb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@videojs/http-streaming", - "version": "3.14.2", + "version": "3.15.0", "description": "Play back HLS and DASH with Video.js, even where it's not natively supported", "main": "dist/videojs-http-streaming.cjs.js", "module": "dist/videojs-http-streaming.es.js",