mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
970d7756ea
The goal is to simplify and abstract feature logic detection. Currently lots of places depend on various calls to `shaka.util.Platform` and mainteinance of this is hard & not easy to read. By introducing device API ideally rest of the player logic would look into device features instead of directly checking platform. Additionally we can more easily cache needed values, so we won't have to parse user agent several times anymore. --------- Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
660 lines
21 KiB
JavaScript
660 lines
21 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
describe('StreamingEngine', () => {
|
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
const Util = shaka.test.Util;
|
|
|
|
let metadata;
|
|
let generators;
|
|
|
|
/** @type {!shaka.util.EventManager} */
|
|
let eventManager;
|
|
/** @type {shaka.test.Waiter} */
|
|
let waiter;
|
|
|
|
/** @type {!HTMLVideoElement} */
|
|
let video;
|
|
/** @type {{start: number, end: number}} */
|
|
let segmentAvailability;
|
|
/** @type {!shaka.test.FakePresentationTimeline} */
|
|
let timeline;
|
|
|
|
/** @type {!shaka.media.Playhead} */
|
|
let playhead;
|
|
/** @type {shaka.extern.StreamingConfiguration} */
|
|
let config;
|
|
|
|
/** @type {!shaka.test.FakeNetworkingEngine} */
|
|
let netEngine;
|
|
/** @type {!shaka.media.MediaSourceEngine} */
|
|
let mediaSourceEngine;
|
|
/** @type {!shaka.media.StreamingEngine} */
|
|
let streamingEngine;
|
|
|
|
/** @type {shaka.extern.Variant} */
|
|
let variant;
|
|
|
|
/** @type {shaka.extern.Manifest} */
|
|
let manifest;
|
|
|
|
/** @type {!jasmine.Spy} */
|
|
let onError;
|
|
/** @type {!jasmine.Spy} */
|
|
let onEvent;
|
|
|
|
beforeAll(() => {
|
|
video = shaka.test.UiUtils.createVideoElement();
|
|
document.body.appendChild(video);
|
|
|
|
metadata = shaka.test.TestScheme.DATA['sintel'];
|
|
generators = {};
|
|
});
|
|
|
|
beforeEach(() => {
|
|
config = shaka.util.PlayerConfiguration.createDefault().streaming;
|
|
|
|
// Disable stall detection, which can interfere with playback tests.
|
|
config.stallEnabled = false;
|
|
|
|
onError = jasmine.createSpy('onError');
|
|
onError.and.callFake(fail);
|
|
onEvent = jasmine.createSpy('onEvent');
|
|
|
|
eventManager = new shaka.util.EventManager();
|
|
waiter = new shaka.test.Waiter(eventManager);
|
|
|
|
const mediaSourceConfig =
|
|
shaka.util.PlayerConfiguration.createDefault().mediaSource;
|
|
mediaSourceEngine = new shaka.media.MediaSourceEngine(
|
|
video,
|
|
new shaka.test.FakeTextDisplayer(),
|
|
{
|
|
getKeySystem: () => null,
|
|
onMetadata: () => {},
|
|
onEmsg: () => {},
|
|
onEvent: () => {},
|
|
onManifestUpdate: () => {},
|
|
},
|
|
mediaSourceConfig);
|
|
waiter.setMediaSourceEngine(mediaSourceEngine);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
eventManager.release();
|
|
|
|
await streamingEngine.destroy();
|
|
await mediaSourceEngine.destroy();
|
|
|
|
playhead.release();
|
|
});
|
|
|
|
afterAll(() => {
|
|
document.body.removeChild(video);
|
|
});
|
|
|
|
async function setupVod() {
|
|
await createVodStreamGenerator(metadata.audio, ContentType.AUDIO);
|
|
await createVodStreamGenerator(metadata.video, ContentType.VIDEO);
|
|
|
|
segmentAvailability = {
|
|
start: 0,
|
|
end: 40,
|
|
};
|
|
|
|
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
|
|
segmentAvailability,
|
|
/* presentationDuration= */ 40,
|
|
/* maxSegmentDuration= */ metadata.video.segmentDuration,
|
|
/* isLive= */ false);
|
|
|
|
setupNetworkingEngine(
|
|
/* presentationDuration= */ 40,
|
|
{
|
|
audio: metadata.audio.segmentDuration,
|
|
video: metadata.video.segmentDuration,
|
|
});
|
|
|
|
setupManifest(
|
|
/* firstPeriodStartTime= */ 0,
|
|
/* secondPeriodStartTime= */ 20,
|
|
/* presentationDuration= */ 40);
|
|
|
|
setupPlayhead();
|
|
|
|
createStreamingEngine();
|
|
}
|
|
|
|
async function setupLive() {
|
|
await createLiveStreamGenerator(
|
|
metadata.audio,
|
|
ContentType.AUDIO,
|
|
/* timeShiftBufferDepth= */ 20);
|
|
|
|
await createLiveStreamGenerator(
|
|
metadata.video,
|
|
ContentType.VIDEO,
|
|
/* timeShiftBufferDepth= */ 20);
|
|
|
|
// The generator's AST is set to 295 seconds in the past, so the live-edge
|
|
// is at 295 - 10 seconds.
|
|
// -10 to account for maxSegmentDuration.
|
|
segmentAvailability = {
|
|
start: 275 - 10,
|
|
end: 295 - 10,
|
|
};
|
|
|
|
timeline = shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
|
|
segmentAvailability,
|
|
/* presentationDuration= */ Infinity,
|
|
/* maxSegmentDuration= */ metadata.video.segmentDuration,
|
|
/* isLive= */ true);
|
|
|
|
setupNetworkingEngine(
|
|
/* presentationDuration= */ Infinity,
|
|
{
|
|
audio: metadata.audio.segmentDuration,
|
|
video: metadata.video.segmentDuration,
|
|
});
|
|
|
|
setupManifest(
|
|
/* firstPeriodStartTime= */ 0,
|
|
/* secondPeriodStartTime= */ 300,
|
|
/* presentationDuration= */ Infinity);
|
|
setupPlayhead();
|
|
|
|
// Retry on failure for live streams.
|
|
config.failureCallback = () => streamingEngine.retry(0.1);
|
|
|
|
// Ignore 404 errors in live stream tests.
|
|
onError.and.callFake((error) => {
|
|
if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
|
|
error.data[1] == 404) {
|
|
// 404 error
|
|
} else {
|
|
fail(error);
|
|
}
|
|
});
|
|
|
|
createStreamingEngine();
|
|
}
|
|
|
|
function createVodStreamGenerator(metadata, type) {
|
|
const generator = new shaka.test.Mp4VodStreamGenerator(
|
|
metadata.initSegmentUri,
|
|
metadata.mdhdOffset,
|
|
metadata.segmentUri,
|
|
metadata.tfdtOffset,
|
|
metadata.segmentDuration);
|
|
generators[type] = generator;
|
|
return generator.init();
|
|
}
|
|
|
|
function createLiveStreamGenerator(metadata, type, timeShiftBufferDepth) {
|
|
// Set the generator's AST to 295 seconds in the past so the
|
|
// StreamingEngine begins streaming close to the end of the first Period.
|
|
const now = Date.now() / 1000;
|
|
const generator = new shaka.test.Mp4LiveStreamGenerator(
|
|
metadata.initSegmentUri,
|
|
metadata.mdhdOffset,
|
|
metadata.segmentUri,
|
|
metadata.tfdtOffset,
|
|
metadata.segmentDuration,
|
|
/* broadcastStartTime= */ now - 295,
|
|
/* availabilityStartTime= */ now - 295,
|
|
timeShiftBufferDepth);
|
|
generators[type] = generator;
|
|
return generator.init();
|
|
}
|
|
|
|
function setupNetworkingEngine(presentationDuration, segmentDurations) {
|
|
// Create the fake NetworkingEngine. Note: the StreamingEngine should never
|
|
// request a segment that does not exist.
|
|
netEngine = shaka.test.StreamingEngineUtil.createFakeNetworkingEngine(
|
|
// Init segment generator:
|
|
(type, periodNumber) => {
|
|
const wallClockTime = Date.now() / 1000;
|
|
const segment = generators[type].getInitSegment(wallClockTime);
|
|
expect(segment).not.toBeNull();
|
|
return segment;
|
|
},
|
|
// Media segment generator:
|
|
(type, periodNumber, position) => {
|
|
const wallClockTime = Date.now() / 1000;
|
|
const segment = generators[type].getSegment(position, wallClockTime);
|
|
return segment;
|
|
},
|
|
/* delays= */{audio: 0, video: 0, text: 0});
|
|
}
|
|
|
|
function setupPlayhead() {
|
|
const onSeek = () => {
|
|
streamingEngine.seeked();
|
|
};
|
|
playhead = new shaka.media.MediaSourcePlayhead(
|
|
/** @type {!HTMLVideoElement} */(video),
|
|
manifest,
|
|
config,
|
|
/* startTime= */ null,
|
|
onSeek,
|
|
shaka.test.Util.spyFunc(onEvent));
|
|
}
|
|
|
|
function setupManifest(
|
|
firstPeriodStartTime, secondPeriodStartTime, presentationDuration) {
|
|
manifest = shaka.test.StreamingEngineUtil.createManifest(
|
|
/** @type {!shaka.media.PresentationTimeline} */(timeline),
|
|
[firstPeriodStartTime, secondPeriodStartTime], presentationDuration,
|
|
/* segmentDurations= */ {
|
|
audio: metadata.audio.segmentDuration,
|
|
video: metadata.video.segmentDuration,
|
|
},
|
|
/* initSegmentRanges= */ {
|
|
audio: [0, null],
|
|
video: [0, null],
|
|
});
|
|
|
|
variant = manifest.variants[0];
|
|
}
|
|
|
|
function createStreamingEngine() {
|
|
const playerInterface = {
|
|
getPresentationTime: () => playhead.getTime(),
|
|
getBandwidthEstimate: () => 1e6,
|
|
getPlaybackRate: () => video.playbackRate,
|
|
video: video,
|
|
mediaSourceEngine: mediaSourceEngine,
|
|
netEngine: /** @type {!shaka.net.NetworkingEngine} */(netEngine),
|
|
onError: Util.spyFunc(onError),
|
|
onEvent: Util.spyFunc(onEvent),
|
|
onSegmentAppended: () => playhead.notifyOfBufferingChange(),
|
|
onInitSegmentAppended: () => {},
|
|
beforeAppendSegment: () => Promise.resolve(),
|
|
disableStream: (stream, time) => false,
|
|
};
|
|
streamingEngine = new shaka.media.StreamingEngine(
|
|
/** @type {shaka.extern.Manifest} */(manifest), playerInterface);
|
|
streamingEngine.configure(config);
|
|
}
|
|
|
|
describe('VOD', () => {
|
|
beforeEach(async () => {
|
|
await setupVod();
|
|
});
|
|
|
|
it('plays', async () => {
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
await video.play();
|
|
// The overall test timeout is 120 seconds, and the content is 40
|
|
// seconds. It should be possible to complete this test in 100 seconds,
|
|
// and if not, we want the error thrown to be within the overall test's
|
|
// timeout window. Note that we have seen some devices fail to play at
|
|
// full speed for reasons beyond our control, so we plan for >= 0.5x.
|
|
await waiter.timeoutAfter(100).waitForEnd(video);
|
|
});
|
|
|
|
it('plays at high playback rates', async () => {
|
|
// Experimentally, we find that playback rates above 2x in this test seem
|
|
// to cause decoder failures on Tizen 3. This is out of our control, and
|
|
// seems to be a Tizen bug, so this test is skipped on Tizen completely.
|
|
if (deviceDetected.getDeviceName() === 'Tizen') {
|
|
pending('High playbackRate tests cause decoder errors on Tizen 3.');
|
|
}
|
|
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
await video.play();
|
|
|
|
// Wait for playback to begin before increasing the playback rate. This
|
|
// improves test reliability on slow platforms like Chromecast.
|
|
await waiter.timeoutAfter(10).waitForMovement(video);
|
|
video.playbackRate = 10;
|
|
|
|
await waiter.timeoutAfter(30).waitForEnd(video);
|
|
});
|
|
|
|
it('can handle buffered seeks', async () => {
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
await video.play();
|
|
|
|
// After 35 seconds seek back 10 seconds into the first Period.
|
|
await waiter.timeoutAfter(80).waitUntilPlayheadReaches(video, 35);
|
|
video.currentTime = 25;
|
|
await waiter.timeoutAfter(80).waitForEnd(video);
|
|
});
|
|
|
|
it('can handle unbuffered seeks', async () => {
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
await video.play();
|
|
await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 20);
|
|
video.currentTime = 40;
|
|
await waiter.timeoutAfter(60).waitForEnd(video);
|
|
});
|
|
});
|
|
|
|
describe('Live', () => {
|
|
/** @type {number} */
|
|
let slideSegmentAvailabilityWindow;
|
|
|
|
beforeEach(async () => {
|
|
await setupLive();
|
|
slideSegmentAvailabilityWindow = window.setInterval(() => {
|
|
segmentAvailability.start++;
|
|
segmentAvailability.end++;
|
|
}, 1000);
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.clearInterval(slideSegmentAvailabilityWindow);
|
|
});
|
|
|
|
it('plays through Period transition', async () => {
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
|
|
await video.play();
|
|
await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305);
|
|
|
|
const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
const segmentContext = {
|
|
type: shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT,
|
|
};
|
|
// firstSegmentNumber =
|
|
// [(segmentAvailabilityEnd - rebufferingGoal) / segmentDuration] + 1
|
|
netEngine.expectRequest('0_video_29', segmentType, segmentContext);
|
|
netEngine.expectRequest('0_audio_29', segmentType, segmentContext);
|
|
});
|
|
|
|
it('can handle seeks ahead of availability window', async () => {
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
|
|
await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
|
|
|
|
// Seek outside the availability window right away. The playhead
|
|
// should adjust the video's current time.
|
|
video.currentTime = segmentAvailability.end + 120;
|
|
await video.play();
|
|
|
|
// Wait until the repositioning is complete so we don't
|
|
// immediately hit this case.
|
|
await shaka.test.Util.delay(/* seconds= */ 1);
|
|
await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305);
|
|
});
|
|
|
|
it('can handle seeks behind availability window', async () => {
|
|
let seekCount = 0;
|
|
eventManager.listen(video, 'seeking', () => {
|
|
seekCount++;
|
|
});
|
|
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
|
|
await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
|
|
|
|
// Seek outside the availability window right away. The playhead
|
|
// should adjust the video's current time.
|
|
video.currentTime = segmentAvailability.start - 120;
|
|
expect(video.currentTime).toBeGreaterThan(0);
|
|
|
|
await video.play();
|
|
await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305);
|
|
|
|
// We are playing close to the beginning of the availability window.
|
|
// We should be playing smoothly and not seeking repeatedly as we fall
|
|
// outside the window. Because we tried to seek before the beginning, we
|
|
// also want to know that this actually happened, so we have a minimum
|
|
// number of seeks we expect. If those didn't happen as we thought, the
|
|
// test expectation to reach a certain time toward the end would be
|
|
// meaningless.
|
|
//
|
|
// Expected seeks:
|
|
// 1. seek to live stream start time during startup
|
|
// 2. explicit seek in the test to get outside the window
|
|
// 3. (usually) Playhead seeks to force us back inside the window
|
|
// 4. (maybe) seek if there is a gap at the period boundary
|
|
// 5. (maybe) seek to flush a pipeline stall
|
|
// 6. (maybe) on slower platforms (e.g. GitHub actions)
|
|
if (shaka.media.Capabilities.isInfiniteLiveStreamDurationSupported()) {
|
|
// On devices that support setLiveSeekableRange(), MediaSource handles
|
|
// keeping the playhead inside the seekable range. We don't see
|
|
// explicit seeking events for the correction when this happens.
|
|
// Instead, the time in the seeking event is greater than what we set
|
|
// currentTime to.
|
|
expect(seekCount).toBeGreaterThan(1);
|
|
} else {
|
|
expect(seekCount).toBeGreaterThan(2);
|
|
}
|
|
expect(seekCount).toBeLessThan(7);
|
|
});
|
|
});
|
|
|
|
// This tests gaps created by missing segments.
|
|
// TODO: Consider also adding tests for missing frames.
|
|
describe('gap jumping', () => {
|
|
it('jumps small gaps at the beginning', async () => {
|
|
await setupGappyContent(/* gapAtStart= */ 1, /* dropSegment= */ false);
|
|
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
await video.play();
|
|
|
|
await waiter.timeoutAfter(5).waitUntilPlayheadReaches(video, 0.01);
|
|
expect(video.buffered.length).toBeGreaterThan(0);
|
|
expect(video.buffered.start(0)).toBeCloseTo(1);
|
|
|
|
await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 5);
|
|
});
|
|
|
|
it('jumps large gaps at the beginning', async () => {
|
|
await setupGappyContent(/* gapAtStart= */ 5, /* dropSegment= */ false);
|
|
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
await video.play();
|
|
|
|
await waiter.timeoutAfter(5).waitUntilPlayheadReaches(video, 0.01);
|
|
expect(video.buffered.length).toBeGreaterThan(0);
|
|
expect(video.buffered.start(0)).toBeCloseTo(5);
|
|
|
|
await waiter.timeoutAfter(20).waitUntilPlayheadReaches(video, 8);
|
|
});
|
|
|
|
it('jumps small gaps in the middle', async () => {
|
|
await setupGappyContent(/* gapAtStart= */ 0, /* dropSegment= */ true);
|
|
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
|
|
await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
|
|
|
|
video.currentTime = 8;
|
|
await video.play();
|
|
|
|
await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 23);
|
|
// Should be close enough to still have the gap buffered.
|
|
expect(video.buffered.length).toBe(2);
|
|
});
|
|
|
|
it('jumps large gaps in the middle', async () => {
|
|
await setupGappyContent(/* gapAtStart= */ 0, /* dropSegment= */ true);
|
|
|
|
// Let's go!
|
|
streamingEngine.switchVariant(variant);
|
|
await streamingEngine.start();
|
|
|
|
await waiter.timeoutAfter(5).waitForEvent(video, 'loadeddata');
|
|
|
|
video.currentTime = 8;
|
|
await video.play();
|
|
|
|
await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 23);
|
|
// Should be close enough to still have the gap buffered.
|
|
expect(video.buffered.length).toBe(2);
|
|
});
|
|
|
|
/**
|
|
* @param {number} gapAtStart The gap to introduce before start, in seconds.
|
|
* @param {boolean} dropSegment Whether to drop a segment in the middle.
|
|
* @return {!Promise}
|
|
*/
|
|
async function setupGappyContent(gapAtStart, dropSegment) {
|
|
// This uses "normal" stream generators and networking engine. The only
|
|
// difference is the segments are removed from the manifest. The segments
|
|
// should not be downloaded.
|
|
await createVodStreamGenerator(metadata.audio, ContentType.AUDIO);
|
|
await createVodStreamGenerator(metadata.video, ContentType.VIDEO);
|
|
|
|
segmentAvailability = {
|
|
start: 0,
|
|
end: 30,
|
|
};
|
|
|
|
timeline =
|
|
shaka.test.StreamingEngineUtil.createFakePresentationTimeline(
|
|
segmentAvailability,
|
|
/* presentationDuration= */ 30,
|
|
/* maxSegmentDuration= */ metadata.video.segmentDuration,
|
|
/* isLive= */ false);
|
|
|
|
setupNetworkingEngine(
|
|
/* presentationDuration= */ 30,
|
|
{
|
|
audio: metadata.audio.segmentDuration,
|
|
video: metadata.video.segmentDuration,
|
|
});
|
|
|
|
manifest = setupGappyManifest(gapAtStart, dropSegment);
|
|
variant = manifest.variants[0];
|
|
|
|
setupPlayhead();
|
|
createStreamingEngine();
|
|
}
|
|
|
|
/**
|
|
* TODO: Consolidate with StreamingEngineUtil.createManifest?
|
|
* @param {number} gapAtStart
|
|
* @param {boolean} dropSegment
|
|
* @return {shaka.extern.Manifest}
|
|
*/
|
|
function setupGappyManifest(gapAtStart, dropSegment) {
|
|
// In these tests we are going to test GapJumpingController so we are
|
|
// interested in activating the stall detector.
|
|
config.stallEnabled = true;
|
|
|
|
/**
|
|
* @param {string} type
|
|
* @param {shaka.media.InitSegmentReference} initSegmentReference
|
|
* @return {!shaka.media.SegmentIndex}
|
|
*/
|
|
function createIndex(type, initSegmentReference) {
|
|
const d = metadata[type].segmentDuration;
|
|
const refs = [];
|
|
let i = 0;
|
|
let time = gapAtStart;
|
|
while (time < 30) {
|
|
let end = time + d;
|
|
// Make segment 0 longer to make the manifest continuous, despite the
|
|
// dropped segment.
|
|
if (i == 0 && dropSegment) {
|
|
end += d;
|
|
}
|
|
|
|
let cur = i;
|
|
const getUris = () => {
|
|
// The times in the media are based on the URL; so to drop a
|
|
// segment, we change the URL.
|
|
if (cur >= 1 && dropSegment) {
|
|
cur++;
|
|
}
|
|
return ['0_' + type + '_' + cur];
|
|
};
|
|
refs.push(new shaka.media.SegmentReference(
|
|
/* startTime= */ time,
|
|
/* endTime= */ end,
|
|
getUris,
|
|
/* startByte= */ 0,
|
|
/* endByte= */ null,
|
|
initSegmentReference,
|
|
/* timestampOffset= */ gapAtStart,
|
|
/* appendWindowStart= */ 0,
|
|
/* appendWindowEnd= */ Infinity));
|
|
i++;
|
|
time = end;
|
|
}
|
|
return new shaka.media.SegmentIndex(refs);
|
|
}
|
|
|
|
function createInit(type) {
|
|
const getUris = () => {
|
|
return ['0_' + type + '_init'];
|
|
};
|
|
return new shaka.media.InitSegmentReference(getUris, 0, null);
|
|
}
|
|
|
|
const videoInit = createInit('video');
|
|
const videoIndex = createIndex('video', videoInit);
|
|
const audioInit = createInit('audio');
|
|
const audioIndex = createIndex('audio', audioInit);
|
|
|
|
return {
|
|
presentationTimeline: timeline,
|
|
offlineSessionIds: [],
|
|
textStreams: [],
|
|
imageStreams: [],
|
|
sequenceMode: false,
|
|
ignoreManifestTimestampsInSegmentsMode: false,
|
|
type: 'UNKNOWN',
|
|
serviceDescription: null,
|
|
nextUrl: null,
|
|
periodCount: 1,
|
|
gapCount: 0,
|
|
isLowLatency: false,
|
|
startTime: null,
|
|
variants: [{
|
|
id: 1,
|
|
video: {
|
|
id: 2,
|
|
createSegmentIndex: () => Promise.resolve(),
|
|
segmentIndex: videoIndex,
|
|
mimeType: 'video/mp4',
|
|
codecs: 'avc1.42c01e',
|
|
bandwidth: 5000000,
|
|
width: 600,
|
|
height: 400,
|
|
type: shaka.util.ManifestParserUtils.ContentType.VIDEO,
|
|
drmInfos: [],
|
|
},
|
|
audio: {
|
|
id: 3,
|
|
createSegmentIndex: () => Promise.resolve(),
|
|
segmentIndex: audioIndex,
|
|
mimeType: 'audio/mp4',
|
|
codecs: 'mp4a.40.2',
|
|
bandwidth: 192000,
|
|
type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
|
|
drmInfos: [],
|
|
},
|
|
}],
|
|
};
|
|
}
|
|
});
|
|
});
|