shaka-player
Version:
DASH/EME video player library
478 lines (385 loc) • 15.8 kB
JavaScript
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
describe('PresentationTimeline', () => {
const originalDateNow = Date.now;
const makeSegmentReference = (startTime, endTime) => {
return shaka.test.ManifestParser.makeReference(
'foo.mp4', startTime, endTime);
};
/** @type {!Date} */
let baseTime;
beforeEach(() => {
baseTime = new Date(2015, 11, 30);
Date.now = () => baseTime.getTime();
});
afterEach(() => {
Date.now = originalDateNow;
});
function setElapsed(secondsSinceBaseTime) {
Date.now = () => baseTime.getTime() + (secondsSinceBaseTime * 1000);
}
/**
* Creates a PresentationTimeline.
*
* @param {boolean} isStatic
* @param {number} duration
* @param {?number} presentationStartTime
* @param {number} segmentAvailabilityDuration
* @param {number} maxSegmentDuration
* @param {number} clockOffset
* @param {number} presentationDelay
* @param {boolean=} autoCorrectDrift
*
* @return {shaka.media.PresentationTimeline}
*/
function makePresentationTimeline(
isStatic,
duration,
presentationStartTime,
segmentAvailabilityDuration,
maxSegmentDuration,
clockOffset,
presentationDelay,
autoCorrectDrift = true) {
const timeline = new shaka.media.PresentationTimeline(
presentationStartTime, presentationDelay, autoCorrectDrift);
timeline.setStatic(isStatic);
timeline.setDuration(duration || Infinity);
timeline.setSegmentAvailabilityDuration(segmentAvailabilityDuration);
timeline.notifyMaxSegmentDuration(maxSegmentDuration);
timeline.setClockOffset(clockOffset);
timeline.assertIsValid();
return timeline;
}
/**
* Creates a VOD PresentationTimeline.
*
* @param {number} duration
* @return {shaka.media.PresentationTimeline}
*/
function makeVodTimeline(duration) {
const timeline = makePresentationTimeline(
/* static= */ true, duration, /* start= */ null,
/* availability= */ Infinity, /* max= */ 10,
/* clock= */ 0, /* presentation= */ 0);
expect(timeline.isLive()).toBe(false);
expect(timeline.isInProgress()).toBe(false);
return timeline;
}
/**
* Creates a IPR PresentationTimeline.
*
* @param {number} duration
* @param {number=} delay
* @return {shaka.media.PresentationTimeline}
*/
function makeIprTimeline(duration, delay) {
const now = Date.now() / 1000;
const timeline = makePresentationTimeline(
/* static= */ false, duration, /* start= */ now,
/* availability= */ Infinity, /* max= */ 10,
/* clock= */ 0, delay || 0);
expect(timeline.isLive()).toBe(false);
expect(timeline.isInProgress()).toBe(true);
return timeline;
}
/**
* Creates a live PresentationTimeline.
*
* @param {number} availability
* @param {number=} delay
* @param {boolean=} autoCorrectDrift
* @return {shaka.media.PresentationTimeline}
*/
function makeLiveTimeline(availability, delay, autoCorrectDrift = true) {
const now = Date.now() / 1000;
const timeline = makePresentationTimeline(
/* static= */ false, /* duration= */ Infinity, /* start= */ now,
availability, /* max= */ 10,
/* clock= */ 0, delay || 0, autoCorrectDrift);
expect(timeline.isLive()).toBe(true);
expect(timeline.isInProgress()).toBe(false);
return timeline;
}
const makeTimeRange = (startTime, endTime) => {
return {
start: startTime,
end: endTime,
};
};
describe('notifyTimeRange', () => {
it('calculates time based on segment times when available', () => {
const timeline = makeLiveTimeline(/* availability= */ 20);
const timeRanges = [
makeTimeRange(0, 10),
makeTimeRange(10, 20),
makeTimeRange(20, 30),
makeTimeRange(30, 40),
makeTimeRange(40, 50),
];
// In spite of the current time, the explicit segment times will decide
// the availability window.
// See https://github.com/shaka-project/shaka-player/issues/999
setElapsed(1000);
timeline.notifyTimeRange(timeRanges, 0);
// last segment time (50) - availability (20)
expect(timeline.getSegmentAvailabilityStart()).toBe(30);
});
it('ignores segment times when configured to', () => {
const timeline = makeLiveTimeline(
/* availability= */ 20, /* drift= */ 0,
/* autoCorrectDrift= */ false);
const timeRanges = [
makeTimeRange(0, 10),
makeTimeRange(10, 20),
makeTimeRange(20, 30),
makeTimeRange(30, 40),
makeTimeRange(40, 50),
];
setElapsed(100);
timeline.notifyTimeRange(timeRanges, 0);
// now (100) - max segment duration (10) - availability start time (0)
expect(timeline.getSegmentAvailabilityEnd()).toBe(90);
});
});
describe('getSegmentAvailabilityStart', () => {
it('returns 0 for VOD and IPR', () => {
const timeline1 = makeVodTimeline(/* duration= */ 60);
const timeline2 = makeIprTimeline(/* duration= */ 60);
setElapsed(0);
expect(timeline1.getSegmentAvailabilityStart()).toBe(0);
expect(timeline2.getSegmentAvailabilityStart()).toBe(0);
setElapsed(100);
expect(timeline1.getSegmentAvailabilityStart()).toBe(0);
expect(timeline2.getSegmentAvailabilityStart()).toBe(0);
});
it('calculates time for live with finite availability', () => {
const timeline = makeLiveTimeline(/* availability= */ 20);
setElapsed(0);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
setElapsed(29);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
setElapsed(30);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
setElapsed(31);
expect(timeline.getSegmentAvailabilityStart()).toBe(1);
setElapsed(69);
expect(timeline.getSegmentAvailabilityStart()).toBe(39);
setElapsed(70);
expect(timeline.getSegmentAvailabilityStart()).toBe(40);
setElapsed(71);
expect(timeline.getSegmentAvailabilityStart()).toBe(41);
});
it('calculates time for live with infinite availability', () => {
const timeline = makeLiveTimeline(/* availability= */ Infinity);
setElapsed(0);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
setElapsed(59);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
setElapsed(60);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
setElapsed(61);
expect(timeline.getSegmentAvailabilityStart()).toBe(0);
});
it('calculates time based on segment times when available', () => {
const timeline = makeLiveTimeline(/* availability= */ 20);
const ref1 = makeSegmentReference(0, 10);
const ref2 = makeSegmentReference(10, 20);
const ref3 = makeSegmentReference(20, 30);
const ref4 = makeSegmentReference(30, 40);
const ref5 = makeSegmentReference(40, 50);
// In spite of the current time, the explicit segment times will decide
// the availability window.
// See https://github.com/shaka-project/shaka-player/issues/999
setElapsed(1000);
timeline.notifySegments([ref1, ref2, ref3, ref4, ref5]);
// last segment time (50) - availability (20)
expect(timeline.getSegmentAvailabilityStart()).toBe(30);
});
it('ignores segment times when configured to', () => {
const timeline = makeLiveTimeline(
/* availability= */ 20, /* drift= */ 0,
/* autoCorrectDrift= */ false);
const ref1 = makeSegmentReference(0, 10);
const ref2 = makeSegmentReference(10, 20);
const ref3 = makeSegmentReference(20, 30);
const ref4 = makeSegmentReference(30, 40);
const ref5 = makeSegmentReference(40, 50);
setElapsed(100);
timeline.notifySegments([ref1, ref2, ref3, ref4, ref5]);
// now (100) - max segment duration (10) - availability start time (0)
expect(timeline.getSegmentAvailabilityEnd()).toBe(90);
});
});
describe('getSegmentAvailabilityEnd', () => {
it('returns duration for VOD', () => {
const timeline = makeVodTimeline(/* duration= */ 60);
setElapsed(0);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
setElapsed(100);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
});
it('calculates time for IPR', () => {
const timeline = makeIprTimeline(/* duration= */ 60);
setElapsed(0);
expect(timeline.getSegmentAvailabilityEnd()).toBe(0);
setElapsed(10);
expect(timeline.getSegmentAvailabilityEnd()).toBe(0);
setElapsed(11);
expect(timeline.getSegmentAvailabilityEnd()).toBe(1);
setElapsed(69);
expect(timeline.getSegmentAvailabilityEnd()).toBe(59);
setElapsed(70);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
setElapsed(100);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
});
it('calculates time for live', () => {
const timeline1 = makeLiveTimeline(/* availability= */ 20);
const timeline2 = makeLiveTimeline(/* availability= */ Infinity);
setElapsed(0);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(0);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(0);
setElapsed(10);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(0);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(0);
setElapsed(11);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(1);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(1);
setElapsed(69);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(59);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(59);
setElapsed(70);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(60);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(60);
setElapsed(71);
expect(timeline1.getSegmentAvailabilityEnd()).toBe(61);
expect(timeline2.getSegmentAvailabilityEnd()).toBe(61);
});
it('calculates time based on segment times when available', () => {
const timeline = makeLiveTimeline(/* availability= */ 20);
const ref1 = makeSegmentReference(0, 10);
const ref2 = makeSegmentReference(10, 20);
const ref3 = makeSegmentReference(20, 30);
const ref4 = makeSegmentReference(30, 40);
const ref5 = makeSegmentReference(40, 50);
// In spite of the current time, the explicit segment times will decide
// the availability window.
// See https://github.com/shaka-project/shaka-player/issues/999
setElapsed(1000);
timeline.notifySegments([ref1, ref2, ref3, ref4, ref5]);
// last segment time (50)
expect(timeline.getSegmentAvailabilityEnd()).toBe(50);
});
it('calculates time when there a transition of live to static', () => {
const timeline = makeLiveTimeline(/* availability= */ 20);
const ref1 = makeSegmentReference(0, 10);
const ref2 = makeSegmentReference(10, 20);
const ref3 = makeSegmentReference(20, 30);
const ref4 = makeSegmentReference(30, 40);
const ref5 = makeSegmentReference(40, 50);
setElapsed(50);
timeline.notifySegments([ref1, ref2, ref3, ref4, ref5]);
expect(timeline.getSegmentAvailabilityEnd()).toBe(50);
timeline.setStatic(true);
expect(timeline.getSegmentAvailabilityEnd()).toBe(50);
});
});
describe('getDuration', () => {
it('returns the timeline duration', () => {
setElapsed(0);
const timeline1 = makeVodTimeline(/* duration= */ 60);
const timeline2 = makeIprTimeline(/* duration= */ 60);
const timeline3 = makeLiveTimeline(/* availability= */ 20);
const timeline4 = makeLiveTimeline(/* availability= */ Infinity);
expect(timeline1.getDuration()).toBe(60);
expect(timeline2.getDuration()).toBe(60);
expect(timeline3.getDuration()).toBe(Infinity);
expect(timeline4.getDuration()).toBe(Infinity);
});
});
describe('setDuration', () => {
it('affects availability end for VOD', () => {
setElapsed(0);
const timeline = makeVodTimeline(/* duration= */ 60);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
timeline.setDuration(90);
expect(timeline.getSegmentAvailabilityEnd()).toBe(90);
});
it('affects availability end for IPR', () => {
const timeline = makeIprTimeline(/* duration= */ 60);
setElapsed(85);
expect(timeline.getSegmentAvailabilityEnd()).toBe(60);
timeline.setDuration(90);
expect(timeline.getSegmentAvailabilityEnd()).toBe(75);
});
});
describe('clockOffset', () => {
it('offsets availability calculations', () => {
const timeline = makeLiveTimeline(/* availability= */ 10);
setElapsed(11);
expect(timeline.getSegmentAvailabilityEnd()).toBe(1);
timeline.setClockOffset(/* ms= */ 5000);
expect(timeline.getSegmentAvailabilityEnd()).toBe(6);
});
});
describe('getSafeSeekRangeStart', () => {
it('ignores offset for VOD', () => {
const timeline = makeVodTimeline(/* duration= */ 60);
expect(timeline.getSafeSeekRangeStart(0)).toBe(0);
expect(timeline.getSafeSeekRangeStart(10)).toBe(0);
expect(timeline.getSafeSeekRangeStart(25)).toBe(0);
});
it('offsets from live edge', () => {
const timeline = makeLiveTimeline(/* availability= */ 60, /* delay= */ 0);
setElapsed(120);
// now (120) - availability (60) - segment size (10) = 50
expect(timeline.getSeekRangeStart()).toBe(50);
expect(timeline.getSafeSeekRangeStart(10)).toBe(60);
expect(timeline.getSafeSeekRangeStart(25)).toBe(75);
});
it('clamps to end', () => {
const timeline = makeLiveTimeline(/* availability= */ 60, /* delay= */ 0);
setElapsed(120);
expect(timeline.getSegmentAvailabilityEnd()).toBe(110);
expect(timeline.getSafeSeekRangeStart(70)).toBe(110);
expect(timeline.getSafeSeekRangeStart(85)).toBe(110);
expect(timeline.getSafeSeekRangeStart(200)).toBe(110);
});
it('will return 0 if safe', () => {
const timeline = makeLiveTimeline(/* availability= */ 60, /* delay= */ 0);
setElapsed(50);
// now (50) - availability (60) - segment size (10) = -20
expect(timeline.getSeekRangeStart()).toBe(0);
expect(timeline.getSafeSeekRangeStart(0)).toBe(0);
expect(timeline.getSafeSeekRangeStart(25)).toBe(5);
});
// Regression test for https://github.com/shaka-project/shaka-player/issues/2831
it('will round up to the nearest ms', () => {
const timeline = makeVodTimeline(/* duration= */ 60);
// Seeking to this exact number may result in seeking to slightly less
// than that, due to rounding.
timeline.setUserSeekStart(1.458666666666666);
// So the safe range start should be slightly higher, with fewer digits.
expect(timeline.getSafeSeekRangeStart(0)).toBe(1.459);
});
});
describe('getSeekRangeEnd', () => {
it('accounts for delay for live and IPR', () => {
const timeline1 = makeIprTimeline(/* duration= */ 60, /* delay= */ 7);
const timeline2 = makeLiveTimeline(/* duration= */ 60, /* delay= */ 7);
setElapsed(11);
expect(timeline1.getSeekRangeEnd()).toBe(0);
expect(timeline2.getSeekRangeEnd()).toBe(0);
setElapsed(18);
expect(timeline1.getSeekRangeEnd()).toBe(1);
expect(timeline2.getSeekRangeEnd()).toBe(1);
setElapsed(37);
expect(timeline1.getSeekRangeEnd()).toBe(20);
expect(timeline2.getSeekRangeEnd()).toBe(20);
});
});
});