Skip to content

Commit

Permalink
Fix multiperiod (#3360)
Browse files Browse the repository at this point in the history
* Move handling for non initial stream start time to getStreamForTime

* Remove unused period switch handler from BolaRule

* Fix type in variable

* Check for manifest duration before assigning it to the media source

* WiP: Multiperiod gap handling

* WiP: Multiperiod gap handling

* WiP: Multiperiod gap handling

* DashIF multiperiod sample working again

* Elemental stream worked for a single testrun and video only

* Video only prebuffering multiple streams instead of a single one

* Only update timestampOffset if buffering is not completed

* First working version with audio in Chrome

* Prebuffering with audio should work now. Adjusted BufferLevelRule because audio buffering is handled different than video buffering

* Add appendWindow update again and improve logic to calculate next streams

* Potential fix for VoD streams not starting at 0

* Remove console logs

* Do not deactivate stream when next stream is prebuffered

* Use global setting for appendWindow

* Add try catch for Sourcebuffer appendWindow reset

* Fix a bug in which the DashHandler was stuck when no liveEdge was found

* Add support for gap jumping when playback has stalled. This covers situations in which the upcoming periods are not prebuffered. Period switch is now performed by jumping to the beginning of the upcoming period.

* Instead of jumping to the end of the current period jump to the start of the next period to avoid small timing errors

* Gap jumping before moving to a separate GapController

* Move gap handling to GapController.js

* Change buffer level rule for audio

* Specific event for gap caused playback seek

* Deactivate postponeTimePeriod for multiperiod, need to understand the implications for multiperiod though

* Fix an error for the bitrate metric in the ref client when playing multiperiod streams

* Fix linting errors

* Move setting of availabilityWindow to BufferController.js

* Fix BufferLevelRule unit test

* Add availabilityWindow to TextBufferController.js

* Check if interval handler already exist

* Small changes in the GapController.js

* Change log level for ref client

* Fix wrong JSDoc in Settings.js

* Rename useAppendWindow in index.d.ts

* Add jumpLargeGaps to JsDoc and index.d.ts

* Fix JsDoc description for gap caused playback seek

* Remove unnecessary reference to streamInfo

* Check for stalls using settings wallclock time parameter

* Remove streamInfo parameter from TextBufferController.js

* Use constants for interval handler
  • Loading branch information
dsilhavy committed Aug 25, 2020
1 parent 2e11dca commit f003ef4
Show file tree
Hide file tree
Showing 21 changed files with 660 additions and 290 deletions.
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ declare namespace dashjs {
bufferToKeep?: number;
bufferAheadToKeep?: number;
jumpGaps?: boolean;
jumpLargeGaps?: boolean;
smallGapLimit?: number;
stableBufferTime?: number;
bufferTimeAtTopQuality?: number;
Expand All @@ -125,7 +126,7 @@ declare namespace dashjs {
keepProtectionMediaKeys?: boolean;
useManifestDateHeaderTimeSource?: boolean;
useSuggestedPresentationDelay?: boolean;
useAppendWindowEnd?: boolean,
useAppendWindow?: boolean,
manifestUpdateRetryInterval?: number;
liveCatchUpMinDrift?: number;
liveCatchUpMaxDrift?: number;
Expand Down
11 changes: 6 additions & 5 deletions samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
// Add provider to beginning of each Vector
var provider = data.provider;
$scope.availableStreams.forEach(function (item) {
if(item && item.submenu && item.submenu.length > 0) {
if (item && item.submenu && item.submenu.length > 0) {
item.submenu.forEach(function (subitem) {
if(subitem && subitem.name && subitem.provider && provider[subitem.provider] && provider[subitem.provider].acronym) {
subitem.name = '[' + provider[subitem.provider].acronym + '] ' + subitem.name;
}
if (subitem && subitem.name && subitem.provider && provider[subitem.provider] && provider[subitem.provider].acronym) {
subitem.name = '[' + provider[subitem.provider].acronym + '] ' + subitem.name;
}
});
}
});
Expand Down Expand Up @@ -853,7 +853,8 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
var dashAdapter = $scope.player.getDashAdapter();

if (dashMetrics && $scope.streamInfo) {
var periodIdx = $scope.streamInfo.index;
var period = dashAdapter.getPeriodById($scope.streamInfo.id);
var periodIdx = period ? period.index : $scope.streamInfo.index;

var maxIndex = dashAdapter.getMaxIndexForBufferType(type, periodIdx);
var repSwitch = dashMetrics.getCurrentRepresentationSwitch(type, true);
Expand Down
2 changes: 1 addition & 1 deletion samples/dash-if-reference-player/dashjs_config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"debug": {
"logLevel": 4
"logLevel": 5
},
"streaming": {
"metricsMaxListDepth": 50,
Expand Down
11 changes: 7 additions & 4 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* bufferToKeep: 20,
* bufferAheadToKeep: 80,
* jumpGaps: true,
* jumpLargeGaps: true,
* smallGapLimit: 1.5,
* stableBufferTime: 12,
* bufferTimeAtTopQuality: 30,
Expand All @@ -72,7 +73,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* keepProtectionMediaKeys: false,
* useManifestDateHeaderTimeSource: true,
* useSuggestedPresentationDelay: true,
* useAppendWindowEnd: true,
* useAppendWindow: true,
* manifestUpdateRetryInterval: 100,
* liveCatchUpMinDrift: 0.02,
* liveCatchUpMaxDrift: 0,
Expand Down Expand Up @@ -243,6 +244,7 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* Allows you to modify the buffer ahead of current time position that is kept in source buffer in seconds.
* <pre>0|--------|currentTime|-----bufferAheadToKeep----|----bufferToPrune-----------|end|</pre>
* @property {boolean} [jumpGaps=true] Sets whether player should jump small gaps (discontinuities) in the buffer.
* @property {boolean} [jumpLargeGaps=true] 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} [stableBufferTime=12]
* The time that the internal buffer target will be set to post startup/seeks (NOT top quality).
Expand Down Expand Up @@ -270,8 +272,8 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* use of the date header will happen only after the other timing source that take precedence fail or are omitted as described.
* @property {boolean} [useSuggestedPresentationDelay=true]
* <p>Set to true if you would like to override the default live delay and honor the SuggestedPresentationDelay attribute in by the manifest.</p>
* @property {boolean} [useAppendWindowEnd=true]
* Specifies if the appendWindowEnd attribute of the MSE SourceBuffers should be set according to content duration from manifest.
* @property {boolean} [useAppendWindow=true]
* Specifies if the appendWindow attributes of the MSE SourceBuffers should be set according to content duration from manifest.
* @property {number} [manifestUpdateRetryInterval=100]
* For live streams, set the interval-frequency in milliseconds at which
* dash.js will check if the current manifest is still processed before
Expand Down Expand Up @@ -392,6 +394,7 @@ function Settings() {
bufferToKeep: 20,
bufferAheadToKeep: 80,
jumpGaps: true,
jumpLargeGaps: true,
smallGapLimit: 1.5,
stableBufferTime: 12,
bufferTimeAtTopQuality: 30,
Expand All @@ -402,7 +405,7 @@ function Settings() {
keepProtectionMediaKeys: false,
useManifestDateHeaderTimeSource: true,
useSuggestedPresentationDelay: true,
useAppendWindowEnd: true,
useAppendWindow: true,
manifestUpdateRetryInterval: 100,
liveCatchUpMinDrift: 0.02,
liveCatchUpMaxDrift: 0,
Expand Down
31 changes: 26 additions & 5 deletions src/dash/DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,14 +635,14 @@ function DashAdapter() {
/**
* Returns the bandwidth for a given representation id
* @param {number} representationId
* @param {number} periodId
* @param {number} periodIdx
* @returns {number} bandwidth
* @memberOf module:DashAdapter
* @instance
*/
function getBandwidthForRepresentation(representationId, periodId) {
function getBandwidthForRepresentation(representationId, periodIdx) {
let representation;
let period = getPeriod(periodId);
let period = getPeriod(periodIdx);

representation = findRepresentation(period, representationId);

Expand Down Expand Up @@ -678,6 +678,26 @@ function DashAdapter() {
return findMaxBufferIndex(period, bufferType);
}

/**
* Returns the voPeriod object for a given id
* @param {String} id
* @returns {object|null}
*/
function getPeriodById(id) {
if (!id || voPeriods.length === 0) {
return null;
}
const periods = voPeriods.filter((p) => {
return p.id === id;
});

if (periods && periods.length > 0) {
return periods[0];
}

return null;
}

function reset() {
voPeriods = [];
voAdaptations = {};
Expand Down Expand Up @@ -821,8 +841,8 @@ function DashAdapter() {
}
}

function getPeriod(periodId) {
return voPeriods.length > 0 ? voPeriods[0].mpd.manifest.Period_asArray[periodId] : null;
function getPeriod(periodIdx) {
return voPeriods.length > 0 ? voPeriods[0].mpd.manifest.Period_asArray[periodIdx] : null;
}

function findRepresentationIndex(period, representationId) {
Expand Down Expand Up @@ -915,6 +935,7 @@ function DashAdapter() {
getCodec: getCodec,
getVoAdaptations: getVoAdaptations,
getVoPeriods: getVoPeriods,
getPeriodById,
setCurrentMediaInfo: setCurrentMediaInfo,
reset: reset
};
Expand Down
3 changes: 1 addition & 2 deletions src/dash/DashHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ function DashHandler(config) {
isFinished = endTime >= duration;
}
}

return isFinished;
}

Expand Down Expand Up @@ -329,7 +328,7 @@ function DashHandler(config) {
// check that there is a segment in this index
const segment = segmentsController.getSegmentByIndex(representation, indexToRequest, lastSegment ? lastSegment.mediaStartTime : -1);
if (!segment && isEndlessMedia(representation) && !dynamicStreamCompleted) {
logger.debug('No segment found at index: ' + indexToRequest + '. Wait for next loop');
logger.debug(getType() + ' No segment found at index: ' + indexToRequest + '. Wait for next loop');
return null;
} else {
if (segment) {
Expand Down
2 changes: 1 addition & 1 deletion src/dash/controllers/RepresentationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ function RepresentationController(config) {
err,
repSwitch;

if (r.adaptation.period.mpd.manifest.type === dashConstants.DYNAMIC && !r.adaptation.period.mpd.manifest.ignorePostponeTimePeriod) {
if (r.adaptation.period.mpd.manifest.type === dashConstants.DYNAMIC && !r.adaptation.period.mpd.manifest.ignorePostponeTimePeriod && playbackController.getStreamController().getStreams().length <= 1) {
// We must put things to sleep unless till e.g. the startTime calculation in ScheduleController.onLiveEdgeSearchCompleted fall after the segmentAvailabilityRange.start
postponeTimePeriod = getRepresentationUpdatePostponeTimePeriod(r, streamInfo);
}
Expand Down
18 changes: 18 additions & 0 deletions src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import DashConstants from '../dash/constants/DashConstants';
import MetricsConstants from './constants/MetricsConstants';
import PlaybackController from './controllers/PlaybackController';
import StreamController from './controllers/StreamController';
import GapController from './controllers/GapController';
import MediaController from './controllers/MediaController';
import BaseURLController from './controllers/BaseURLController';
import ManifestLoader from './ManifestLoader';
Expand Down Expand Up @@ -145,6 +146,7 @@ function MediaPlayer() {
baseURLController,
capabilities,
streamController,
gapController,
playbackController,
dashMetrics,
manifestModel,
Expand Down Expand Up @@ -196,6 +198,9 @@ function MediaPlayer() {
if (config.streamController) {
streamController = config.streamController;
}
if (config.gapController) {
gapController = config.gapController;
}
if (config.playbackController) {
playbackController = config.playbackController;
}
Expand Down Expand Up @@ -272,6 +277,10 @@ function MediaPlayer() {
streamController = StreamController(context).getInstance();
}

if (!gapController) {
gapController = GapController(context).getInstance();
}

adapter = DashAdapter(context).getInstance();

manifestModel = ManifestModel(context).getInstance();
Expand Down Expand Up @@ -1903,6 +1912,7 @@ function MediaPlayer() {
streamingInitialized = false;
adapter.reset();
streamController.reset();
gapController.reset();
playbackController.reset();
abrController.reset();
mediaController.reset();
Expand Down Expand Up @@ -1951,6 +1961,13 @@ function MediaPlayer() {
baseURLController: baseURLController
});

gapController.setConfig({
settings,
playbackController,
streamController,
videoModel
});

playbackController.setConfig({
streamController: streamController,
dashMetrics: dashMetrics,
Expand Down Expand Up @@ -1990,6 +2007,7 @@ function MediaPlayer() {

// initialises controller
streamController.initialize(autoPlay, protectionData);
gapController.initialize();
cmcdModel.initialize();
}

Expand Down
6 changes: 6 additions & 0 deletions src/streaming/MediaPlayerEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,12 @@ class MediaPlayerEvents extends EventsBase {
* @event MediaPlayerEvents#MANIFEST_VALIDITY_CHANGED
*/
this.MANIFEST_VALIDITY_CHANGED = 'manifestValidityChanged';

/**
* A gap occured in the timeline which requires a seek
* @event MediaPlayerEvents#MANIFEST_VALIDITY_CHANGED
*/
this.GAP_CAUSED_PLAYBACK_SEEK = 'gapCausedPlaybackSeek';
}
}

Expand Down
46 changes: 35 additions & 11 deletions src/streaming/SourceBufferSink.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const MAX_ALLOWED_DISCONTINUITY = 0.1; // 100 milliseconds
* @ignore
* @implements FragmentSink
*/
function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendWindowEnd, oldBuffer) {
function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, oldBuffer) {
const context = this.context;
const eventBus = EventBus(context).getInstance();

Expand All @@ -56,7 +56,6 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
let callbacks = [];
let appendQueue = [];
let onAppended = onAppendedCallback;
let setAppendWindowEnd = (useAppendWindowEnd === false) ? false : true;

function setup() {
logger = Debug(context).getInstance().getLogger(instance);
Expand All @@ -77,10 +76,6 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
buffer.changeType(codec);
}

if (setAppendWindowEnd && buffer) {
buffer.appendWindowEnd = mediaSource.duration;
}

const CHECK_INTERVAL = 50;
// use updateend event if possible
if (typeof buffer.addEventListener === 'function') {
Expand Down Expand Up @@ -116,7 +111,12 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
buffer.removeEventListener('abort', errHandler, false);
}
clearInterval(intervalId);
buffer.appendWindowEnd = Infinity;
try {
buffer.appendWindowEnd = Infinity;
buffer.appendWindowStart = 0;
} catch (e) {
logger.error('Failed to reset append window');
}
if (!keepBuffer) {
try {
if (!buffer.getClassName || buffer.getClassName() !== 'TextSourceBuffer') {
Expand Down Expand Up @@ -181,11 +181,34 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
function updateTimestampOffset(MSETimeOffset) {
if (buffer.timestampOffset !== MSETimeOffset && !isNaN(MSETimeOffset)) {
waitForUpdateEnd(() => {
if (MSETimeOffset < 0) {
MSETimeOffset += 0.001;
}
buffer.timestampOffset = MSETimeOffset;
});
}
}

function updateAppendWindow(sInfo) {
if (!buffer) {
return;
}
waitForUpdateEnd(() => {
let appendWindowEnd = mediaSource.duration;
let appendWindowStart = 0;
if (sInfo.start && sInfo.duration && isFinite(sInfo.duration)) {
appendWindowEnd = sInfo.start + sInfo.duration;
}
if (sInfo.start) {
appendWindowStart = sInfo.start;
}
buffer.appendWindowStart = 0;
buffer.appendWindowEnd = appendWindowEnd;
buffer.appendWindowStart = appendWindowStart;
logger.debug(`Updated append window for ${mediaInfo.type}. Set start to ${buffer.appendWindowStart} and end to ${buffer.appendWindowEnd}`);
});
}

function remove(start, end, forceRemoval) {
const sourceBufferSink = this;
// make sure that the given time range is correct. Otherwise we will get InvalidAccessError
Expand Down Expand Up @@ -221,7 +244,7 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
if (appendQueue.length > 0) {
isAppendingInProgress = true;
const nextChunk = appendQueue[0];
appendQueue.splice(0,1);
appendQueue.splice(0, 1);
let oldRanges = [];
const afterSuccess = function () {
// Safari sometimes drops a portion of a buffer after appending. Handle these situations here
Expand Down Expand Up @@ -284,10 +307,10 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
}

function isChunkAlignedWithRange(oldRanges, chunk) {
for (let i = 0; i < oldRanges.length; i++ ) {
for (let i = 0; i < oldRanges.length; i++) {
const start = Math.round(oldRanges.start(i));
const end = Math.round(oldRanges.end(i));
if (end === chunk.start || start === chunk.end || (chunk.start >= start && chunk.end <= end) ) {
if (end === chunk.start || start === chunk.end || (chunk.start >= start && chunk.end <= end)) {
return true;
}
}
Expand Down Expand Up @@ -354,7 +377,8 @@ function SourceBufferSink(mediaSource, mediaInfo, onAppendedCallback, useAppendW
reset: reset,
updateTimestampOffset: updateTimestampOffset,
hasDiscontinuitiesAfter: hasDiscontinuitiesAfter,
waitForUpdateEnd: waitForUpdateEnd
waitForUpdateEnd: waitForUpdateEnd,
updateAppendWindow
};

setup();
Expand Down
Loading

0 comments on commit f003ef4

Please sign in to comment.