mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-26 17:46:26 +03:00
78fc6d9b36
This is part of adding a new conformance rule to add additional type safety. This will disallow using properties of unknown types or using unknown properties. The first parts will be fixing errors caused by the new rule. These are backwards compatible, so can be applied before the rule is enabled. Once all the errors and bugs are fixed, the rule will be enabled. Change-Id: I48335fc6659d3c33a6d55b00d087b59410c79cf7
550 lines
17 KiB
JavaScript
550 lines
17 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2016 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
describe('PlayheadObserver', function() {
|
|
/** @const */
|
|
var Util = shaka.test.Util;
|
|
|
|
/** @type {!shaka.media.PlayheadObserver} */
|
|
var observer;
|
|
/** @type {!shaka.test.FakeVideo} */
|
|
var video;
|
|
/** @type {!shaka.test.FakePresentationTimeline} */
|
|
var timeline;
|
|
/** @type {shakaExtern.Manifest} */
|
|
var manifest;
|
|
/** @type {shakaExtern.StreamingConfiguration} */
|
|
var config;
|
|
|
|
/** @type {!jasmine.Spy} */
|
|
var onBuffering;
|
|
/** @type {!jasmine.Spy} */
|
|
var onChangePeriod;
|
|
/** @type {!jasmine.Spy} */
|
|
var onEvent;
|
|
|
|
beforeAll(function() {
|
|
jasmine.clock().install();
|
|
});
|
|
|
|
afterAll(function() {
|
|
jasmine.clock().uninstall();
|
|
});
|
|
|
|
beforeEach(function() {
|
|
video = new shaka.test.FakeVideo();
|
|
video.currentTime = 0;
|
|
video.duration = 60;
|
|
video.buffered = createFakeBuffered([]);
|
|
|
|
timeline = new shaka.test.FakePresentationTimeline();
|
|
|
|
manifest = {
|
|
periods: [],
|
|
offlineSessionIds: [],
|
|
minBufferTime: 0,
|
|
presentationTimeline: timeline
|
|
};
|
|
|
|
config = {
|
|
rebufferingGoal: 10,
|
|
bufferingGoal: 5,
|
|
retryParameters: shaka.net.NetworkingEngine.defaultRetryParameters(),
|
|
infiniteRetriesForLiveStreams: true,
|
|
bufferBehind: 15,
|
|
ignoreTextStreamFailures: false,
|
|
useRelativeCueTimestamps: false,
|
|
startAtSegmentBoundary: false,
|
|
smallGapLimit: 0.5,
|
|
jumpLargeGaps: false
|
|
};
|
|
|
|
onBuffering = jasmine.createSpy('onBuffering');
|
|
onChangePeriod = jasmine.createSpy('onChangePeriod');
|
|
onEvent = jasmine.createSpy('onEvent');
|
|
|
|
// The observer should only call getSegmentAvailabilityEnd.
|
|
shaka.test.Util.makeMockObjectStrict(timeline);
|
|
timeline.getSegmentAvailabilityEnd.and.returnValue(60);
|
|
});
|
|
|
|
afterEach(function(done) {
|
|
observer.destroy().catch(fail).then(done);
|
|
});
|
|
|
|
describe('buffering', function() {
|
|
it('doesn\'t change buffering state when enough is buffered', function() {
|
|
video.buffered = createFakeBuffered([{start: 0, end: 20}]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('enters buffering state when playing to end of buffer', function() {
|
|
video.buffered = createFakeBuffered([{start: 0, end: 20}]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
video.currentTime = 20;
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(true);
|
|
});
|
|
|
|
it('enters buffering state when seeking to unbuffered region', function() {
|
|
video.buffered = createFakeBuffered([{start: 0, end: 20}]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
video.currentTime = 40;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(true);
|
|
});
|
|
|
|
it('leaves buffering state when enough is buffered', function() {
|
|
video.buffered = createFakeBuffered([{start: 0, end: 20}]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
video.currentTime = 22;
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(true);
|
|
onBuffering.calls.reset();
|
|
|
|
video.buffered = createFakeBuffered([{start: 15, end: 40}]);
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('leaves buffering state with small non-zero start time', function() {
|
|
video.buffered = createFakeBuffered([]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(true);
|
|
onBuffering.calls.reset();
|
|
|
|
video.buffered = createFakeBuffered([{start: 0.2, end: 15}]);
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('leaves buffering state with exact amount buffered', function() {
|
|
video.buffered = createFakeBuffered([]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(true);
|
|
onBuffering.calls.reset();
|
|
|
|
video.buffered = createFakeBuffered([{start: 0, end: 10}]);
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledTimes(1);
|
|
expect(onBuffering).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('doesn\'t enter buffering state at end of VOD stream', function() {
|
|
video.buffered = createFakeBuffered([{start: 40, end: 60}]);
|
|
video.currentTime = 40;
|
|
video.duration = 60;
|
|
timeline.isLive.and.returnValue(false);
|
|
timeline.getSegmentAvailabilityEnd.and.returnValue(60);
|
|
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).not.toHaveBeenCalled();
|
|
|
|
video.currentTime = 60;
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('doesn\'t enter buffering state at live edge', function() {
|
|
video.buffered = createFakeBuffered([{start: 40, end: 60}]);
|
|
video.currentTime = 40;
|
|
video.duration = 9999999999;
|
|
timeline.isLive.and.returnValue(true);
|
|
timeline.getSegmentAvailabilityEnd.and.returnValue(60);
|
|
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).not.toHaveBeenCalled();
|
|
|
|
video.currentTime = 60;
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).not.toHaveBeenCalled();
|
|
|
|
// When the live edge moves, should enter buffering state.
|
|
timeline.getSegmentAvailabilityEnd.and.returnValue(90);
|
|
jasmine.clock().tick(1000);
|
|
expect(onBuffering).toHaveBeenCalledWith(true);
|
|
});
|
|
});
|
|
|
|
describe('timeline regions', function() {
|
|
var regionInfo;
|
|
|
|
beforeEach(function() {
|
|
// expect(...).toEquals doesn't compare DOM nodes well, so create a custom
|
|
// wrapper that will match it.
|
|
var fakeElement = {
|
|
asymmetricMatch: function(other) {
|
|
// The |regionInfo.fakeElement| member should be copied by-reference.
|
|
return other == this;
|
|
},
|
|
jasmineToString: function() {
|
|
return '<Event element>';
|
|
}
|
|
};
|
|
|
|
regionInfo = {
|
|
schemeIdUri: 'http://example.com',
|
|
value: 'something',
|
|
startTime: 10,
|
|
endTime: 20,
|
|
id: 'abc',
|
|
eventElement: /** @type {?} */ (fakeElement)
|
|
};
|
|
|
|
video.buffered = createFakeBuffered([{start: 0, end: 60}]);
|
|
video.currentTime = 0;
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
});
|
|
|
|
describe('adding regions', function() {
|
|
it('fires added event when adding a new region', function() {
|
|
expect(onEvent).not.toHaveBeenCalled();
|
|
|
|
observer.addTimelineRegion(regionInfo);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(1);
|
|
expectTimelineEvent('timelineregionadded', regionInfo);
|
|
});
|
|
|
|
it('won\'t fire an added event when adding a duplicate', function() {
|
|
observer.addTimelineRegion(regionInfo);
|
|
onEvent.calls.reset();
|
|
|
|
observer.addTimelineRegion(regionInfo);
|
|
observer.addTimelineRegion(regionInfo);
|
|
observer.addTimelineRegion({
|
|
// "Similar" event (i.e. same time and schemeIdUri.
|
|
schemeIdUri: 'http://example.com',
|
|
value: 'other',
|
|
startTime: 10,
|
|
endTime: 20,
|
|
id: 'xyz',
|
|
eventElement: null
|
|
});
|
|
observer.addTimelineRegion(regionInfo);
|
|
|
|
expect(onEvent).not.toHaveBeenCalled();
|
|
observer.addTimelineRegion({
|
|
// Different event
|
|
schemeIdUri: 'http://example.com/other',
|
|
value: 'dog',
|
|
startTime: 0,
|
|
endTime: 50,
|
|
id: '123',
|
|
eventElement: null
|
|
});
|
|
expect(onEvent).toHaveBeenCalled();
|
|
});
|
|
|
|
it('fires an enter event when adding a region the playhead is in',
|
|
function() {
|
|
video.currentTime = 15;
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).not.toHaveBeenCalled();
|
|
observer.addTimelineRegion(regionInfo);
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(2);
|
|
expectTimelineEvent('timelineregionadded', regionInfo, 0);
|
|
expectTimelineEvent('timelineregionenter', regionInfo, 1);
|
|
});
|
|
});
|
|
|
|
describe('seeking', function() {
|
|
beforeEach(function() {
|
|
observer.addTimelineRegion(regionInfo);
|
|
onEvent.calls.reset();
|
|
});
|
|
|
|
it('won\'t fire events when seeking over a region', function() {
|
|
video.currentTime = 5;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 25;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fires an enter event when seeking into a region', function() {
|
|
video.currentTime = 15;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(1);
|
|
expectTimelineEvent('timelineregionenter', regionInfo);
|
|
});
|
|
|
|
it('fires an exit event when seeking out of a region', function() {
|
|
video.currentTime = 15;
|
|
jasmine.clock().tick(1000);
|
|
onEvent.calls.reset();
|
|
|
|
video.currentTime = 0;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(1);
|
|
expectTimelineEvent('timelineregionexit', regionInfo);
|
|
});
|
|
});
|
|
|
|
describe('playing', function() {
|
|
beforeEach(function() {
|
|
observer.addTimelineRegion(regionInfo);
|
|
onEvent.calls.reset();
|
|
});
|
|
|
|
it('fires an enter/exit event when playing through a region', function() {
|
|
moveToAndExpectEvent(15, 'timelineregionenter', regionInfo);
|
|
moveToAndExpectEvent(25, 'timelineregionexit', regionInfo);
|
|
});
|
|
|
|
it('fires an enter/exit event when playing over a region', function() {
|
|
video.currentTime = 25;
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(2);
|
|
expectTimelineEvent('timelineregionenter', regionInfo, 0);
|
|
expectTimelineEvent('timelineregionexit', regionInfo, 1);
|
|
});
|
|
|
|
it('fires an enter/exit event for zero-duration regions', function() {
|
|
var otherInfo = {
|
|
schemeIdUri: 'http://example.com',
|
|
value: 'something',
|
|
startTime: 3,
|
|
endTime: 3,
|
|
id: 'abc',
|
|
eventElement: null
|
|
};
|
|
observer.addTimelineRegion(otherInfo);
|
|
onEvent.calls.reset();
|
|
|
|
video.currentTime = 5;
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(2);
|
|
expectTimelineEvent('timelineregionenter', otherInfo, 0);
|
|
expectTimelineEvent('timelineregionexit', otherInfo, 1);
|
|
});
|
|
|
|
it('fires correctly for overlapping regions', function() {
|
|
// |---------|---------|---------|
|
|
// | | 1 | |
|
|
// | |2 | |
|
|
// | | 3 | |
|
|
// |---------|---------|---------|
|
|
// 10 20 30
|
|
// 1: regionInfo, 2: nestedInfo, 3: overlapInfo
|
|
|
|
var nestedInfo = {
|
|
schemeIdUri: 'http://example.com',
|
|
value: 'something',
|
|
startTime: 13,
|
|
endTime: 16,
|
|
id: 'abc',
|
|
eventElement: null
|
|
};
|
|
var overlapInfo = {
|
|
schemeIdUri: 'http://example.com',
|
|
value: 'something',
|
|
startTime: 18,
|
|
endTime: 23,
|
|
id: 'abc',
|
|
eventElement: null
|
|
};
|
|
observer.addTimelineRegion(nestedInfo);
|
|
observer.addTimelineRegion(overlapInfo);
|
|
onEvent.calls.reset();
|
|
|
|
video.currentTime = 5;
|
|
jasmine.clock().tick(1000);
|
|
expect(onEvent).not.toHaveBeenCalled();
|
|
|
|
moveToAndExpectEvent(12, 'timelineregionenter', regionInfo);
|
|
moveToAndExpectEvent(15, 'timelineregionenter', nestedInfo);
|
|
moveToAndExpectEvent(17, 'timelineregionexit', nestedInfo);
|
|
moveToAndExpectEvent(19, 'timelineregionenter', overlapInfo);
|
|
moveToAndExpectEvent(22, 'timelineregionexit', regionInfo);
|
|
moveToAndExpectEvent(27, 'timelineregionexit', overlapInfo);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {shakaExtern.TimelineRegionInfo} info
|
|
* @param {number=} opt_index
|
|
*/
|
|
function expectTimelineEvent(name, info, opt_index) {
|
|
var event = onEvent.calls.argsFor(opt_index || 0)[0];
|
|
expect(event.type).toBe(name);
|
|
expect(event.detail).toEqual(info);
|
|
}
|
|
|
|
/**
|
|
* @param {number} newTime
|
|
* @param {string} eventName
|
|
* @param {shakaExtern.TimelineRegionInfo} info
|
|
*/
|
|
function moveToAndExpectEvent(newTime, eventName, info) {
|
|
video.currentTime = newTime;
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onEvent).toHaveBeenCalledTimes(1);
|
|
expectTimelineEvent(eventName, info);
|
|
onEvent.calls.reset();
|
|
}
|
|
});
|
|
|
|
describe('changing periods', function() {
|
|
beforeEach(function() {
|
|
manifest.periods = [
|
|
{startTime: 0},
|
|
{startTime: 20},
|
|
{startTime: 30}
|
|
];
|
|
|
|
observer = new shaka.media.PlayheadObserver(
|
|
video, manifest, config, Util.spyFunc(onBuffering),
|
|
Util.spyFunc(onEvent), Util.spyFunc(onChangePeriod));
|
|
|
|
// Ignore the call for the initial Period.
|
|
jasmine.clock().tick(1000);
|
|
onChangePeriod.calls.reset();
|
|
});
|
|
|
|
it('won\'t call callback while playing inside Period', function() {
|
|
video.currentTime = 0;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 6;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 12;
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onChangePeriod).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('will call callback when playing into a Period', function() {
|
|
video.currentTime = 6;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 22;
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onChangePeriod).toHaveBeenCalled();
|
|
});
|
|
|
|
it('won\'t call callback when seeking within a Period', function() {
|
|
video.currentTime = 2;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 13;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onChangePeriod).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('will call callback when seeking into a different Period', function() {
|
|
video.currentTime = 2;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 25;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onChangePeriod).toHaveBeenCalled();
|
|
});
|
|
|
|
it('wil call callback when seeking backward into a different Period',
|
|
function() {
|
|
video.currentTime = 26;
|
|
jasmine.clock().tick(1000);
|
|
onChangePeriod.calls.reset();
|
|
|
|
video.currentTime = 2;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onChangePeriod).toHaveBeenCalled();
|
|
});
|
|
|
|
it('will call callback once when seeking over Periods', function() {
|
|
video.currentTime = 2;
|
|
jasmine.clock().tick(1000);
|
|
|
|
video.currentTime = 35;
|
|
observer.seeked();
|
|
jasmine.clock().tick(1000);
|
|
|
|
expect(onChangePeriod).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|