mirror of
https://github.com/video-dev/hls.js.git
synced 2026-06-16 13:34:44 +00:00
359197f20b
* chore(deps): update dependency prettier to v3 * run prettier --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Tom Jenkinson <tom@tjenkinson.me>
360 lines
12 KiB
JavaScript
360 lines
12 KiB
JavaScript
import sinon from 'sinon';
|
|
import Hls from '../../../src/hls';
|
|
import BufferController from '../../../src/controller/buffer-controller';
|
|
import { Events } from '../../../src/events';
|
|
|
|
describe('BufferController tests', function () {
|
|
let hls;
|
|
let bufferController;
|
|
const sandbox = sinon.createSandbox();
|
|
|
|
beforeEach(function () {
|
|
hls = new Hls({});
|
|
bufferController = new BufferController(hls);
|
|
});
|
|
|
|
afterEach(function () {
|
|
sandbox.restore();
|
|
});
|
|
|
|
describe('onBufferFlushing', function () {
|
|
beforeEach(function () {
|
|
bufferController.operationQueue.buffers.audio = {};
|
|
bufferController.operationQueue.buffers.video = {};
|
|
});
|
|
|
|
it('flushes a specific type when provided a type', function () {
|
|
const spy = sandbox.spy(bufferController.operationQueue, 'append');
|
|
bufferController.onBufferFlushing(Events.BUFFER_FLUSHING, {
|
|
startOffset: 0,
|
|
endOffset: 10,
|
|
type: 'video',
|
|
});
|
|
expect(spy).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('flushes all source buffers when buffer flush event type is undefined', function () {
|
|
const spy = sandbox.spy(bufferController.operationQueue, 'append');
|
|
bufferController.onBufferFlushing(Events.BUFFER_FLUSHING, {
|
|
startOffset: 0,
|
|
endOffset: 10,
|
|
});
|
|
expect(spy).to.have.been.calledTwice;
|
|
});
|
|
});
|
|
|
|
// describe('Live back buffer enforcement', function () {
|
|
// let mockMedia;
|
|
// let mockSourceBuffer;
|
|
// let bufStart;
|
|
//
|
|
// beforeEach(function () {
|
|
// bufStart = 0;
|
|
// bufferController._levelTargetDuration = 10;
|
|
// bufferController.media = mockMedia = {
|
|
// currentTime: 0
|
|
// };
|
|
// bufferController.sourceBuffer = mockSourceBuffer = {
|
|
// video: {
|
|
// buffered: {
|
|
// start () {
|
|
// return bufStart;
|
|
// },
|
|
// length: 1
|
|
// }
|
|
// }
|
|
// };
|
|
// bufferController._live = true;
|
|
// hls.config.backBufferLength = 10;
|
|
// });
|
|
//
|
|
// it('exits early if not live', function () {
|
|
// bufferController.flushBackBuffer();
|
|
// expect(removeStub).to.not.have.been.called;
|
|
// });
|
|
//
|
|
// it('exits early if backBufferLength is not a finite number, or is less than 0', function () {
|
|
// hls.config.backBufferLength = 'foo';
|
|
// bufferController.flushBackBuffer();
|
|
//
|
|
// hls.config.backBufferLength = -1;
|
|
// bufferController.flushBackBuffer();
|
|
//
|
|
// expect(removeStub).to.not.have.been.called;
|
|
// });
|
|
//
|
|
// it('does not flush if nothing is buffered', function () {
|
|
// delete mockSourceBuffer.buffered;
|
|
// bufferController.flushBackBuffer();
|
|
//
|
|
// mockSourceBuffer = null;
|
|
// bufferController.flushBackBuffer();
|
|
//
|
|
// expect(removeStub).to.not.have.been.called;
|
|
// });
|
|
//
|
|
// it('does not flush if no buffered range intersects with back buffer limit', function () {
|
|
// bufStart = 5;
|
|
// mockMedia.currentTime = 10;
|
|
// bufferController.flushBackBuffer();
|
|
// expect(removeStub).to.not.have.been.called;
|
|
// });
|
|
//
|
|
// it('does not flush if the backBufferLength is Infinity', function () {
|
|
// hls.config.backBufferLength = Infinity;
|
|
// mockMedia.currentTime = 15;
|
|
// bufferController.flushBackBuffer();
|
|
// expect(removeStub).to.not.have.been.called;
|
|
// });
|
|
//
|
|
// it('flushes up to the back buffer limit if the buffer intersects with that point', function () {
|
|
// mockMedia.currentTime = 15;
|
|
// bufferController.flushBackBuffer();
|
|
// expect(removeStub).to.have.been.calledOnce;
|
|
// expect(bufferController.flushBufferCounter).to.equal(0);
|
|
// expect(removeStub).to.have.been.calledWith('video', mockSourceBuffer.video, 0, 5);
|
|
// });
|
|
//
|
|
// it('flushes to a max of one targetDuration from currentTime, regardless of backBufferLength', function () {
|
|
// mockMedia.currentTime = 15;
|
|
// bufferController._levelTargetDuration = 5;
|
|
// hls.config.backBufferLength = 0;
|
|
// bufferController.flushBackBuffer();
|
|
// expect(removeStub).to.have.been.calledWith('video', mockSourceBuffer.video, 0, 10);
|
|
// });
|
|
//
|
|
// it('should trigger clean back buffer when there are no pending appends', function () {
|
|
// bufferController.parent = {};
|
|
// bufferController.segments = [{ parent: bufferController.parent }];
|
|
//
|
|
// sandbox.stub(bufferController, 'doAppending');
|
|
//
|
|
// bufferController._onSBUpdateEnd();
|
|
//
|
|
// expect(flushSpy).to.not.have.been.called;
|
|
//
|
|
// bufferController.segments = [];
|
|
// bufferController._onSBUpdateEnd();
|
|
//
|
|
// expect(flushSpy).to.have.been.calledOnce;
|
|
// });
|
|
// });
|
|
|
|
describe('sourcebuffer creation', function () {
|
|
let createSbStub;
|
|
let checkPendingTracksSpy;
|
|
beforeEach(function () {
|
|
createSbStub = sandbox
|
|
.stub(bufferController, 'createSourceBuffers')
|
|
.callsFake(() => {
|
|
Object.keys(bufferController.pendingTracks).forEach((type) => {
|
|
bufferController.sourceBuffer ||= {};
|
|
bufferController.sourceBuffer[type] = {
|
|
appendBuffer: () => {},
|
|
remove: () => {},
|
|
};
|
|
});
|
|
});
|
|
checkPendingTracksSpy = sandbox.spy(
|
|
bufferController,
|
|
'checkPendingTracks',
|
|
);
|
|
});
|
|
|
|
it('initializes with zero expected BUFFER_CODEC events', function () {
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(0);
|
|
});
|
|
|
|
it('should throw if no media element has been attached', function () {
|
|
bufferController.createSourceBuffers.restore();
|
|
bufferController.pendingTracks = { video: {} };
|
|
|
|
expect(bufferController.checkPendingTracks).to.throw();
|
|
});
|
|
|
|
it('exposes tracks from buffer controller through BUFFER_CREATED event', function (done) {
|
|
bufferController.createSourceBuffers.restore();
|
|
|
|
let video = document.createElement('video');
|
|
bufferController.onMediaAttaching(Events.MEDIA_ATTACHING, {
|
|
media: video,
|
|
});
|
|
sandbox.stub(bufferController.mediaSource, 'addSourceBuffer');
|
|
|
|
hls.on(Hls.Events.BUFFER_CREATED, (event, data) => {
|
|
const tracks = data.tracks;
|
|
expect(bufferController.pendingTracks).to.not.equal(tracks);
|
|
expect(bufferController.tracks).to.equal(tracks);
|
|
done();
|
|
});
|
|
|
|
hls.once(Hls.Events.ERROR, (event, data) => {
|
|
// Async timeout prevents assertion from throwing in event handler
|
|
self.setTimeout(() => {
|
|
expect(data.error.message).to.equal(null);
|
|
done();
|
|
});
|
|
});
|
|
|
|
bufferController.pendingTracks = {
|
|
video: {
|
|
container: 'video/mp4',
|
|
codec: 'avc1.42e01e',
|
|
},
|
|
};
|
|
bufferController.checkPendingTracks();
|
|
|
|
video = null;
|
|
});
|
|
|
|
it('expects one bufferCodec event by default', function () {
|
|
bufferController.onManifestParsed(Events.MANIFEST_PARSED, {});
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(1);
|
|
});
|
|
|
|
it('expects two bufferCodec events if altAudio is signaled', function () {
|
|
bufferController.onManifestParsed(Events.MANIFEST_PARSED, {
|
|
altAudio: true,
|
|
});
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(2);
|
|
});
|
|
|
|
it('expects one bufferCodec event if altAudio is signaled with audio only', function () {
|
|
bufferController.onManifestParsed(Events.MANIFEST_PARSED, {
|
|
altAudio: true,
|
|
audio: true,
|
|
video: false,
|
|
});
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(1);
|
|
});
|
|
|
|
it('creates sourceBuffers when no more BUFFER_CODEC events are expected', function () {
|
|
bufferController.pendingTracks = { video: {} };
|
|
|
|
bufferController.checkPendingTracks();
|
|
expect(createSbStub).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('creates sourceBuffers on the first even if two tracks are received', function () {
|
|
bufferController.pendingTracks = { audio: {}, video: {} };
|
|
bufferController.bufferCodecEventsExpected = 2;
|
|
|
|
bufferController.checkPendingTracks();
|
|
expect(createSbStub).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('does not create sourceBuffers when BUFFER_CODEC events are expected', function () {
|
|
bufferController.pendingTracks = { video: {} };
|
|
bufferController.bufferCodecEventsExpected = 1;
|
|
|
|
bufferController.checkPendingTracks();
|
|
expect(createSbStub).to.not.have.been.called;
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(1);
|
|
});
|
|
|
|
it('checks pending tracks in onMediaSourceOpen', function () {
|
|
bufferController._onMediaSourceOpen();
|
|
expect(checkPendingTracksSpy).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('checks pending tracks even when more events are expected', function () {
|
|
bufferController.sourceBuffer = {};
|
|
bufferController.mediaSource = { readyState: 'open' };
|
|
bufferController.bufferCodecEventsExpected = 2;
|
|
|
|
bufferController.onBufferCodecs(Events.BUFFER_CODECS, {});
|
|
expect(checkPendingTracksSpy).to.have.been.calledOnce;
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(1);
|
|
|
|
bufferController.onBufferCodecs(Events.BUFFER_CODECS, {});
|
|
expect(checkPendingTracksSpy).to.have.been.calledTwice;
|
|
expect(bufferController.bufferCodecEventsExpected).to.equal(0);
|
|
});
|
|
|
|
it('creates the expected amount of sourceBuffers given the standard event flow', function () {
|
|
bufferController.sourceBuffer = {};
|
|
bufferController.mediaSource = {
|
|
readyState: 'open',
|
|
removeEventListener: sandbox.stub(),
|
|
};
|
|
|
|
bufferController.onManifestParsed(Events.MANIFEST_PARSED, {
|
|
altAudio: true,
|
|
});
|
|
bufferController._onMediaSourceOpen();
|
|
bufferController.onBufferCodecs(Events.BUFFER_CODECS, { audio: {} });
|
|
bufferController.onBufferCodecs(Events.BUFFER_CODECS, { video: {} });
|
|
|
|
expect(createSbStub).to.have.been.calledOnce;
|
|
expect(createSbStub).to.have.been.calledWith({ audio: {}, video: {} });
|
|
});
|
|
});
|
|
|
|
describe('onBufferCodecs', function () {
|
|
it('calls changeType if needed and stores current track info', function () {
|
|
const getSourceBufferTypes = sandbox
|
|
.stub(bufferController, 'getSourceBufferTypes')
|
|
.returns(['audio', 'video']);
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const appendChangeType = sandbox.stub(
|
|
bufferController,
|
|
'appendChangeType',
|
|
);
|
|
const buffer = {
|
|
changeType: sandbox.stub(),
|
|
};
|
|
const originalAudioTrack = {
|
|
id: 'main',
|
|
codec: 'mp4a.40.2',
|
|
levelCodec: undefined,
|
|
container: 'audio/mp4',
|
|
metadata: {
|
|
channelCount: 1,
|
|
},
|
|
};
|
|
const newAudioTrack = {
|
|
id: 'main',
|
|
codec: 'mp4a.40.5',
|
|
levelCodec: undefined,
|
|
container: 'audio/mp4',
|
|
metadata: {
|
|
channelCount: 1,
|
|
},
|
|
};
|
|
bufferController.tracks = {
|
|
audio: {
|
|
...originalAudioTrack,
|
|
buffer,
|
|
},
|
|
};
|
|
bufferController.onBufferCodecs(Events.BUFFER_CODECS, {
|
|
audio: newAudioTrack,
|
|
});
|
|
expect(getSourceBufferTypes).to.have.been.calledOnce;
|
|
expect(bufferController.appendChangeType).to.have.been.calledOnce;
|
|
expect(bufferController.appendChangeType).to.have.been.calledWith(
|
|
'audio',
|
|
'audio/mp4;codecs=mp4a.40.5',
|
|
);
|
|
expect(bufferController.tracks.audio).to.deep.equal({
|
|
buffer,
|
|
...newAudioTrack,
|
|
});
|
|
|
|
bufferController.onBufferCodecs(Events.BUFFER_CODECS, {
|
|
audio: originalAudioTrack,
|
|
});
|
|
expect(getSourceBufferTypes).to.have.been.calledTwice;
|
|
expect(bufferController.appendChangeType).to.have.been.calledTwice;
|
|
expect(bufferController.appendChangeType).to.have.been.calledWith(
|
|
'audio',
|
|
'audio/mp4;codecs=mp4a.40.2',
|
|
);
|
|
expect(bufferController.tracks.audio).to.deep.equal({
|
|
buffer,
|
|
...originalAudioTrack,
|
|
});
|
|
});
|
|
});
|
|
});
|