mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
feat: Enable AirPlay in MSE (#7431)
Fixes #5022 --------- Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
This commit is contained in:
committed by
GitHub
parent
5ee6a4d2f5
commit
a6cf9cbfd3
@@ -127,6 +127,13 @@ shaka.media.MediaSourceEngine = class {
|
||||
/** @private {HTMLSourceElement} */
|
||||
this.source_ = null;
|
||||
|
||||
/**
|
||||
* Fallback source element with direct media URI, used for casting
|
||||
* purposes.
|
||||
* @private {HTMLSourceElement}
|
||||
*/
|
||||
this.secondarySource_ = null;
|
||||
|
||||
/** @private {MediaSource} */
|
||||
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
||||
|
||||
@@ -208,7 +215,9 @@ shaka.media.MediaSourceEngine = class {
|
||||
let mediaSource;
|
||||
|
||||
if (window.ManagedMediaSource) {
|
||||
this.video_.disableRemotePlayback = true;
|
||||
if (!this.secondarySource_) {
|
||||
this.video_.disableRemotePlayback = true;
|
||||
}
|
||||
|
||||
mediaSource = new ManagedMediaSource();
|
||||
|
||||
@@ -243,13 +252,37 @@ shaka.media.MediaSourceEngine = class {
|
||||
if (this.source_) {
|
||||
this.video_.removeChild(this.source_);
|
||||
}
|
||||
if (this.secondarySource_) {
|
||||
this.video_.removeChild(this.secondarySource_);
|
||||
}
|
||||
this.source_ = shaka.util.Dom.createSourceElement(this.url_);
|
||||
this.video_.appendChild(this.source_);
|
||||
if (this.secondarySource_) {
|
||||
this.video_.appendChild(this.secondarySource_);
|
||||
}
|
||||
this.video_.load();
|
||||
|
||||
return mediaSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} uri
|
||||
* @param {string} mimeType
|
||||
*/
|
||||
addSecondarySource(uri, mimeType) {
|
||||
if (!this.video_ || !(this.mediaSource_ instanceof ManagedMediaSource)) {
|
||||
shaka.log.warning(
|
||||
'Secondary source is used only with ManagedMediaSource');
|
||||
return;
|
||||
}
|
||||
if (this.secondarySource_) {
|
||||
this.video_.removeChild(this.secondarySource_);
|
||||
}
|
||||
this.secondarySource_ = shaka.util.Dom.createSourceElement(uri, mimeType);
|
||||
this.video_.appendChild(this.secondarySource_);
|
||||
this.video_.disableRemotePlayback = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {shaka.util.PublicPromise} p
|
||||
* @private
|
||||
@@ -443,15 +476,19 @@ shaka.media.MediaSourceEngine = class {
|
||||
this.eventManager_ = null;
|
||||
}
|
||||
|
||||
if (this.video_ && this.secondarySource_) {
|
||||
this.video_.removeChild(this.secondarySource_);
|
||||
}
|
||||
if (this.video_ && this.source_) {
|
||||
// "unload" the video element.
|
||||
this.video_.removeChild(this.source_);
|
||||
this.video_.load();
|
||||
this.video_.disableRemotePlayback = false;
|
||||
this.video_ = null;
|
||||
this.source_ = null;
|
||||
}
|
||||
|
||||
this.video_ = null;
|
||||
this.source_ = null;
|
||||
this.secondarySource_ = null;
|
||||
this.config_ = null;
|
||||
this.mediaSource_ = null;
|
||||
this.textEngine_ = null;
|
||||
|
||||
+132
-52
@@ -1792,6 +1792,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
startTimeOfLoad, prefetchedVariant, segmentPrefetchById);
|
||||
}, 'loadInner_');
|
||||
preloadManager.stopQueuingLatePhaseQueuedOperations();
|
||||
|
||||
if (this.mimeType_ && shaka.util.Platform.isSafari() &&
|
||||
shaka.util.MimeUtils.isHlsType(this.mimeType_)) {
|
||||
this.mediaSourceEngine_.addSecondarySource(
|
||||
this.assetUri_, this.mimeType_);
|
||||
}
|
||||
}
|
||||
this.dispatchEvent(shaka.Player.makeEvent_(
|
||||
shaka.util.FakeEvent.EventName.Loaded));
|
||||
@@ -4969,12 +4975,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @export
|
||||
*/
|
||||
selectTextTrack(track) {
|
||||
if (this.manifest_ && this.streamingEngine_&& !this.isRemotePlayback()) {
|
||||
const selectMediaSourceMode = () => {
|
||||
const stream = this.manifest_.textStreams.find(
|
||||
(stream) => stream.id == track.id);
|
||||
|
||||
if (!stream) {
|
||||
shaka.log.error('No stream with id', track.id);
|
||||
if (!this.isRemotePlayback()) {
|
||||
shaka.log.error('No stream with id', track.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4994,25 +5002,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
// When track is selected, back-propagate the language to
|
||||
// currentTextLanguage_.
|
||||
this.currentTextLanguage_ = stream.language;
|
||||
} else if (this.video_ && this.video_.src && this.video_.textTracks) {
|
||||
const textTracks = this.getFilteredTextTracks_();
|
||||
const oldTrack = textTracks.find((textTrack) =>
|
||||
textTrack.mode !== 'disabled');
|
||||
const newTrack = textTracks.find((textTrack) =>
|
||||
shaka.util.StreamUtils.html5TrackId(textTrack) === track.id);
|
||||
if (oldTrack !== newTrack) {
|
||||
if (oldTrack) {
|
||||
oldTrack.mode = 'disabled';
|
||||
this.loadEventManager_.unlisten(oldTrack, 'cuechange');
|
||||
this.textDisplayer_.remove(0, Infinity);
|
||||
};
|
||||
const selectSrcEqualsMode = () => {
|
||||
if (this.video_ && this.video_.textTracks) {
|
||||
const textTracks = this.getFilteredTextTracks_();
|
||||
const oldTrack = textTracks.find((textTrack) =>
|
||||
textTrack.mode !== 'disabled');
|
||||
const newTrack = textTracks.find((textTrack) =>
|
||||
shaka.util.StreamUtils.html5TrackId(textTrack) === track.id);
|
||||
if (!newTrack) {
|
||||
shaka.log.error('No track with id', track.id);
|
||||
return;
|
||||
}
|
||||
if (newTrack) {
|
||||
this.enableNativeTrack_(newTrack);
|
||||
if (oldTrack !== newTrack) {
|
||||
if (oldTrack) {
|
||||
oldTrack.mode = 'disabled';
|
||||
this.loadEventManager_.unlisten(oldTrack, 'cuechange');
|
||||
this.textDisplayer_.remove(0, Infinity);
|
||||
}
|
||||
if (newTrack) {
|
||||
this.enableNativeTrack_(newTrack);
|
||||
}
|
||||
}
|
||||
this.onTextChanged_();
|
||||
this.setTextDisplayerLanguage_();
|
||||
}
|
||||
};
|
||||
if (this.manifest_ && this.playhead_) {
|
||||
selectMediaSourceMode();
|
||||
// When using MSE + remote we need to set tracks for both MSE and native
|
||||
// apis so that synchronization is maintained.
|
||||
if (!this.isRemotePlayback()) {
|
||||
return;
|
||||
}
|
||||
this.onTextChanged_();
|
||||
this.setTextDisplayerLanguage_();
|
||||
}
|
||||
selectSrcEqualsMode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5062,11 +5086,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @export
|
||||
*/
|
||||
selectVariantTrack(track, clearBuffer = false, safeMargin = 0) {
|
||||
if (this.manifest_ && this.streamingEngine_&& !this.isRemotePlayback()) {
|
||||
const selectMediaSourceMode = () => {
|
||||
const variant = this.manifest_.variants.find(
|
||||
(variant) => variant.id == track.id);
|
||||
if (!variant) {
|
||||
shaka.log.error('No variant with id', track.id);
|
||||
if (!this.isRemotePlayback()) {
|
||||
shaka.log.error('No variant with id', track.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5090,8 +5116,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
'calling selectVariantTrack().');
|
||||
}
|
||||
|
||||
this.switchVariant_(
|
||||
variant, /* fromAdaptation= */ false, clearBuffer, safeMargin);
|
||||
if (this.isRemotePlayback()) {
|
||||
this.switchVariant_(
|
||||
variant, /* fromAdaptation= */ false,
|
||||
/* clearBuffer= */ false, /* safeMargin= */ 0);
|
||||
} else {
|
||||
this.switchVariant_(
|
||||
variant, /* fromAdaptation= */ false,
|
||||
clearBuffer || false, safeMargin || 0);
|
||||
}
|
||||
|
||||
// Workaround for
|
||||
// https://github.com/shaka-project/shaka-player/issues/1299
|
||||
@@ -5104,18 +5137,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
|
||||
// Update AbrManager variants to match these new settings.
|
||||
this.updateAbrManagerVariants_();
|
||||
} else if (this.video_ && this.video_.audioTracks) {
|
||||
// Safari's native HLS won't let you choose an explicit variant, though
|
||||
// you can choose audio languages this way.
|
||||
const audioTracks = Array.from(this.video_.audioTracks);
|
||||
for (const audioTrack of audioTracks) {
|
||||
if (shaka.util.StreamUtils.html5TrackId(audioTrack) == track.id) {
|
||||
// This will reset the "enabled" of other tracks to false.
|
||||
this.switchHtml5Track_(audioTrack);
|
||||
return;
|
||||
};
|
||||
const selectSrcEqualsMode = () => {
|
||||
if (this.video_ && this.video_.audioTracks) {
|
||||
// Safari's native HLS won't let you choose an explicit variant, though
|
||||
// you can choose audio languages this way.
|
||||
const audioTracks = Array.from(this.video_.audioTracks);
|
||||
for (const audioTrack of audioTracks) {
|
||||
if (shaka.util.StreamUtils.html5TrackId(audioTrack) == track.id) {
|
||||
// This will reset the "enabled" of other tracks to false.
|
||||
this.switchHtml5Track_(audioTrack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this.manifest_ && this.playhead_) {
|
||||
selectMediaSourceMode();
|
||||
// When using MSE + remote we need to set tracks for both MSE and native
|
||||
// apis so that synchronization is maintained.
|
||||
if (!this.isRemotePlayback()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectSrcEqualsMode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5176,12 +5221,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
*/
|
||||
selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0,
|
||||
codec = '') {
|
||||
if (this.manifest_ && this.playhead_&& !this.isRemotePlayback()) {
|
||||
const selectMediaSourceMode = () => {
|
||||
this.currentAdaptationSetCriteria_ =
|
||||
new shaka.media.PreferenceBasedCriteria(
|
||||
language,
|
||||
role || '',
|
||||
channelsCount,
|
||||
channelsCount || 0,
|
||||
/* hdrLevel= */ '',
|
||||
/* spatialAudio= */ false,
|
||||
/* videoLayout= */ '',
|
||||
@@ -5189,7 +5234,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
/* videoLabel= */ '',
|
||||
this.config_.mediaSource.codecSwitchingStrategy,
|
||||
this.config_.manifest.dash.enableAudioGroups,
|
||||
codec);
|
||||
codec || '');
|
||||
|
||||
const diff = (a, b) => {
|
||||
if (!a.video && !b.video) {
|
||||
@@ -5223,19 +5268,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
}
|
||||
if (bestVariant) {
|
||||
const track = shaka.util.StreamUtils.variantToTrack(bestVariant);
|
||||
this.selectVariantTrack(track, /* clearBuffer= */ true, safeMargin);
|
||||
this.selectVariantTrack(
|
||||
track, /* clearBuffer= */ true, safeMargin || 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we haven't switched yet, just use ABR to find a new track.
|
||||
this.chooseVariantAndSwitch_();
|
||||
} else if (this.video_ && this.video_.audioTracks) {
|
||||
const track = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
|
||||
this.getVariantTracks(), language, role || '', false)[0];
|
||||
if (track) {
|
||||
this.selectVariantTrack(track);
|
||||
};
|
||||
const selectSrcEqualsMode = () => {
|
||||
if (this.video_ && this.video_.audioTracks) {
|
||||
const track = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
|
||||
this.getVariantTracks(), language, role || '', false)[0];
|
||||
if (track) {
|
||||
this.selectVariantTrack(track);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this.manifest_ && this.playhead_) {
|
||||
selectMediaSourceMode();
|
||||
// When using MSE + remote we need to set tracks for both MSE and native
|
||||
// apis so that synchronization is maintained.
|
||||
if (!this.isRemotePlayback()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectSrcEqualsMode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5249,10 +5307,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @export
|
||||
*/
|
||||
selectTextLanguage(language, role, forced = false) {
|
||||
if (this.manifest_ && this.playhead_ && !this.isRemotePlayback()) {
|
||||
const selectMediaSourceMode = () => {
|
||||
this.currentTextLanguage_ = language;
|
||||
this.currentTextRole_ = role || '';
|
||||
this.currentTextForced_ = forced;
|
||||
this.currentTextForced_ = forced || false;
|
||||
|
||||
const chosenText = this.chooseTextStream_();
|
||||
if (chosenText) {
|
||||
@@ -5269,13 +5327,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
this.setTextDisplayerLanguage_();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
};
|
||||
const selectSrcEqualsMode = () => {
|
||||
const track = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
|
||||
this.getTextTracks(), language, role || '', forced)[0];
|
||||
this.getTextTracks(), language, role || '', forced || false)[0];
|
||||
if (track) {
|
||||
this.selectTextTrack(track);
|
||||
}
|
||||
};
|
||||
if (this.manifest_ && this.playhead_) {
|
||||
selectMediaSourceMode();
|
||||
// When using MSE + remote we need to set tracks for both MSE and native
|
||||
// apis so that synchronization is maintained.
|
||||
if (!this.isRemotePlayback()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectSrcEqualsMode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5293,7 +5361,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
* @export
|
||||
*/
|
||||
selectVariantsByLabel(label, clearBuffer = true, safeMargin = 0) {
|
||||
if (this.manifest_ && this.playhead_ && !this.isRemotePlayback()) {
|
||||
const selectMediaSourceMode = () => {
|
||||
let firstVariantWithLabel = null;
|
||||
for (const variant of this.manifest_.variants) {
|
||||
if (variant.audio.label == label) {
|
||||
@@ -5326,20 +5394,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
||||
this.config_.manifest.dash.enableAudioGroups);
|
||||
|
||||
this.chooseVariantAndSwitch_(clearBuffer, safeMargin);
|
||||
} else if (this.video_ && this.video_.audioTracks) {
|
||||
const audioTracks = Array.from(this.video_.audioTracks);
|
||||
};
|
||||
const selectSrcEqualsMode = () => {
|
||||
if (this.video_ && this.video_.audioTracks) {
|
||||
const audioTracks = Array.from(this.video_.audioTracks);
|
||||
|
||||
let trackMatch = null;
|
||||
let trackMatch = null;
|
||||
|
||||
for (const audioTrack of audioTracks) {
|
||||
if (audioTrack.label == label) {
|
||||
trackMatch = audioTrack;
|
||||
for (const audioTrack of audioTracks) {
|
||||
if (audioTrack.label == label) {
|
||||
trackMatch = audioTrack;
|
||||
}
|
||||
}
|
||||
if (trackMatch) {
|
||||
this.switchHtml5Track_(trackMatch);
|
||||
}
|
||||
}
|
||||
if (trackMatch) {
|
||||
this.switchHtml5Track_(trackMatch);
|
||||
};
|
||||
if (this.manifest_ && this.playhead_) {
|
||||
selectMediaSourceMode();
|
||||
// When using MSE + remote we need to set tracks for both MSE and native
|
||||
// apis so that synchronization is maintained.
|
||||
if (!this.isRemotePlayback()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectSrcEqualsMode();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user