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 config to allow reset MSE on cross boundary #8156

Merged
merged 58 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0a5018b
Reset MSE on period switch
matvp91 Feb 25, 2025
354f450
Some fixes
avelad Feb 25, 2025
0e132d2
More fixes
avelad Feb 25, 2025
e3528e1
Different approach
matvp91 Feb 25, 2025
7505970
Cleanup
matvp91 Feb 25, 2025
6ecff95
Removed unused code
matvp91 Feb 25, 2025
b0ebd0e
Use getLastEndTime to figure out time
matvp91 Feb 25, 2025
23f916d
Added comments
matvp91 Feb 25, 2025
08f285d
Code cleanup
matvp91 Feb 25, 2025
6c6c804
Reset MSE when we're near the end of the period and want to play
matvp91 Feb 25, 2025
913d5b4
Added resetMediaSourceOnStreamSwitch streaming config
matvp91 Feb 25, 2025
f2deadc
Moved everything to StreamingEngine
matvp91 Feb 25, 2025
75e84c7
Fixed tests
matvp91 Feb 25, 2025
d1fca6b
Some fixes
avelad Feb 25, 2025
46c4654
Added constant
matvp91 Feb 25, 2025
9c5247e
Merge branch 'feature/reset-mse' of github.com:matvp91/shaka-player i…
matvp91 Feb 25, 2025
034b9e8
Added PeriodSwitchingStrategy enum
matvp91 Feb 25, 2025
b5d5383
Style issues
avelad Feb 25, 2025
eac3c2b
Added logging and removed isWithinPeriod
matvp91 Feb 25, 2025
e41c5de
Merge branch 'feature/reset-mse' of github.com:matvp91/shaka-player i…
matvp91 Feb 25, 2025
ef5ac16
Renamed PeriodSwitchingStrategy
matvp91 Feb 25, 2025
1338a0b
Revert test setup in demo
matvp91 Feb 25, 2025
bd9ec83
Bugfix where, when we start in an encrypted period, we switch the str…
matvp91 Feb 25, 2025
e2c8562
Added optional startTime to InitSegmentReference
matvp91 Feb 26, 2025
50e5673
Added CrossBoundaryStrategy
matvp91 Feb 26, 2025
1c3a789
Typo fix
matvp91 Feb 26, 2025
4ceaed0
Fix style issues
avelad Feb 26, 2025
d69969e
Enforce inaccurateManifestTolerance: 0 when using crossBoundaryStrate…
avelad Feb 26, 2025
472a8f7
Merge branch 'main' into feature/reset-mse
avelad Feb 26, 2025
1474388
Added cross boundary timer
matvp91 Feb 26, 2025
8d2188d
Merge branch 'feature/reset-mse' of github.com:matvp91/shaka-player i…
matvp91 Feb 26, 2025
ff1e7b5
Sanity checks
matvp91 Feb 26, 2025
7136a13
Added comments and seek safeguard
matvp91 Feb 26, 2025
93aa76c
inaccurateManifestTolerance belogns to streaming config
matvp91 Feb 26, 2025
ce4be7d
Added boundaryTime_
matvp91 Feb 26, 2025
a6d4e76
Stop timer for each new cross boundary check
matvp91 Feb 26, 2025
e960099
Added log
matvp91 Feb 26, 2025
40db573
Naming of variables
matvp91 Feb 26, 2025
1e3182d
Annotations
matvp91 Feb 26, 2025
6b22b58
Added boundarycrossed event
matvp91 Feb 27, 2025
de4dce9
Merge branch 'main' of github.com:matvp91/shaka-player into feature/r…
matvp91 Feb 27, 2025
389cce2
Do not reset when crossing a plain boundary from a plain boundary and…
matvp91 Feb 27, 2025
0498435
Styling issue
matvp91 Feb 27, 2025
fded3d5
Added cspell words
matvp91 Feb 27, 2025
1a0a52b
Revert plain to plain
matvp91 Feb 27, 2025
92c7cb5
Simplified RESET_TO_ENCRYPTED
matvp91 Feb 27, 2025
aa4a577
Removed demo code
matvp91 Feb 27, 2025
eea7065
Merge branch 'main' of github.com:matvp91/shaka-player into feature/r…
matvp91 Mar 4, 2025
633b191
Support SegmentList and SegmentBase boundaryEnd
matvp91 Mar 4, 2025
c73acfc
Update default config
avelad Mar 4, 2025
a6d27de
Added integration test
matvp91 Mar 4, 2025
82f4d3e
Merge branch 'feature/reset-mse' of github.com:matvp91/shaka-player i…
matvp91 Mar 4, 2025
942bd2a
Fixed tests and skip on non Widevine devices
matvp91 Mar 5, 2025
3f72346
Update lib/media/streaming_engine.js
avelad Mar 5, 2025
8aededa
Updated AUTHORS
matvp91 Mar 7, 2025
e6a9e36
Merge branch 'main' of github.com:matvp91/shaka-player into feature/r…
matvp91 Mar 7, 2025
3990f49
Sanity check for streamingEngine
matvp91 Mar 7, 2025
6a924d0
Lint error
matvp91 Mar 7, 2025
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
1 change: 1 addition & 0 deletions build/types/core
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

