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
99 changes: 72 additions & 27 deletions src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from './manifest';
import containerRequest from './util/container-request.js';
import {toUint8} from '@videojs/vhs-utils/es/byte-helpers';
import logger from './util/logger';

const { EventTarget, mergeOptions } = videojs;

Expand Down Expand Up @@ -291,6 +292,7 @@ export default class DashPlaylistLoader extends EventTarget {

this.state = 'HAVE_NOTHING';
this.loadedPlaylists_ = {};
this.logger_ = logger('DashPlaylistLoader');

// initialize the loader state
// The masterPlaylistLoader will be created with a string
Expand Down Expand Up @@ -415,6 +417,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 +515,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 +533,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 +712,65 @@ 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
if (mpl.minimumUpdatePeriodTimeout_) {
window.clearTimeout(mpl.minimumUpdatePeriodTimeout_);
mpl.minimumUpdatePeriodTimeout_ = null;
}

if (minimumUpdatePeriod > 0) {
createMUPTimeout(minimumUpdatePeriod);
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, which
// can happen when a live video becomes VOD. skip timeout
// creation.
Comment on lines +753 to +755
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can use the DASH Interop as a reference:

https://dashif.org/docs/DASH-IF-IOP-v4.2-clean.htm

4.6.4.             Transition Phase between Live and On-Demand

In the scenario for which the same MPD URL is used for live and On-Demand content, once the URL and publish time of the last Segment is known for the live service, and the duration of the service is known as well, the service provider acts as defined in clause 4.4.3.1, i.e.,

-          adds the attribute MPD@mediaPresentationDuration
-          removes the attribute MPD@minimumUpdatePeriod

If the value is < 0 we should probably issue a warning, as I don't think that should be valid.

if (typeof mup !== 'number' || mup <= 0) {
if (mup < 0) {
this.logger_(`found invalid minimumUpdatePeriod of ${mup}, not setting a timeout`);
}
return;
}

this.createMUPTimeout_(mup);
}

createMUPTimeout_(mup) {
const mpl = this.masterPlaylistLoader_;

mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
mpl.minimumUpdatePeriodTimeout_ = null;
mpl.trigger('minimumUpdatePeriod');
mpl.createMUPTimeout_(mup);
}, mup);
}

/**
Expand Down Expand Up @@ -791,10 +827,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