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

fix: Add exclude reason and skip duplicate playlist-unchanged #1082

Merged
merged 9 commits into from
Mar 5, 2021
Merged
95 changes: 68 additions & 27 deletions src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,14 @@ export default class DashPlaylistLoader extends EventTarget {
window.clearTimeout(this.minimumUpdatePeriodTimeout_);
window.clearTimeout(this.mediaRequest_);
window.clearTimeout(this.mediaUpdateTimeout);
this.mediaUpdateTimeout = null;
this.mediaRequest_ = null;
this.minimumUpdatePeriodTimeout_ = null;

if (this.masterPlaylistLoader_.createMupOnMedia_) {
this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_);
this.masterPlaylistLoader_.createMupOnMedia_ = null;
}

this.off();
}
Expand Down Expand Up @@ -505,9 +513,15 @@ export default class DashPlaylistLoader extends EventTarget {
}

pause() {
if (this.masterPlaylistLoader_.createMupOnMedia_) {
this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_);
this.masterPlaylistLoader_.createMupOnMedia_ = null;
}
this.stopRequest();
window.clearTimeout(this.mediaUpdateTimeout);
window.clearTimeout(this.minimumUpdatePeriodTimeout_);
window.clearTimeout(this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_);
this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
this.mediaUpdateTimeout = null;
if (this.state === 'HAVE_NOTHING') {
// If we pause the loader before any data has been retrieved, its as if we never
// started, so reset to an unstarted state.
Expand All @@ -517,7 +531,7 @@ export default class DashPlaylistLoader extends EventTarget {

load(isFinalRendition) {
window.clearTimeout(this.mediaUpdateTimeout);
window.clearTimeout(this.minimumUpdatePeriodTimeout_);
this.mediaUpdateTimeout = null;

const media = this.media();

Expand Down Expand Up @@ -696,45 +710,63 @@ export default class DashPlaylistLoader extends EventTarget {
this.masterPlaylistLoader_.srcUrl = location;
}

// if the minimumUpdatePeriod was changed, update the minimumUpdatePeriodTimeout_
if (!oldMaster || (newMaster && oldMaster.minimumUpdatePeriod !== newMaster.minimumUpdatePeriod)) {
if (!oldMaster || (newMaster && newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod)) {
this.updateMinimumUpdatePeriodTimeout_();
}

return Boolean(newMaster);
}

updateMinimumUpdatePeriodTimeout_() {
// Clear existing timeout
window.clearTimeout(this.minimumUpdatePeriodTimeout_);
const mpl = this.masterPlaylistLoader_;

const createMUPTimeout = (mup) => {
this.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
this.trigger('minimumUpdatePeriod');
createMUPTimeout(mup);
}, mup);
};
// cancel any pending creation of mup on media
// a new one will be added if needed.
if (mpl.createMupOnMedia_) {
mpl.off('loadedmetadata', mpl.createMupOnMedia_);
mpl.createMupOnMedia_ = null;
}

const minimumUpdatePeriod = this.masterPlaylistLoader_.master && this.masterPlaylistLoader_.master.minimumUpdatePeriod;
// clear any pending timeouts, master was just updated
// so if minimumUpdatePeriodTimeout_ is still valid
gesinger marked this conversation as resolved.
Show resolved Hide resolved
window.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
mpl.minimumUpdatePeriodTimeout_ = null;
mpl.createMUPTimeout_();
}

if (minimumUpdatePeriod > 0) {
createMUPTimeout(minimumUpdatePeriod);
createMUPTimeout_() {
const mpl = this.masterPlaylistLoader_;
let mup = mpl.master && mpl.master.minimumUpdatePeriod;

// If the minimumUpdatePeriod has a value of 0, that indicates that the current
gesinger marked this conversation as resolved.
Show resolved Hide resolved
// MPD has no future validity, so a new one will need to be acquired when new
// media segments are to be made available. Thus, we use the target duration
// in this case
} else if (minimumUpdatePeriod === 0) {
// If we haven't yet selected a playlist, wait until then so we know the
// target duration
if (!this.media()) {
this.one('loadedplaylist', () => {
createMUPTimeout(this.media().targetDuration * 1000);
});
if (mup === 0) {
if (mpl.media()) {
mup = mpl.media().targetDuration * 1000;
} else {
createMUPTimeout(this.media().targetDuration * 1000);
mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_;
mpl.one('loadedmetadata', mpl.createMupOnMedia_);
}
}

// if minimumUpdatePeriod is invalid or <= zero skip creating a timeout
Copy link
Contributor

Choose a reason for hiding this comment

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

In what cases can this happen? Removed to become a VOD?

It might be worth some log messages here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm honestly not sure, that's just what the original code assumed. I would think that it is removed as the source becomes a VOD, and I will clarify that in the comment.

if (typeof mup !== 'number' || mup <= 0) {
return;
}

mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
mpl.minimumUpdatePeriodTimeout_ = null;
mpl.createMUPTimeout_();

// if a minimumUpdatePeriodTimeout_ was created again
// then the minimumUpdatePeriod was valid
// trigger minimumUpdatePeriod
if (mpl.minimumUpdatePeriodTimeout_) {
gesinger marked this conversation as resolved.
Show resolved Hide resolved
mpl.trigger('minimumUpdatePeriod');
}
}, mup);
}

/**
Expand Down Expand Up @@ -791,10 +823,19 @@ export default class DashPlaylistLoader extends EventTarget {
this.trigger('playlistunchanged');
}

if (!this.media().endList) {
this.mediaUpdateTimeout = window.setTimeout(() => {
this.trigger('mediaupdatetimeout');
}, refreshDelay(this.media(), Boolean(mediaChanged)));
if (!this.mediaUpdateTimeout) {
const createMediaUpdateTimeout = () => {
if (this.media().endList) {
return;
}

this.mediaUpdateTimeout = window.setTimeout(() => {
this.trigger('mediaupdatetimeout');
createMediaUpdateTimeout();
}, refreshDelay(this.media(), Boolean(mediaChanged)));
};

createMediaUpdateTimeout();
}

this.trigger('loadedplaylist');
Expand Down
14 changes: 13 additions & 1 deletion src/master-playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,14 @@ export class MasterPlaylistController extends videojs.EventTarget {

this.masterPlaylistLoader_.on('playlistunchanged', () => {
const updatedPlaylist = this.masterPlaylistLoader_.media();

// ignore unchanged playlists that have already been
// excluded for not-changing. We likely just have a really slowly updating
// playlist.
if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') {
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 also worth checking the last excludeUntil date to see if we're passed the point where the exclusion should be considered?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤷🏻 It might be but I think that playlist-unchanged excludes are not likely to stop happening no matter how much time has passed.

return;
}

const playlistOutdated = this.stuckAtPlaylistEnd_(updatedPlaylist);

if (playlistOutdated) {
Expand All @@ -480,7 +488,8 @@ export class MasterPlaylistController extends videojs.EventTarget {
// one is updating (and give the player a chance to re-adjust to the
// safe live point).
this.blacklistCurrentPlaylist({
message: 'Playlist no longer updating.'
message: 'Playlist no longer updating.',
reason: 'playlist-unchanged'
});
// useful for monitoring QoS
this.tech_.trigger('playliststuck');
Expand Down Expand Up @@ -1066,6 +1075,9 @@ export class MasterPlaylistController extends videojs.EventTarget {

// Blacklist this playlist
currentPlaylist.excludeUntil = Date.now() + (blacklistDuration * 1000);
if (error.reason) {
currentPlaylist.lastExcludeReason_ = error.reason;
}
this.tech_.trigger('blacklistplaylist');
this.tech_.trigger({type: 'usage', name: 'vhs-rendition-blacklisted'});
this.tech_.trigger({type: 'usage', name: 'hls-rendition-blacklisted'});
Expand Down