Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Airplay support when overriding native HLS in Safari/iOS #1543

Merged
merged 3 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 13 additions & 1 deletion src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that addSourceElement will not exist on some techs? Do we want to check for it's existence first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSE is an extension of HTML5 so we should be able to safely assume that all tech references are for the HTML5 tech.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though unlikely, doing a check here adds safety in case someone is using a later version of vhs with the current version of videojs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a check

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though unlikely, doing a check here adds safety in case someone is using a later version of vhs with the current version of videojs.

I guess if someone tries to use it this way, npm should warn that peer dependency is incompatible

this.tech_.addSourceElement(this.source_.src);
} else {
this.tech_.src(this.mediaSourceUrl_);
wseymour15 marked this conversation as resolved.
Show resolved Hide resolved
}
}

createKeySessions_() {
Expand Down
39 changes: 39 additions & 0 deletions test/videojs-http-streaming.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading