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

Refactor streams playback start #3308

Merged
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);
dsilhavy marked this conversation as resolved.
Show resolved Hide resolved
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