+../../lib/config/auto_show_text.js
+../../lib/config/codec_switching_strategy.js
+../../lib/config/cross_boundary_strategy.js

+../../lib/debug/asserts.js
+../../lib/debug/log.js
Expand Down
12 changes: 11 additions & 1 deletion demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,13 @@ shakaDemo.Config = class {
this.addSelectInput_('Preferred video layout', 'preferredVideoLayout',
videoLayouts, videoLayoutsNames);

const strategyOptions = shaka.config.CrossBoundaryStrategy;
const strategyOptionsNames = {
'KEEP': 'Keep',
'RESET': 'Reset',
'RESET_TO_ENCRYPTED': 'Reset to encrypted',
};

this.addBoolInput_('Start At Segment Boundary',
'streaming.startAtSegmentBoundary')
.addBoolInput_('Ignore Text Stream Failures',
Expand All @@ -608,7 +615,10 @@ shakaDemo.Config = class {
.addBoolInput_('Should fix timestampOffset',
'streaming.shouldFixTimestampOffset')
.addBoolInput_('Avoid eviction on QuotaExceededError',
'streaming.avoidEvictionOnQuotaExceededError');
'streaming.avoidEvictionOnQuotaExceededError')
.addSelectInput_('Cross Boundary Strategy',
'streaming.crossBoundaryStrategy',
strategyOptions, strategyOptionsNames);
this.addRetrySection_('streaming', 'Streaming Retry Parameters');
this.addLiveSyncSection_();
}
Expand Down
9 changes: 8 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1730,7 +1730,8 @@ shaka.extern.LiveSyncConfiguration;
* clearDecodingCache: boolean,
* dontChooseCodecs: boolean,
* shouldFixTimestampOffset: boolean,
* avoidEvictionOnQuotaExceededError: boolean
* avoidEvictionOnQuotaExceededError: boolean,
* crossBoundaryStrategy: shaka.config.CrossBoundaryStrategy
* }}
*
* @description
Expand Down Expand Up @@ -1986,6 +1987,12 @@ shaka.extern.LiveSyncConfiguration;
* Avoid evict content on QuotaExceededError.
* <br>
* Defaults to <code>false</code>.
* @property {shaka.config.CrossBoundaryStrategy} crossBoundaryStrategy
* Allows MSE to be reset when crossing a boundary. Optionally, we can stop
* resetting MSE when MSE passed an encrypted boundary.
* Defaults to <code>KEEP</code> except on Tizen 3 where the default value
* is <code>RESET_TO_ENCRYPTED</code> and WebOS 3 where the default value
* is <code>RESET</code>.
* @exportDoc
*/
shaka.extern.StreamingConfiguration;
Expand Down
27 changes: 27 additions & 0 deletions lib/config/cross_boundary_strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

goog.provide('shaka.config.CrossBoundaryStrategy');

/**
* @enum {string}
* @export
*/
shaka.config.CrossBoundaryStrategy = {
/**
* Never reset MediaSource when crossing boundary.
*/
'KEEP': 'keep',
/**
* Always reset MediaSource when crossing boundary.
*/
'RESET': 'reset',
/**
* Reset MediaSource once, when transitioning from a plain
* boundary to an encrypted boundary.
*/
'RESET_TO_ENCRYPTED': 'reset_to_encrypted',
};
13 changes: 10 additions & 3 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2004,7 +2004,10 @@ shaka.dash.DashParser = class {
this.config_.ignoreDrmInfo,
this.config_.dash.keySystemsByURI);

context.adaptationSet.encrypted = contentProtection.drmInfos.length > 0;
// We us contentProtectionElements instead of drmInfos as the latter is
// not populated yet, and we need the encrypted flag for the upcoming
// parseRepresentation that will set the encrypted flag to the init seg.
context.adaptationSet.encrypted = contentProtectionElements.length > 0;

const language = shaka.util.LanguageUtils.normalize(
context.adaptationSet.language || 'und');
Expand Down Expand Up @@ -2216,6 +2219,8 @@ shaka.dash.DashParser = class {
TXml.findChildren(node, 'SupplementalProperty');
const essentialPropertyElements =
TXml.findChildren(node, 'EssentialProperty');
const contentProtectionElements =
TXml.findChildren(node, 'ContentProtection');

let representationUrlParams = null;
let urlParamsElement = essentialPropertyElements.find((element) => {
Expand Down Expand Up @@ -2248,6 +2253,10 @@ shaka.dash.DashParser = class {
contentType == ContentType.APPLICATION;
const isImage = contentType == ContentType.IMAGE;

if (contentProtectionElements.length) {
context.adaptationSet.encrypted = true;
}

try {
/** @type {shaka.extern.aesKey|undefined} */
let aesKey = undefined;
Expand Down Expand Up @@ -2340,8 +2349,6 @@ shaka.dash.DashParser = class {
throw error;
}

const contentProtectionElements =
TXml.findChildren(node, 'ContentProtection');
const keyId = shaka.dash.ContentProtection.parseFromRepresentation(
contentProtectionElements, contentProtection,
this.config_.ignoreDrmInfo,
Expand Down
3 changes: 3 additions & 0 deletions lib/dash/segment_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ shaka.dash.SegmentBase = class {
encrypted);
ref.codecs = context.representation.codecs;
ref.mimeType = context.representation.mimeType;
if (context.periodInfo) {
ref.boundaryEnd = context.periodInfo.start + context.periodInfo.duration;
}
return ref;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/dash/segment_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,9 @@ shaka.dash.SegmentTemplate = class {
encrypted);
ref.codecs = context.representation.codecs;
ref.mimeType = context.representation.mimeType;
if (context.periodInfo) {
ref.boundaryEnd = context.periodInfo.start + context.periodInfo.duration;
}
return ref;
}
};
Expand Down
3 changes: 3 additions & 0 deletions lib/media/segment_reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ shaka.media.InitSegmentReference = class {
/** @type {?string} */
this.mimeType = null;

/** @type {?number} */
this.boundaryEnd = null;

/** @const {boolean} */
this.encrypted = encrypted;
}
Expand Down
147 changes: 146 additions & 1 deletion lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
goog.provide('shaka.media.StreamingEngine');

goog.require('goog.asserts');
goog.require('shaka.config.CrossBoundaryStrategy');
goog.require('shaka.log');
goog.require('shaka.media.Capabilities');
goog.require('shaka.media.InitSegmentReference');
Expand Down Expand Up @@ -173,6 +174,22 @@ shaka.media.StreamingEngine = class {
this.playerInterface_.mediaSourceEngine.clearLiveSeekableRange();
}
});

/** @private {?number} */
this.boundaryTime_ = null;

/** @private {?shaka.util.Timer} */
this.crossBoundaryTimer_ = new shaka.util.Timer(() => {
const video = this.playerInterface_.video;
if (video.ended) {
return;
}
if (this.boundaryTime_) {
shaka.log.info('Crossing boundary at', this.boundaryTime_);
video.currentTime = this.boundaryTime_;
this.boundaryTime_ = null;
}
});
}

/** @override */
Expand Down Expand Up @@ -211,6 +228,10 @@ shaka.media.StreamingEngine = class {
this.updateLiveSeekableRangeTime_.stop();
}
this.updateLiveSeekableRangeTime_ = null;
if (this.crossBoundaryTimer_) {
this.crossBoundaryTimer_.stop();
}
this.crossBoundaryTimer_ = null;
}

/**
Expand Down Expand Up @@ -1522,6 +1543,14 @@ shaka.media.StreamingEngine = class {
return updateIntervalSeconds;
}

const CrossBoundaryStrategy = shaka.config.CrossBoundaryStrategy;
if (this.config_.crossBoundaryStrategy !== CrossBoundaryStrategy.KEEP &&
this.discardReferenceByBoundary_(mediaState, reference)) {
// Return null as we do not want to fetch and append segments outside
// of the current boundary.
return null;
}

if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
!this.audioPrefetchMap_.has(mediaState.stream)) {
mediaState.segmentPrefetch.evict(reference.startTime);
Expand Down Expand Up @@ -2979,6 +3008,109 @@ shaka.media.StreamingEngine = class {
}
}

/**
* Checks if need to push time forward to cross a boundary. If so,
* an MSE reset will happen. If the strategy is KEEP, this logic is skipped.
* Called on timeupdate to schedule a theoretical, future, offset or on
* waiting, which is another indicator we might need to cross a boundary.
* @param {boolean=} immediate
*/
forwardTimeForCrossBoundary(immediate = false) {
if (this.config_.crossBoundaryStrategy ===
shaka.config.CrossBoundaryStrategy.KEEP) {
// When crossBoundaryStrategy changed to keep mid stream, we can bail
// out early.
return;
}
const video = this.playerInterface_.video;
if (video.seeking) {
// When seeking, close to a boundary, we can reset too early due to
// a subsequent waiting event. Schedule a theoretical delay.
immediate = false;
}

// Stop timer first, in case someone seeked back during the time a timer
// was scheduled.
this.crossBoundaryTimer_.stop();

const presentationTime = this.playerInterface_.getPresentationTime();

const ContentType = shaka.util.ManifestParserUtils.ContentType;
const mediaState = this.mediaStates_.get(ContentType.VIDEO) ||
this.mediaStates_.get(ContentType.AUDIO);

if (!mediaState || !mediaState.lastAppendWindowEnd ||
mediaState.clearingBuffer) {
return;
}

const threshold = shaka.media.StreamingEngine.CROSS_BOUNDARY_END_THRESHOLD_;
const fromEnd = mediaState.lastAppendWindowEnd - presentationTime;
// Check if greater than 0 to eliminate a backwards seek.
if (fromEnd > 0 && fromEnd < threshold) {
// Set the intended time to seek to in order to cross the boundary.
this.boundaryTime_ = mediaState.lastAppendWindowEnd;

if (immediate) {
this.crossBoundaryTimer_.tickNow();
} else {
// When not immediate, we schedule a time tick when the boundary
// theoretically should be reached, else we'd be stalled when
// a waiting event doesn't come (due to segment misalignment).
this.crossBoundaryTimer_.tickAfter(fromEnd);
}
}
}

