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/positive ept delta dynamic manifest #3878

Merged
merged 6 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions src/dash/DashHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ function DashHandler(config) {
* @param {object} representation
* @param {number} targetThreshold
*/
function getValidSeekTimeCloseToTargetTime(time, mediaInfo, representation, targetThreshold) {
function getValidTimeCloseToTargetTime(time, mediaInfo, representation, targetThreshold) {
try {

if (isNaN(time) || !mediaInfo || !representation) {
Expand Down Expand Up @@ -381,6 +381,83 @@ function DashHandler(config) {
}
}

/**
* This function returns a time larger than the current time for which we can generate a request.
* This is useful in scenarios in which the user seeks into a gap in a dynamic Timeline manifest. We will not find a valid request then and need to adjust the seektime.
* @param {number} time
* @param {object} mediaInfo
* @param {object} representation
* @param {number} targetThreshold
*/
function getValidTimeAheadOfTargetTime(time, mediaInfo, representation, targetThreshold) {
try {

if (isNaN(time) || !mediaInfo || !representation) {
return NaN;
}

if (time < 0) {
time = 0;
}

if (isNaN(targetThreshold)) {
targetThreshold = DEFAULT_ADJUST_SEEK_TIME_THRESHOLD;
}

if (getSegmentRequestForTime(mediaInfo, representation, time)) {
return time;
}

if (representation.adaptation.period.start + representation.adaptation.period.duration < time) {
return NaN;
}

// Only look 30 seconds ahead
const end = Math.min(representation.adaptation.period.start + representation.adaptation.period.duration, time + 30);
let currentUpperTime = Math.min(time + targetThreshold, end);
let adjustedTime = NaN;
let targetRequest = null;

while (currentUpperTime <= end) {
let upperRequest = null;

if (currentUpperTime <= end) {
upperRequest = getSegmentRequestForTime(mediaInfo, representation, currentUpperTime);
}

if (upperRequest) {
adjustedTime = currentUpperTime;
targetRequest = upperRequest;
break;
}

currentUpperTime += targetThreshold;
}

if (targetRequest) {
const requestEndTime = targetRequest.startTime + targetRequest.duration;

// Keep the original start time in case it is covered by a segment
if (time >= targetRequest.startTime && requestEndTime - time > targetThreshold) {
return time;
}

// If target time is before the start of the request use request starttime
if (time < targetRequest.startTime) {
return targetRequest.startTime;
}

return Math.min(requestEndTime - targetThreshold, adjustedTime);
}

return adjustedTime;


} catch (e) {
return NaN;
}
}

function getCurrentIndex() {
return lastSegment ? lastSegment.index : -1;
}
Expand All @@ -402,7 +479,8 @@ function DashHandler(config) {
isLastSegmentRequested,
reset,
getNextSegmentRequestIdempotent,
getValidSeekTimeCloseToTargetTime
getValidTimeCloseToTargetTime,
getValidTimeAheadOfTargetTime
};

setup();
Expand Down
29 changes: 24 additions & 5 deletions src/streaming/StreamProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,12 +435,31 @@ function StreamProcessor(config) {
function _noMediaRequestGenerated(rescheduleIfNoRequest) {
const representation = representationController.getCurrentRepresentation();

// If this statement is true we are stuck. A static manifest does not change and we did not find a valid request for the target time
// If this statement is true we might be stuck. A static manifest does not change and we did not find a valid request for the target time
// There is no point in trying again. We need to adjust the time in order to find a valid request. This can happen if the user/app seeked into a gap.
if (settings.get().streaming.gaps.enableSeekFix && !isDynamic && shouldUseExplicitTimeForRequest && (playbackController.isSeeking() || playbackController.getTime() === 0)) {
const adjustedTime = dashHandler.getValidSeekTimeCloseToTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold);
if (!isNaN(adjustedTime)) {
playbackController.seek(adjustedTime, false, false);
// For dynamic manifests this can also happen especially if we jump over the gap in the previous period and are using SegmentTimeline and in case there is a positive eptDelta at the beginning of the period we are stuck.
if (settings.get().streaming.gaps.enableSeekFix && (shouldUseExplicitTimeForRequest || playbackController.getTime() === 0)) {
let adjustedTime;
if (!isDynamic) {
adjustedTime = dashHandler.getValidTimeCloseToTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold);
} else if (isDynamic && representation.segmentInfoType === DashConstants.SEGMENT_TIMELINE) {
// If we find a valid request ahead of the current time then we are in a gap. Segments are only added at the end of the timeline
adjustedTime = dashHandler.getValidTimeAheadOfTargetTime(bufferingTime, mediaInfo, representation, settings.get().streaming.gaps.threshold,);
}
if (!isNaN(adjustedTime) && adjustedTime !== bufferingTime) {
if (playbackController.isSeeking() || playbackController.getTime() === 0) {
// If we are seeking then playback is stalled. Do a seek to get out of this situation
logger.warn(`Adjusting playback time ${adjustedTime} because of gap in the manifest. Seeking by ${adjustedTime - bufferingTime}`);
playbackController.seek(adjustedTime, false, false);
} else {
// If we are not seeking we should still be playing but we cant find anything to buffer. So we adjust the buffering time and leave the gap jump to the GapController
logger.warn(`Adjusting buffering time ${adjustedTime} because of gap in the manifest. Adjusting time by ${adjustedTime - bufferingTime}`);
setExplicitBufferingTime(adjustedTime)

if (rescheduleIfNoRequest) {
_noValidRequest();
}
}
return;
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/streaming/controllers/StreamController.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,10 @@ function StreamController() {
const streamsInfo = adapter.getStreamsInfo();

if (!activeStream && streamsInfo.length === 0) {
throw new Error('There are no streams');
throw new Error('There are no periods in the MPD');
}

if (activeStream) {
if (activeStream && streamsInfo.length > 0) {
dashMetrics.updateManifestUpdateInfo({
currentTime: playbackController.getTime(),
buffered: videoModel.getBufferRange(),
Expand Down Expand Up @@ -268,7 +268,7 @@ function StreamController() {
})

} catch (e) {
errHandler.error(new DashJSError(Errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE, e.message + 'nostreamscomposed', manifestModel.getValue()));
errHandler.error(new DashJSError(Errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE, e.message + ' nostreamscomposed', manifestModel.getValue()));
hasInitialisationError = true;
reset();
}
Expand Down Expand Up @@ -1065,6 +1065,11 @@ function StreamController() {
* @private
*/
function _filterOutdatedStreams(streamsInfo) {
if (streamsInfo.length === 0) {
logger.warn(`No periods included in the current manifest. Skipping the filtering of outdated stream objects.`);
return;
}

streams = streams.filter((stream) => {
const isStillIncluded = streamsInfo.filter((sInfo) => {
return sInfo.id === stream.getId();
Expand Down
Loading