Skip to content

Commit

Permalink
Add processing of MPD time anchor (#t) according to Annex C of MPEG D…
Browse files Browse the repository at this point in the history
…ASH spec
  • Loading branch information
Bertrand Berthelot committed Jun 29, 2020
1 parent 35d0dda commit 6b772c4
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 31 deletions.
59 changes: 40 additions & 19 deletions src/streaming/controllers/PlaybackController.js
Expand Up @@ -106,13 +106,36 @@ function PlaybackController() {
if (streamSwitch && isNaN(streamSeekTime)) return;

// Seek new stream in priority order:
// - at seek time (streamSeekTime) that required period switch
// - at seek time (streamSeekTime) when switching period
// - at start time provided in URI parameters
// - at stream/period start time (for static streams) or live start time (for dynamic streams)
let startTime = streamSeekTime;
if (isNaN(startTime)) {
startTime = isDynamic ? e.liveStartTime : e.streamInfo.start;
startTime = Math.max(startTime, getStartTimeFromUri());
if (isDynamic) {
// For dynamic stream, start by default at (live edge - live delay)
startTime = e.liveStartTime;
// If start time in URI, take min value between live edge time and time from URI (if in DVR window range)
const dvrInfo = dashMetrics.getCurrentDVRInfo();
const dvrWindow = dvrInfo ? dvrInfo.range : null;
if (dvrWindow) {
const startTimeFromUri = getStartTimeFromUriParameters(dvrWindow.start);
if (!isNaN(startTimeFromUri) && startTimeFromUri >= dvrWindow.start) {
logger.info('Start time from URI parameters: ' + startTimeFromUri);
startTime = Math.min(startTime, startTimeFromUri);
}
}
} else {
// For static stream, start by default at period start
startTime = streamInfo.start;
// If start time in URI, take max value between period start and time from URI (if in period range)
const startTimeFromUri = getStartTimeFromUriParameters(streamInfo.start);
if (!isNaN(startTimeFromUri) && startTimeFromUri < (startTime + streamInfo.duration)) {
logger.info('Start time from URI parameters: ' + startTimeFromUri);
startTime = Math.max(startTime, startTimeFromUri);
}
}

// Check if not seeking at current time
if (startTime === videoModel.getTime()) return;
}

Expand Down Expand Up @@ -348,25 +371,23 @@ function PlaybackController() {
}
}

function getStartTimeFromUriParameters() {
function getStartTimeFromUriParameters(periodStart) {
const fragData = uriFragmentModel.getURIFragmentData();
let uriParameters;
if (fragData) {
uriParameters = {};
const r = parseInt(fragData.r, 10);
if (r >= 0 && streamInfo && r < streamInfo.manifestInfo.DVRWindowSize && fragData.t === null) {
fragData.t = Math.max(Math.floor(Date.now() / 1000) - streamInfo.manifestInfo.DVRWindowSize, (streamInfo.manifestInfo.availableFrom.getTime() / 1000) + streamInfo.start) + r;
}
uriParameters.fragS = parseFloat(fragData.s);
uriParameters.fragT = parseFloat(fragData.t);
if (!fragData || !fragData.t) {
return NaN;
}
return uriParameters;
}

function getStartTimeFromUri() {
const uriParameters = getStartTimeFromUriParameters();
let start = !isNaN(uriParameters.fragS) ? uriParameters.fragS : (!isNaN(uriParameters.fragT) ? uriParameters.fragT : -1);
return start;
let startTime = NaN;

// Consider only start time of MediaRange
// TODO: consider end time of MediaRange to stop playback at provided end time
fragData.t = fragData.t.split(',')[0];

// t is relative to period start
// posix notation (t=posix:xxx) = absolute time range as number of seconds since 01-01-1970
startTime = (fragData.t.indexOf('posix:') !== -1) ? parseInt(fragData.t.substring(6)) : (periodStart + parseInt(fragData.t));

return startTime;
}

function getActualPresentationTime(currentTime) {
Expand Down
7 changes: 3 additions & 4 deletions src/streaming/controllers/StreamController.js
Expand Up @@ -660,11 +660,10 @@ function StreamController() {
}

// we need to figure out what the correct starting period is
const startTimeFormUriParameters = playbackController.getStartTimeFromUriParameters();
let initialStream = null;
if (startTimeFormUriParameters) {
const initialTime = !isNaN(startTimeFormUriParameters.fragS) ? startTimeFormUriParameters.fragS : startTimeFormUriParameters.fragT;
initialStream = getStreamForTime(initialTime);
const startTimeFormUri = playbackController.getStartTimeFromUriParameters(streamsInfo[0].start);
if (!isNaN(startTimeFormUri)) {
initialStream = getStreamForTime(startTimeFormUri);
}
// For multiperiod streams we should avoid a switch of streams after the seek to the live edge. So we do a calculation of the expected seek time to find the right stream object.
if (!initialStream && adapter.getIsDynamic() && streams.length) {
Expand Down
129 changes: 121 additions & 8 deletions test/unit/streaming.controllers.PlaybackControllers.js
Expand Up @@ -85,7 +85,8 @@ describe('PlaybackController', function () {
isDynamic: true,
availableFrom: new Date()
},
start: 10
start: 10,
duration: 600
};

playbackController.initialize(streamInfo);
Expand Down Expand Up @@ -160,13 +161,6 @@ describe('PlaybackController', function () {
videoModelMock.ended = true;
expect(playbackController.getEnded()).to.equal(videoModelMock.ended);
});

it('getStartTimeFromUriParameters should return the expected value', function () {
uriFragmentModelMock.setURIFragmentData({t: 18.2});
const uriParameters = playbackController.getStartTimeFromUriParameters();
expect(uriParameters.fragT).to.exist; // jshint ignore:line
expect(uriParameters.fragT).to.equal(18.2);
});
});

describe('video event handler', function () {
Expand Down Expand Up @@ -325,6 +319,125 @@ describe('PlaybackController', function () {
videoModelMock.fireEvent('waiting');
});
});
});

describe('start time', function () {
let expectedSeekTime;
let doneFn;

let staticStreamInfo = { manifestInfo: { isDynamic: false }, start: 10, duration: 600 };
let dynamicStreamInfo = { manifestInfo: { isDynamic: true }, start: 0, duration: Infinity };
let dvrWindowRange = { start: 10, end: 90 };

let onPlaybackSeeking = function (e) {
eventBus.off(Events.PLAYBACK_SEEKING, onPlaybackSeeking);
expect(e.seekTime).to.equal(expectedSeekTime);
doneFn();
};

beforeEach(function () {
videoModelMock.time = -1;
eventBus.on(Events.PLAYBACK_SEEKING, onPlaybackSeeking, this);
});

it('should start static stream at period start', function (done) {
doneFn = done;

expectedSeekTime = staticStreamInfo.start;

playbackController.initialize(staticStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {});
});

it('should start static stream at #t', function (done) {
doneFn = done;

let uriStartTime = 10;
uriFragmentModelMock.setURIFragmentData({t: uriStartTime.toString()});

expectedSeekTime = staticStreamInfo.start + uriStartTime;

playbackController.initialize(staticStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {});
});

it('should start static stream at period start if #t is beyond period end', function (done) {
doneFn = done;

let uriStartTime = 700;
uriFragmentModelMock.setURIFragmentData({t: uriStartTime.toString()});

expectedSeekTime = staticStreamInfo.start;

playbackController.initialize(staticStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {});
});

it('should start dynamic stream at live start time', function (done) {
doneFn = done;

let liveStartTime = 90;

expectedSeekTime = liveStartTime;

playbackController.initialize(dynamicStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {liveStartTime: liveStartTime});
});

it('should start dynamic stream at #t', function (done) {
doneFn = done;

let liveStartTime = 70;
dashMetricsMock.addDVRInfo('video', Date.now(), null, dvrWindowRange);
let uriStartTime = 50;
uriFragmentModelMock.setURIFragmentData({t: uriStartTime.toString()});

expectedSeekTime = dvrWindowRange.start + uriStartTime;

playbackController.initialize(dynamicStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {liveStartTime: liveStartTime});
});

it('should start dynamic stream at #t=posix:', function (done) {
doneFn = done;

let liveStartTime = 70;
dashMetricsMock.addDVRInfo('video', Date.now(), null, dvrWindowRange);
let uriStartTime = 50;
uriFragmentModelMock.setURIFragmentData({t: 'posix:' + uriStartTime.toString()});

expectedSeekTime = uriStartTime;

playbackController.initialize(dynamicStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {liveStartTime: liveStartTime});
});

it('should start dynamic stream at live start time if #t=posix is before DVR window range', function (done) {
doneFn = done;

let liveStartTime = 70;
dashMetricsMock.addDVRInfo('video', Date.now(), null, dvrWindowRange);
let uriStartTime = 0;
uriFragmentModelMock.setURIFragmentData({t: 'posix:' + uriStartTime.toString()});

expectedSeekTime = liveStartTime;

playbackController.initialize(dynamicStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {liveStartTime: liveStartTime});
});

it('should start dynamic stream at live start time if #t=posix is beyond live start time', function (done) {
doneFn = done;

let liveStartTime = 70;
dashMetricsMock.addDVRInfo('video', Date.now(), null, dvrWindowRange);
let uriStartTime = 80;
uriFragmentModelMock.setURIFragmentData({t: 'posix:' + uriStartTime.toString()});

expectedSeekTime = liveStartTime;

playbackController.initialize(dynamicStreamInfo);
eventBus.trigger(Events.STREAM_INITIALIZED, {liveStartTime: liveStartTime});
});
});
});

0 comments on commit 6b772c4

Please sign in to comment.