/**
* Returns whether the reference should be discarded. If the segment crosses
* a boundary, we'll discard it based on the crossBoundaryStrategy.
*
* @param {!shaka.media.StreamingEngine.MediaState_} mediaState
* @param {!shaka.media.SegmentReference} reference
* @private
*/
discardReferenceByBoundary_(mediaState, reference) {
const lastInitRef = mediaState.lastInitSegmentReference;
if (!lastInitRef) {
return false;
}

const CrossBoundaryStrategy = shaka.config.CrossBoundaryStrategy;
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);

const initRef = reference.initSegmentReference;
let discard = lastInitRef.boundaryEnd !== initRef.boundaryEnd;
// Some devices can play plain data when initialized with an encrypted
// init segment. We can keep the MediaSource in this case.
if (this.config_.crossBoundaryStrategy ===
CrossBoundaryStrategy.RESET_TO_ENCRYPTED) {
if (!lastInitRef.encrypted && !initRef.encrypted) {
// We're crossing a plain to plain boundary, allow the reference.
discard = false;
}
if (lastInitRef.encrypted) {
// We initialized MediaSource with an encrypted init segment, from
// now on, we can keep the buffer.
shaka.log.debug(logPrefix, 'stream is encrypted, ' +
'discard crossBoundaryStrategy');
this.config_.crossBoundaryStrategy = CrossBoundaryStrategy.KEEP;
}
}
// If discarded & seeked across a boundary, reset MediaSource.
if (discard && mediaState.seeked) {
shaka.log.debug(logPrefix, 'reset mediaSource',
'from=', mediaState.lastInitSegmentReference,
'to=', reference.initSegmentReference);

this.resetMediaSource(/* force= */ true).then(() => {
const eventName = shaka.util.FakeEvent.EventName.BoundaryCrossed;
this.playerInterface_.onEvent(new shaka.util.FakeEvent(eventName));
});
}
return discard;
}

/**
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
* @return {string} A log prefix of the form ($CONTENT_TYPE:$STREAM_ID), e.g.,
Expand All @@ -2996,6 +3128,7 @@ shaka.media.StreamingEngine = class {
* getPresentationTime: function():number,
* getBandwidthEstimate: function():number,
* getPlaybackRate: function():number,
* video: !HTMLMediaElement,
* mediaSourceEngine: !shaka.media.MediaSourceEngine,
* netEngine: shaka.net.NetworkingEngine,
* onError: function(!shaka.util.Error),
Expand All @@ -3014,7 +3147,9 @@ shaka.media.StreamingEngine = class {
* @property {function():number} getBandwidthEstimate
* Get the estimated bandwidth in bits per second.
* @property {function():number} getPlaybackRate
* Get the playback rate
* Get the playback rate.
* @property {!HTMLVideoElement} video
* Get the video element.
* @property {!shaka.media.MediaSourceEngine} mediaSourceEngine
* The MediaSourceEngine. The caller retains ownership.
* @property {shaka.net.NetworkingEngine} netEngine
Expand Down Expand Up @@ -3176,3 +3311,13 @@ shaka.media.StreamingEngine.APPEND_WINDOW_END_FUDGE_ = 0.01;
* @private
*/
shaka.media.StreamingEngine.MAX_RUN_AHEAD_SEGMENTS_ = 1;


/**
* The threshold to decide if we're close to a boundary. If presentation time
* is before this offset, boundary crossing logic will be skipped.
*
* @const {number}
* @private
*/
shaka.media.StreamingEngine.CROSS_BOUNDARY_END_THRESHOLD_ = 1;
Loading