Skip to content

Commit

Permalink
Fix MSS live seeking (#3574)
Browse files Browse the repository at this point in the history
* [MSS] fix segment timeline update according to DVR window

* [MSS] reset MSSHandler first

* [MSS] never abort FragmentInfo requests

* [MSS] seek to DVR window range start in case DVR window is updating while seeking to range start

* [MSS] do not set availabilityStartTime

* [MSS] fix FragmentInfo requests timing in case of Precondition failed (start global timer once first FragmentInfo request has been received)

* [MSS] update DVR window range only for audio and video tracks

* [MSS] Ignore FragmentInfo requests errors

* PlaybackController: in updateCurrentTime, check if not already seeking when seeking back to DVR window start

* Settings: allow for setting non-existing(declared) values

* [MSS] set retry attempts and intervals for FragmentInfo requests

* [MSS] do not remove segment from timeline (DVR window) if currently playing

* [MSS] Add HTTPRequest type for FragmentInfo segments

* [MSS] add retryAttempts and retryIntreval for FragmentInfo requests

* Revert "Settings: allow for setting non-existing(declared) values"

This reverts commit d0a3a1a.

* complete Settings jsdoc

* clean code
  • Loading branch information
bbert authored Mar 30, 2021
1 parent 84c552e commit a41cf42
Show file tree
Hide file tree
Showing 11 changed files with 55 additions and 24 deletions.
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ declare namespace dashjs {
'InitializationSegment'?: number;
'BitstreamSwitchingSegment'?: number;
'IndexSegment'?: number;
'FragmentInfoSegment'?: number;
'other'?: number;
'lowLatencyReductionFactor'?: number;
};
Expand All @@ -197,6 +198,7 @@ declare namespace dashjs {
'InitializationSegment'?: number;
'BitstreamSwitchingSegment'?: number;
'IndexSegment'?: number;
'FragmentInfoSegment'?: number;
'other'?: number;
'lowLatencyMultiplyFactor'?: number;
};
Expand Down
6 changes: 6 additions & 0 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* IndexSegment: 1000,
* MediaSegment: 1000,
* BitstreamSwitchingSegment: 1000,
* FragmentInfoSegment: 1000,
* other: 1000,
* lowLatencyReductionFactor: 10
* },
Expand All @@ -131,6 +132,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* IndexSegment: 3,
* MediaSegment: 3,
* BitstreamSwitchingSegment: 3,
* FragmentInfoSegment: 3,
* other: 3,
* lowLatencyMultiplyFactor: 5
* },
Expand Down Expand Up @@ -462,6 +464,8 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* Request to retrieve a media segment (video/audio/image/text chunk).
* @property {number} [BitstreamSwitchingSegment]
* Bitrate stream switching type of request.
* @property {number} [FragmentInfoSegment]
* Request to retrieve a FragmentInfo segment (specific to Smooth Streaming live streams).
* @property {number} [other]
* Other type of request.
* @property {number} [lowLatencyReductionFactor]
Expand Down Expand Up @@ -672,6 +676,7 @@ function Settings() {
[HTTPRequest.INIT_SEGMENT_TYPE]: 1000,
[HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE]: 1000,
[HTTPRequest.INDEX_SEGMENT_TYPE]: 1000,
[HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE]: 1000,
[HTTPRequest.OTHER_TYPE]: 1000,
lowLatencyReductionFactor: 10
},
Expand All @@ -682,6 +687,7 @@ function Settings() {
[HTTPRequest.INIT_SEGMENT_TYPE]: 3,
[HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE]: 3,
[HTTPRequest.INDEX_SEGMENT_TYPE]: 3,
[HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE]: 3,
[HTTPRequest.OTHER_TYPE]: 3,
lowLatencyMultiplyFactor: 5
},
Expand Down
8 changes: 6 additions & 2 deletions src/mss/MssFragmentInfoController.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*/

import FragmentRequest from '../streaming/vo/FragmentRequest';
import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';

