123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378 |
- /* eslint-disable max-len */
- import document from 'global/document';
- import videojs from 'video.js';
- import Events from 'video.js';
- import QUnit from 'qunit';
- import testDataManifests from './test-manifests.js';
- import {
- useFakeEnvironment,
- useFakeMediaSource,
- createPlayer,
- openMediaSource,
- standardXHRResponse,
- absoluteUrl
- } from './test-helpers.js';
- /* eslint-disable no-unused-vars */
- // we need this so that it can register hls with videojs
- import {HlsSourceHandler, HlsHandler, Hls} from '../src/videojs-contrib-hls';
- import window from 'global/window';
- // we need this so the plugin registers itself
- import 'videojs-contrib-quality-levels';
- /* eslint-enable no-unused-vars */
- const Flash = videojs.getTech('Flash');
- const ogHlsHandlerSetupQualityLevels = videojs.HlsHandler.prototype.setupQualityLevels_;
- let nextId = 0;
- // do a shallow copy of the properties of source onto the target object
- const merge = function(target, source) {
- let name;
- for (name in source) {
- target[name] = source[name];
- }
- };
- QUnit.module('HLS', {
- beforeEach(assert) {
- this.env = useFakeEnvironment(assert);
- this.requests = this.env.requests;
- this.mse = useFakeMediaSource();
- this.clock = this.env.clock;
- this.old = {};
- // mock out Flash features for phantomjs
- this.old.Flash = videojs.mergeOptions({}, Flash);
- /* eslint-disable camelcase */
- Flash.embed = function(swf, flashVars) {
- let el = document.createElement('div');
- el.id = 'vjs_mock_flash_' + nextId++;
- el.className = 'vjs-tech vjs-mock-flash';
- el.duration = Infinity;
- el.vjs_load = function() {};
- el.vjs_getProperty = function(attr) {
- if (attr === 'buffered') {
- return [[0, 0]];
- }
- return el[attr];
- };
- el.vjs_setProperty = function(attr, value) {
- el[attr] = value;
- };
- el.vjs_src = function() {};
- el.vjs_play = function() {};
- el.vjs_discontinuity = function() {};
- if (flashVars.autoplay) {
- el.autoplay = true;
- }
- if (flashVars.preload) {
- el.preload = flashVars.preload;
- }
- el.currentTime = 0;
- return el;
- };
- /* eslint-enable camelcase */
- this.old.FlashSupported = Flash.isSupported;
- Flash.isSupported = function() {
- return true;
- };
- // store functionality that some tests need to mock
- this.old.GlobalOptions = videojs.mergeOptions(videojs.options);
- // force the HLS tech to run
- this.old.NativeHlsSupport = videojs.Hls.supportsNativeHls;
- videojs.Hls.supportsNativeHls = false;
- this.old.Decrypt = videojs.Hls.Decrypter;
- videojs.Hls.Decrypter = function() {};
- // save and restore browser detection for the Firefox-specific tests
- this.old.browser = videojs.browser;
- videojs.browser = videojs.mergeOptions({}, videojs.browser);
- this.standardXHRResponse = (request, data) => {
- standardXHRResponse(request, data);
- // Because SegmentLoader#fillBuffer_ is now scheduled asynchronously
- // we have to use clock.tick to get the expected side effects of
- // SegmentLoader#handleUpdateEnd_
- this.clock.tick(1);
- };
- // setup a player
- this.player = createPlayer();
- this.clock.tick(1);
- },
- afterEach() {
- this.env.restore();
- this.mse.restore();
- merge(videojs.options, this.old.GlobalOptions);
- Flash.isSupported = this.old.FlashSupported;
- merge(Flash, this.old.Flash);
- videojs.Hls.supportsNativeHls = this.old.NativeHlsSupport;
- videojs.Hls.Decrypter = this.old.Decrypt;
- videojs.browser = this.old.browser;
- this.player.dispose();
- }
- });
- QUnit.test('deprecation warning is show when using player.hls', function(assert) {
- let oldWarn = videojs.log.warn;
- let warning = '';
- let hlsPlayerAccessEvents = 0;
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.on('usage', (event) => {
- if (event.name === 'hls-player-access') {
- hlsPlayerAccessEvents++;
- }
- });
- videojs.log.warn = (text) => {
- warning = text;
- };
- assert.equal(hlsPlayerAccessEvents, 0, 'no hls-player-access event was fired');
- let hls = this.player.hls;
- assert.equal(hlsPlayerAccessEvents, 1, 'an hls-player-access event was fired');
- assert.equal(warning, 'player.hls is deprecated. Use player.tech_.hls instead.', 'warning would have been shown');
- assert.ok(hls, 'an instance of hls is returned by player.hls');
- videojs.log.warn = oldWarn;
- });
- QUnit.test('starts playing if autoplay is specified', function(assert) {
- this.player.autoplay(true);
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- // make sure play() is called *after* the media source opens
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- assert.ok(!this.player.paused(), 'not paused');
- });
- QUnit.test('stats are reset on each new source', function(assert) {
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- // make sure play() is called *after* the media source opens
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests.shift());
- this.standardXHRResponse(this.requests.shift());
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, 'stat is set');
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 0, 'stat is reset');
- });
- QUnit.test('XHR requests first byte range on play', function(assert) {
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.triggerReady();
- this.clock.tick(1);
- this.player.tech_.trigger('play');
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- assert.equal(this.requests[1].headers.Range, 'bytes=0-522827');
- });
- QUnit.test('Seeking requests correct byte range', function(assert) {
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.trigger('play');
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- this.clock.tick(1);
- this.player.currentTime(41);
- this.clock.tick(2);
- assert.equal(this.requests[2].headers.Range, 'bytes=2299992-2835603');
- });
- QUnit.test('autoplay seeks to the live point after playlist load', function(assert) {
- let currentTime = 0;
- this.player.autoplay(true);
- this.player.on('seeking', () => {
- currentTime = this.player.currentTime();
- });
- this.player.src({
- src: 'liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.trigger('play');
- this.standardXHRResponse(this.requests.shift());
- this.clock.tick(1);
- assert.notEqual(currentTime, 0, 'seeked on autoplay');
- });
- QUnit.test('autoplay seeks to the live point after media source open', function(assert) {
- let currentTime = 0;
- this.player.autoplay(true);
- this.player.on('seeking', () => {
- currentTime = this.player.currentTime();
- });
- this.player.src({
- src: 'liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.triggerReady();
- this.clock.tick(1);
- this.standardXHRResponse(this.requests.shift());
- openMediaSource(this.player, this.clock);
- this.player.tech_.trigger('play');
- this.clock.tick(1);
- assert.notEqual(currentTime, 0, 'seeked on autoplay');
- });
- QUnit.test('autoplay seeks to the live point after tech fires loadedmetadata in ie11',
- function(assert) {
- videojs.browser.IE_VERSION = 11;
- let currentTime = 0;
- this.player.autoplay(true);
- this.player.on('seeking', () => {
- currentTime = this.player.currentTime();
- });
- this.player.src({
- src: 'liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.trigger('play');
- this.standardXHRResponse(this.requests.shift());
- this.clock.tick(1);
- assert.equal(currentTime, 0, 'have not played yet');
- this.player.tech_.trigger('loadedmetadata');
- this.clock.tick(1);
- assert.notEqual(currentTime, 0, 'seeked after tech is ready');
- });
- QUnit.test('duration is set when the source opens after the playlist is loaded',
- function(assert) {
- this.player.src({
- src: 'media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.triggerReady();
- this.clock.tick(1);
- this.standardXHRResponse(this.requests.shift());
- openMediaSource(this.player, this.clock);
- assert.equal(this.player.tech_.hls.mediaSource.duration,
- 40,
- 'set the duration');
- });
- QUnit.test('codecs are passed to the source buffer', function(assert) {
- let codecs = [];
- this.player.src({
- src: 'custom-codecs.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- let addSourceBuffer = this.player.tech_.hls.mediaSource.addSourceBuffer;
- this.player.tech_.hls.mediaSource.addSourceBuffer = function(codec) {
- codecs.push(codec);
- return addSourceBuffer.call(this, codec);
- };
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:CODECS="avc1.dd00dd, mp4a.40.f"\n' +
- 'media.m3u8\n');
- this.standardXHRResponse(this.requests.shift());
- assert.equal(codecs.length, 1, 'created a source buffer');
- assert.equal(codecs[0], 'video/mp2t; codecs="avc1.dd00dd, mp4a.40.f"', 'specified the codecs');
- });
- QUnit.test('including HLS as a tech does not error', function(assert) {
- let player = createPlayer({
- techOrder: ['hls', 'html5']
- });
- this.clock.tick(1);
- assert.ok(player, 'created the player');
- assert.equal(this.env.log.warn.calls, 2, 'logged two warnings for deprecations');
- });
- QUnit.test('creates a PlaylistLoader on init', function(assert) {
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.src({
- src: 'manifest/playlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.equal(this.requests[0].aborted, true, 'aborted previous src');
- this.standardXHRResponse(this.requests[1]);
- assert.ok(this.player.tech_.hls.playlists.master,
- 'set the master playlist');
- assert.ok(this.player.tech_.hls.playlists.media(),
- 'set the media playlist');
- assert.ok(this.player.tech_.hls.playlists.media().segments,
- 'the segment entries are parsed');
- assert.strictEqual(this.player.tech_.hls.playlists.master.playlists[0],
- this.player.tech_.hls.playlists.media(),
- 'the playlist is selected');
- });
- QUnit.test('sets the duration if one is available on the playlist', function(assert) {
- let events = 0;
- this.player.src({
- src: 'manifest/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.on('durationchange', function() {
- events++;
- });
- this.standardXHRResponse(this.requests[0]);
- assert.equal(this.player.tech_.hls.mediaSource.duration,
- 40,
- 'set the duration');
- assert.equal(events, 1, 'durationchange is fired');
- });
- QUnit.test('estimates individual segment durations if needed', function(assert) {
- let changes = 0;
- this.player.src({
- src: 'http://example.com/manifest/missingExtinf.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.mediaSource.duration = NaN;
- this.player.tech_.on('durationchange', function() {
- changes++;
- });
- this.standardXHRResponse(this.requests[0]);
- assert.strictEqual(this.player.tech_.hls.mediaSource.duration,
- this.player.tech_.hls.playlists.media().segments.length * 10,
- 'duration is updated');
- assert.strictEqual(changes, 1, 'one durationchange fired');
- });
- QUnit.test('translates seekable by the starting time for live playlists', function(assert) {
- let seekable;
- this.player.src({
- src: 'media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:15\n' +
- '#EXT-X-TARGETDURATION:10\n' +
- '#EXTINF:10,\n' +
- '0.ts\n' +
- '#EXTINF:10,\n' +
- '1.ts\n' +
- '#EXTINF:10,\n' +
- '2.ts\n' +
- '#EXTINF:10,\n' +
- '3.ts\n');
- seekable = this.player.seekable();
- assert.equal(seekable.length, 1, 'one seekable range');
- assert.equal(seekable.start(0), 0, 'the earliest possible position is at zero');
- assert.equal(seekable.end(0), 10, 'end is relative to the start');
- });
- QUnit.test('starts downloading a segment on loadedmetadata', function(assert) {
- this.player.src({
- src: 'manifest/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.buffered = function() {
- return videojs.createTimeRange(0, 0);
- };
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- this.standardXHRResponse(this.requests[1]);
- assert.strictEqual(this.requests[1].url,
- absoluteUrl('manifest/media-00001.ts'),
- 'the first segment is requested');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('re-initializes the handler for each source', function(assert) {
- let firstPlaylists;
- let secondPlaylists;
- let firstMSE;
- let secondMSE;
- let aborts = 0;
- let masterPlaylistController;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- firstPlaylists = this.player.tech_.hls.playlists;
- firstMSE = this.player.tech_.hls.mediaSource;
- this.standardXHRResponse(this.requests.shift());
- this.standardXHRResponse(this.requests.shift());
- masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
- masterPlaylistController.mainSegmentLoader_.sourceUpdater_.sourceBuffer_.abort = () => {
- aborts++;
- };
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- secondPlaylists = this.player.tech_.hls.playlists;
- secondMSE = this.player.tech_.hls.mediaSource;
- assert.equal(1, aborts, 'aborted the old source buffer');
- assert.ok(this.requests[0].aborted, 'aborted the old segment request');
- assert.notStrictEqual(firstPlaylists,
- secondPlaylists,
- 'the playlist object is not reused');
- assert.notStrictEqual(firstMSE, secondMSE, 'the media source object is not reused');
- });
- QUnit.test('triggers a media source error when an initial playlist request errors',
- function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.pop().respond(500);
- assert.equal(this.player.tech_.hls.mediaSource.error_,
- 'network',
- 'a network error is triggered');
- });
- QUnit.test(
- 'triggers a player error when an initial playlist request errors and the media source ' +
- 'isn\'t open',
- function(assert) {
- const done = assert.async();
- const origError = videojs.log.error;
- const errLogs = [];
- const endOfStreams = [];
- videojs.log.error = (log) => errLogs.push(log);
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.masterPlaylistController_.mediaSource.endOfStream = (type) => {
- endOfStreams.push(type);
- throw new Error();
- };
- this.player.on('error', () => {
- const error = this.player.error();
- assert.equal(endOfStreams.length, 1, 'one endOfStream called');
- assert.equal(endOfStreams[0], 'network', 'endOfStream called with network');
- assert.equal(error.code, 2, 'error has correct code');
- assert.equal(error.message,
- 'HLS playlist request error at URL: manifest/master.m3u8',
- 'error has correct message');
- assert.equal(errLogs.length, 1, 'logged an error');
- videojs.log.error = origError;
- assert.notOk(this.player.tech_.hls.mediaSource.error_, 'no media source error');
- done();
- });
- this.requests.pop().respond(500);
- });
- QUnit.test('downloads media playlists after loading the master', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 20e10;
- this.standardXHRResponse(this.requests[0]);
- this.standardXHRResponse(this.requests[1]);
- this.standardXHRResponse(this.requests[2]);
- assert.strictEqual(this.requests[0].url,
- 'manifest/master.m3u8',
- 'master playlist requested');
- assert.strictEqual(this.requests[1].url,
- absoluteUrl('manifest/media2.m3u8'),
- 'media playlist requested');
- assert.strictEqual(this.requests[2].url,
- absoluteUrl('manifest/media2-00001.ts'),
- 'first segment requested');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('setting bandwidth resets throughput', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.hls.throughput = 1000;
- assert.strictEqual(this.player.tech_.hls.throughput,
- 1000,
- 'throughput is set');
- this.player.tech_.hls.bandwidth = 20e10;
- assert.strictEqual(this.player.tech_.hls.throughput,
- 0,
- 'throughput is reset when bandwidth is specified');
- });
- QUnit.test('a thoughput of zero is ignored in systemBandwidth', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.hls.bandwidth = 20e10;
- assert.strictEqual(this.player.tech_.hls.throughput,
- 0,
- 'throughput is reset when bandwidth is specified');
- assert.strictEqual(this.player.tech_.hls.systemBandwidth,
- 20e10,
- 'systemBandwidth is the same as bandwidth');
- });
- QUnit.test('systemBandwidth is a combination of thoughput and bandwidth', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.hls.bandwidth = 20e10;
- this.player.tech_.hls.throughput = 20e10;
- // 1 / ( 1 / 20e10 + 1 / 20e10) = 10e10
- assert.strictEqual(this.player.tech_.hls.systemBandwidth,
- 10e10,
- 'systemBandwidth is the combination of bandwidth and throughput');
- });
- QUnit.test('upshifts if the initial bandwidth hint is high', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 10e20;
- this.standardXHRResponse(this.requests[0]);
- this.standardXHRResponse(this.requests[1]);
- this.standardXHRResponse(this.requests[2]);
- assert.strictEqual(
- this.requests[0].url,
- 'manifest/master.m3u8',
- 'master playlist requested'
- );
- assert.strictEqual(
- this.requests[1].url,
- absoluteUrl('manifest/media2.m3u8'),
- 'media playlist requested'
- );
- assert.strictEqual(
- this.requests[2].url,
- absoluteUrl('manifest/media2-00001.ts'),
- 'first segment requested'
- );
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('downshifts if the initial bandwidth hint is low', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 100;
- this.standardXHRResponse(this.requests[0]);
- this.standardXHRResponse(this.requests[1]);
- this.standardXHRResponse(this.requests[2]);
- assert.strictEqual(this.requests[0].url,
- 'manifest/master.m3u8',
- 'master playlist requested');
- assert.strictEqual(this.requests[1].url,
- absoluteUrl('manifest/media1.m3u8'),
- 'media playlist requested');
- assert.strictEqual(this.requests[2].url,
- absoluteUrl('manifest/media1-00001.ts'),
- 'first segment requested');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('buffer checks are noops until a media playlist is ready', function(assert) {
- this.player.src({
- src: 'manifest/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.clock.tick(10 * 1000);
- assert.strictEqual(1, this.requests.length, 'one request was made');
- assert.strictEqual(this.requests[0].url,
- 'manifest/media.m3u8',
- 'media playlist requested');
- });
- QUnit.test('buffer checks are noops when only the master is ready', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- // ignore any outstanding segment requests
- this.requests.length = 0;
- // load in a new playlist which will cause playlists.media() to be
- // undefined while it is being fetched
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- // respond with the master playlist but don't send the media playlist yet
- // force media1 to be requested
- this.player.tech_.hls.bandwidth = 1;
- // master
- this.standardXHRResponse(this.requests.shift());
- this.clock.tick(10 * 1000);
- assert.strictEqual(this.requests.length, 1, 'one request was made');
- assert.strictEqual(this.requests[0].url,
- absoluteUrl('manifest/media1.m3u8'),
- 'media playlist requested');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth set above');
- });
- QUnit.test('selects a playlist below the current bandwidth', function(assert) {
- let playlist;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- // the default playlist has a really high bitrate
- this.player.tech_.hls.playlists.master.playlists[0].attributes.BANDWIDTH = 9e10;
- // playlist 1 has a very low bitrate
- this.player.tech_.hls.playlists.master.playlists[1].attributes.BANDWIDTH = 1;
- // but the detected client bandwidth is really low
- this.player.tech_.hls.bandwidth = 10;
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.strictEqual(playlist,
- this.player.tech_.hls.playlists.master.playlists[1],
- 'the low bitrate stream is selected');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 10, 'bandwidth set above');
- });
- QUnit.test('selects a primary rendtion when there are multiple rendtions share same attributes', function(assert) {
- let playlist;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- standardXHRResponse(this.requests[0]);
- // covers playlists with same bandwidth but different resolution and different bandwidth but same resolution
- this.player.tech_.hls.playlists.master.playlists[0].attributes.BANDWIDTH = 528;
- this.player.tech_.hls.playlists.master.playlists[1].attributes.BANDWIDTH = 528;
- this.player.tech_.hls.playlists.master.playlists[2].attributes.BANDWIDTH = 728;
- this.player.tech_.hls.playlists.master.playlists[3].attributes.BANDWIDTH = 728;
- this.player.tech_.hls.bandwidth = 1000;
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.strictEqual(playlist,
- this.player.tech_.hls.playlists.master.playlists[2],
- 'select the rendition with largest bandwidth and just-larger-than video player');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1000, 'bandwidth set above');
- // covers playlists share same bandwidth and resolutions
- this.player.tech_.hls.playlists.master.playlists[0].attributes.BANDWIDTH = 728;
- this.player.tech_.hls.playlists.master.playlists[0].attributes.RESOLUTION.width = 960;
- this.player.tech_.hls.playlists.master.playlists[0].attributes.RESOLUTION.height = 540;
- this.player.tech_.hls.playlists.master.playlists[1].attributes.BANDWIDTH = 728;
- this.player.tech_.hls.playlists.master.playlists[2].attributes.BANDWIDTH = 728;
- this.player.tech_.hls.playlists.master.playlists[2].attributes.RESOLUTION.width = 960;
- this.player.tech_.hls.playlists.master.playlists[2].attributes.RESOLUTION.height = 540;
- this.player.tech_.hls.playlists.master.playlists[3].attributes.BANDWIDTH = 728;
- this.player.tech_.hls.bandwidth = 1000;
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.strictEqual(playlist,
- this.player.tech_.hls.playlists.master.playlists[0],
- 'the primary rendition is selected');
- });
- QUnit.test('allows initial bandwidth to be provided', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 500;
- this.requests[0].bandwidth = 1;
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-PLAYLIST-TYPE:VOD\n' +
- '#EXT-X-TARGETDURATION:10\n');
- assert.equal(this.player.tech_.hls.bandwidth,
- 500,
- 'prefers user-specified initial bandwidth');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 500, 'bandwidth set above');
- });
- QUnit.test('raises the minimum bitrate for a stream proportionially', function(assert) {
- let playlist;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- // the default playlist's bandwidth + 10% is assert.equal to the current bandwidth
- this.player.tech_.hls.playlists.master.playlists[0].attributes.BANDWIDTH = 10;
- this.player.tech_.hls.bandwidth = 11;
- // 9.9 * 1.1 < 11
- this.player.tech_.hls.playlists.master.playlists[1].attributes.BANDWIDTH = 9.9;
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.strictEqual(playlist,
- this.player.tech_.hls.playlists.master.playlists[1],
- 'a lower bitrate stream is selected');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 11, 'bandwidth set above');
- });
- QUnit.test('uses the lowest bitrate if no other is suitable', function(assert) {
- let playlist;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- // the lowest bitrate playlist is much greater than 1b/s
- this.player.tech_.hls.bandwidth = 1;
- playlist = this.player.tech_.hls.selectPlaylist();
- // playlist 1 has the lowest advertised bitrate
- assert.strictEqual(playlist,
- this.player.tech_.hls.playlists.master.playlists[1],
- 'the lowest bitrate stream is selected');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth set above');
- });
- QUnit.test('selects the correct rendition by tech dimensions', function(assert) {
- let playlist;
- let hls;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- hls = this.player.tech_.hls;
- this.player.width(640);
- this.player.height(360);
- hls.bandwidth = 3000000;
- playlist = hls.selectPlaylist();
- assert.deepEqual(playlist.attributes.RESOLUTION,
- {width: 960, height: 540},
- 'should return the correct resolution by tech dimensions');
- assert.equal(playlist.attributes.BANDWIDTH,
- 1928000,
- 'should have the expected bandwidth in case of multiple');
- this.player.width(1920);
- this.player.height(1080);
- hls.bandwidth = 3000000;
- playlist = hls.selectPlaylist();
- assert.deepEqual(playlist.attributes.RESOLUTION,
- {width: 960, height: 540},
- 'should return the correct resolution by tech dimensions');
- assert.equal(playlist.attributes.BANDWIDTH,
- 1928000,
- 'should have the expected bandwidth in case of multiple');
- this.player.width(396);
- this.player.height(224);
- playlist = hls.selectPlaylist();
- assert.deepEqual(playlist.attributes.RESOLUTION,
- {width: 396, height: 224},
- 'should return the correct resolution by ' +
- 'tech dimensions, if exact match');
- assert.equal(playlist.attributes.BANDWIDTH,
- 440000,
- 'should have the expected bandwidth in case of multiple, if exact match');
- this.player.width(395);
- this.player.height(222);
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.deepEqual(playlist.attributes.RESOLUTION,
- {width: 396, height: 224},
- 'should return the next larger resolution by tech dimensions, ' +
- 'if no exact match exists');
- assert.equal(playlist.attributes.BANDWIDTH,
- 440000,
- 'should have the expected bandwidth in case of multiple, if exact match');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 3000000, 'bandwidth set above');
- });
- QUnit.test('selects the highest bitrate playlist when the player dimensions are ' +
- 'larger than any of the variants', function(assert) {
- let playlist;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- // master
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000,RESOLUTION=2x1\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1,RESOLUTION=1x1\n' +
- 'media1.m3u8\n');
- // media
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.hls.bandwidth = 1e10;
- this.player.width(1024);
- this.player.height(768);
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.equal(playlist.attributes.BANDWIDTH,
- 1000,
- 'selected the highest bandwidth variant');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1e10, 'bandwidth set above');
- });
- QUnit.test('filters playlists that are currently excluded', function(assert) {
- let playlist;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 1e10;
- // master
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
- 'media1.m3u8\n');
- // media
- this.standardXHRResponse(this.requests.shift());
- // exclude the current playlist
- this.player.tech_.hls.playlists.master.playlists[0].excludeUntil = +new Date() + 1000;
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.equal(playlist,
- this.player.tech_.hls.playlists.master.playlists[1],
- 'respected exclusions');
- // timeout the exclusion
- this.clock.tick(1000);
- playlist = this.player.tech_.hls.selectPlaylist();
- assert.equal(playlist,
- this.player.tech_.hls.playlists.master.playlists[0],
- 'expired the exclusion');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1e10, 'bandwidth set above');
- });
- QUnit.test('does not blacklist compatible H.264 codec strings', function(assert) {
- let master;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 1;
- // master
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="avc1.4d400d,mp4a.40.5"\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=10,CODECS="avc1.4d400f,mp4a.40.5"\n' +
- 'media1.m3u8\n');
- // media
- this.standardXHRResponse(this.requests.shift());
- master = this.player.tech_.hls.playlists.master;
- assert.strictEqual(typeof master.playlists[0].excludeUntil,
- 'undefined',
- 'did not blacklist');
- assert.strictEqual(typeof master.playlists[1].excludeUntil,
- 'undefined',
- 'did not blacklist');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth set above');
- });
- QUnit.test('does not blacklist compatible AAC codec strings', function(assert) {
- let master;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 1;
- // master
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="avc1.4d400d,mp4a.40.2"\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=10,CODECS="avc1.4d400d,not-an-audio-codec"\n' +
- 'media1.m3u8\n');
- // media
- this.standardXHRResponse(this.requests.shift());
- master = this.player.tech_.hls.playlists.master;
- assert.strictEqual(typeof master.playlists[0].excludeUntil,
- 'undefined',
- 'did not blacklist mp4a.40.2');
- assert.strictEqual(master.playlists[1].excludeUntil,
- Infinity,
- 'blacklisted invalid audio codec');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth set above');
- });
- QUnit.test('cancels outstanding XHRs when seeking', function(assert) {
- this.player.src({
- src: 'manifest/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- this.player.tech_.hls.media = {
- segments: [{
- uri: '0.ts',
- duration: 10
- }, {
- uri: '1.ts',
- duration: 10
- }]
- };
- // attempt to seek while the download is in progress
- this.player.currentTime(7);
- this.clock.tick(2);
- assert.ok(this.requests[1].aborted, 'XHR aborted');
- assert.strictEqual(this.requests.length, 3, 'opened new XHR');
- });
- QUnit.test('does not abort segment loading for in-buffer seeking', function(assert) {
- this.player.src({
- src: 'manifest/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.buffered = function() {
- return videojs.createTimeRange(0, 20);
- };
- this.player.tech_.setCurrentTime(11);
- this.clock.tick(1);
- assert.equal(this.requests.length, 1, 'did not abort the outstanding request');
- });
- QUnit.test('segment 404 should trigger blacklisting of media', function(assert) {
- let media;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 20000;
- // master
- this.standardXHRResponse(this.requests[0]);
- // media
- this.standardXHRResponse(this.requests[1]);
- media = this.player.tech_.hls.playlists.media_;
- // segment
- this.requests[2].respond(400);
- assert.ok(media.excludeUntil > 0, 'original media blacklisted for some time');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 20000, 'bandwidth set above');
- });
- QUnit.test('playlist 404 should blacklist media', function(assert) {
- let media;
- let url;
- let blacklistplaylist = 0;
- let retryplaylist = 0;
- let hlsRenditionBlacklistedEvents = 0;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.on('blacklistplaylist', () => blacklistplaylist++);
- this.player.tech_.on('retryplaylist', () => retryplaylist++);
- this.player.tech_.on('usage', (event) => {
- if (event.name === 'hls-rendition-blacklisted') {
- hlsRenditionBlacklistedEvents++;
- }
- });
- this.player.tech_.hls.bandwidth = 1e10;
- // master
- this.requests[0].respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
- 'media1.m3u8\n');
- assert.equal(typeof this.player.tech_.hls.playlists.media_,
- 'undefined',
- 'no media is initially set');
- assert.equal(blacklistplaylist, 0, 'there is no blacklisted playlist');
- assert.equal(hlsRenditionBlacklistedEvents, 0, 'no hls-rendition-blacklisted event was fired');
- // media
- this.requests[1].respond(404);
- url = this.requests[1].url.slice(this.requests[1].url.lastIndexOf('/') + 1);
- media = this.player.tech_.hls.playlists.master.playlists[url];
- assert.ok(media.excludeUntil > 0, 'original media blacklisted for some time');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- assert.equal(this.env.log.warn.args[0],
- 'Problem encountered with the current HLS playlist. HLS playlist request error at URL: media.m3u8 Switching to another playlist.',
- 'log generic error message');
- assert.equal(blacklistplaylist, 1, 'there is one blacklisted playlist');
- assert.equal(hlsRenditionBlacklistedEvents, 1, 'an hls-rendition-blacklisted event was fired');
- assert.equal(retryplaylist, 0, 'haven\'t retried any playlist');
- // request for the final available media
- this.requests[2].respond(404);
- url = this.requests[2].url.slice(this.requests[2].url.lastIndexOf('/') + 1);
- media = this.player.tech_.hls.playlists.master.playlists[url];
- // media wasn't blacklisted because it's final rendition
- assert.ok(!media.excludeUntil, 'media not blacklisted after playlist 404');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- assert.equal(this.env.log.warn.args[1],
- 'Problem encountered with the current HLS playlist. Trying again since it is the final playlist.',
- 'log specific error message for final playlist');
- assert.equal(retryplaylist, 1, 'retried final playlist for once');
- assert.equal(blacklistplaylist, 1, 'there is one blacklisted playlist');
- assert.equal(hlsRenditionBlacklistedEvents, 1, 'an hls-rendition-blacklisted event was fired');
- this.clock.tick(2 * 1000);
- // no new request was made since it hasn't been half the segment duration
- assert.strictEqual(3, this.requests.length, 'no new request was made');
- this.clock.tick(3 * 1000);
- // continue loading the final remaining playlist after it wasn't blacklisted
- // when half the segment duaration passed
- assert.strictEqual(4, this.requests.length, 'one more request was made');
- assert.strictEqual(this.requests[3].url,
- absoluteUrl('manifest/media1.m3u8'),
- 'media playlist requested');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1e10, 'bandwidth set above');
- });
- QUnit.test('blacklists playlist if it has stopped being updated', function(assert) {
- let playliststuck = 0;
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- this.player.tech_.triggerReady();
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.hls.masterPlaylistController_.seekable = function() {
- return videojs.createTimeRange(90, 130);
- };
- this.player.tech_.setCurrentTime(170);
- this.player.tech_.buffered = function() {
- return videojs.createTimeRange(0, 170);
- };
- Hls.Playlist.playlistEnd = function() {
- return 170;
- };
- this.player.tech_.on('playliststuck', () => playliststuck++);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:16\n' +
- '#EXTINF:10,\n' +
- '16.ts\n');
- assert.ok(!this.player.tech_.hls.playlists.media().excludeUntil, 'playlist was not blacklisted');
- assert.equal(this.env.log.warn.calls, 0, 'no warning logged for blacklist');
- assert.equal(playliststuck, 0, 'there is no stuck playlist');
- this.player.tech_.trigger('play');
- this.player.tech_.trigger('playing');
- // trigger a refresh
- this.clock.tick(10 * 1000);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:16\n' +
- '#EXTINF:10,\n' +
- '16.ts\n');
- assert.ok(this.player.tech_.hls.playlists.media().excludeUntil > 0, 'playlist blacklisted for some time');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- assert.equal(this.env.log.warn.args[0],
- 'Problem encountered with the current HLS playlist. Playlist no longer updating. Switching to another playlist.',
- 'log specific error message for not updated playlist');
- assert.equal(playliststuck, 1, 'there is one stuck playlist');
- });
- QUnit.test('never blacklist the playlist if it is the only playlist', function(assert) {
- let media;
- this.player.src({
- src: 'manifest/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXTINF:10,\n' +
- '0.ts\n');
- this.clock.tick(10 * 1000);
- this.requests.shift().respond(404);
- media = this.player.tech_.hls.playlists.media();
- // media wasn't blacklisted because it's final rendition
- assert.ok(!media.excludeUntil, 'media was not blacklisted after playlist 404');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- assert.equal(this.env.log.warn.args[0],
- 'Problem encountered with the current HLS playlist. Trying again since it is the final playlist.',
- 'log specific error message for final playlist');
- });
- QUnit.test('error on the first playlist request does not trigger an error ' +
- 'when there is master playlist with only one media playlist', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- this.requests[0]
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000\n' +
- 'media.m3u8\n');
- this.requests[1].respond(404);
- let url = this.requests[1].url.slice(this.requests[1].url.lastIndexOf('/') + 1);
- let media = this.player.tech_.hls.playlists.master.playlists[url];
- // media wasn't blacklisted because it's final rendition
- assert.ok(!media.excludeUntil, 'media was not blacklisted after playlist 404');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- assert.equal(this.env.log.warn.args[0],
- 'Problem encountered with the current HLS playlist. Trying again since it is the final playlist.',
- 'log specific error message for final playlist');
- });
- QUnit.test('seeking in an empty playlist is a non-erroring noop', function(assert) {
- let requestsLength;
- this.player.src({
- src: 'manifest/empty-live.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null, '#EXTM3U\n');
- requestsLength = this.requests.length;
- this.player.tech_.setCurrentTime(183);
- this.clock.tick(1);
- assert.equal(this.requests.length, requestsLength, 'made no additional requests');
- });
- QUnit.test('fire loadedmetadata once we successfully load a playlist', function(assert) {
- let count = 0;
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- let hls = this.player.tech_.hls;
- hls.bandwidth = 20000;
- hls.masterPlaylistController_.masterPlaylistLoader_.on('loadedmetadata', function() {
- count += 1;
- });
- // master
- this.standardXHRResponse(this.requests.shift());
- assert.equal(count, 0,
- 'loadedMedia not triggered before requesting playlist');
- // media
- this.requests.shift().respond(404);
- assert.equal(count, 0,
- 'loadedMedia not triggered after playlist 404');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.equal(count, 1,
- 'loadedMedia triggered after successful recovery from 404');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 20000, 'bandwidth set above');
- });
- QUnit.test('sets seekable and duration for live playlists', function(assert) {
- this.player.src({
- src: 'http://example.com/manifest/missingEndlist.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- assert.equal(this.player.tech_.hls.mediaSource.seekable.length,
- 1,
- 'set one seekable range');
- assert.equal(this.player.tech_.hls.mediaSource.seekable.start(0),
- this.player.tech_.hls.seekable().start(0),
- 'set seekable start');
- assert.equal(this.player.tech_.hls.mediaSource.seekable.end(0),
- this.player.tech_.hls.seekable().end(0),
- 'set seekable end');
- assert.strictEqual(this.player.tech_.hls.mediaSource.duration,
- Infinity,
- 'duration on the mediaSource is infinity');
- });
- QUnit.test('live playlist starts with correct currentTime value', function(assert) {
- this.player.src({
- src: 'http://example.com/manifest/liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- this.player.tech_.hls.playlists.trigger('loadedmetadata');
- this.player.tech_.paused = function() {
- return false;
- };
- this.player.tech_.trigger('play');
- this.clock.tick(1);
- let media = this.player.tech_.hls.playlists.media();
- assert.strictEqual(this.player.currentTime(),
- Hls.Playlist.seekable(media).end(0),
- 'currentTime is updated at playback');
- });
- QUnit.test('estimates seekable ranges for live streams that have been paused for a long time', function(assert) {
- this.player.src({
- src: 'http://example.com/manifest/liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.hls.playlists.media().mediaSequence = 172;
- this.player.tech_.hls.playlists.media().syncInfo = {
- mediaSequence: 130,
- time: 80
- };
- this.player.tech_.hls.masterPlaylistController_.onSyncInfoUpdate_();
- assert.equal(this.player.seekable().start(0),
- 500,
- 'offset the seekable start');
- });
- QUnit.test('resets the time to the live point when resuming a live stream after a ' +
- 'long break', function(assert) {
- let seekTarget;
- this.player.src({
- src: 'live0.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:16\n' +
- '#EXTINF:10,\n' +
- '16.ts\n');
- // mock out the player to simulate a live stream that has been
- // playing for awhile
- this.player.tech_.hls.seekable = function() {
- return videojs.createTimeRange(160, 170);
- };
- this.player.tech_.setCurrentTime = function(time) {
- if (typeof time !== 'undefined') {
- seekTarget = time;
- }
- };
- this.player.tech_.played = function() {
- return videojs.createTimeRange(120, 170);
- };
- this.player.tech_.trigger('playing');
- let seekable = this.player.seekable();
- this.player.tech_.trigger('play');
- assert.equal(seekTarget, seekable.end(seekable.length - 1), 'seeked to live point');
- this.player.tech_.trigger('seeked');
- });
- QUnit.test('reloads out-of-date live playlists when switching variants', function(assert) {
- let oldManifest = testDataManifests['variant-update'];
- this.player.src({
- src: 'http://example.com/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.master = {
- playlists: [{
- mediaSequence: 15,
- segments: [1, 1, 1]
- }, {
- uri: 'http://example.com/variant-update.m3u8',
- mediaSequence: 0,
- segments: [1, 1]
- }]
- };
- // playing segment 15 on playlist zero
- this.player.tech_.hls.media = this.player.tech_.hls.master.playlists[0];
- this.player.mediaIndex = 1;
- testDataManifests['variant-update'] = '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:16\n' +
- '#EXTINF:10,\n' +
- '16.ts\n' +
- '#EXTINF:10,\n' +
- '17.ts\n';
- // switch playlists
- this.player.tech_.hls.selectPlaylist = function() {
- return this.player.tech_.hls.master.playlists[1];
- };
- // timeupdate downloads segment 16 then switches playlists
- this.player.trigger('timeupdate');
- assert.strictEqual(this.player.mediaIndex, 1, 'mediaIndex points at the next segment');
- testDataManifests['variant-update'] = oldManifest;
- });
- QUnit.test('if withCredentials global option is used, withCredentials is set on the XHR object', function(assert) {
- let hlsOptions = videojs.options.hls;
- this.player.dispose();
- videojs.options.hls = {
- withCredentials: true
- };
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.ok(this.requests[0].withCredentials,
- 'with credentials should be set to true if that option is passed in');
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('the withCredentials option overrides the global default', function(assert) {
- let hlsOptions = videojs.options.hls;
- this.player.dispose();
- videojs.options.hls = {
- withCredentials: true
- };
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl',
- withCredentials: false
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.ok(!this.requests[0].withCredentials,
- 'with credentials should be set to false if if overrode global option');
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('if handleManifestRedirects global option is used, it should be passed to PlaylistLoader', function(assert) {
- let hlsOptions = videojs.options.hls;
- this.player.dispose();
- videojs.options.hls = {
- handleManifestRedirects: true
- };
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.ok(this.player.tech_.hls.masterPlaylistController_.masterPlaylistLoader_.handleManifestRedirects);
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('the handleManifestRedirects option overrides the global default', function(assert) {
- let hlsOptions = videojs.options.hls;
- this.player.dispose();
- videojs.options.hls = {
- handleManifestRedirects: true
- };
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl',
- handleManifestRedirects: false
- });
- this.clock.tick(1);
- assert.notOk(this.player.tech_.hls.masterPlaylistController_.masterPlaylistLoader_.handleManifestRedirects);
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('playlist blacklisting duration is set through options', function(assert) {
- let hlsOptions = videojs.options.hls;
- let url;
- let media;
- this.player.dispose();
- videojs.options.hls = {
- blacklistDuration: 3 * 60
- };
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.player.tech_.triggerReady();
- openMediaSource(this.player, this.clock);
- this.requests[0].respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
- 'media1.m3u8\n');
- this.requests[1].respond(404);
- // media
- url = this.requests[1].url.slice(this.requests[1].url.lastIndexOf('/') + 1);
- media = this.player.tech_.hls.playlists.master.playlists[url];
- assert.ok(media.excludeUntil > 0, 'original media blacklisted for some time');
- assert.equal(this.env.log.warn.calls, 1, 'warning logged for blacklist');
- assert.equal(this.env.log.warn.args[0],
- 'Problem encountered with the current HLS playlist. HLS playlist request error at URL: media.m3u8 Switching to another playlist.',
- 'log generic error message');
- this.clock.tick(2 * 60 * 1000);
- assert.ok(media.excludeUntil - Date.now() > 0, 'original media still be blacklisted');
- this.clock.tick(1 * 60 * 1000);
- assert.equal(media.excludeUntil, Date.now(), 'media\'s exclude time reach to the current time');
- assert.equal(this.env.log.warn.calls, 3, 'warning logged for blacklist');
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('if mode global option is used, mode is set to global option', function(assert) {
- let hlsOptions = videojs.options.hls;
- this.player.dispose();
- videojs.options.hls = {
- mode: 'flash'
- };
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.equal(this.player.tech_.hls.options_.mode, 'flash', 'mode set to flash');
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('respects bandwidth option of 0', function(assert) {
- this.player.dispose();
- this.player = createPlayer({ html5: { hls: { bandwidth: 0 } } });
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.equal(this.player.tech_.hls.bandwidth, 0, 'set bandwidth to 0');
- });
- QUnit.test('uses default bandwidth option if non-numerical value provided', function(assert) {
- this.player.dispose();
- this.player = createPlayer({ html5: { hls: { bandwidth: 'garbage' } } });
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.equal(this.player.tech_.hls.bandwidth, 4194304, 'set bandwidth to default');
- });
- QUnit.test('uses default bandwidth if browser is Android', function(assert) {
- this.player.dispose();
- const origIsAndroid = videojs.browser.IS_ANDROID;
- videojs.browser.IS_ANDROID = false;
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- assert.equal(this.player.tech_.hls.bandwidth,
- 4194304,
- 'set bandwidth to desktop default');
- this.player.dispose();
- videojs.browser.IS_ANDROID = true;
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- assert.equal(this.player.tech_.hls.bandwidth,
- 4194304,
- 'set bandwidth to mobile default');
- videojs.browser.IS_ANDROID = origIsAndroid;
- });
- QUnit.test('does not break if the playlist has no segments', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- try {
- openMediaSource(this.player, this.clock);
- this.requests[0].respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-PLAYLIST-TYPE:VOD\n' +
- '#EXT-X-TARGETDURATION:10\n');
- } catch (e) {
- assert.ok(false, 'an error was thrown');
- throw e;
- }
- assert.ok(true, 'no error was thrown');
- assert.strictEqual(
- this.requests.length,
- 1,
- 'no this.requestsfor non-existent segments were queued'
- );
- });
- QUnit.test('can seek before the source buffer opens', function(assert) {
- this.player.src({
- src: 'media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- this.player.tech_.triggerReady();
- this.clock.tick(1);
- this.standardXHRResponse(this.requests.shift());
- this.player.triggerReady();
- this.player.currentTime(1);
- assert.equal(this.player.currentTime(), 1, 'seeked');
- });
- QUnit.test('resets the switching algorithm if a request times out', function(assert) {
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.bandwidth = 1e20;
- // master
- this.standardXHRResponse(this.requests.shift());
- // media.m3u8
- this.standardXHRResponse(this.requests.shift());
- // simulate a segment timeout
- this.requests[0].timedout = true;
- // segment
- this.requests.shift().abort();
- this.standardXHRResponse(this.requests.shift());
- assert.strictEqual(this.player.tech_.hls.playlists.media(),
- this.player.tech_.hls.playlists.master.playlists[1],
- 'reset to the lowest bitrate playlist');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth is reset too');
- });
- QUnit.test('disposes the playlist loader', function(assert) {
- let disposes = 0;
- let player;
- let loaderDispose;
- player = createPlayer();
- player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(player, this.clock);
- loaderDispose = player.tech_.hls.playlists.dispose;
- player.tech_.hls.playlists.dispose = function() {
- disposes++;
- loaderDispose.call(player.tech_.hls.playlists);
- };
- player.dispose();
- assert.strictEqual(disposes, 1, 'disposed playlist loader');
- });
- QUnit.test('remove event handlers on dispose', function(assert) {
- let player;
- let unscoped = 0;
- player = createPlayer();
- const origPlayerOn = player.on.bind(player);
- const origPlayerOff = player.off.bind(player);
- player.on = function(...args) {
- if (typeof args[0] !== 'object') {
- unscoped++;
- }
- origPlayerOn(...args);
- };
- player.off = function(...args) {
- if (typeof args[0] !== 'object') {
- unscoped--;
- }
- origPlayerOff(...args);
- };
- player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(player, this.clock);
- this.standardXHRResponse(this.requests[0]);
- this.standardXHRResponse(this.requests[1]);
- assert.ok(unscoped > 0, 'has unscoped handlers');
- player.dispose();
- assert.ok(unscoped <= 0, 'no unscoped handlers');
- });
- QUnit.test('the source handler supports HLS mime types', function(assert) {
- const techs = ['html5', 'flash'];
- techs.forEach(function(techName) {
- assert.ok(HlsSourceHandler(techName).canHandleSource({
- type: 'aPplicatiOn/x-MPegUrl'
- }), 'supports x-mpegurl');
- assert.ok(HlsSourceHandler(techName).canHandleSource({
- type: 'aPplicatiOn/VnD.aPPle.MpEgUrL'
- }), 'supports vnd.apple.mpegurl');
- assert.ok(HlsSourceHandler(techName).canPlayType('aPplicatiOn/VnD.aPPle.MpEgUrL'),
- 'supports vnd.apple.mpegurl');
- assert.ok(HlsSourceHandler(techName).canPlayType('aPplicatiOn/x-MPegUrl'),
- 'supports x-mpegurl');
- assert.ok(!(HlsSourceHandler(techName).canHandleSource({
- type: 'video/mp4'
- }) instanceof HlsHandler), 'does not support mp4');
- assert.ok(!(HlsSourceHandler(techName).canHandleSource({
- type: 'video/x-flv'
- }) instanceof HlsHandler), 'does not support flv');
- assert.ok(!(HlsSourceHandler(techName).canPlayType('video/mp4')),
- 'does not support mp4');
- assert.ok(!(HlsSourceHandler(techName).canPlayType('video/x-flv')),
- 'does not support flv');
- });
- });
- QUnit.test('source handler does not support sources when IE 10 or below', function(assert) {
- videojs.browser.IE_VERSION = 10;
- ['html5', 'flash'].forEach(function(techName) {
- assert.ok(!HlsSourceHandler(techName).canHandleSource({
- type: 'application/x-mpegURL'
- }), 'does not support when browser is IE10');
- });
- });
- QUnit.test('fires loadstart manually if Flash is used', function(assert) {
- videojs.HlsHandler.prototype.setupQualityLevels_ = () => {};
- let tech = new (videojs.getTech('Flash'))({});
- let loadstarts = 0;
- tech.on('loadstart', function() {
- loadstarts++;
- });
- HlsSourceHandler('flash').handleSource({
- src: 'movie.m3u8',
- type: 'application/x-mpegURL'
- }, tech);
- assert.equal(loadstarts, 0, 'loadstart is not synchronous');
- this.clock.tick(1);
- assert.equal(loadstarts, 1, 'fired loadstart');
- tech.dispose();
- videojs.HlsHandler.prototype.setupQualityLevels_ = ogHlsHandlerSetupQualityLevels;
- });
- QUnit.test('has no effect if native HLS is available', function(assert) {
- const Html5 = videojs.getTech('Html5');
- const oldHtml5CanPlaySource = Html5.canPlaySource;
- let player;
- Html5.canPlaySource = () => true;
- Hls.supportsNativeHls = true;
- player = createPlayer();
- player.src({
- src: 'http://example.com/manifest/master.m3u8',
- type: 'application/x-mpegURL'
- });
- this.clock.tick(1);
- assert.ok(!player.tech_.hls, 'did not load hls tech');
- player.dispose();
- Html5.canPlaySource = oldHtml5CanPlaySource;
- });
- QUnit.test('loads if native HLS is available and override is set locally', function(assert) {
- let player;
- Hls.supportsNativeHls = true;
- player = createPlayer({html5: {hls: {overrideNative: true}}});
- this.clock.tick(1);
- player.tech_.featuresNativeVideoTracks = true;
- assert.throws(function() {
- player.src({
- src: 'http://example.com/manifest/master.m3u8',
- type: 'application/x-mpegURL'
- });
- this.clock.tick(1);
- }, 'errors if native tracks are enabled');
- player.dispose();
- player = createPlayer({html5: {hls: {overrideNative: true}}});
- this.clock.tick(1);
- player.tech_.featuresNativeVideoTracks = false;
- player.tech_.featuresNativeAudioTracks = false;
- player.src({
- src: 'http://example.com/manifest/master.m3u8',
- type: 'application/x-mpegURL'
- });
- this.clock.tick(1);
- assert.ok(player.tech_.hls, 'did load hls tech');
- player.dispose();
- });
- QUnit.test('loads if native HLS is available and override is set globally', function(assert) {
- videojs.options.hls.overrideNative = true;
- let player;
- Hls.supportsNativeHls = true;
- player = createPlayer();
- player.tech_.featuresNativeVideoTracks = true;
- assert.throws(function() {
- player.src({
- src: 'http://example.com/manifest/master.m3u8',
- type: 'application/x-mpegURL'
- });
- this.clock.tick(1);
- }, 'errors if native tracks are enabled');
- player.dispose();
- player = createPlayer();
- player.tech_.featuresNativeVideoTracks = false;
- player.tech_.featuresNativeAudioTracks = false;
- player.src({
- src: 'http://example.com/manifest/master.m3u8',
- type: 'application/x-mpegURL'
- });
- this.clock.tick(1);
- assert.ok(player.tech_.hls, 'did load hls tech');
- player.dispose();
- });
- QUnit.test('re-emits mediachange events', function(assert) {
- let mediaChanges = 0;
- this.player.on('mediachange', function() {
- mediaChanges++;
- });
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.hls.playlists.trigger('mediachange');
- assert.strictEqual(mediaChanges, 1, 'fired mediachange');
- });
- QUnit.test('can be disposed before finishing initialization', function(assert) {
- let readyHandlers = [];
- this.player.ready = function(callback) {
- readyHandlers.push(callback);
- };
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- readyHandlers.shift().call(this.player);
- this.player.src({
- src: 'http://example.com/media.mp4',
- type: 'video/mp4'
- });
- assert.ok(readyHandlers.length > 0, 'registered a ready handler');
- try {
- while (readyHandlers.length) {
- readyHandlers.shift().call(this.player);
- openMediaSource(this.player, this.clock);
- }
- assert.ok(true, 'did not throw an exception');
- } catch (e) {
- assert.ok(false, 'threw an exception');
- }
- });
- QUnit.test('calling play() at the end of a video replays', function(assert) {
- let seekTime = -1;
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.setCurrentTime = function(time) {
- if (typeof time !== 'undefined') {
- seekTime = time;
- }
- return 0;
- };
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXTINF:10,\n' +
- '0.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.ended = function() {
- return true;
- };
- this.player.tech_.trigger('play');
- this.clock.tick(1);
- assert.equal(seekTime, 0, 'seeked to the beginning');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('keys are resolved relative to the master playlist', function(assert) {
- this.player.src({
- src: 'video/master-encrypted.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=17\n' +
- 'playlist/playlist.m3u8\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-TARGETDURATION:15\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="keys/key.php"\n' +
- '#EXTINF:2.833,\n' +
- 'http://media.example.com/fileSequence1.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- assert.equal(this.requests.length, 2, 'requested the key');
- assert.equal(this.requests[0].url,
- absoluteUrl('video/playlist/keys/key.php'),
- 'resolves multiple relative paths');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default');
- });
- QUnit.test('keys are resolved relative to their containing playlist', function(assert) {
- this.player.src({
- src: 'video/media-encrypted.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-TARGETDURATION:15\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="keys/key.php"\n' +
- '#EXTINF:2.833,\n' +
- 'http://media.example.com/fileSequence1.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- assert.equal(this.requests.length, 2, 'requested a key');
- assert.equal(this.requests[0].url,
- absoluteUrl('video/keys/key.php'),
- 'resolves multiple relative paths');
- });
- QUnit.test('seeking should abort an outstanding key request and create a new one', function(assert) {
- this.player.src({
- src: 'https://example.com/encrypted.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-TARGETDURATION:15\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="keys/key.php"\n' +
- '#EXTINF:9,\n' +
- 'http://media.example.com/fileSequence1.ts\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="keys/key2.php"\n' +
- '#EXTINF:9,\n' +
- 'http://media.example.com/fileSequence2.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- // segment 1
- this.standardXHRResponse(this.requests.pop());
- this.player.currentTime(11);
- this.clock.tick(2);
- assert.ok(this.requests[0].aborted, 'the key XHR should be aborted');
- // aborted key 1
- this.requests.shift();
- assert.equal(this.requests.length, 2, 'requested the new key');
- assert.equal(this.requests[0].url,
- 'https://example.com/' +
- this.player.tech_.hls.playlists.media().segments[1].key.uri,
- 'urls should match');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('switching playlists with an outstanding key request aborts request and ' +
- 'loads segment', function(assert) {
- let keyXhr;
- let media = '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:5\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n' +
- '#EXTINF:2.833,\n' +
- 'http://media.example.com/fileSequence52-A.ts\n' +
- '#EXTINF:15.0,\n' +
- 'http://media.example.com/fileSequence52-B.ts\n' +
- '#EXT-X-ENDLIST\n';
- this.player.src({
- src: 'https://example.com/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.trigger('play');
- this.clock.tick(1);
- // master playlist
- this.standardXHRResponse(this.requests.shift());
- // media playlist
- this.requests.shift().respond(200, null, media);
- this.clock.tick(1);
- // first segment of the original media playlist
- this.standardXHRResponse(this.requests.pop());
- assert.equal(this.requests.length, 1, 'key request only one outstanding');
- keyXhr = this.requests.shift();
- assert.ok(!keyXhr.aborted, 'key request outstanding');
- this.player.tech_.hls.playlists.trigger('mediachanging');
- this.player.tech_.hls.playlists.trigger('mediachange');
- this.clock.tick(1);
- assert.ok(keyXhr.aborted, 'key request aborted');
- assert.equal(this.requests.length, 2, 'loaded key and segment');
- assert.equal(this.requests[0].url,
- 'https://priv.example.com/key.php?r=52',
- 'requested the key');
- assert.equal(this.requests[1].url,
- 'http://media.example.com/fileSequence52-A.ts',
- 'requested the segment');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('does not download segments if preload option set to none', function(assert) {
- this.player.preload('none');
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- this.clock.tick(10 * 1000);
- this.requests = this.requests.filter(function(request) {
- return !(/m3u8$/).test(request.uri);
- });
- assert.equal(this.requests.length, 0, 'did not download any segments');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default');
- });
- // workaround https://bugzilla.mozilla.org/show_bug.cgi?id=548397
- QUnit.test('selectPlaylist does not fail if getComputedStyle returns null', function(assert) {
- let oldGetComputedStyle = window.getComputedStyle;
- window.getComputedStyle = function() {
- return null;
- };
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- this.player.tech_.hls.selectPlaylist();
- assert.ok(true, 'should not throw');
- window.getComputedStyle = oldGetComputedStyle;
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default');
- });
- QUnit.test('resolves relative key URLs against the playlist', function(assert) {
- this.player.src({
- src: 'https://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:5\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="key.php?r=52"\n' +
- '#EXTINF:2.833,\n' +
- 'http://media.example.com/fileSequence52-A.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- assert.equal(this.requests[0].url,
- 'https://example.com/key.php?r=52',
- 'resolves the key URL');
- });
- QUnit.test('adds 1 default audio track if we have not parsed any and the playlist is loaded', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.equal(this.player.audioTracks().length, 0, 'zero audio tracks at load time');
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.equal(this.player.audioTracks().length, 1, 'one audio track after load');
- assert.equal(this.player.audioTracks()[0].label, 'default', 'set the label');
- });
- QUnit.test('adds 1 default audio track if in flash mode', function(assert) {
- let hlsOptions = videojs.options.hls;
- this.player.dispose();
- videojs.options.hls = {
- mode: 'flash'
- };
- this.player = createPlayer();
- this.player.src({
- src: 'manifest/multipleAudioGroups.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.equal(this.player.audioTracks().length, 0, 'zero audio tracks at load time');
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.equal(this.player.audioTracks().length, 1, 'one audio track after load');
- assert.equal(this.player.audioTracks()[0].label, 'default', 'set the label');
- videojs.options.hls = hlsOptions;
- });
- QUnit.test('adds audio tracks if we have parsed some from a playlist', function(assert) {
- this.player.src({
- src: 'manifest/multipleAudioGroups.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.equal(this.player.audioTracks().length, 0, 'zero audio tracks at load time');
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- let vjsAudioTracks = this.player.audioTracks();
- assert.equal(vjsAudioTracks.length, 3, '3 active vjs tracks');
- assert.equal(vjsAudioTracks[0].enabled, true, 'default track is enabled');
- vjsAudioTracks[1].enabled = true;
- assert.equal(vjsAudioTracks[1].enabled, true, 'new track is enabled on vjs');
- assert.equal(vjsAudioTracks[0].enabled, false, 'main track is disabled');
- });
- QUnit.test('cleans up the buffer when loading live segments', function(assert) {
- let removes = [];
- let seekable = videojs.createTimeRanges([[0, 70]]);
- this.player.src({
- src: 'liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.masterPlaylistController_.seekable = function() {
- return seekable;
- };
- // This is so we do not track first call to remove during segment loader init
- this.player.tech_.hls.masterPlaylistController_.mainSegmentLoader_.resetEverything = function() {
- this.resetLoader();
- };
- this.player.tech_.hls.mediaSource.addSourceBuffer = () => {
- let buffer = new (videojs.extend(videojs.EventTarget, {
- constructor() {},
- abort() {},
- buffered: videojs.createTimeRange(),
- appendBuffer() {},
- remove(start, end) {
- removes.push([start, end]);
- }
- }))();
- this.player.tech_.hls.mediaSource.sourceBuffers = [buffer];
- return buffer;
- };
- this.player.tech_.hls.bandwidth = 20e10;
- this.player.tech_.triggerReady();
- this.standardXHRResponse(this.requests[0]);
- this.player.tech_.hls.playlists.trigger('loadedmetadata');
- this.player.tech_.trigger('canplay');
- this.player.tech_.paused = function() {
- return false;
- };
- this.player.tech_.trigger('play');
- this.clock.tick(1);
- // request first playable segment
- this.standardXHRResponse(this.requests[1]);
- this.clock.tick(1);
- this.player.tech_.hls.mediaSource.sourceBuffers[0].trigger('updateend');
- this.clock.tick(1);
- // request second playable segment
- this.standardXHRResponse(this.requests[2]);
- assert.strictEqual(this.requests[0].url, 'liveStart30sBefore.m3u8',
- 'master playlist requested');
- assert.equal(removes.length, 1, 'remove called');
- // segment-loader removes at currentTime - 30
- assert.deepEqual(removes[0], [0, 40],
- 'remove called with the right range');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 2048, '2048 bytes');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 2, '2 requests');
- });
- QUnit.test('cleans up the buffer based on currentTime when loading a live segment ' +
- 'if seekable start is after currentTime', function(assert) {
- let removes = [];
- let seekable = videojs.createTimeRanges([[0, 80]]);
- this.player.src({
- src: 'liveStart30sBefore.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.masterPlaylistController_.seekable = function() {
- return seekable;
- };
- // This is so we do not track first call to remove during segment loader init
- this.player.tech_.hls.masterPlaylistController_.mainSegmentLoader_.resetEverything = function() {
- this.resetLoader();
- };
- this.player.tech_.hls.mediaSource.addSourceBuffer = () => {
- let buffer = new (videojs.extend(videojs.EventTarget, {
- constructor() {},
- abort() {},
- buffered: videojs.createTimeRange(),
- appendBuffer() {},
- remove(start, end) {
- removes.push([start, end]);
- }
- }))();
- this.player.tech_.hls.mediaSource.sourceBuffers = [buffer];
- return buffer;
- };
- this.player.tech_.hls.bandwidth = 20e10;
- this.player.tech_.triggerReady();
- this.standardXHRResponse(this.requests[0]);
- this.player.tech_.hls.playlists.trigger('loadedmetadata');
- this.player.tech_.trigger('canplay');
- this.player.tech_.paused = function() {
- return false;
- };
- this.player.tech_.trigger('play');
- this.clock.tick(1);
- // request first playable segment
- this.standardXHRResponse(this.requests[1]);
- this.clock.tick(1);
- this.player.tech_.hls.mediaSource.sourceBuffers[0].trigger('updateend');
- // Change seekable so that it starts *after* the currentTime which was set
- // based on the previous seekable range (the end of 80)
- seekable = videojs.createTimeRanges([[100, 120]]);
- this.clock.tick(1);
- // request second playable segment
- this.standardXHRResponse(this.requests[2]);
- assert.strictEqual(this.requests[0].url, 'liveStart30sBefore.m3u8', 'master playlist requested');
- assert.equal(removes.length, 1, 'remove called');
- assert.deepEqual(removes[0], [0, 80 - 30], 'remove called with the right range');
- });
- QUnit.test('cleans up the buffer when loading VOD segments', function(assert) {
- let removes = [];
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- // This is so we do not track first call to remove during segment loader init
- this.player.tech_.hls.masterPlaylistController_.mainSegmentLoader_.resetEverything = function() {
- this.resetLoader();
- };
- this.player.tech_.hls.mediaSource.addSourceBuffer = () => {
- let buffer = new (videojs.extend(videojs.EventTarget, {
- constructor() {},
- abort() {},
- buffered: videojs.createTimeRange(),
- appendBuffer() {},
- remove(start, end) {
- removes.push([start, end]);
- }
- }))();
- this.player.tech_.hls.mediaSource.sourceBuffers = [buffer];
- return buffer;
- };
- this.player.width(640);
- this.player.height(360);
- this.player.tech_.hls.bandwidth = 20e10;
- this.standardXHRResponse(this.requests[0]);
- this.standardXHRResponse(this.requests[1]);
- this.standardXHRResponse(this.requests[2]);
- this.clock.tick(1);
- this.player.currentTime(120);
- this.player.tech_.hls.mediaSource.sourceBuffers[0].trigger('updateend');
- // This requires 2 clock ticks because after updateend monitorBuffer_ is called
- // to setup fillBuffer on the next tick, but the seek also causes monitorBuffer_ to be
- // called, which cancels the previously set timeout and sets a new one for the following
- // tick.
- this.clock.tick(2);
- this.standardXHRResponse(this.requests[3]);
- assert.strictEqual(this.requests[0].url, 'manifest/master.m3u8',
- 'master playlist requested');
- assert.strictEqual(this.requests[1].url, absoluteUrl('manifest/media3.m3u8'),
- 'media playlist requested');
- assert.equal(removes.length, 1, 'remove called');
- assert.deepEqual(removes[0], [0, 120 - 30], 'remove called with the right range');
- });
- QUnit.test('when mediaGroup changes enabled track should not change', function(assert) {
- let hlsAudioChangeEvents = 0;
- this.player.src({
- src: 'manifest/multipleAudioGroups.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.on('usage', (event) => {
- if (event.name === 'hls-audio-change') {
- hlsAudioChangeEvents++;
- }
- });
- // master
- this.standardXHRResponse(this.requests.shift());
- // video media
- this.standardXHRResponse(this.requests.shift());
- let hls = this.player.tech_.hls;
- let mpc = hls.masterPlaylistController_;
- let audioTracks = this.player.audioTracks();
- assert.equal(hlsAudioChangeEvents, 0, 'no hls-audio-change event was fired');
- assert.equal(audioTracks.length, 3, 'three audio tracks after load');
- assert.equal(audioTracks[0].enabled, true, 'track one enabled after load');
- let oldMediaGroup = hls.playlists.media().attributes.AUDIO;
- // clear out any outstanding requests
- this.requests.length = 0;
- // force mpc to select a playlist from a new media group
- mpc.masterPlaylistLoader_.media(mpc.master().playlists[0]);
- this.clock.tick(1);
- // video media
- this.standardXHRResponse(this.requests.shift());
- assert.notEqual(oldMediaGroup, hls.playlists.media().attributes.AUDIO, 'selected a new playlist');
- audioTracks = this.player.audioTracks();
- let activeGroup = mpc.mediaTypes_.AUDIO.activeGroup(audioTracks[0]);
- assert.equal(audioTracks.length, 3, 'three audio tracks after changing mediaGroup');
- assert.ok(activeGroup.default, 'track one should be the default');
- assert.ok(audioTracks[0].enabled, 'enabled the default track');
- assert.notOk(audioTracks[1].enabled, 'disabled track two');
- assert.notOk(audioTracks[2].enabled, 'disabled track three');
- audioTracks[1].enabled = true;
- assert.notOk(audioTracks[0].enabled, 'disabled track one');
- assert.ok(audioTracks[1].enabled, 'enabled track two');
- assert.notOk(audioTracks[2].enabled, 'disabled track three');
- oldMediaGroup = hls.playlists.media().attributes.AUDIO;
- // clear out any outstanding requests
- this.requests.length = 0;
- // swap back to the old media group
- // this playlist is already loaded so no new requests are made
- mpc.masterPlaylistLoader_.media(mpc.master().playlists[3]);
- this.clock.tick(1);
- assert.notEqual(oldMediaGroup, hls.playlists.media().attributes.AUDIO, 'selected a new playlist');
- audioTracks = this.player.audioTracks();
- assert.equal(hlsAudioChangeEvents, 1, 'an hls-audio-change event was fired');
- assert.equal(audioTracks.length, 3, 'three audio tracks after reverting mediaGroup');
- assert.notOk(audioTracks[0].enabled, 'the default track is still disabled');
- assert.ok(audioTracks[1].enabled, 'track two is still enabled');
- assert.notOk(audioTracks[2].enabled, 'track three is still disabled');
- });
- QUnit.test('Allows specifying the beforeRequest function on the player', function(assert) {
- let beforeRequestCalled = false;
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.xhr.beforeRequest = function() {
- beforeRequestCalled = true;
- };
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.ok(beforeRequestCalled, 'beforeRequest was called');
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default');
- });
- QUnit.test('Allows specifying the beforeRequest function globally', function(assert) {
- let beforeRequestCalled = false;
- videojs.Hls.xhr.beforeRequest = function() {
- beforeRequestCalled = true;
- };
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- // master
- this.standardXHRResponse(this.requests.shift());
- assert.ok(beforeRequestCalled, 'beforeRequest was called');
- delete videojs.Hls.xhr.beforeRequest;
- // verify stats
- assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default');
- });
- QUnit.test('Allows overriding the global beforeRequest function', function(assert) {
- let beforeGlobalRequestCalled = 0;
- let beforeLocalRequestCalled = 0;
- videojs.Hls.xhr.beforeRequest = function() {
- beforeGlobalRequestCalled++;
- };
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- this.player.tech_.hls.xhr.beforeRequest = function() {
- beforeLocalRequestCalled++;
- };
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- // ts
- this.standardXHRResponse(this.requests.shift());
- assert.equal(beforeLocalRequestCalled, 2, 'local beforeRequest was called twice ' +
- 'for the media playlist and media');
- assert.equal(beforeGlobalRequestCalled, 1, 'global beforeRequest was called once ' +
- 'for the master playlist');
- delete videojs.Hls.xhr.beforeRequest;
- // verify stats
- assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred, 1024, 'seen above');
- assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, 'one segment request');
- });
- QUnit.test('passes useCueTags hls option to master playlist controller', function(assert) {
- this.player.src({
- src: 'master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.ok(!this.player.tech_.hls.masterPlaylistController_.useCueTags_,
- 'useCueTags is falsy by default');
- let origHlsOptions = videojs.options.hls;
- videojs.options.hls = {
- useCueTags: true
- };
- this.player.dispose();
- this.player = createPlayer();
- this.player.src({
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- assert.ok(this.player.tech_.hls.masterPlaylistController_.useCueTags_,
- 'useCueTags passed to master playlist controller');
- videojs.options.hls = origHlsOptions;
- });
- QUnit.skip('populates quality levels list when available', function(assert) {
- this.player.src({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- });
- this.clock.tick(1);
- openMediaSource(this.player, this.clock);
- assert.ok(this.player.tech_.hls.qualityLevels_, 'added quality levels');
- let qualityLevels = this.player.qualityLevels();
- let addCount = 0;
- let changeCount = 0;
- qualityLevels.on('addqualitylevel', () => {
- addCount++;
- });
- qualityLevels.on('change', () => {
- changeCount++;
- });
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.equal(addCount, 4, 'four levels added from master');
- assert.equal(changeCount, 1, 'selected initial quality level');
- this.player.dispose();
- this.player = createPlayer({}, {
- src: 'http://example.com/media.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.clock);
- openMediaSource(this.player, this.clock);
- assert.ok(this.player.tech_.hls.qualityLevels_, 'added quality levels from video with source');
- });
- QUnit.module('HLS Integration', {
- beforeEach(assert) {
- this.env = useFakeEnvironment(assert);
- this.requests = this.env.requests;
- this.mse = useFakeMediaSource();
- this.tech = new (videojs.getTech('Html5'))({});
- this.clock = this.env.clock;
- this.standardXHRResponse = (request, data) => {
- standardXHRResponse(request, data);
- // Because SegmentLoader#fillBuffer_ is now scheduled asynchronously
- // we have to use clock.tick to get the expected side effects of
- // SegmentLoader#handleUpdateEnd_
- this.clock.tick(1);
- };
- videojs.HlsHandler.prototype.setupQualityLevels_ = () => {};
- },
- afterEach() {
- this.env.restore();
- this.mse.restore();
- videojs.HlsHandler.prototype.setupQualityLevels_ = ogHlsHandlerSetupQualityLevels;
- }
- });
- QUnit.test('does not error when MediaSource is not defined', function(assert) {
- window.MediaSource = null;
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/alternateAudio.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.mediaSource.trigger('sourceopen');
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.ok(true, 'did not throw an exception');
- });
- QUnit.test('aborts all in-flight work when disposed', function(assert) {
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.mediaSource.trigger('sourceopen');
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- hls.dispose();
- assert.ok(this.requests[0].aborted, 'aborted the old segment request');
- hls.mediaSource.sourceBuffers.forEach(sourceBuffer => {
- let lastUpdate = sourceBuffer.updates_[sourceBuffer.updates_.length - 1];
- assert.ok(lastUpdate.abort, 'aborted the source buffer');
- });
- });
- QUnit.test('stats are reset on dispose', function(assert) {
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.mediaSource.trigger('sourceopen');
- // master
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- // media
- this.standardXHRResponse(this.requests.shift());
- assert.equal(hls.stats.mediaBytesTransferred, 1024, 'stat is set');
- hls.dispose();
- assert.equal(hls.stats.mediaBytesTransferred, 0, 'stat is reset');
- });
- QUnit.test('detects fullscreen and triggers a quality change', function(assert) {
- let qualityChanges = 0;
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- let fullscreenElementName;
- ['fullscreenElement', 'webkitFullscreenElement',
- 'mozFullScreenElement', 'msFullscreenElement'
- ].forEach((name) => {
- if (!fullscreenElementName && !document.hasOwnProperty(name)) {
- fullscreenElementName = name;
- }
- });
- hls.masterPlaylistController_.fastQualityChange_ = function() {
- qualityChanges++;
- };
- // take advantage of capability detection to mock fullscreen activation
- document[fullscreenElementName] = this.tech.el();
- Events.trigger(document, 'fullscreenchange');
- assert.equal(qualityChanges, 1, 'made a fast quality change');
- // don't do a fast quality change when returning from fullscreen;
- // allow the video element to rescale the already buffered video
- document[fullscreenElementName] = null;
- Events.trigger(document, 'fullscreenchange');
- assert.equal(qualityChanges, 1, 'did not make another quality change');
- });
- QUnit.test('downloads additional playlists if required', function(assert) {
- let originalPlaylist;
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- // Make segment metadata noop since most test segments dont have real data
- hls.masterPlaylistController_.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
- hls.mediaSource.trigger('sourceopen');
- hls.bandwidth = 1;
- // master
- this.standardXHRResponse(this.requests[0]);
- // media
- this.standardXHRResponse(this.requests[1]);
- originalPlaylist = hls.playlists.media();
- hls.masterPlaylistController_.mainSegmentLoader_.mediaIndex = 0;
- // the playlist selection is revisited after a new segment is downloaded
- this.requests[2].bandwidth = 3000000;
- // segment
- this.standardXHRResponse(this.requests[2]);
- // update the buffer to reflect the appended segment, and have enough buffer to
- // change playlist
- this.tech.buffered = () => videojs.createTimeRanges([[0, 30]]);
- hls.mediaSource.sourceBuffers[0].trigger('updateend');
- // new media
- this.standardXHRResponse(this.requests[3]);
- assert.ok((/manifest\/media\d+.m3u8$/).test(this.requests[3].url),
- 'made a playlist request');
- assert.notEqual(originalPlaylist.resolvedUri,
- hls.playlists.media().resolvedUri,
- 'a new playlists was selected');
- assert.ok(hls.playlists.media().segments, 'segments are now available');
- // verify stats
- assert.equal(hls.stats.bandwidth, 3000000, 'default');
- assert.equal(hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('waits to download new segments until the media playlist is stable', function(assert) {
- let sourceBuffer;
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.masterPlaylistController_.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
- hls.mediaSource.trigger('sourceopen');
- // make sure we stay on the lowest variant
- hls.bandwidth = 1;
- // master
- this.standardXHRResponse(this.requests.shift());
- // media1
- this.standardXHRResponse(this.requests.shift());
- // source buffer created after media source is open and first media playlist is selected
- sourceBuffer = hls.mediaSource.sourceBuffers[0];
- hls.masterPlaylistController_.mainSegmentLoader_.mediaIndex = 0;
- // segment 0
- this.standardXHRResponse(this.requests.shift());
- // update the buffer to reflect the appended segment, and have enough buffer to
- // change playlist
- this.tech.buffered = () => videojs.createTimeRanges([[0, 30]]);
- // no time has elapsed, so bandwidth is really high and we'll switch
- // playlists
- sourceBuffer.trigger('updateend');
- assert.equal(this.requests.length, 1, 'only the playlist request outstanding');
- this.clock.tick(10 * 1000);
- assert.equal(this.requests.length, 1, 'delays segment fetching');
- // another media playlist
- this.standardXHRResponse(this.requests.shift());
- this.clock.tick(10 * 1000);
- assert.equal(this.requests.length, 1, 'resumes segment fetching');
- // verify stats
- assert.equal(hls.stats.bandwidth, Infinity, 'default');
- assert.equal(hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(hls.stats.mediaRequests, 1, '1 request');
- });
- QUnit.test('live playlist starts three target durations before live', function(assert) {
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.mediaSource.trigger('sourceopen');
- this.requests.shift().respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:101\n' +
- '#EXTINF:10,\n' +
- '0.ts\n' +
- '#EXTINF:10,\n' +
- '1.ts\n' +
- '#EXTINF:10,\n' +
- '2.ts\n' +
- '#EXTINF:10,\n' +
- '3.ts\n' +
- '#EXTINF:10,\n' +
- '4.ts\n');
- assert.equal(this.requests.length, 0, 'no outstanding segment request');
- this.tech.paused = function() {
- return false;
- };
- this.tech.trigger('play');
- this.clock.tick(1);
- assert.equal(this.tech.currentTime(),
- hls.seekable().end(0),
- 'seeked to the seekable end');
- assert.equal(this.requests.length, 1, 'begins buffering');
- });
- QUnit.test('uses user defined selectPlaylist from HlsHandler if specified', function(assert) {
- let origStandardPlaylistSelector = Hls.STANDARD_PLAYLIST_SELECTOR;
- let defaultSelectPlaylistCount = 0;
- Hls.STANDARD_PLAYLIST_SELECTOR = () => defaultSelectPlaylistCount++;
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.masterPlaylistController_.selectPlaylist();
- assert.equal(defaultSelectPlaylistCount, 1, 'uses default playlist selector');
- defaultSelectPlaylistCount = 0;
- let newSelectPlaylistCount = 0;
- let newSelectPlaylist = () => newSelectPlaylistCount++;
- HlsHandler.prototype.selectPlaylist = newSelectPlaylist;
- hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.masterPlaylistController_.selectPlaylist();
- assert.equal(defaultSelectPlaylistCount, 0, 'standard playlist selector not run');
- assert.equal(newSelectPlaylistCount, 1, 'uses overridden playlist selector');
- newSelectPlaylistCount = 0;
- let setSelectPlaylistCount = 0;
- hls.selectPlaylist = () => setSelectPlaylistCount++;
- hls.masterPlaylistController_.selectPlaylist();
- assert.equal(defaultSelectPlaylistCount, 0, 'standard playlist selector not run');
- assert.equal(newSelectPlaylistCount, 0, 'overridden playlist selector not run');
- assert.equal(setSelectPlaylistCount, 1, 'uses set playlist selector');
- Hls.STANDARD_PLAYLIST_SELECTOR = origStandardPlaylistSelector;
- delete HlsHandler.prototype.selectPlaylist;
- });
- QUnit.module('HLS - Encryption', {
- beforeEach(assert) {
- this.env = useFakeEnvironment(assert);
- this.requests = this.env.requests;
- this.mse = useFakeMediaSource();
- this.tech = new (videojs.getTech('Html5'))({});
- this.clock = this.env.clock;
- this.standardXHRResponse = (request, data) => {
- standardXHRResponse(request, data);
- // Because SegmentLoader#fillBuffer_ is now scheduled asynchronously
- // we have to use clock.tick to get the expected side effects of
- // SegmentLoader#handleUpdateEnd_
- this.clock.tick(1);
- };
- videojs.HlsHandler.prototype.setupQualityLevels_ = () => {};
- },
- afterEach() {
- this.env.restore();
- this.mse.restore();
- videojs.HlsHandler.prototype.setupQualityLevels_ = ogHlsHandlerSetupQualityLevels;
- }
- });
- QUnit.test('blacklists playlist if key requests fail', function(assert) {
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/encrypted-master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.mediaSource.trigger('sourceopen');
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
- 'media1.m3u8\n');
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=52"\n' +
- '#EXTINF:2.833,\n' +
- 'http://media.example.com/fileSequence52-A.ts\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="htts://priv.example.com/key.php?r=53"\n' +
- '#EXTINF:15.0,\n' +
- 'http://media.example.com/fileSequence53-A.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- // segment 1
- if (/key\.php/i.test(this.requests[0].url)) {
- this.standardXHRResponse(this.requests.pop());
- } else {
- this.standardXHRResponse(this.requests.shift());
- }
- // fail key
- this.requests.shift().respond(404);
- assert.ok(hls.playlists.media().excludeUntil > 0,
- 'playlist blacklisted');
- assert.equal(this.env.log.warn.calls, 1, 'logged warning for blacklist');
- });
- QUnit.test('treats invalid keys as a key request failure and blacklists playlist', function(assert) {
- let hls = HlsSourceHandler('html5').handleSource({
- src: 'manifest/encrypted-master.m3u8',
- type: 'application/vnd.apple.mpegurl'
- }, this.tech);
- hls.mediaSource.trigger('sourceopen');
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1000\n' +
- 'media.m3u8\n' +
- '#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
- 'media1.m3u8\n');
- this.requests.shift()
- .respond(200, null,
- '#EXTM3U\n' +
- '#EXT-X-MEDIA-SEQUENCE:5\n' +
- '#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n' +
- '#EXTINF:2.833,\n' +
- 'http://media.example.com/fileSequence52-A.ts\n' +
- '#EXT-X-KEY:METHOD=NONE\n' +
- '#EXTINF:15.0,\n' +
- 'http://media.example.com/fileSequence52-B.ts\n' +
- '#EXT-X-ENDLIST\n');
- this.clock.tick(1);
- // segment request
- this.standardXHRResponse(this.requests.pop());
- assert.equal(this.requests[0].url,
- 'https://priv.example.com/key.php?r=52',
- 'requested the key');
- // keys *should* be 16 bytes long -- this one is too small
- this.requests[0].response = new Uint8Array(1).buffer;
- this.requests.shift().respond(200, null, '');
- this.clock.tick(1);
- // blacklist this playlist
- assert.ok(hls.playlists.media().excludeUntil > 0,
- 'blacklisted playlist');
- assert.equal(this.env.log.warn.calls, 1, 'logged warning for blacklist');
- // verify stats
- assert.equal(hls.stats.mediaBytesTransferred, 1024, '1024 bytes');
- assert.equal(hls.stats.mediaRequests, 1, '1 request');
- });
|