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

Refactor streams playback start #3308

Merged
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);
dsilhavy marked this conversation as resolved.
Show resolved Hide resolved
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) {
dsilhavy marked this conversation as resolved.
Show resolved Hide resolved
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to define this as staticStreamInfo.duration + 100 in case we ever change the value in staticStreamInfo.duration

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});
});
});
});