function MssFragmentInfoController(config) {

Expand Down Expand Up @@ -69,7 +70,6 @@ function MssFragmentInfoController(config) {
logger.debug('Start');

started = true;
startTime = new Date().getTime();
index = 0;

loadNextFragmentInfo();
Expand Down Expand Up @@ -114,7 +114,7 @@ function MssFragmentInfoController(config) {
let request = new FragmentRequest();

request.mediaType = type;
request.type = 'FragmentInfoSegment';
request.type = HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE;
// request.range = segment.mediaRange;
request.startTime = segment.t / timescale;
request.duration = segment.d / timescale;
Expand Down Expand Up @@ -168,6 +168,10 @@ function MssFragmentInfoController(config) {

// logger.debug('FragmentInfo loaded: ', request.url);

if (startTime === null) {
startTime = new Date().getTime();
}

if (!startFragmentTime) {
startFragmentTime = request.startTime;
}
Expand Down
19 changes: 14 additions & 5 deletions src/mss/MssFragmentMoofProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ function MssFragmentMoofProcessor(config) {
range;
let segment = null;
let t = 0;
let endTime;
let availabilityStartTime = null;

if (entries.length === 0) {
Expand Down Expand Up @@ -144,9 +145,9 @@ function MssFragmentMoofProcessor(config) {
if (manifest.type === 'static') {
if (type === 'video') {
segment = segments[segments.length - 1];
var end = (segment.t + segment.d) / timescale;
if (end > representation.adaptation.period.duration) {
eventBus.trigger(Events.MANIFEST_VALIDITY_CHANGED, { sender: this, newDuration: end });
endTime = (segment.t + segment.d) / timescale;
if (endTime > representation.adaptation.period.duration) {
eventBus.trigger(Events.MANIFEST_VALIDITY_CHANGED, { sender: this, newDuration: endTime });
}
}
return;
Expand All @@ -159,14 +160,20 @@ function MssFragmentMoofProcessor(config) {
t = segment.t;

// Determine the segments' availability start time
availabilityStartTime = Math.round((t - (manifest.timeShiftBufferDepth * timescale)) / timescale);
availabilityStartTime = (t - (manifest.timeShiftBufferDepth * timescale)) / timescale;

// Remove segments prior to availability start time
segment = segments[0];
while (Math.round(segment.t / timescale) < availabilityStartTime) {
endTime = (segment.t + segment.d) / timescale;
while (endTime < availabilityStartTime) {
// Check if not currently playing the segment to be removed
if (!playbackController.isPaused() && playbackController.getTime() < endTime) {
break;
}
// logger.debug('Remove segment - t = ' + (segment.t / timescale));
segments.splice(0, 1);
segment = segments[0];
endTime = (segment.t + segment.d) / timescale;
}
}

Expand All @@ -183,10 +190,12 @@ function MssFragmentMoofProcessor(config) {
}

function updateDVR(type, range, manifestInfo) {
if (type !== 'video' && type !== 'audio') return;
const dvrInfos = dashMetrics.getCurrentDVRInfo(type);
if (!dvrInfos || (range.end > dvrInfos.range.end)) {
logger.debug('Update DVR range: [' + range.start + ' - ' + range.end + ']');
dashMetrics.addDVRInfo(type, playbackController.getTime(), manifestInfo, range);
playbackController.updateCurrentTime(type);
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/mss/MssFragmentProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

import MssFragmentMoofProcessor from './MssFragmentMoofProcessor';
import MssFragmentMoovProcessor from './MssFragmentMoovProcessor';
import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';


// Add specific box processors not provided by codem-isoboxer library

Expand Down Expand Up @@ -163,7 +165,7 @@ function MssFragmentProcessor(config) {
// MediaSegment => convert to Smooth Streaming moof format
mssFragmentMoofProcessor.convertFragment(e, streamProcessor);

} else if (e.request.type === 'FragmentInfoSegment') {
} else if (e.request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) {
// FragmentInfo (live) => update segments list
mssFragmentMoofProcessor.updateSegmentList(e, streamProcessor);

Expand Down
3 changes: 2 additions & 1 deletion src/mss/MssHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import MssParser from './parser/MssParser';
import MssErrors from './errors/MssErrors';
import DashJSError from '../streaming/vo/DashJSError';
import InitCache from '../streaming/utils/InitCache';
import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';

function MssHandler(config) {

Expand Down Expand Up @@ -176,7 +177,7 @@ function MssHandler(config) {
// Process moof to transcode it from MSS to DASH (or to update segment timeline for SegmentInfo fragments)
mssFragmentProcessor.processFragment(e, streamProcessor);

if (e.request.type === 'FragmentInfoSegment') {
if (e.request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) {
// If FragmentInfo loaded, then notify corresponding MssFragmentInfoController
let fragmentInfoController = getFragmentInfoController(e.request.mediaType);
if (fragmentInfoController) {
Expand Down
8 changes: 2 additions & 6 deletions src/mss/parser/MssParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ function MssParser(config) {
return widevineCP;
}

function processManifest(xmlDoc, manifestLoadedTime) {
function processManifest(xmlDoc) {
const manifest = {};
const contentProtections = [];
const smoothStreamingMedia = xmlDoc.getElementsByTagName('SmoothStreamingMedia')[0];
Expand Down Expand Up @@ -706,11 +706,6 @@ function MssParser(config) {
manifest.minBufferTime = segmentDuration;

if (manifest.type === 'dynamic' ) {
// Set availabilityStartTime
segments = adaptations[i].SegmentTemplate.SegmentTimeline.S_asArray;
let endTime = (segments[segments.length - 1].t + segments[segments.length - 1].d) / adaptations[i].SegmentTemplate.timescale * 1000;
manifest.availabilityStartTime = new Date(manifestLoadedTime.getTime() - endTime);

// Match timeShiftBufferDepth to video segment timeline duration
if (manifest.timeShiftBufferDepth > 0 &&
manifest.timeShiftBufferDepth !== Infinity &&
Expand All @@ -727,6 +722,7 @@ function MssParser(config) {
// In case of live streams:
// 1- configure player buffering properties according to target live delay
// 2- adapt live delay and then buffers length in case timeShiftBufferDepth is too small compared to target live delay (see PlaybackController.computeLiveDelay())
// 3- Set retry attempts and intervals for FragmentInfo requests
if (manifest.type === 'dynamic') {
let targetLiveDelay = mediaPlayerModel.getLiveDelay();
if (!targetLiveDelay) {
Expand Down
8 changes: 4 additions & 4 deletions src/streaming/ManifestLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ function ManifestLoader(config) {
function reset() {
eventBus.off(Events.XLINK_READY, onXlinkReady, instance);

if (mssHandler) {
mssHandler.reset();
}

if (xlinkController) {
xlinkController.reset();
xlinkController = null;
Expand All @@ -234,10 +238,6 @@ function ManifestLoader(config) {
urlLoader.abort();
urlLoader = null;
}

if (mssHandler) {
mssHandler.reset();
}
}

instance = {
Expand Down
11 changes: 6 additions & 5 deletions src/streaming/controllers/PlaybackController.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,8 @@ function PlaybackController() {
return startTime;
}

function getActualPresentationTime(currentTime) {
const DVRMetrics = dashMetrics.getCurrentDVRInfo();
function getActualPresentationTime(currentTime, mediatype) {
const DVRMetrics = dashMetrics.getCurrentDVRInfo(mediatype);
const DVRWindow = DVRMetrics ? DVRMetrics.range : null;
let actualTime;

Expand Down Expand Up @@ -430,12 +430,12 @@ function PlaybackController() {
wallclockTimeIntervalId = null;
}

function updateCurrentTime() {
function updateCurrentTime(mediaType) {
if (isPaused() || !isDynamic || videoModel.getReadyState() === 0) return;
const currentTime = getNormalizedTime();
const actualTime = getActualPresentationTime(currentTime);
const actualTime = getActualPresentationTime(currentTime, mediaType);
const timeChanged = (!isNaN(actualTime) && actualTime !== currentTime);
if (timeChanged) {
if (timeChanged && !isSeeking()) {
logger.debug(`UpdateCurrentTime: Seek to actual time: ${actualTime} from currentTime: ${currentTime}`);
seek(actualTime);
}
Expand Down Expand Up @@ -979,6 +979,7 @@ function PlaybackController() {
isSeeking: isSeeking,
getStreamEndTime,
seek: seek,
updateCurrentTime: updateCurrentTime,
reset: reset
};

Expand Down
9 changes: 9 additions & 0 deletions src/streaming/net/HTTPLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ function HTTPLoader(cfg) {
internalLoad(config, remainingAttempts);
}, mediaPlayerModel.getRetryIntervalsForType(request.type));
} else {
if (request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) {
return;
}

errHandler.error(new DashJSError(downloadErrorToRequestTypeMap[request.type], request.url + ' is not available', {
request: request,
response: httpRequest.response
Expand Down Expand Up @@ -355,6 +359,11 @@ function HTTPLoader(cfg) {
delayedRequests = [];

requests.forEach(x => {
// MSS patch: ignore FragmentInfo requests
if (x.request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) {
return;
}

// abort will trigger onloadend which we don't want
// when deliberately aborting inflight requests -
// set them to undefined so they are not called
Expand Down
1 change: 1 addition & 0 deletions src/streaming/vo/metrics/HTTPRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ HTTPRequest.INIT_SEGMENT_TYPE = 'InitializationSegment';
HTTPRequest.INDEX_SEGMENT_TYPE = 'IndexSegment';
HTTPRequest.MEDIA_SEGMENT_TYPE = 'MediaSegment';
HTTPRequest.BITSTREAM_SWITCHING_SEGMENT_TYPE = 'BitstreamSwitchingSegment';
HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE = 'FragmentInfoSegment';
HTTPRequest.LICENSE = 'license';
HTTPRequest.OTHER_TYPE = 'other';

Expand Down

0 comments on commit a41cf42

Please sign in to comment.