Skip to content

Commit

Permalink
Refactor streams playback start (#3308)
Browse files Browse the repository at this point in the history
* Refactor stream playback starting process:
- always use seeking process from PlaybackController to seek controllers
- adjust seek time in BufferController according to effective appended ranges
- ScheduleController: do not stop scheduler when updating data (at manifest updates)

* revert gap/discontinuity limit for buffer range tolerance (to do in another PR)

* update code comments

* BufferController: optimise effective range seeking if seek target

* avoid seeking at NaN

* remove commented code

* remove commented code

* Add processing of MPD time anchor (#t) according to Annex C of MPEG DASH spec

* StreamProcessor: update DVR info metrics also if SEGMENTS_UPDATE_FAILED_ERROR_CODE

* rename getStartTimeFromUriParameters() parameter to 'rangeStart'

* Start at DVR window start if #t is before DVR window range

* Ignore #t=posix: notation for static streams

* #t=<time> shall be relative to period start for dynamic streams

* update unit tests
  • Loading branch information
Bertrand Berthelot committed Jun 30, 2020
1 parent 44e38ce commit 4c2baba
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 307 deletions.
27 changes: 24 additions & 3 deletions src/streaming/Stream.js
Expand Up @@ -67,10 +67,11 @@ function Stream(config) {

let instance,
logger,
streamInfo,
streamProcessors,
isStreamInitialized,
isStreamActivated,
isMediaInitialized,
streamInfo,
hasVideoTrack,
hasAudioTrack,
updateError,
Expand Down Expand Up @@ -227,6 +228,7 @@ function Stream(config) {
function resetInitialSettings() {
deactivate();
streamInfo = null;
isStreamInitialized = false;
hasVideoTrack = false;
hasAudioTrack = false;
updateError = {};
Expand Down Expand Up @@ -261,6 +263,19 @@ function Stream(config) {
return streamInfo ? streamInfo.start : NaN;
}

function getLiveStartTime() {
if (!streamInfo.manifestInfo.isDynamic) return NaN;
// Get live start time of the video stream (1st in array of streams)
// or audio if no video stream
for (let i = 0; i < streamProcessors.length; i++) {
if (streamProcessors[i].getType() === Constants.AUDIO ||
streamProcessors[i].getType() === Constants.VIDEO) {
return streamProcessors[i].getLiveStartTime();
}
}
return NaN;
}

function getId() {
return streamInfo ? streamInfo.id : null;
}
Expand Down Expand Up @@ -610,8 +625,14 @@ function Stream(config) {

if (error) {
errHandler.error(error);
} else {
eventBus.trigger(Events.STREAM_INITIALIZED, { streamInfo: streamInfo });
} else if (!isStreamInitialized) {
isStreamInitialized = true;
timelineConverter.setTimeSyncCompleted(true);

eventBus.trigger(Events.STREAM_INITIALIZED, {
streamInfo: streamInfo,
liveStartTime: getLiveStartTime()
});
}
}

Expand Down
59 changes: 15 additions & 44 deletions src/streaming/StreamProcessor.js
Expand Up @@ -90,8 +90,7 @@ function StreamProcessor(config) {
logger = Debug(context).getInstance().getLogger(instance);
resetInitialSettings();

eventBus.on(Events.STREAM_INITIALIZED, onStreamInitialized, instance);
eventBus.on(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance);
eventBus.on(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance, EventBus.EVENT_PRIORITY_HIGH); // High priority to be notified before Stream
eventBus.on(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, instance);
eventBus.on(Events.INIT_FRAGMENT_NEEDED, onInitFragmentNeeded, instance);
eventBus.on(Events.MEDIA_FRAGMENT_NEEDED, onMediaFragmentNeeded, instance);
Expand Down Expand Up @@ -208,7 +207,6 @@ function StreamProcessor(config) {
abrController.unRegisterStreamType(type);
}

eventBus.off(Events.STREAM_INITIALIZED, onStreamInitialized, instance);
eventBus.off(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance);
eventBus.off(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, instance);
eventBus.off(Events.INIT_FRAGMENT_NEEDED, onInitFragmentNeeded, instance);
Expand All @@ -228,31 +226,17 @@ function StreamProcessor(config) {
return representationController ? representationController.isUpdating() : false;
}

function onStreamInitialized(e) {
if (!e.streamInfo || streamInfo.id !== e.streamInfo.id) return;

if (!streamInitialized) {
streamInitialized = true;
if (isDynamic) {
timelineConverter.setTimeSyncCompleted(true);
setLiveEdgeSeekTarget();
} else {
const seekTarget = playbackController.getStreamStartTime(false);
bufferController.setSeekStartTime(seekTarget);
scheduleController.setCurrentRepresentation(getRepresentationInfo());
scheduleController.setSeekTarget(seekTarget);
}
}

scheduleController.start();
}

function onDataUpdateCompleted(e) {
if (e.sender.getType() !== getType() || e.sender.getStreamId() !== streamInfo.id) return;
logger.info('DataUpdateCompleted');

if (!e.error) {
// Update representation if no error
scheduleController.setCurrentRepresentation(adapter.convertDataToRepresentationInfo(e.currentRepresentation));
} else if (e.error.code !== Errors.SEGMENTS_UPDATE_FAILED_ERROR_CODE) {
}
if (!e.error || e.error.code === Errors.SEGMENTS_UPDATE_FAILED_ERROR_CODE) {
// Update has been postponed, update nevertheless DVR info
addDVRMetric();
}
}
Expand Down Expand Up @@ -624,9 +608,12 @@ function StreamProcessor(config) {
return controller;
}

function setLiveEdgeSeekTarget() {
if (!liveEdgeFinder) return;

function getLiveStartTime() {
if (!isDynamic) return NaN;
if (!liveEdgeFinder) return NaN;

let liveStartTime = NaN;
const currentRepresentationInfo = getRepresentationInfo();
const liveEdge = liveEdgeFinder.getLiveEdge(currentRepresentationInfo);
const request = findRequestForLiveEdge(liveEdge, currentRepresentationInfo);
Expand All @@ -635,30 +622,13 @@ function StreamProcessor(config) {
// When low latency mode is selected but browser doesn't support fetch
// start at the beginning of the segment to avoid consuming the whole buffer
if (settings.get().streaming.lowLatencyEnabled) {
const liveStartTime = request.duration < mediaPlayerModel.getLiveDelay() ? request.startTime : request.startTime + request.duration - mediaPlayerModel.getLiveDelay();
playbackController.setLiveStartTime(liveStartTime);
liveStartTime = request.duration < mediaPlayerModel.getLiveDelay() ? request.startTime : request.startTime + request.duration - mediaPlayerModel.getLiveDelay();
} else {
playbackController.setLiveStartTime(request.startTime);
liveStartTime = request.startTime;
}
}

const seekTarget = playbackController.getStreamStartTime(false, liveEdge);
bufferController.setSeekStartTime(seekTarget);
scheduleController.setCurrentRepresentation(currentRepresentationInfo);
scheduleController.setSeekTarget(seekTarget);
scheduleController.start();

// For multi periods stream, if the startTime is beyond current period then seek to corresponding period (see StreamController::onPlaybackSeeking)
if (seekTarget > (currentRepresentationInfo.mediaInfo.streamInfo.start + currentRepresentationInfo.mediaInfo.streamInfo.duration)) {
playbackController.seek(seekTarget);
}

dashMetrics.updateManifestUpdateInfo({
currentTime: seekTarget,
presentationStartTime: liveEdge,
latency: liveEdge - seekTarget,
clientTimeOffset: timelineConverter.getClientTimeOffset()
});
return liveStartTime;
}

function findRequestForLiveEdge(liveEdge, currentRepresentationInfo) {
Expand Down Expand Up @@ -763,6 +733,7 @@ function StreamProcessor(config) {
getStreamInfo: getStreamInfo,
selectMediaInfo: selectMediaInfo,
addMediaInfo: addMediaInfo,
getLiveStartTime: getLiveStartTime,
switchTrackAsked: switchTrackAsked,
getMediaInfoArr: getMediaInfoArr,
getMediaInfo: getMediaInfo,
Expand Down
41 changes: 14 additions & 27 deletions src/streaming/controllers/BufferController.js
Expand Up @@ -85,7 +85,7 @@ function BufferController(config) {
isPruningInProgress,
isQuotaExceeded,
initCache,
seekStartTime,
seekTarget,
seekClearedBufferingCompleted,
pendingPruningRanges,
replacingBuffer,
Expand Down Expand Up @@ -281,6 +281,13 @@ function BufferController(config) {
if (appendedBytesInfo.segmentType === HTTPRequest.MEDIA_SEGMENT_TYPE) {
showBufferRanges(ranges);
onPlaybackProgression();

// If seeking, seek video model to range start in case appended segment starts beyond seek target
if (!isNaN(seekTarget) &&
(playbackController.getTime() === 0 || playbackController.getTime() < ranges.start(0))) {
playbackController.seek(ranges.start(0), true, true);
seekTarget = NaN;
}
} else {
if (replacingBuffer) {
const currentTime = playbackController.getTime();
Expand Down Expand Up @@ -309,7 +316,8 @@ function BufferController(config) {
//**********************************************************************
// START Buffer Level, State & Sufficiency Handling.
//**********************************************************************
function onPlaybackSeeking(/*e*/) {
function onPlaybackSeeking(e) {
seekTarget = e.seekTime;
if (isBufferingCompleted) {
seekClearedBufferingCompleted = true;
isBufferingCompleted = false;
Expand All @@ -325,7 +333,7 @@ function BufferController(config) {
}

function onPlaybackSeeked() {
setSeekStartTime(undefined);
seekTarget = NaN;
}

// Prune full buffer but what is around current time position
Expand Down Expand Up @@ -405,17 +413,7 @@ function BufferController(config) {
}

function getWorkingTime() {
// This function returns current working time for buffer (either start time or current time if playback has started)
let ret = playbackController.getTime();

if (seekStartTime) {
// if there is a seek start time, the first buffer data will be available on maximum value between first buffer range value and seek start time.
let ranges = buffer.getAllBufferRanges();
if (ranges && ranges.length) {
ret = Math.max(ranges.start(0), seekStartTime);
}
}
return ret;
return isNaN(seekTarget) ? playbackController.getTime() : seekTarget;
}

function onPlaybackProgression() {
Expand All @@ -429,9 +427,7 @@ function BufferController(config) {
}

function onPlaybackPlaying() {
if (seekStartTime !== undefined) {
setSeekStartTime(undefined);
}
seekTarget = NaN;
checkIfSufficientBuffer();
}

Expand Down Expand Up @@ -488,11 +484,6 @@ function BufferController(config) {
let range,
length;

// Consider gap/discontinuity limit as tolerance
if (settings.get().streaming.jumpGaps) {
tolerance = settings.get().streaming.smallGapLimit;
}

range = getRangeAt(time, tolerance);

if (range === null) {
Expand Down Expand Up @@ -762,10 +753,6 @@ function BufferController(config) {
return type;
}

function setSeekStartTime(value) {
seekStartTime = value;
}

function getBuffer() {
return buffer;
}
Expand Down Expand Up @@ -846,6 +833,7 @@ function BufferController(config) {
bufferLevel = 0;
wallclockTicked = 0;
pendingPruningRanges = [];
seekTarget = NaN;

if (buffer) {
if (!errored) {
Expand Down Expand Up @@ -885,7 +873,6 @@ function BufferController(config) {
createBuffer: createBuffer,
dischargePreBuffer: dischargePreBuffer,
getType: getType,
setSeekStartTime: setSeekStartTime,
getBuffer: getBuffer,
setBuffer: setBuffer,
getBufferLevel: getBufferLevel,
Expand Down

0 comments on commit 4c2baba

Please sign in to comment.