mirror of
https://github.com/video-dev/hls.js.git
synced 2026-05-17 13:30:38 +00:00
190 lines
7.4 KiB
TypeScript
190 lines
7.4 KiB
TypeScript
import { config as chaiConfig, expect, use } from 'chai';
|
|
import sinon from 'sinon';
|
|
import sinonChai from 'sinon-chai';
|
|
import { Events } from '../../../src/events';
|
|
import Hls from '../../../src/hls';
|
|
import { LoadStats } from '../../../src/loader/load-stats';
|
|
import { PlaylistLevelType } from '../../../src/types/loader';
|
|
import type { HlsConfig } from '../../../src/config';
|
|
import type {
|
|
HlsIFramesOnly,
|
|
IFrameController,
|
|
} from '../../../src/controller/iframe-controller';
|
|
import type PlaylistLoader from '../../../src/loader/playlist-loader';
|
|
import type {
|
|
ComponentAPI,
|
|
NetworkComponentAPI,
|
|
} from '../../../src/types/component-api';
|
|
import type { TimestampOffset } from '../../../src/utils/timescale-conversion';
|
|
|
|
use(sinonChai);
|
|
chaiConfig.truncateThreshold = 0;
|
|
|
|
type HlsTestable = Omit<Hls, 'networkControllers' | 'coreComponents'> & {
|
|
coreComponents: ComponentAPI[];
|
|
networkControllers: NetworkComponentAPI[];
|
|
iframeController: IFrameController;
|
|
trigger: Hls['trigger'] & sinon.SinonSpy;
|
|
};
|
|
|
|
type PlaylistLoaderTestable = Omit<PlaylistLoader, 'handleMasterPlaylist'> & {
|
|
handleMasterPlaylist: (
|
|
response: { data: string; url: string },
|
|
stats: LoadStats,
|
|
) => void;
|
|
};
|
|
|
|
class HLSTestPlayer extends Hls {
|
|
constructor(config: Partial<HlsConfig>) {
|
|
super(config);
|
|
}
|
|
}
|
|
|
|
const playlistWithIFrameVariants = `#EXTM3U
|
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2",URI="audio/en/mp4a.40.2/media.m3u8"
|
|
#EXT-X-STREAM-INF:AUDIO="audio",AVERAGE-BANDWIDTH=6383725,BANDWIDTH=7495785,CODECS="avc1.64002A,mp4a.40.2",RESOLUTION=1920x1080
|
|
video/avc1/1/media.m3u8
|
|
#EXT-X-STREAM-INF:AUDIO="audio",AVERAGE-BANDWIDTH=2131576,BANDWIDTH=2653633,CODECS="avc1.64002A,mp4a.40.2",RESOLUTION=960x540
|
|
video/avc1/3/media.m3u8
|
|
#EXT-X-STREAM-INF:AUDIO="audio",AVERAGE-BANDWIDTH=3552258,BANDWIDTH=4191484,CODECS="avc1.64002A,mp4a.40.2",RESOLUTION=1280x720
|
|
video/avc1/4/media.m3u8
|
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=309502,BANDWIDTH=481062,CODECS="avc1.64002A",RESOLUTION=1920x1080,URI="video/avc1/1/iframes.m3u8"
|
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=65770,BANDWIDTH=110693,CODECS="avc1.64002A",RESOLUTION=960x540,URI="video/avc1/3/iframes.m3u8"
|
|
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=169447,BANDWIDTH=271378,CODECS="avc1.64002A",RESOLUTION=1280x720,URI="video/avc1/4/iframes.m3u8"
|
|
`;
|
|
|
|
const playlistWithSteeringPathways = `#EXTM3U
|
|
#EXT-X-STREAM-INF:PATHWAY-ID=".",BANDWIDTH=7495785,CODECS="avc1.64002A,mp4a.40.2",RESOLUTION=1920x1080
|
|
video/avc1/1/media.m3u8
|
|
#EXT-X-STREAM-INF:PATHWAY-ID="..",BANDWIDTH=7495785,CODECS="avc1.64002A,mp4a.40.2",RESOLUTION=1920x1080
|
|
https://b.com/video/avc1/1/media.m3u8
|
|
#EXT-X-I-FRAME-STREAM-INF:PATHWAY-ID=".",BANDWIDTH=481062,CODECS="avc1.64002A",RESOLUTION=1920x1080,URI="video/avc1/1/iframes.m3u8"
|
|
#EXT-X-I-FRAME-STREAM-INF:PATHWAY-ID="..",BANDWIDTH=481062,CODECS="avc1.64002A",RESOLUTION=1920x1080,URI="https://b.com/video/avc1/1/media.m3u8"
|
|
`;
|
|
|
|
describe('IFrameController', function () {
|
|
const sandbox = sinon.createSandbox();
|
|
let hls: HlsTestable;
|
|
let playlistLoader: PlaylistLoaderTestable;
|
|
let iframeController: IFrameController;
|
|
|
|
beforeEach(function () {
|
|
hls = new HLSTestPlayer({
|
|
autoStartLoad: false,
|
|
// debug: true,
|
|
debug: {
|
|
trace: () => null,
|
|
debug: () => null,
|
|
log: () => null,
|
|
warn: () => null,
|
|
info: () => null,
|
|
error: () => null,
|
|
},
|
|
}) as unknown as HlsTestable;
|
|
playlistLoader = hls.networkControllers[0] as PlaylistLoaderTestable;
|
|
sandbox.spy(hls, 'trigger');
|
|
iframeController = hls.iframeController;
|
|
});
|
|
|
|
afterEach(function () {
|
|
hls.destroy();
|
|
sandbox.restore();
|
|
});
|
|
|
|
function loadManifest(data: string) {
|
|
const url = 'main.m3u8';
|
|
(iframeController as any).clearAsset();
|
|
(hls as any)._url = url;
|
|
playlistLoader.handleMasterPlaylist({ data, url }, new LoadStats());
|
|
expect(hls.trigger).to.be.calledWith(Events.MANIFEST_LOADED);
|
|
}
|
|
|
|
function loadedIFramePlayer(data: string): HlsIFramesOnly {
|
|
loadManifest(data);
|
|
const iframePlayer = iframeController.createIFramePlayer();
|
|
expect(iframePlayer).to.be.an.instanceOf(Hls);
|
|
return iframePlayer as HlsIFramesOnly;
|
|
}
|
|
|
|
it('Does not return IFramePlayers before iframes are loaded', function () {
|
|
const iframePlayer = iframeController.createIFramePlayer();
|
|
expect(iframePlayer).to.be.null;
|
|
});
|
|
|
|
it('creates IFramePlayer instances once iframes are loaded', function () {
|
|
loadManifest(playlistWithIFrameVariants);
|
|
expect(hls.iframeVariants).to.have.lengthOf(3);
|
|
const iframePlayer = iframeController.createIFramePlayer();
|
|
expect(iframePlayer).to.be.an.instanceOf(Hls);
|
|
});
|
|
|
|
it('configures IFramePlayer instances to only handle iframe video variants', function () {
|
|
const iframePlayer = loadedIFramePlayer(playlistWithIFrameVariants);
|
|
expect(iframePlayer.levels).to.have.to.have.lengthOf(3);
|
|
expect(iframePlayer.iframeVariants).to.have.lengthOf(0);
|
|
});
|
|
|
|
it('destroys child IFramePlayer instances when destroyed', function () {
|
|
const iframePlayer = loadedIFramePlayer(playlistWithIFrameVariants);
|
|
expect(hls.url).to.eql('main.m3u8');
|
|
expect(iframePlayer.url).to.eql('main.m3u8');
|
|
hls.destroy();
|
|
expect(hls.url).to.eql(null);
|
|
expect(iframePlayer.url).to.eql(null);
|
|
});
|
|
|
|
it('destroys child IFramePlayer instances when loading a new asset', function () {
|
|
const iframePlayer = loadedIFramePlayer(playlistWithIFrameVariants);
|
|
expect(hls.url).to.eql('main.m3u8');
|
|
expect(iframePlayer.url).to.eql('main.m3u8');
|
|
hls.loadSource('another.m3u8');
|
|
expect(iframePlayer.url).to.eql(null);
|
|
});
|
|
|
|
it('sets Initial Pathway of IFramePlayer to parent instances active variant pathway', function () {
|
|
loadManifest(playlistWithSteeringPathways);
|
|
expect(hls.iframeVariants).to.have.lengthOf(2);
|
|
expect(hls.pathways).to.deep.eq(['.', '..']);
|
|
|
|
hls.loadLevel = 0;
|
|
expect(hls.loadLevel).to.eq(0);
|
|
expect(hls.levels[hls.loadLevel].pathwayId).to.eq('.');
|
|
|
|
hls.pathwayPriority = ['..', '.'];
|
|
expect(hls.pathwayPriority).to.deep.eq(['..', '.']);
|
|
expect(hls.levels[hls.loadLevel].pathwayId).to.eq('..');
|
|
|
|
const iframePlayer = iframeController.createIFramePlayer();
|
|
expect(iframePlayer).to.be.an.instanceOf(Hls);
|
|
if (iframePlayer) {
|
|
expect(iframePlayer.levels[0].pathwayId).to.eq('..');
|
|
}
|
|
});
|
|
|
|
it('updates IFramePlayer Pathway priorty once it has changed on the parent instance', function () {
|
|
const iframePlayer = loadedIFramePlayer(playlistWithSteeringPathways);
|
|
expect(hls.pathways).to.deep.eq(['.', '..']);
|
|
expect(iframePlayer.levels[0].pathwayId).to.eq('.');
|
|
hls.pathwayPriority = ['..', '.'];
|
|
expect(iframePlayer.levels[0].pathwayId).to.eq('..');
|
|
});
|
|
|
|
it('passes copy of initPTS to a newly created IFramePlayer', function () {
|
|
loadManifest(playlistWithIFrameVariants);
|
|
const timestamps: TimestampOffset[] = [
|
|
{ baseTime: 900000, timescale: 90000, trackId: 0 },
|
|
];
|
|
hls.trigger(Events.INIT_PTS_FOUND, {
|
|
id: PlaylistLevelType.MAIN,
|
|
timestampOffsets: timestamps,
|
|
frag: { cc: 0 } as any,
|
|
initPTS: 900000,
|
|
timescale: 90000,
|
|
});
|
|
const iframePlayer = iframeController.createIFramePlayer();
|
|
const iframeStreamController = (iframePlayer as any).streamController;
|
|
expect(iframeStreamController.initPTS).to.not.equal(timestamps);
|
|
expect(iframeStreamController.initPTS).to.deep.equal(timestamps);
|
|
});
|
|
});
|