Skip to content

Commit

Permalink
Feature mp optimizations (#3655)
Browse files Browse the repository at this point in the history
* Configurable threshold in GapController.js

* Use promises for updating the internal info after an MPD update to avoid race conditions
  • Loading branch information
dsilhavy authored Jun 3, 2021
1 parent be8f2b7 commit 2bddc43
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 131 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ declare namespace dashjs {
jumpGaps?: boolean,
jumpLargeGaps?: boolean,
smallGapLimit?: number,
threshold?:number
},
utcSynchronization?: {
useManifestDateHeaderTimeSource?: boolean,
Expand Down
6 changes: 6 additions & 0 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* jumpGaps: true,
* jumpLargeGaps: true,
* smallGapLimit: 1.5,
* threshold: 0.3
* },
* utcSynchronization: {
* useManifestDateHeaderTimeSource: true,
Expand Down Expand Up @@ -332,6 +333,10 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* Sets whether player should jump large gaps (discontinuities) in the buffer.
* @property {number} [smallGapLimit=1.8]
* Time in seconds for a gap to be considered small.
* @property {number} [threshold=0.3]
* Threshold at which the gap handling is executed. If currentRangeEnd - currentTime < threshold the gap jump will be triggered.
* For live stream the jump might be delayed to keep a consistent live edge.
* Note that the amount of buffer at which platforms automatically stall might differ.
*/

/**
Expand Down Expand Up @@ -736,6 +741,7 @@ function Settings() {
jumpGaps: true,
jumpLargeGaps: true,
smallGapLimit: 1.5,
threshold: 0.3
},
utcSynchronization: {
useManifestDateHeaderTimeSource: true,
Expand Down
80 changes: 46 additions & 34 deletions src/streaming/Stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,47 +806,59 @@ function Stream(config) {
}

function updateData(updatedStreamInfo) {
isUpdating = true;
streamInfo = updatedStreamInfo;

eventBus.trigger(Events.STREAM_UPDATED, { streamInfo: streamInfo });
return new Promise((resolve) => {
isUpdating = true;
streamInfo = updatedStreamInfo;

if (eventController) {
addInlineEvents();
}
if (eventController) {
addInlineEvents();
}

for (let i = 0, ln = streamProcessors.length; i < ln; i++) {
let streamProcessor = streamProcessors[i];
const currentMediaInfo = streamProcessor.getMediaInfo();
streamProcessor.updateStreamInfo(streamInfo);
let allMediaForType = adapter.getAllMediaInfoForType(streamInfo, streamProcessor.getType());
// Check if AdaptationSet has not been removed in MPD update
if (allMediaForType) {
// Remove the current mediaInfo objects before adding the updated ones
streamProcessor.clearMediaInfoArray();
for (let j = 0; j < allMediaForType.length; j++) {
const mInfo = allMediaForType[j];
streamProcessor.addMediaInfo(allMediaForType[j]);
if (adapter.areMediaInfosEqual(currentMediaInfo, mInfo)) {
abrController.updateTopQualityIndex(mInfo);
streamProcessor.selectMediaInfo(mInfo)
let promises = [];
for (let i = 0, ln = streamProcessors.length; i < ln; i++) {
let streamProcessor = streamProcessors[i];
const currentMediaInfo = streamProcessor.getMediaInfo();
promises.push(streamProcessor.updateStreamInfo(streamInfo));
let allMediaForType = adapter.getAllMediaInfoForType(streamInfo, streamProcessor.getType());
// Check if AdaptationSet has not been removed in MPD update
if (allMediaForType) {
// Remove the current mediaInfo objects before adding the updated ones
streamProcessor.clearMediaInfoArray();
for (let j = 0; j < allMediaForType.length; j++) {
const mInfo = allMediaForType[j];
streamProcessor.addMediaInfo(allMediaForType[j]);
if (adapter.areMediaInfosEqual(currentMediaInfo, mInfo)) {
abrController.updateTopQualityIndex(mInfo);
promises.push(streamProcessor.selectMediaInfo(mInfo))
}
}
}
}
}

if (trackChangedEvent) {
let mediaInfo = trackChangedEvent.newMediaInfo;
if (mediaInfo.type !== Constants.FRAGMENTED_TEXT) {
let processor = getProcessorForMediaInfo(trackChangedEvent.oldMediaInfo);
if (!processor) return;
processor.prepareTrackSwitch();
trackChangedEvent = undefined;
}
}
Promise.all(promises)
.then(() => {
promises = [];

if (trackChangedEvent) {
let mediaInfo = trackChangedEvent.newMediaInfo;
if (mediaInfo.type !== Constants.FRAGMENTED_TEXT) {
let processor = getProcessorForMediaInfo(trackChangedEvent.oldMediaInfo);
if (!processor) return;
promises.push(processor.prepareTrackSwitch());
trackChangedEvent = undefined;
}
}

isUpdating = false;
_checkIfInitializationCompleted();
return Promise.all(promises)
})
.then(() => {
isUpdating = false;
_checkIfInitializationCompleted();
eventBus.trigger(Events.STREAM_UPDATED, { streamInfo: streamInfo });
resolve();
})

})
}

function isMediaCodecCompatible(newStream, previousStream = null) {
Expand Down
110 changes: 59 additions & 51 deletions src/streaming/StreamProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,9 @@ function StreamProcessor(config) {
function updateStreamInfo(newStreamInfo) {
streamInfo = newStreamInfo;
if (!isBufferingCompleted()) {
bufferController.updateAppendWindow();
return bufferController.updateAppendWindow();
}
return Promise.resolve();
}

function getStreamInfo() {
Expand Down Expand Up @@ -910,61 +911,68 @@ function StreamProcessor(config) {
}

function prepareTrackSwitch() {
logger.debug(`Preparing track switch for type ${type}`);
const shouldReplace = type === Constants.FRAGMENTED_TEXT || (settings.get().streaming.trackSwitchMode[type] === Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE && playbackController.getTimeToStreamEnd(streamInfo) > settings.get().streaming.buffer.stallThreshold);
return new Promise((resolve) => {
logger.debug(`Preparing track switch for type ${type}`);
const shouldReplace = type === Constants.FRAGMENTED_TEXT || (settings.get().streaming.trackSwitchMode[type] === Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE && playbackController.getTimeToStreamEnd(streamInfo) > settings.get().streaming.buffer.stallThreshold);

// when buffering is completed and we are not supposed to replace anything do nothing.
// Still we need to trigger preloading again and call change type in case user seeks back before transitioning to next period
if (bufferController.getIsBufferingCompleted() && !shouldReplace) {
bufferController.prepareForNonReplacementTrackSwitch(mediaInfo.codec)
.then(() => {
eventBus.trigger(Events.BUFFERING_COMPLETED, {}, { streamId: streamInfo.id, mediaType: type })
})
.catch(() => {
eventBus.trigger(Events.BUFFERING_COMPLETED, {}, { streamId: streamInfo.id, mediaType: type })
})
return;
}
// when buffering is completed and we are not supposed to replace anything do nothing.
// Still we need to trigger preloading again and call change type in case user seeks back before transitioning to next period
if (bufferController.getIsBufferingCompleted() && !shouldReplace) {
bufferController.prepareForNonReplacementTrackSwitch(mediaInfo.codec)
.then(() => {
eventBus.trigger(Events.BUFFERING_COMPLETED, {}, { streamId: streamInfo.id, mediaType: type })
})
.catch(() => {
eventBus.trigger(Events.BUFFERING_COMPLETED, {}, { streamId: streamInfo.id, mediaType: type })
})
resolve();
return;
}

// We stop the schedule controller and signal a track switch. That way we request a new init segment next
scheduleController.clearScheduleTimer();
scheduleController.setSwitchTrack(true);
// We stop the schedule controller and signal a track switch. That way we request a new init segment next
scheduleController.clearScheduleTimer();
scheduleController.setSwitchTrack(true);

// when we are supposed to replace it does not matter if buffering is already completed
if (shouldReplace) {
// Inform other classes like the GapController that we are replacing existing stuff
eventBus.trigger(Events.BUFFER_REPLACEMENT_STARTED, {
mediaType: type,
streamId: streamInfo.id
}, { mediaType: type, streamId: streamInfo.id });
// when we are supposed to replace it does not matter if buffering is already completed
if (shouldReplace) {
// Inform other classes like the GapController that we are replacing existing stuff
eventBus.trigger(Events.BUFFER_REPLACEMENT_STARTED, {
mediaType: type,
streamId: streamInfo.id
}, { mediaType: type, streamId: streamInfo.id });

// Abort the current request it will be removed from the buffer anyways
fragmentModel.abortRequests();
// Abort the current request it will be removed from the buffer anyways
fragmentModel.abortRequests();

// Abort appending segments to the buffer. Also adjust the appendWindow as we might have been in the progress of prebuffering stuff.
bufferController.prepareForReplacementTrackSwitch(mediaInfo.codec)
.then(() => {
// Timestamp offset couldve been changed by preloading period
const representationInfo = getRepresentationInfo();
return bufferController.updateBufferTimestampOffset(representationInfo);
})
.then(() => {
_bufferClearedForReplacement();
resolve();
})
.catch(() => {
_bufferClearedForReplacement();
resolve();
});
} else {
// We do not replace anything that is already in the buffer. Still we need to prepare the buffer for the track switch
bufferController.prepareForNonReplacementTrackSwitch(mediaInfo.codec)
.then(() => {
_bufferClearedForNonReplacement();
resolve();
})
.catch(() => {
_bufferClearedForNonReplacement();
resolve();
});
}
})

// Abort appending segments to the buffer. Also adjust the appendWindow as we might have been in the progress of prebuffering stuff.
bufferController.prepareForReplacementTrackSwitch(mediaInfo.codec)
.then(() => {
// Timestamp offset couldve been changed by preloading period
const representationInfo = getRepresentationInfo();
return bufferController.updateBufferTimestampOffset(representationInfo);
})
.then(() => {
_bufferClearedForReplacement();
})
.catch(() => {
_bufferClearedForReplacement();
});
} else {
// We do not replace anything that is already in the buffer. Still we need to prepare the buffer for the track switch
bufferController.prepareForNonReplacementTrackSwitch(mediaInfo.codec)
.then(() => {
_bufferClearedForNonReplacement();
})
.catch
(() => {
_bufferClearedForNonReplacement();
});
}
}

/**
Expand Down
7 changes: 3 additions & 4 deletions src/streaming/controllers/GapController.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ import Events from '../../core/events/Events';
import EventBus from '../../core/EventBus';

const GAP_HANDLER_INTERVAL = 100;
const THRESHOLD_TO_STALLS = 30;
const GAP_THRESHOLD = 0.1;
const THRESHOLD_TO_STALLS = 10;
const GAP_JUMP_WAITING_TIME_OFFSET = 0.1;

function GapController() {
Expand Down Expand Up @@ -192,7 +191,7 @@ function GapController() {

while (isNaN(nextRangeIndex) && j < ranges.length) {
const rangeEnd = j > 0 ? ranges.end(j - 1) : 0;
if (currentTime < ranges.start(j) && rangeEnd - currentTime < GAP_THRESHOLD) {
if (currentTime < ranges.start(j) && rangeEnd - currentTime < settings.get().streaming.gaps.threshold) {
nextRangeIndex = j;
}
j += 1;
Expand Down Expand Up @@ -273,7 +272,7 @@ function GapController() {

jumpTimeoutHandler = window.setTimeout(() => {
playbackController.seek(seekToPosition, true, true);
logger.warn(`Jumping gap occuring in period ${streamController.getActiveStream().getStreamId()} starting at ${start} and ending at ${seekToPosition}. Jumping by: ${timeUntilGapEnd}`);
logger.warn(`Jumping gap occuring in period ${streamController.getActiveStream().getStreamId()} starting at ${start} and ending at ${seekToPosition}. Jumping by: ${timeUntilGapEnd - (timeToWait / 1000)}`);
jumpTimeoutHandler = null;
}, timeToWait);
}
Expand Down
Loading

0 comments on commit 2bddc43

Please sign in to comment.