Skip to content

Commit

Permalink
Merge pull request #2418 from epiclabsDASH/jump-gaps
Browse files Browse the repository at this point in the history
Jump gaps feature
  • Loading branch information
epiclabsDASH committed Feb 16, 2018
2 parents b2e2515 + 16e908c commit f3fca64
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 23 deletions.
4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ declare namespace dashjs {
attachVideoContainer(container: HTMLElement): void;
attachTTMLRenderingDiv(div: HTMLDivElement): void;
getCurrentTextTrackIndex(): number;
setJumpGaps(value: boolean): void;
getJumpGaps(): boolean;
setSmallGapLimit(value: number): void;
getSmallGapLimit(): number;
reset(): void;
}

Expand Down
2 changes: 1 addition & 1 deletion samples/dash-if-reference-player/app/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ a:hover {
OPTIONS PANEL
*****************************/
.options-show {
height: 235px;
height: 260px;
transition-property: all;
transition-duration: .5s;
}
Expand Down
8 changes: 7 additions & 1 deletion samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.loopSelected = true;
$scope.scheduleWhilePausedSelected = true;
$scope.localStorageSelected = true;
$scope.jumpGapsSelected = true;
$scope.fastSwitchSelected = true;
$scope.ABRStrategy = 'abrDynamic';

Expand All @@ -214,7 +215,8 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.player = dashjs.MediaPlayer().create(); /* jshint ignore:line */

$scope.player.initialize($scope.video, null, $scope.autoPlaySelected);
$scope.player.setFastSwitchEnabled(true);
$scope.player.setFastSwitchEnabled($scope.fastSwitchSelected);
$scope.player.setJumpGaps($scope.jumpGapsSelected);
$scope.player.attachVideoContainer(document.getElementById('videoContainer'));
// Add HTML-rendered TTML subtitles except for Firefox < v49 (issue #1164)
if (doesTimeMarchesOn()) {
Expand Down Expand Up @@ -338,6 +340,10 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.player.enableLastMediaSettingsCaching($scope.localStorageSelected);
};

$scope.toggleJumpGaps = function () {
$scope.player.setJumpGaps($scope.jumpGapsSelected);
};

$scope.setStream = function (item) {
$scope.selectedItem = JSON.parse(JSON.stringify(item));
};
Expand Down
5 changes: 5 additions & 0 deletions samples/dash-if-reference-player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
<input type="checkbox" id="localStorageCB" ng-model="localStorageSelected" ng-change="toggleLocalStorage()" ng-checked="localStorageSelected">
Allow Local Storage
</label>
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
title="Enables jump small gaps (discontinuities) in the media streams">
<input type="checkbox" id="jumpGapsCB" ng-model="jumpGapsSelected" ng-change="toggleJumpGaps()" ng-checked="jumpGapsSelected">
Jump Small Gaps
</label>
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
title="Enables faster ABR switching (time to render). Only when the new quality is higher than the current.">
<input type="checkbox" id="fastSwitchCB" ng-model="fastSwitchSelected" ng-change="toggleFastSwitch()" ng-checked="fastSwitchSelected">
Expand Down
50 changes: 50 additions & 0 deletions src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,52 @@ function MediaPlayer() {
return mediaPlayerModel.getXHRWithCredentialsForType(type);
}

/**
* Sets whether player should jump small gaps (discontinuities) in the buffer.
*
* @param {boolean} value
* @default false
* @memberof module:MediaPlayer
* @instance
*
*/
function setJumpGaps(value) {
mediaPlayerModel.setJumpGaps(value);
}

/**
* Gets current status of jump gaps feature.
* @returns {boolean} The current jump gaps state.
* @memberof module:MediaPlayer
* @instance
*/
function getJumpGaps() {
return mediaPlayerModel.getJumpGaps();
}

/**
* Time in seconds for a gap to be considered small.
*
* @param {boolean} value
* @default 0.8
* @memberof module:MediaPlayer
* @instance
*
*/
function setSmallGapLimit(value) {
mediaPlayerModel.setSmallGapLimit(value);
}

/**
* Time in seconds for a gap to be considered small.
* @returns {boolean} Current small gap limit
* @memberof module:MediaPlayer
* @instance
*/
function getSmallGapLimit() {
return mediaPlayerModel.getSmallGapLimit();
}

/*
---------------------------------------------------------------------------
Expand Down Expand Up @@ -2746,6 +2792,10 @@ function MediaPlayer() {
setManifestLoaderRetryInterval: setManifestLoaderRetryInterval,
setXHRWithCredentialsForType: setXHRWithCredentialsForType,
getXHRWithCredentialsForType: getXHRWithCredentialsForType,
setJumpGaps: setJumpGaps,
getJumpGaps: getJumpGaps,
setSmallGapLimit: setSmallGapLimit,
getSmallGapLimit: getSmallGapLimit,
setLongFormContentDurationThreshold: setLongFormContentDurationThreshold,
setSegmentOverlapToleranceTime: setSegmentOverlapToleranceTime,
setCacheLoadThresholdForType: setCacheLoadThresholdForType,
Expand Down
95 changes: 77 additions & 18 deletions src/streaming/controllers/StreamController.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ import BaseURLController from './BaseURLController';
import MediaSourceController from './MediaSourceController';

function StreamController() {
let context = this.context;
let log = Debug(context).getInstance().log;
let eventBus = EventBus(context).getInstance();
// Check whether there is a gap every 20 wallClockUpdateEvent times
const STALL_THRESHOLD_TO_CHECK_GAPS = 20;

const context = this.context;
const log = Debug(context).getInstance().log;
const eventBus = EventBus(context).getInstance();

let instance,
capabilities,
Expand Down Expand Up @@ -92,7 +95,9 @@ function StreamController() {
videoTrackDetected,
audioTrackDetected,
isStreamBufferingCompleted,
playbackEndedTimerId;
playbackEndedTimerId,
wallclockTicked,
lastPlaybackTime;

function setup() {
timeSyncController = TimeSyncController(context).getInstance();
Expand Down Expand Up @@ -134,6 +139,7 @@ function StreamController() {
eventBus.on(Events.MANIFEST_UPDATED, onManifestUpdated, this);
eventBus.on(Events.STREAM_BUFFERING_COMPLETED, onStreamBufferingCompleted, this);
eventBus.on(Events.MANIFEST_VALIDITY_CHANGED, onManifestValidityChanged, this);
eventBus.on(Events.WALLCLOCK_TIME_UPDATED, onWallclockTimeUpdated, this);
eventBus.on(MediaPlayerEvents.METRIC_ADDED, onMetricAdded, this);
}

Expand All @@ -150,6 +156,67 @@ function StreamController() {
}
}

function onWallclockTimeUpdated(e) {
if (!mediaPlayerModel.getJumpGaps() || isPaused || isStreamSwitchingInProgress ||
!activeStream || playbackController.isSeeking()) {
return;
}

wallclockTicked++;
if (wallclockTicked >= STALL_THRESHOLD_TO_CHECK_GAPS) {
const currentTime = playbackController.getTime();
if (lastPlaybackTime === currentTime) {
log('Warning - Playback stalled for', (wallclockTicked * mediaPlayerModel.getWallclockTimeUpdateInterval()) ,'milliseconds. Time for a jump?');
jumpGap(currentTime, e.timeToEnd);
} else {
lastPlaybackTime = currentTime;
}
wallclockTicked = 0;
}
}

function jumpGap(time, timeToStreamEnd) {
const streamProcessors = activeStream.getProcessors();
let seekToPosition;

// Find out what is the right time position to jump to taking
// into account state of buffer
for (let i = 0; i < streamProcessors.length; i ++) {
const mediaBuffer = streamProcessors[i].getBuffer();
const ranges = sourceBufferController.getAllRanges(mediaBuffer);
let nextRangeStartTime;
if (!ranges || ranges.length <= 1) continue;

// Get the range just after current time position
for (let j = 0; j < ranges.length; j++) {
if (time < ranges.start(j)) {
nextRangeStartTime = ranges.start(j);
break;
}
}

if (nextRangeStartTime > 0) {
const gap = nextRangeStartTime - time;
if (gap > 0 && gap <= mediaPlayerModel.getSmallGapLimit()) {
if (seekToPosition === undefined || nextRangeStartTime > seekToPosition) {
seekToPosition = nextRangeStartTime;
}
}
}
}

// If there is a safe position to jump to, do the seeking
if (seekToPosition > 0) {
if (!isNaN(timeToStreamEnd) && seekToPosition >= time + timeToStreamEnd) {
log('Jumping media gap (discontinuity) at time ', time, '. Jumping to end of the stream');
onEnded();
} else {
log('Jumping media gap (discontinuity) at time ', time, '. Jumping to time position', seekToPosition);
playbackController.seek(seekToPosition);
}
}
}

function onPlaybackSeeking(e) {
const seekingStream = getStreamForTime(e.seekTime);

Expand Down Expand Up @@ -319,7 +386,6 @@ function StreamController() {
}

function switchStream(oldStream, newStream, seekTime) {

if (isStreamSwitchingInProgress || !newStream || oldStream === newStream) return;
isStreamSwitchingInProgress = true;

Expand All @@ -340,7 +406,6 @@ function StreamController() {
}

function openMediaSource(seekTime, oldStream) {

let sourceUrl;

function onMediaSourceOpen() {
Expand Down Expand Up @@ -369,7 +434,6 @@ function StreamController() {
}

function activateStream(seekTime) {

activeStream.activate(mediaSource);

audioTrackDetected = checkTrackPresence(Constants.AUDIO);
Expand Down Expand Up @@ -413,7 +477,6 @@ function StreamController() {
}

function composeStreams() {

try {
const streamsInfo = adapter.getStreamsInfo();
if (streamsInfo.length === 0) {
Expand All @@ -429,14 +492,12 @@ function StreamController() {
});

for (let i = 0, ln = streamsInfo.length; i < ln; i++) {

// If the Stream object does not exist we probably loaded the manifest the first time or it was
// introduced in the updated manifest, so we need to create a new Stream and perform all the initialization operations
const streamInfo = streamsInfo[i];
let stream = getComposedStream(streamInfo);

if (!stream) {

stream = Stream(context).create({
manifestModel: manifestModel,
dashManifestModel: dashManifestModel,
Expand All @@ -460,7 +521,6 @@ function StreamController() {
});
streams.push(stream);
stream.initialize(streamInfo, protectionController);

} else {
stream.updateData(streamInfo);
}
Expand Down Expand Up @@ -509,10 +569,10 @@ function StreamController() {
if (!e.error) {
//Since streams are not composed yet , need to manually look up useCalculatedLiveEdgeTime to detect if stream
//is SegmentTimeline to avoid using time source
let manifest = e.manifest;
const manifest = e.manifest;
adapter.updatePeriods(manifest);
let streamInfo = adapter.getStreamsInfo(manifest)[0];
let mediaInfo = (
const streamInfo = adapter.getStreamsInfo(manifest)[0];
const mediaInfo = (
adapter.getMediaInfoForType(streamInfo, Constants.VIDEO) ||
adapter.getMediaInfoForType(streamInfo, Constants.AUDIO)
);
Expand Down Expand Up @@ -582,7 +642,7 @@ function StreamController() {
if (playListMetrics) {
if (activeStream) {
activeStream.getProcessors().forEach(p => {
let ctrlr = p.getScheduleController();
const ctrlr = p.getScheduleController();
if (ctrlr) {
ctrlr.finalisePlayList(time, reason);
}
Expand Down Expand Up @@ -611,7 +671,6 @@ function StreamController() {


function onPlaybackError(e) {

if (!e.error) return;

let msg = '';
Expand Down Expand Up @@ -768,6 +827,7 @@ function StreamController() {
playListMetrics = null;
playbackEndedTimerId = undefined;
isStreamBufferingCompleted = false;
wallclockTicked = 0;
}

function reset() {
Expand All @@ -782,7 +842,7 @@ function StreamController() {
);

for (let i = 0, ln = streams ? streams.length : 0; i < ln; i++) {
let stream = streams[i];
const stream = streams[i];
stream.reset(hasMediaError);
}

Expand Down Expand Up @@ -856,5 +916,4 @@ function StreamController() {
}

StreamController.__dashjs_factory_name = 'StreamController';

export default FactoryMaker.getSingletonFactory(StreamController);

0 comments on commit f3fca64

Please sign in to comment.