mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
849bff1db3
Make cloning buffers (or not) explicit in readBytes. When we use a range of bytes temporarily for further parsing, we pass clone=false and get a view on the existing memory buffer. When we want to store the range of bytes, we pass clone=true and avoid holding a reference to an entire segment in memory. The call for the EMSG parser in MediaSourceEngine had an explicit clone, but now uses the new clone parameter. This is not a functional change, though. The only readBytes call that changed in this audit was in the UI seek bar. The rest all appear to be values for temporary usage, and so are not being cloned. The new `clone` parameter will require future callers of `readBytes()` to think about their purpose and make a choice.
374 lines
13 KiB
JavaScript
374 lines
13 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
describe('Mp4Parser', () => {
|
|
const Util = shaka.test.Util;
|
|
|
|
let boxData;
|
|
let fullBoxData;
|
|
let boxWithChildData;
|
|
let fullBoxWithChildData;
|
|
let boxWithSampleDescription;
|
|
let partialBoxWithSampleDescription;
|
|
let multipleSingleLevelBoxes;
|
|
let twoLevelBoxStructure;
|
|
|
|
beforeAll(() => {
|
|
boxData = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x0C, // size
|
|
0x62, 0x30, 0x30, 0x31, // type
|
|
0x00, 0x11, 0x22, 0x33, // payload
|
|
]);
|
|
|
|
fullBoxData = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x10, // size
|
|
0x62, 0x30, 0x30, 0x31, // type
|
|
0x01, // version
|
|
0x12, 0x34, 0x56, // flags
|
|
0x00, 0x11, 0x22, 0x33, // payload
|
|
]);
|
|
|
|
boxWithChildData = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x14, // size
|
|
0x62, 0x30, 0x30, 0x33, // type
|
|
0x00, 0x00, 0x00, 0x0C, // child [0] size
|
|
0x62, 0x30, 0x33, 0x31, // child [0] type
|
|
0x00, 0x11, 0x22, 0x33, // child [0] payload
|
|
0x00, 0x00, 0x00, 0x0C, // child [1] size
|
|
0x62, 0x30, 0x33, 0x32, // child [1] type
|
|
0x44, 0x55, 0x66, 0x77, // child [1] payload
|
|
]);
|
|
|
|
fullBoxWithChildData = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x18, // size
|
|
0x62, 0x30, 0x30, 0x33, // type
|
|
0x01, // version
|
|
0x12, 0x34, 0x56, // flags
|
|
0x00, 0x00, 0x00, 0x0C, // child [0] size
|
|
0x62, 0x30, 0x33, 0x31, // child [0] type
|
|
0x00, 0x11, 0x22, 0x33, // child [0] payload
|
|
0x00, 0x00, 0x00, 0x0C, // child [1] size
|
|
0x62, 0x30, 0x33, 0x32, // child [1] type
|
|
0x44, 0x55, 0x66, 0x77, // child [1] payload
|
|
]);
|
|
|
|
boxWithSampleDescription = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x24, // size
|
|
0x62, 0x30, 0x30, 0x33, // type
|
|
0x00, 0x00, 0x00, 0x02, // number of children
|
|
0x00, 0x00, 0x00, 0x0C, // child [0] size
|
|
0x62, 0x30, 0x33, 0x32, // child [0] type
|
|
0x00, 0x11, 0x22, 0x33, // child [0] payload
|
|
0x00, 0x00, 0x00, 0x0C, // child [1] size
|
|
0x62, 0x30, 0x33, 0x33, // child [1] type
|
|
0x44, 0x55, 0x66, 0x77, // child [1] payload
|
|
]);
|
|
|
|
partialBoxWithSampleDescription = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x24, // size
|
|
0x62, 0x30, 0x30, 0x33, // type
|
|
0x00, 0x00, 0x00, 0x02, // number of children
|
|
0x00, 0x00, 0x00, 0x0C, // child [0] size
|
|
0x62, 0x30, 0x33, 0x32, // child [0] type
|
|
0x00, 0x11, 0x22, 0x33, // child [0] payload
|
|
// Omit child [1]
|
|
]);
|
|
|
|
multipleSingleLevelBoxes = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x0C, // box [0] size
|
|
0x62, 0x30, 0x30, 0x31, // box [0] type
|
|
0x00, 0x11, 0x22, 0x33, // box [0] payload
|
|
0x00, 0x00, 0x00, 0x0C, // box [1] size
|
|
0x62, 0x30, 0x30, 0x32, // box [1] type
|
|
0x00, 0x11, 0x22, 0x33, // box [1] payload
|
|
0x00, 0x00, 0x00, 0x0C, // box [2] size
|
|
0x62, 0x30, 0x30, 0x33, // box [2] type
|
|
0x00, 0x11, 0x22, 0x33, // box [2] payload
|
|
0x00, 0x00, 0x00, 0x0C, // box [3] size
|
|
0x62, 0x30, 0x30, 0x34, // box [3] type
|
|
0x00, 0x11, 0x22, 0x33, // box [3] payload
|
|
]);
|
|
|
|
twoLevelBoxStructure = new Uint8Array([
|
|
0x00, 0x00, 0x00, 0x14, // box [0] size
|
|
0x62, 0x30, 0x31, 0x30, // box [0] type
|
|
0x00, 0x00, 0x00, 0x0C, // box [0] [0] size
|
|
0x00, 0x30, 0x31, 0x31, // box [0] [0] type
|
|
0x00, 0x11, 0x22, 0x33, // box [0] [0] payload
|
|
0x00, 0x00, 0x00, 0x14, // box [1] size
|
|
0x62, 0x30, 0x32, 0x30, // box [1] type
|
|
0x00, 0x00, 0x00, 0x0C, // box [1] [0] size
|
|
0x62, 0x30, 0x32, 0x31, // box [1] [0] type
|
|
0x00, 0x11, 0x22, 0x33, // box [1] [0] payload
|
|
0x00, 0x00, 0x00, 0x14, // box [2] size
|
|
0x62, 0x30, 0x33, 0x30, // box [2] type
|
|
0x00, 0x00, 0x00, 0x0C, // box [2] [0] size
|
|
0x62, 0x30, 0x33, 0x31, // box [2] [0] type
|
|
0x00, 0x11, 0x22, 0x33, // box [2] [0] payload
|
|
0x00, 0x00, 0x00, 0x14, // box [3] size
|
|
0x62, 0x30, 0x34, 0x30, // box [3] type
|
|
0x00, 0x00, 0x00, 0x0C, // box [3] [0] size
|
|
0x62, 0x30, 0x34, 0x31, // box [3] [0] type
|
|
0x00, 0x11, 0x22, 0x33, // box [3] [0] payload
|
|
]);
|
|
});
|
|
|
|
describe('headerDefinitions', () => {
|
|
it('reads box header', () => {
|
|
const callback = jasmine.createSpy('parser callback').and.callFake(
|
|
(box) => {
|
|
expect(box.start).toBe(0);
|
|
expect(box.size).toBe(12);
|
|
expect(box.version).toBe(null);
|
|
expect(box.flags).toBe(null);
|
|
});
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b001', Util.spyFunc(callback)).parse(boxData);
|
|
|
|
expect(callback).toHaveBeenCalled();
|
|
});
|
|
|
|
it('reads full box header', () => {
|
|
const callback = jasmine.createSpy('parser callback').and.callFake(
|
|
(box) => {
|
|
expect(box.start).toBe(0);
|
|
expect(box.size).toBe(16);
|
|
expect(box.version).toBe(1);
|
|
expect(box.flags).toBe(0x123456);
|
|
});
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.fullBox('b001', Util.spyFunc(callback)).parse(fullBoxData);
|
|
|
|
expect(callback).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('boxDefinitions', () => {
|
|
it('reads children definition', () => {
|
|
const parentBox = jasmine.createSpy('parent box').and.callFake(
|
|
shaka.util.Mp4Parser.children);
|
|
|
|
const childBox1 = jasmine.createSpy('child box 1');
|
|
const childBox2 = jasmine.createSpy('child box 2');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b003', Util.spyFunc(parentBox))
|
|
.box('b031', Util.spyFunc(childBox1))
|
|
.box('b032', Util.spyFunc(childBox2)).parse(boxWithChildData);
|
|
|
|
expect(parentBox).toHaveBeenCalled();
|
|
expect(childBox1).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
start: 8,
|
|
size: 12,
|
|
version: null,
|
|
flags: null,
|
|
}));
|
|
expect(childBox2).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
start: 20,
|
|
size: 12,
|
|
version: null,
|
|
flags: null,
|
|
}));
|
|
});
|
|
|
|
it('reads children definition with full boxes', () => {
|
|
const parentBox = jasmine.createSpy('parent box').and.callFake(
|
|
shaka.util.Mp4Parser.children);
|
|
|
|
const childBox1 = jasmine.createSpy('child box 1');
|
|
const childBox2 = jasmine.createSpy('child box 2');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.fullBox('b003', Util.spyFunc(parentBox))
|
|
.box('b031', Util.spyFunc(childBox1))
|
|
.box('b032', Util.spyFunc(childBox2)).parse(fullBoxWithChildData);
|
|
|
|
expect(parentBox).toHaveBeenCalled();
|
|
expect(childBox1).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
start: 12,
|
|
size: 12,
|
|
version: null,
|
|
flags: null,
|
|
}));
|
|
expect(childBox2).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
start: 24,
|
|
size: 12,
|
|
version: null,
|
|
flags: null,
|
|
}));
|
|
});
|
|
|
|
it('stops reading children when asked to', () => {
|
|
const parentBox = jasmine.createSpy('parent box').and.callFake(
|
|
shaka.util.Mp4Parser.children);
|
|
|
|
const childBox1 = jasmine.createSpy('child box 1').and.callFake(
|
|
(box) => {
|
|
box.parser.stop();
|
|
});
|
|
|
|
const childBox2 = jasmine.createSpy('child box 2');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b003', Util.spyFunc(parentBox))
|
|
.box('b031', Util.spyFunc(childBox1))
|
|
.box('b032', Util.spyFunc(childBox2)).parse(boxWithChildData);
|
|
|
|
expect(parentBox).toHaveBeenCalled();
|
|
expect(childBox1).toHaveBeenCalled();
|
|
expect(childBox2).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('reads all data definition', () => {
|
|
let payload = [];
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b001', shaka.util.Mp4Parser.allData(
|
|
(data) => {
|
|
payload = data;
|
|
}, /* clone= */ false)).parse(boxData);
|
|
|
|
expect(payload.length).toBe(4);
|
|
expect(payload[0]).toBe(0x00);
|
|
expect(payload[1]).toBe(0x11);
|
|
expect(payload[2]).toBe(0x22);
|
|
expect(payload[3]).toBe(0x33);
|
|
});
|
|
|
|
it('reads sample description definition', () => {
|
|
const parentBox = jasmine.createSpy('parent box').and.callFake(
|
|
shaka.util.Mp4Parser.sampleDescription);
|
|
const childBox1 = jasmine.createSpy('child box 1');
|
|
const childBox2 = jasmine.createSpy('child box 2');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b003', Util.spyFunc(parentBox))
|
|
.box('b032', Util.spyFunc(childBox1))
|
|
.box('b033', Util.spyFunc(childBox2)).parse(boxWithSampleDescription);
|
|
|
|
expect(parentBox).toHaveBeenCalledTimes(1);
|
|
expect(childBox1).toHaveBeenCalledTimes(1);
|
|
expect(childBox1).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
start: 12,
|
|
size: 12,
|
|
version: null,
|
|
flags: null,
|
|
}));
|
|
expect(childBox2).toHaveBeenCalledTimes(1);
|
|
expect(childBox2).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
start: 24,
|
|
size: 12,
|
|
version: null,
|
|
flags: null,
|
|
}));
|
|
});
|
|
|
|
it('stops reading sample description when asked to', () => {
|
|
const parentBox = jasmine.createSpy('parent box').and.callFake(
|
|
shaka.util.Mp4Parser.sampleDescription);
|
|
const childBox1 = jasmine.createSpy('child box 1').and.callFake(
|
|
(box) => {
|
|
box.parser.stop();
|
|
});
|
|
const childBox2 = jasmine.createSpy('child box 2');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b003', Util.spyFunc(parentBox))
|
|
.box('b032', Util.spyFunc(childBox1))
|
|
.box('b033', Util.spyFunc(childBox2)).parse(boxWithSampleDescription);
|
|
|
|
expect(parentBox).toHaveBeenCalledTimes(1);
|
|
expect(childBox1).toHaveBeenCalledTimes(1);
|
|
expect(childBox2).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('parsing', () => {
|
|
it('finds all top level boxes', () => {
|
|
const box1 = jasmine.createSpy('box 1');
|
|
const box2 = jasmine.createSpy('box 2');
|
|
const box3 = jasmine.createSpy('box 3');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b001', Util.spyFunc(box1))
|
|
.box('b002', Util.spyFunc(box2))
|
|
.box('b003', Util.spyFunc(box3)).parse(multipleSingleLevelBoxes);
|
|
|
|
expect(box1).toHaveBeenCalled();
|
|
expect(box2).toHaveBeenCalled();
|
|
expect(box3).toHaveBeenCalled();
|
|
});
|
|
|
|
it('skips undefined top level boxes', () => {
|
|
// By leaving a single box undefined, it should not interfere
|
|
// with the other boxes (on the same level) from being read.
|
|
|
|
const box1 = jasmine.createSpy('box 1');
|
|
const box3 = jasmine.createSpy('box 3');
|
|
|
|
new shaka.util.Mp4Parser()
|
|
.box('b001', Util.spyFunc(box1))
|
|
.box('b003', Util.spyFunc(box3)).parse(multipleSingleLevelBoxes);
|
|
|
|
expect(box1).toHaveBeenCalled();
|
|
expect(box3).toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not parse child boxes with undefined parent box', () => {
|
|
const box1 = jasmine.createSpy('box 1');
|
|
const box2Child = jasmine.createSpy('box 2 child');
|
|
const box3 = jasmine.createSpy('box 3');
|
|
|
|
// Listing a definition for box 2's child but not for box 2 should mean
|
|
// box 2's child is never parsed.
|
|
new shaka.util.Mp4Parser()
|
|
.box('b010', Util.spyFunc(box1))
|
|
.box('b021', Util.spyFunc(box2Child))
|
|
.box('b030', Util.spyFunc(box3)).parse(twoLevelBoxStructure);
|
|
|
|
expect(box1).toHaveBeenCalled();
|
|
expect(box2Child).not.toHaveBeenCalled();
|
|
expect(box3).toHaveBeenCalled();
|
|
});
|
|
|
|
it('can parse partial parent box and find first child', () => {
|
|
const parentBox = jasmine.createSpy('parent box').and.callFake(
|
|
shaka.util.Mp4Parser.sampleDescription);
|
|
|
|
const childBox1 = jasmine.createSpy('child box 1').and.callFake(
|
|
(box) => {
|
|
// We found what we were looking for, so stop parsing.
|
|
box.parser.stop();
|
|
});
|
|
|
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.MEDIA,
|
|
shaka.util.Error.Code.BUFFER_READ_OUT_OF_BOUNDS));
|
|
expect(() => {
|
|
new shaka.util.Mp4Parser()
|
|
.box('b003', Util.spyFunc(parentBox))
|
|
.box('b032', Util.spyFunc(childBox1))
|
|
.parse(partialBoxWithSampleDescription, /* partialOkay= */ false);
|
|
}).toThrow(expected);
|
|
|
|
parentBox.calls.reset();
|
|
childBox1.calls.reset();
|
|
|
|
// With the partialOkay flag set to true, this should succeed.
|
|
new shaka.util.Mp4Parser()
|
|
.box('b003', Util.spyFunc(parentBox))
|
|
.box('b032', Util.spyFunc(childBox1))
|
|
.parse(partialBoxWithSampleDescription, /* partialOkay= */ true);
|
|
|
|
expect(parentBox).toHaveBeenCalledTimes(1);
|
|
expect(childBox1).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|