master-playlist-controller.test.js 84 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462
  1. import QUnit from 'qunit';
  2. import videojs from 'video.js';
  3. import {
  4. useFakeEnvironment,
  5. useFakeMediaSource,
  6. createPlayer,
  7. standardXHRResponse,
  8. openMediaSource
  9. } from './test-helpers.js';
  10. import manifests from './test-manifests.js';
  11. import {
  12. MasterPlaylistController,
  13. mimeTypesForPlaylist_,
  14. mapLegacyAvcCodecs_
  15. } from '../src/master-playlist-controller';
  16. /* eslint-disable no-unused-vars */
  17. // we need this so that it can register hls with videojs
  18. import { Hls } from '../src/videojs-contrib-hls';
  19. /* eslint-enable no-unused-vars */
  20. import Playlist from '../src/playlist';
  21. import Config from '../src/config';
  22. const generateMedia = function(isMaat, isMuxed, hasVideoCodec, hasAudioCodec, isFMP4) {
  23. const codec = (hasVideoCodec ? 'avc1.deadbeef' : '') +
  24. (hasVideoCodec && hasAudioCodec ? ',' : '') +
  25. (hasAudioCodec ? 'mp4a.40.E' : '');
  26. const master = {
  27. mediaGroups: {},
  28. playlists: []
  29. };
  30. const media = {
  31. attributes: {}
  32. };
  33. if (isMaat) {
  34. master.mediaGroups.AUDIO = {
  35. test: {
  36. demuxed: {
  37. uri: 'foo.bar'
  38. }
  39. }
  40. };
  41. if (isMuxed) {
  42. master.mediaGroups.AUDIO.test.muxed = {};
  43. }
  44. media.attributes.AUDIO = 'test';
  45. }
  46. if (isFMP4) {
  47. // This is not a great way to signal that the playlist is fmp4 but
  48. // this is how we currently detect it in HLS so let's emulate it here
  49. media.segments = [
  50. {
  51. map: 'test'
  52. }
  53. ];
  54. }
  55. if (hasVideoCodec || hasAudioCodec) {
  56. media.attributes.CODECS = codec;
  57. }
  58. return [master, media];
  59. };
  60. QUnit.module('MasterPlaylistController', {
  61. beforeEach(assert) {
  62. this.env = useFakeEnvironment(assert);
  63. this.clock = this.env.clock;
  64. this.requests = this.env.requests;
  65. this.mse = useFakeMediaSource();
  66. // force the HLS tech to run
  67. this.origSupportsNativeHls = videojs.Hls.supportsNativeHls;
  68. videojs.Hls.supportsNativeHls = false;
  69. this.oldBrowser = videojs.browser;
  70. videojs.browser = videojs.mergeOptions({}, videojs.browser);
  71. this.player = createPlayer();
  72. this.player.src({
  73. src: 'manifest/master.m3u8',
  74. type: 'application/vnd.apple.mpegurl'
  75. });
  76. this.clock.tick(1);
  77. this.standardXHRResponse = (request, data) => {
  78. standardXHRResponse(request, data);
  79. // Because SegmentLoader#fillBuffer_ is now scheduled asynchronously
  80. // we have to use clock.tick to get the expected side effects of
  81. // SegmentLoader#handleUpdateEnd_
  82. this.clock.tick(1);
  83. };
  84. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  85. // Make segment metadata noop since most test segments dont have real data
  86. this.masterPlaylistController.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
  87. },
  88. afterEach() {
  89. this.env.restore();
  90. this.mse.restore();
  91. videojs.Hls.supportsNativeHls = this.origSupportsNativeHls;
  92. videojs.browser = this.oldBrowser;
  93. this.player.dispose();
  94. }
  95. });
  96. QUnit.test('throws error when given an empty URL', function(assert) {
  97. let options = {
  98. url: 'test',
  99. tech: this.player.tech_
  100. };
  101. assert.ok(new MasterPlaylistController(options), 'can create with options');
  102. options.url = '';
  103. assert.throws(() => {
  104. new MasterPlaylistController(options); // eslint-disable-line no-new
  105. }, /A non-empty playlist URL is required/, 'requires a non empty url');
  106. });
  107. QUnit.test('obeys none preload option', function(assert) {
  108. this.player.preload('none');
  109. // master
  110. this.standardXHRResponse(this.requests.shift());
  111. // playlist
  112. this.standardXHRResponse(this.requests.shift());
  113. openMediaSource(this.player, this.clock);
  114. assert.equal(this.requests.length, 0, 'no segment requests');
  115. // verify stats
  116. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  117. });
  118. QUnit.test('passes options to PlaylistLoader', function(assert) {
  119. const options = {
  120. url: 'test',
  121. tech: this.player.tech_
  122. };
  123. let controller = new MasterPlaylistController(options);
  124. assert.notOk(controller.masterPlaylistLoader_.withCredentials, 'credentials wont be sent by default');
  125. assert.notOk(controller.masterPlaylistLoader_.handleManifestRedirects, 'redirects are ignored by default');
  126. controller = new MasterPlaylistController(Object.assign({
  127. withCredentials: true,
  128. handleManifestRedirects: true
  129. }, options));
  130. assert.ok(controller.masterPlaylistLoader_.withCredentials, 'withCredentials enabled');
  131. assert.ok(controller.masterPlaylistLoader_.handleManifestRedirects, 'handleManifestRedirects enabled');
  132. });
  133. QUnit.test('obeys auto preload option', function(assert) {
  134. this.player.preload('auto');
  135. // master
  136. this.standardXHRResponse(this.requests.shift());
  137. // playlist
  138. this.standardXHRResponse(this.requests.shift());
  139. openMediaSource(this.player, this.clock);
  140. assert.equal(this.requests.length, 1, '1 segment request');
  141. // verify stats
  142. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  143. });
  144. QUnit.test('obeys metadata preload option', function(assert) {
  145. this.player.preload('metadata');
  146. // master
  147. this.standardXHRResponse(this.requests.shift());
  148. // playlist
  149. this.standardXHRResponse(this.requests.shift());
  150. openMediaSource(this.player, this.clock);
  151. assert.equal(this.requests.length, 1, '1 segment request');
  152. // verify stats
  153. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  154. });
  155. QUnit.test('resets SegmentLoader when seeking in flash for both in and out of buffer',
  156. function(assert) {
  157. let resets = 0;
  158. // master
  159. this.standardXHRResponse(this.requests.shift());
  160. // media
  161. this.standardXHRResponse(this.requests.shift());
  162. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  163. let mpc = this.masterPlaylistController;
  164. let segmentLoader = mpc.mainSegmentLoader_;
  165. segmentLoader.resetEverything = function() {
  166. resets++;
  167. };
  168. let buffered;
  169. mpc.tech_.buffered = function() {
  170. return buffered;
  171. };
  172. buffered = videojs.createTimeRanges([[0, 20]]);
  173. mpc.mode_ = 'html5';
  174. mpc.setCurrentTime(10);
  175. assert.equal(resets, 0,
  176. 'does not reset loader when seeking into a buffered region in html5');
  177. mpc.setCurrentTime(21);
  178. assert.equal(resets, 1,
  179. 'does reset loader when seeking outside of the buffered region in html5');
  180. mpc.mode_ = 'flash';
  181. mpc.setCurrentTime(10);
  182. assert.equal(resets, 2,
  183. 'does reset loader when seeking into a buffered region in flash');
  184. mpc.setCurrentTime(21);
  185. assert.equal(resets, 3,
  186. 'does reset loader when seeking outside of the buffered region in flash');
  187. });
  188. QUnit.test('selects lowest bitrate rendition when enableLowInitialPlaylist is set',
  189. function(assert) {
  190. // Set requests.length to 0, otherwise it will use the requests generated in the
  191. // beforeEach function
  192. this.requests.length = 0;
  193. this.player = createPlayer({ html5: { hls: { enableLowInitialPlaylist: true } } });
  194. this.player.src({
  195. src: 'manifest/master.m3u8',
  196. type: 'application/vnd.apple.mpegurl'
  197. });
  198. this.clock.tick(1);
  199. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  200. let numCallsToSelectInitialPlaylistCalls = 0;
  201. let numCallsToSelectPlaylist = 0;
  202. this.masterPlaylistController.selectPlaylist = () => {
  203. numCallsToSelectPlaylist++;
  204. return this.masterPlaylistController.master().playlists[0];
  205. };
  206. this.masterPlaylistController.selectInitialPlaylist = () => {
  207. numCallsToSelectInitialPlaylistCalls++;
  208. return this.masterPlaylistController.master().playlists[0];
  209. };
  210. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  211. // master
  212. this.standardXHRResponse(this.requests.shift());
  213. // media
  214. this.standardXHRResponse(this.requests.shift());
  215. this.clock.tick(1);
  216. assert.equal(numCallsToSelectInitialPlaylistCalls, 1, 'selectInitialPlaylist');
  217. assert.equal(numCallsToSelectPlaylist, 0, 'selectPlaylist');
  218. // Simulate a live reload
  219. this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist');
  220. assert.equal(numCallsToSelectInitialPlaylistCalls, 1, 'selectInitialPlaylist');
  221. assert.equal(numCallsToSelectPlaylist, 0, 'selectPlaylist');
  222. });
  223. QUnit.test('resyncs SegmentLoader for a fast quality change', function(assert) {
  224. let resyncs = 0;
  225. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  226. // master
  227. this.standardXHRResponse(this.requests.shift());
  228. // media
  229. this.standardXHRResponse(this.requests.shift());
  230. let segmentLoader = this.masterPlaylistController.mainSegmentLoader_;
  231. segmentLoader.resyncLoader = function() {
  232. resyncs++;
  233. };
  234. this.masterPlaylistController.selectPlaylist = () => {
  235. return this.masterPlaylistController.master().playlists[0];
  236. };
  237. this.masterPlaylistController.fastQualityChange_();
  238. assert.equal(resyncs, 1, 'resynced the segmentLoader');
  239. // verify stats
  240. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  241. });
  242. QUnit.test('does not resync the segmentLoader when no fast quality change occurs',
  243. function(assert) {
  244. let resyncs = 0;
  245. // master
  246. this.standardXHRResponse(this.requests.shift());
  247. // media
  248. this.standardXHRResponse(this.requests.shift());
  249. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  250. let segmentLoader = this.masterPlaylistController.mainSegmentLoader_;
  251. segmentLoader.resyncLoader = function() {
  252. resyncs++;
  253. };
  254. this.masterPlaylistController.fastQualityChange_();
  255. assert.equal(resyncs, 0, 'did not resync the segmentLoader');
  256. // verify stats
  257. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  258. });
  259. QUnit.test('fast quality change resyncs audio segment loader', function(assert) {
  260. this.requests.length = 0;
  261. this.player = createPlayer();
  262. this.player.src({
  263. src: 'alternate-audio-multiple-groups.m3u8',
  264. type: 'application/vnd.apple.mpegurl'
  265. });
  266. this.clock.tick(1);
  267. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  268. masterPlaylistController.selectPlaylist = () => {
  269. return masterPlaylistController.master().playlists[0];
  270. };
  271. // master
  272. this.standardXHRResponse(this.requests.shift());
  273. // media
  274. this.standardXHRResponse(this.requests.shift());
  275. masterPlaylistController.mediaSource.trigger('sourceopen');
  276. this.player.audioTracks()[0].enabled = true;
  277. let resyncs = 0;
  278. let resets = 0;
  279. let realReset = masterPlaylistController.audioSegmentLoader_.resetLoader;
  280. masterPlaylistController.audioSegmentLoader_.resetLoader = function() {
  281. resets++;
  282. realReset.call(this);
  283. };
  284. masterPlaylistController.audioSegmentLoader_.resyncLoader = () => resyncs++;
  285. masterPlaylistController.fastQualityChange_();
  286. assert.equal(resyncs, 0, 'does not resync the audio segment loader when media same');
  287. // force different media
  288. masterPlaylistController.selectPlaylist = () => {
  289. return masterPlaylistController.master().playlists[1];
  290. };
  291. assert.equal(this.requests.length, 1, 'one request');
  292. masterPlaylistController.fastQualityChange_();
  293. assert.equal(this.requests.length, 2, 'added a request for new media');
  294. assert.equal(resyncs, 0, 'does not resync the audio segment loader yet');
  295. // new media request
  296. this.standardXHRResponse(this.requests[1]);
  297. assert.equal(resyncs, 1, 'resyncs the audio segment loader when media changes');
  298. assert.equal(resets, 0, 'does not reset the audio segment loader when media changes');
  299. });
  300. QUnit.test('audio segment loader is reset on audio track change', function(assert) {
  301. this.requests.length = 0;
  302. this.player = createPlayer();
  303. this.player.src({
  304. src: 'alternate-audio-multiple-groups.m3u8',
  305. type: 'application/vnd.apple.mpegurl'
  306. });
  307. this.clock.tick(1);
  308. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  309. masterPlaylistController.selectPlaylist = () => {
  310. return masterPlaylistController.master().playlists[0];
  311. };
  312. // master
  313. this.standardXHRResponse(this.requests.shift());
  314. // media
  315. this.standardXHRResponse(this.requests.shift());
  316. masterPlaylistController.mediaSource.trigger('sourceopen');
  317. let resyncs = 0;
  318. let resets = 0;
  319. let realReset = masterPlaylistController.audioSegmentLoader_.resetLoader;
  320. masterPlaylistController.audioSegmentLoader_.resetLoader = function() {
  321. resets++;
  322. realReset.call(this);
  323. };
  324. masterPlaylistController.audioSegmentLoader_.resyncLoader = () => resyncs++;
  325. assert.equal(this.requests.length, 1, 'one request');
  326. assert.equal(resyncs, 0, 'does not resync the audio segment loader yet');
  327. this.player.audioTracks()[1].enabled = true;
  328. assert.equal(this.requests.length, 2, 'two requests');
  329. assert.equal(resyncs, 1, 'resyncs the audio segment loader when audio track changes');
  330. assert.equal(resets, 1, 'resets the audio segment loader when audio track changes');
  331. });
  332. QUnit.test('if buffered, will request second segment byte range', function(assert) {
  333. this.requests.length = 0;
  334. this.player.src({
  335. src: 'manifest/playlist.m3u8',
  336. type: 'application/vnd.apple.mpegurl'
  337. });
  338. this.clock.tick(1);
  339. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  340. // Make segment metadata noop since most test segments dont have real data
  341. this.masterPlaylistController.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
  342. // mock that the user has played the video before
  343. this.player.tech_.triggerReady();
  344. this.clock.tick(1);
  345. this.player.tech_.trigger('play');
  346. this.player.tech_.paused_ = false;
  347. this.player.tech_.played = () => videojs.createTimeRanges([[0, 20]]);
  348. openMediaSource(this.player, this.clock);
  349. // playlist
  350. this.standardXHRResponse(this.requests[0]);
  351. this.masterPlaylistController.mainSegmentLoader_.sourceUpdater_.buffered = () => {
  352. return videojs.createTimeRanges([[0, 20]]);
  353. };
  354. // 1ms has passed to upload 1kb
  355. // that gives us a bandwidth of 1024 / 1 * 8 * 1000 = 8192000
  356. this.clock.tick(1);
  357. // segment
  358. this.standardXHRResponse(this.requests[1]);
  359. this.masterPlaylistController.mainSegmentLoader_.fetchAtBuffer_ = true;
  360. this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
  361. this.clock.tick(10 * 1000);
  362. this.clock.tick(1);
  363. assert.equal(this.requests[2].headers.Range, 'bytes=522828-1110327');
  364. // verify stats
  365. assert.equal(this.player.tech_.hls.stats.bandwidth, 8192000, 'Live stream');
  366. assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
  367. assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
  368. 1024,
  369. '1024 bytes downloaded');
  370. });
  371. QUnit.test('re-initializes the combined playlist loader when switching sources',
  372. function(assert) {
  373. openMediaSource(this.player, this.clock);
  374. // master
  375. this.standardXHRResponse(this.requests.shift());
  376. // playlist
  377. this.standardXHRResponse(this.requests.shift());
  378. // segment
  379. this.standardXHRResponse(this.requests.shift());
  380. // change the source
  381. this.player.src({
  382. src: 'manifest/master.m3u8',
  383. type: 'application/vnd.apple.mpegurl'
  384. });
  385. this.clock.tick(1);
  386. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  387. // Make segment metadata noop since most test segments dont have real data
  388. this.masterPlaylistController.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
  389. // maybe not needed if https://github.com/videojs/video.js/issues/2326 gets fixed
  390. this.clock.tick(1);
  391. assert.ok(!this.masterPlaylistController.masterPlaylistLoader_.media(),
  392. 'no media playlist');
  393. assert.equal(this.masterPlaylistController.masterPlaylistLoader_.state,
  394. 'HAVE_NOTHING',
  395. 'reset the playlist loader state');
  396. assert.equal(this.requests.length, 1, 'requested the new src');
  397. // buffer check
  398. this.clock.tick(10 * 1000);
  399. assert.equal(this.requests.length, 1, 'did not request a stale segment');
  400. // sourceopen
  401. openMediaSource(this.player, this.clock);
  402. assert.equal(this.requests.length, 1, 'made one request');
  403. assert.ok(
  404. this.requests[0].url.indexOf('master.m3u8') >= 0,
  405. 'requested only the new playlist'
  406. );
  407. });
  408. QUnit.test('updates the combined segment loader on live playlist refreshes',
  409. function(assert) {
  410. let updates = [];
  411. openMediaSource(this.player, this.clock);
  412. // master
  413. this.standardXHRResponse(this.requests.shift());
  414. // media
  415. this.standardXHRResponse(this.requests.shift());
  416. this.masterPlaylistController.mainSegmentLoader_.playlist = function(update) {
  417. updates.push(update);
  418. };
  419. this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist');
  420. assert.equal(updates.length, 1, 'updated the segment list');
  421. // verify stats
  422. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  423. });
  424. QUnit.test(
  425. 'fires a progress event after downloading a segment from combined segment loader',
  426. function(assert) {
  427. let progressCount = 0;
  428. openMediaSource(this.player, this.clock);
  429. // master
  430. this.standardXHRResponse(this.requests.shift());
  431. // media
  432. this.standardXHRResponse(this.requests.shift());
  433. this.player.tech_.on('progress', function() {
  434. progressCount++;
  435. });
  436. // 1ms has passed to upload 1kb
  437. // that gives us a bandwidth of 1024 / 1 * 8 * 1000 = 8192000
  438. this.clock.tick(1);
  439. // segment
  440. this.standardXHRResponse(this.requests.shift());
  441. this.masterPlaylistController.mainSegmentLoader_.trigger('progress');
  442. assert.equal(progressCount, 1, 'fired a progress event');
  443. // verify stats
  444. assert.equal(this.player.tech_.hls.stats.bandwidth, 8192000, 'Live stream');
  445. assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
  446. assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
  447. 1024,
  448. '1024 bytes downloaded');
  449. });
  450. QUnit.test('updates the active loader when switching from unmuxed to muxed audio group',
  451. function(assert) {
  452. openMediaSource(this.player, this.clock);
  453. // master
  454. this.requests.shift().respond(200, null,
  455. manifests.multipleAudioGroupsCombinedMain);
  456. // media
  457. this.standardXHRResponse(this.requests.shift());
  458. // init segment
  459. this.standardXHRResponse(this.requests.shift());
  460. // video segment
  461. this.standardXHRResponse(this.requests.shift());
  462. // audio media
  463. this.standardXHRResponse(this.requests.shift());
  464. // ignore audio segment requests
  465. this.requests.length = 0;
  466. let mpc = this.masterPlaylistController;
  467. let combinedPlaylist = mpc.master().playlists[0];
  468. assert.ok(mpc.mediaTypes_.AUDIO.activePlaylistLoader,
  469. 'starts with an active playlist loader');
  470. mpc.masterPlaylistLoader_.media(combinedPlaylist);
  471. // updated media
  472. this.requests.shift().respond(200, null,
  473. '#EXTM3U\n' +
  474. '#EXTINF:5.0\n' +
  475. '0.ts\n' +
  476. '#EXT-X-ENDLIST\n');
  477. assert.notOk(mpc.mediaTypes_.AUDIO.activePlaylistLoader,
  478. 'enabled a track in the new audio group');
  479. });
  480. QUnit.test('waits for both main and audio loaders to finish before calling endOfStream',
  481. function(assert) {
  482. openMediaSource(this.player, this.clock);
  483. const videoMedia = '#EXTM3U\n' +
  484. '#EXT-X-VERSION:3\n' +
  485. '#EXT-X-PLAYLIST-TYPE:VOD\n' +
  486. '#EXT-X-MEDIA-SEQUENCE:0\n' +
  487. '#EXT-X-TARGETDURATION:10\n' +
  488. '#EXTINF:10,\n' +
  489. 'video-0.ts\n' +
  490. '#EXT-X-ENDLIST\n';
  491. const audioMedia = '#EXTM3U\n' +
  492. '#EXT-X-VERSION:3\n' +
  493. '#EXT-X-PLAYLIST-TYPE:VOD\n' +
  494. '#EXT-X-MEDIA-SEQUENCE:0\n' +
  495. '#EXT-X-TARGETDURATION:10\n' +
  496. '#EXTINF:10,\n' +
  497. 'audio-0.ts\n' +
  498. '#EXT-X-ENDLIST\n';
  499. let videoEnded = 0;
  500. let audioEnded = 0;
  501. const MPC = this.masterPlaylistController;
  502. MPC.mainSegmentLoader_.on('ended', () => videoEnded++);
  503. MPC.audioSegmentLoader_.on('ended', () => audioEnded++);
  504. // master
  505. this.standardXHRResponse(this.requests.shift(), manifests.demuxed);
  506. // video media
  507. this.standardXHRResponse(this.requests.shift(), videoMedia);
  508. // audio media
  509. this.standardXHRResponse(this.requests.shift(), audioMedia);
  510. // video segment
  511. this.standardXHRResponse(this.requests.shift());
  512. MPC.mediaSource.sourceBuffers[0].trigger('updateend');
  513. assert.equal(videoEnded, 1, 'main segment loader triggered endded');
  514. assert.equal(audioEnded, 0, 'audio segment loader did not trigger ended');
  515. assert.equal(MPC.mediaSource.readyState, 'open', 'Media Source not yet ended');
  516. // audio segment
  517. this.standardXHRResponse(this.requests.shift());
  518. MPC.mediaSource.sourceBuffers[1].trigger('updateend');
  519. assert.equal(videoEnded, 1, 'main segment loader did not trigger ended again');
  520. assert.equal(audioEnded, 1, 'audio segment loader triggered ended');
  521. assert.equal(MPC.mediaSource.readyState, 'ended', 'Media Source ended');
  522. });
  523. QUnit.test('Segment loaders are unpaused when seeking after player has ended',
  524. function(assert) {
  525. openMediaSource(this.player, this.clock);
  526. const videoMedia = '#EXTM3U\n' +
  527. '#EXT-X-VERSION:3\n' +
  528. '#EXT-X-PLAYLIST-TYPE:VOD\n' +
  529. '#EXT-X-MEDIA-SEQUENCE:0\n' +
  530. '#EXT-X-TARGETDURATION:10\n' +
  531. '#EXTINF:10,\n' +
  532. 'video-0.ts\n' +
  533. '#EXT-X-ENDLIST\n';
  534. let ended = 0;
  535. this.masterPlaylistController.mainSegmentLoader_.on('ended', () => ended++);
  536. this.player.tech_.trigger('play');
  537. // master
  538. this.standardXHRResponse(this.requests.shift());
  539. // media
  540. this.standardXHRResponse(this.requests.shift(), videoMedia);
  541. // segment
  542. this.standardXHRResponse(this.requests.shift());
  543. assert.notOk(this.masterPlaylistController.mainSegmentLoader_.paused(),
  544. 'segment loader not yet paused');
  545. this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
  546. assert.ok(this.masterPlaylistController.mainSegmentLoader_.paused(),
  547. 'segment loader is paused after ending');
  548. assert.equal(ended, 1, 'segment loader triggered ended event');
  549. this.player.currentTime(5);
  550. this.clock.tick(1);
  551. assert.notOk(this.masterPlaylistController.mainSegmentLoader_.paused(),
  552. 'segment loader unpaused after a seek');
  553. assert.equal(ended, 1, 'segment loader did not trigger ended event again yet');
  554. });
  555. QUnit.test('detects if the player is stuck at the playlist end', function(assert) {
  556. let playlistCopy = Hls.Playlist.playlistEnd;
  557. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  558. this.standardXHRResponse(this.requests.shift());
  559. let playlist = this.player.tech_.hls.selectPlaylist();
  560. // not stuck at playlist end when no seekable, even if empty buffer
  561. // and positive currentTime
  562. this.masterPlaylistController.seekable = () => videojs.createTimeRange();
  563. this.player.tech_.buffered = () => videojs.createTimeRange();
  564. this.player.tech_.setCurrentTime(170);
  565. assert.ok(!this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  566. 'not stuck at playlist end');
  567. // not stuck at playlist end when no seekable, even if empty buffer
  568. // and currentTime 0
  569. this.player.tech_.setCurrentTime(0);
  570. assert.ok(!this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  571. 'not stuck at playlist end');
  572. // not stuck at playlist end when no seekable but current time is at
  573. // the end of the buffered range
  574. this.player.tech_.buffered = () => videojs.createTimeRange(0, 170);
  575. assert.ok(!this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  576. 'not stuck at playlist end');
  577. // not stuck at playlist end when currentTime not at seekable end
  578. // even if the buffer is empty
  579. this.masterPlaylistController.seekable = () => videojs.createTimeRange(0, 130);
  580. this.masterPlaylistController.syncController_.getExpiredTime = () => 0;
  581. this.player.tech_.setCurrentTime(50);
  582. this.player.tech_.buffered = () => videojs.createTimeRange();
  583. Hls.Playlist.playlistEnd = () => 130;
  584. assert.ok(!this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  585. 'not stuck at playlist end');
  586. // not stuck at playlist end when buffer reached the absolute end of the playlist
  587. // and current time is in the buffered range
  588. this.player.tech_.setCurrentTime(159);
  589. this.player.tech_.buffered = () => videojs.createTimeRange(0, 160);
  590. Hls.Playlist.playlistEnd = () => 160;
  591. assert.ok(!this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  592. 'not stuck at playlist end');
  593. // stuck at playlist end when there is no buffer and playhead
  594. // reached absolute end of playlist
  595. this.player.tech_.setCurrentTime(160);
  596. assert.ok(this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  597. 'stuck at playlist end');
  598. // stuck at playlist end when current time reached the buffer end
  599. // and buffer has reached absolute end of playlist
  600. this.masterPlaylistController.seekable = () => videojs.createTimeRange(90, 130);
  601. this.player.tech_.buffered = () => videojs.createTimeRange(0, 170);
  602. this.player.tech_.setCurrentTime(170);
  603. Hls.Playlist.playlistEnd = () => 170;
  604. assert.ok(this.masterPlaylistController.stuckAtPlaylistEnd_(playlist),
  605. 'stuck at playlist end');
  606. Hls.Playlist.playlistEnd = playlistCopy;
  607. });
  608. QUnit.test('blacklists switching from video+audio playlists to audio only',
  609. function(assert) {
  610. let audioPlaylist;
  611. openMediaSource(this.player, this.clock);
  612. this.player.tech_.hls.bandwidth = 1e10;
  613. // master
  614. this.requests.shift().respond(200, null,
  615. '#EXTM3U\n' +
  616. '#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="mp4a.40.2"\n' +
  617. 'media.m3u8\n' +
  618. '#EXT-X-STREAM-INF:BANDWIDTH=10,RESOLUTION=1x1\n' +
  619. 'media1.m3u8\n');
  620. // media1
  621. this.standardXHRResponse(this.requests.shift());
  622. assert.equal(this.masterPlaylistController.masterPlaylistLoader_.media(),
  623. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1],
  624. 'selected video+audio');
  625. audioPlaylist = this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0];
  626. assert.equal(audioPlaylist.excludeUntil, Infinity, 'excluded incompatible playlist');
  627. // verify stats
  628. assert.equal(this.player.tech_.hls.stats.bandwidth, 1e10, 'bandwidth we set above');
  629. });
  630. QUnit.test('blacklists switching from audio-only playlists to video+audio',
  631. function(assert) {
  632. let videoAudioPlaylist;
  633. openMediaSource(this.player, this.clock);
  634. this.player.tech_.hls.bandwidth = 1;
  635. // master
  636. this.requests.shift().respond(200, null,
  637. '#EXTM3U\n' +
  638. '#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="mp4a.40.2"\n' +
  639. 'media.m3u8\n' +
  640. '#EXT-X-STREAM-INF:BANDWIDTH=10,RESOLUTION=1x1\n' +
  641. 'media1.m3u8\n');
  642. // media1
  643. this.standardXHRResponse(this.requests.shift());
  644. assert.equal(this.masterPlaylistController.masterPlaylistLoader_.media(),
  645. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0],
  646. 'selected audio only');
  647. videoAudioPlaylist =
  648. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1];
  649. assert.equal(videoAudioPlaylist.excludeUntil,
  650. Infinity,
  651. 'excluded incompatible playlist');
  652. // verify stats
  653. assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
  654. });
  655. QUnit.test('blacklists switching from video-only playlists to video+audio',
  656. function(assert) {
  657. let videoAudioPlaylist;
  658. openMediaSource(this.player, this.clock);
  659. this.player.tech_.hls.bandwidth = 1;
  660. // master
  661. this.requests.shift()
  662. .respond(200, null,
  663. '#EXTM3U\n' +
  664. '#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="avc1.4d400d"\n' +
  665. 'media.m3u8\n' +
  666. '#EXT-X-STREAM-INF:BANDWIDTH=10,CODECS="avc1.4d400d,mp4a.40.2"\n' +
  667. 'media1.m3u8\n');
  668. // media
  669. this.standardXHRResponse(this.requests.shift());
  670. assert.equal(this.masterPlaylistController.masterPlaylistLoader_.media(),
  671. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0],
  672. 'selected video only');
  673. videoAudioPlaylist =
  674. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1];
  675. assert.equal(videoAudioPlaylist.excludeUntil,
  676. Infinity,
  677. 'excluded incompatible playlist');
  678. // verify stats
  679. assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
  680. });
  681. QUnit.test('blacklists switching between playlists with incompatible audio codecs',
  682. function(assert) {
  683. let alternatePlaylist;
  684. openMediaSource(this.player, this.clock);
  685. this.player.tech_.hls.bandwidth = 1;
  686. // master
  687. this.requests.shift()
  688. .respond(200, null,
  689. '#EXTM3U\n' +
  690. '#EXT-X-STREAM-INF:BANDWIDTH=1,CODECS="avc1.4d400d,mp4a.40.5"\n' +
  691. 'media.m3u8\n' +
  692. '#EXT-X-STREAM-INF:BANDWIDTH=10,CODECS="avc1.4d400d,mp4a.40.2"\n' +
  693. 'media1.m3u8\n');
  694. // media
  695. this.standardXHRResponse(this.requests.shift());
  696. assert.equal(this.masterPlaylistController.masterPlaylistLoader_.media(),
  697. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0],
  698. 'selected HE-AAC stream');
  699. alternatePlaylist =
  700. this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1];
  701. assert.equal(alternatePlaylist.excludeUntil,
  702. undefined,
  703. 'not excluded incompatible playlist');
  704. // verify stats
  705. assert.equal(this.player.tech_.hls.stats.bandwidth, 1, 'bandwidth we set above');
  706. });
  707. QUnit.test('updates the combined segment loader on media changes', function(assert) {
  708. let updates = [];
  709. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  710. this.masterPlaylistController.mainSegmentLoader_.bandwidth = 1;
  711. // master
  712. this.standardXHRResponse(this.requests.shift());
  713. // media
  714. this.standardXHRResponse(this.requests.shift());
  715. this.masterPlaylistController.mainSegmentLoader_.playlist = function(update) {
  716. updates.push(update);
  717. };
  718. // 1ms has passed to upload 1kb
  719. // that gives us a bandwidth of 1024 / 1 * 8 * 1000 = 8192000
  720. this.clock.tick(1);
  721. this.masterPlaylistController.mainSegmentLoader_.mediaIndex = 0;
  722. // downloading the new segment will update bandwidth and cause a
  723. // playlist change
  724. // segment 0
  725. this.standardXHRResponse(this.requests.shift());
  726. // update the buffer to reflect the appended segment, and have enough buffer to
  727. // change playlist
  728. this.masterPlaylistController.tech_.buffered = () => {
  729. return videojs.createTimeRanges([[0, 30]]);
  730. };
  731. this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
  732. // media
  733. this.standardXHRResponse(this.requests.shift());
  734. assert.ok(updates.length > 0, 'updated the segment list');
  735. // verify stats
  736. assert.equal(this.player.tech_.hls.stats.bandwidth, 8192000, 'Live stream');
  737. assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
  738. assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
  739. 1024,
  740. '1024 bytes downloaded');
  741. });
  742. QUnit.test('selects a playlist after main/combined segment downloads', function(assert) {
  743. let calls = 0;
  744. this.masterPlaylistController.selectPlaylist = () => {
  745. calls++;
  746. return this.masterPlaylistController.masterPlaylistLoader_.master.playlists[0];
  747. };
  748. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  749. // master
  750. this.standardXHRResponse(this.requests.shift());
  751. // media
  752. this.standardXHRResponse(this.requests.shift());
  753. // "downloaded" a segment
  754. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  755. assert.strictEqual(calls, 2, 'selects after the initial segment');
  756. // and another
  757. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  758. assert.strictEqual(calls, 3, 'selects after additional segments');
  759. // verify stats
  760. assert.equal(this.player.tech_.hls.stats.bandwidth, 4194304, 'default bandwidth');
  761. });
  762. QUnit.test('re-triggers bandwidthupdate events on the tech', function(assert) {
  763. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  764. // master
  765. this.standardXHRResponse(this.requests.shift());
  766. // media
  767. this.standardXHRResponse(this.requests.shift());
  768. let bandwidthupdateEvents = 0;
  769. this.player.tech_.on('bandwidthupdate', () => bandwidthupdateEvents++);
  770. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  771. assert.equal(bandwidthupdateEvents, 1, 'triggered bandwidthupdate');
  772. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  773. assert.equal(bandwidthupdateEvents, 2, 'triggered bandwidthupdate');
  774. });
  775. QUnit.test('switches to lower renditions immediately, higher dependent on buffer',
  776. function(assert) {
  777. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  778. // master
  779. this.standardXHRResponse(this.requests.shift());
  780. // media
  781. this.standardXHRResponse(this.requests.shift());
  782. let buffered = [];
  783. let currentPlaylistBandwidth = 0;
  784. let nextPlaylistBandwidth = 0;
  785. let mediaChanges = [];
  786. let currentTime = 0;
  787. let endList = true;
  788. let duration = 100;
  789. this.masterPlaylistController.tech_.currentTime = () => currentTime;
  790. this.masterPlaylistController.tech_.buffered = () => videojs.createTimeRanges(buffered);
  791. this.masterPlaylistController.duration = () => duration;
  792. this.masterPlaylistController.selectPlaylist = () => {
  793. return {
  794. attributes: {
  795. BANDWIDTH: nextPlaylistBandwidth
  796. },
  797. endList
  798. };
  799. };
  800. this.masterPlaylistController.masterPlaylistLoader_.media = (media) => {
  801. if (!media) {
  802. return {
  803. attributes: {
  804. BANDWIDTH: currentPlaylistBandwidth
  805. },
  806. endList
  807. };
  808. }
  809. mediaChanges.push(media);
  810. };
  811. currentTime = 0;
  812. currentPlaylistBandwidth = 1000;
  813. nextPlaylistBandwidth = 1000;
  814. buffered = [];
  815. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  816. assert.equal(mediaChanges.length,
  817. 1,
  818. 'changes media when no buffer and equal bandwidth playlist');
  819. buffered = [[0, 9]];
  820. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  821. assert.equal(mediaChanges.length,
  822. 2,
  823. 'changes media when sufficient forward buffer and equal ' +
  824. 'bandwidth playlist');
  825. buffered = [[0, 30]];
  826. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  827. assert.equal(mediaChanges.length,
  828. 3,
  829. 'changes media when sufficient forward buffer and equal ' +
  830. 'bandwidth playlist');
  831. mediaChanges.length = 0;
  832. currentTime = 10;
  833. currentPlaylistBandwidth = 1000;
  834. nextPlaylistBandwidth = 1001;
  835. buffered = [];
  836. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  837. assert.equal(mediaChanges.length,
  838. 0,
  839. 'did not change media when no buffer and and higher bandwidth playlist');
  840. buffered = [[0, 19]];
  841. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  842. assert.equal(mediaChanges.length,
  843. 0,
  844. 'did not change media when insufficient forward buffer and higher ' +
  845. 'bandwidth playlist');
  846. buffered = [[0, 20]];
  847. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  848. assert.equal(mediaChanges.length,
  849. 1,
  850. 'changes media when sufficient forward buffer and higher ' +
  851. 'bandwidth playlist');
  852. buffered = [[0, 21]];
  853. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  854. assert.equal(mediaChanges.length,
  855. 2,
  856. 'changes media when sufficient forward buffer and higher ' +
  857. 'bandwidth playlist');
  858. mediaChanges.length = 0;
  859. currentTime = 100;
  860. currentPlaylistBandwidth = 1000;
  861. nextPlaylistBandwidth = 1001;
  862. buffered = [];
  863. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  864. assert.equal(mediaChanges.length,
  865. 0,
  866. 'did not change media when no buffer and higher bandwidth playlist');
  867. buffered = [[0, 100], [100, 109]];
  868. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  869. assert.equal(mediaChanges.length,
  870. 0,
  871. 'did not change media when insufficient forward buffer and higher ' +
  872. 'bandwidth playlist');
  873. buffered = [[0, 100], [100, 130]];
  874. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  875. assert.equal(mediaChanges.length,
  876. 1,
  877. 'changes media when sufficient forward buffer and higher ' +
  878. 'bandwidth playlist');
  879. mediaChanges.length = 0;
  880. buffered = [];
  881. currentPlaylistBandwidth = 1000;
  882. nextPlaylistBandwidth = 999;
  883. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  884. assert.equal(mediaChanges.length,
  885. 1,
  886. 'changes media when no buffer but lower bandwidth playlist');
  887. buffered = [[100, 109]];
  888. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  889. assert.equal(mediaChanges.length,
  890. 2,
  891. 'changes media when insufficient forward buffer but lower ' +
  892. 'bandwidth playlist');
  893. buffered = [[100, 110]];
  894. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  895. assert.equal(mediaChanges.length,
  896. 3,
  897. 'changes media when sufficient forward buffer and lower ' +
  898. 'bandwidth playlist');
  899. mediaChanges.length = 0;
  900. endList = false;
  901. currentTime = 100;
  902. currentPlaylistBandwidth = 1000;
  903. nextPlaylistBandwidth = 1001;
  904. buffered = [];
  905. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  906. assert.equal(mediaChanges.length,
  907. 1,
  908. 'changes live media when no buffer and higher bandwidth playlist');
  909. buffered = [[0, 100], [100, 109]];
  910. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  911. assert.equal(mediaChanges.length,
  912. 2,
  913. 'changes live media when insufficient forward buffer and higher ' +
  914. 'bandwidth playlist');
  915. buffered = [[0, 100], [100, 130]];
  916. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  917. assert.equal(mediaChanges.length,
  918. 3,
  919. 'changes live media when sufficient forward buffer and higher ' +
  920. 'bandwidth playlist');
  921. mediaChanges.length = 0;
  922. endList = true;
  923. currentTime = 9;
  924. duration = 18;
  925. buffered = [];
  926. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  927. assert.equal(mediaChanges.length,
  928. 1,
  929. 'changes media when no buffer and duration less than low water line');
  930. buffered = [[0, 10]];
  931. this.masterPlaylistController.mainSegmentLoader_.trigger('bandwidthupdate');
  932. assert.equal(mediaChanges.length,
  933. 2,
  934. 'changes media when insufficient forward buffer and duration ' +
  935. 'less than low water line');
  936. });
  937. QUnit.test('blacklists playlist on earlyabort', function(assert) {
  938. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  939. // master
  940. this.standardXHRResponse(this.requests.shift());
  941. // media
  942. this.standardXHRResponse(this.requests.shift());
  943. let mediaChanges = [];
  944. const playlistLoader = this.masterPlaylistController.masterPlaylistLoader_;
  945. const currentMedia = playlistLoader.media();
  946. const origMedia = playlistLoader.media.bind(playlistLoader);
  947. const origWarn = videojs.log.warn;
  948. let warnings = [];
  949. this.masterPlaylistController.masterPlaylistLoader_.media = (media) => {
  950. if (media) {
  951. mediaChanges.push(media);
  952. }
  953. return origMedia(media);
  954. };
  955. videojs.log.warn = (text) => warnings.push(text);
  956. assert.notOk(currentMedia.excludeUntil > 0, 'playlist not blacklisted');
  957. assert.equal(mediaChanges.length, 0, 'no media change');
  958. this.masterPlaylistController.mainSegmentLoader_.trigger('earlyabort');
  959. assert.ok(currentMedia.excludeUntil > 0, 'playlist blacklisted');
  960. assert.equal(mediaChanges.length, 1, 'one media change');
  961. assert.equal(warnings.length, 1, 'one warning logged');
  962. assert.equal(warnings[0],
  963. 'Problem encountered with the current HLS playlist. ' +
  964. 'Aborted early because there isn\'t enough bandwidth to complete the ' +
  965. 'request without rebuffering. Switching to another playlist.',
  966. 'warning message is correct');
  967. videojs.log.warn = origWarn;
  968. });
  969. QUnit.test('does not get stuck in a loop due to inconsistent network/caching',
  970. function(assert) {
  971. /*
  972. * This test is a long one, but it is meant to follow a true path to a possible loop.
  973. * The reason for the loop is due to inconsistent network bandwidth, often caused or
  974. * amplified by caching at the browser or edge server level.
  975. * The steps are as follows:
  976. *
  977. * 1) Request segment 0 from low bandwidth playlist
  978. * 2) Request segment 1 from low bandwidth playlist
  979. * 3) Switch up due to good bandwidth (2 segments are required before upswitching)
  980. * 4) Request segment 0 from high bandwidth playlist
  981. * 5) Abort request early due to low bandwidth
  982. * 6) Request segment 0 from low bandwidth playlist
  983. * 7) Request segment 1 from low bandwidth playlist
  984. * 8) Request segment 2 from low bandwidth playlist, despite enough bandwidth to
  985. * upswitch. This part is the key, as the behavior we want to avoid is an upswitch
  986. * back to the high bandwidth playlist (thus starting a potentially infinite loop).
  987. */
  988. const mediaContents =
  989. '#EXTM3U\n' +
  990. '#EXTINF:10\n' +
  991. '0.ts\n' +
  992. '#EXTINF:10\n' +
  993. '1.ts\n' +
  994. '#EXTINF:10\n' +
  995. '2.ts\n' +
  996. '#EXTINF:10\n' +
  997. '3.ts\n' +
  998. '#EXT-X-ENDLIST\n';
  999. const segmentLoader = this.masterPlaylistController.mainSegmentLoader_;
  1000. // start on lowest bandwidth rendition (will be media.m3u8)
  1001. segmentLoader.bandwidth = 0;
  1002. this.player.tech_.paused = () => false;
  1003. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  1004. // master
  1005. this.requests.shift().respond(200, null,
  1006. '#EXTM3U\n' +
  1007. '#EXT-X-STREAM-INF:BANDWIDTH=10\n' +
  1008. 'media.m3u8\n' +
  1009. '#EXT-X-STREAM-INF:BANDWIDTH=100\n' +
  1010. 'media1.m3u8\n');
  1011. // media.m3u8
  1012. this.requests.shift().respond(200, null, mediaContents);
  1013. let playlistLoader = this.masterPlaylistController.masterPlaylistLoader_;
  1014. let origMedia = playlistLoader.media.bind(playlistLoader);
  1015. let mediaChanges = [];
  1016. this.masterPlaylistController.masterPlaylistLoader_.media = (media) => {
  1017. if (media) {
  1018. mediaChanges.push(media);
  1019. }
  1020. return origMedia(media);
  1021. };
  1022. this.clock.tick(1);
  1023. let segmentRequest = this.requests[0];
  1024. assert.equal(segmentRequest.uri.substring(segmentRequest.uri.length - 4),
  1025. '0.ts',
  1026. 'requested first segment');
  1027. // 100ms for the segment response
  1028. this.clock.tick(100);
  1029. // 10 bytes in 100ms = 800 bits/s
  1030. this.requests[0].response = new Uint8Array(10).buffer;
  1031. this.requests.shift().respond(200, null, '');
  1032. segmentLoader.mediaSource_.sourceBuffers[0].trigger('updateend');
  1033. this.clock.tick(1);
  1034. segmentRequest = this.requests[0];
  1035. // should be walking forwards (need two segments before we can switch)
  1036. assert.equal(segmentLoader.bandwidth, 800, 'bandwidth is correct');
  1037. assert.equal(segmentRequest.uri.substring(segmentRequest.uri.length - 4),
  1038. '1.ts',
  1039. 'requested second segment');
  1040. assert.equal(mediaChanges.length, 0, 'no media changes');
  1041. // 100ms for the segment response
  1042. this.clock.tick(100);
  1043. // 11 bytes in 100ms = 880 bits/s
  1044. this.requests[0].response = new Uint8Array(11).buffer;
  1045. this.requests.shift().respond(200, null, '');
  1046. segmentLoader.mediaSource_.sourceBuffers[0].trigger('updateend');
  1047. this.clock.tick(1);
  1048. let mediaRequest = this.requests[0];
  1049. // after two segments, bandwidth is high enough to switch up to media1.m3u8
  1050. assert.equal(segmentLoader.bandwidth, 880, 'bandwidth is correct');
  1051. assert.equal(mediaChanges.length, 1, 'changed media');
  1052. assert.equal(mediaChanges[0].uri, 'media1.m3u8', 'changed to media1');
  1053. assert.equal(mediaRequest.uri.substring(mediaRequest.uri.length - 'media1.m3u8'.length),
  1054. 'media1.m3u8',
  1055. 'requested media1');
  1056. // media1.m3u8
  1057. this.requests.shift().respond(200, null, mediaContents);
  1058. this.clock.tick(1);
  1059. segmentRequest = this.requests[0];
  1060. assert.equal(segmentLoader.playlist_.uri,
  1061. 'media1.m3u8',
  1062. 'segment loader playlist is media1');
  1063. const media1ResolvedPlaylist = segmentLoader.playlist_;
  1064. assert.notOk(media1ResolvedPlaylist.excludeUntil, 'media1 not blacklisted');
  1065. assert.equal(segmentRequest.uri.substring(segmentRequest.uri.length - 4),
  1066. '0.ts',
  1067. 'requested first segment');
  1068. // needs a timeout for early abort to occur (we skip the function otherwise, since no
  1069. // timeout means we are on the last rendition)
  1070. segmentLoader.xhrOptions_.timeout = 60000;
  1071. // we need to wait 1 second from first byte receieved in order to consider aborting
  1072. this.requests[0].downloadProgress({
  1073. target: this.requests[0],
  1074. total: 100,
  1075. loaded: 1
  1076. });
  1077. this.clock.tick(1000);
  1078. // should abort request early because we don't have enough bandwidth
  1079. this.requests[0].downloadProgress({
  1080. target: this.requests[0],
  1081. total: 100,
  1082. // 1 bit per second
  1083. loaded: 2
  1084. });
  1085. this.clock.tick(1);
  1086. // aborted request, so switched back to lowest rendition
  1087. assert.equal(segmentLoader.bandwidth,
  1088. 10 * Config.BANDWIDTH_VARIANCE + 1,
  1089. 'bandwidth is correct for abort');
  1090. assert.equal(mediaChanges.length, 2, 'changed media');
  1091. assert.equal(mediaChanges[1].uri, 'media.m3u8', 'changed to media');
  1092. assert.ok(media1ResolvedPlaylist.excludeUntil, 'blacklisted media1');
  1093. assert.equal(segmentRequest.uri.substring(segmentRequest.uri.length - 4),
  1094. '0.ts',
  1095. 'requested first segment');
  1096. // remove aborted request
  1097. this.requests.shift();
  1098. // 1ms for the cached segment response
  1099. this.clock.tick(1);
  1100. // 10 bytes in 1ms = 80 kbps
  1101. this.requests[0].response = new Uint8Array(10).buffer;
  1102. this.requests.shift().respond(200, null, '');
  1103. segmentLoader.mediaSource_.sourceBuffers[0].trigger('updateend');
  1104. this.clock.tick(1);
  1105. segmentRequest = this.requests[0];
  1106. // walking forwards, still need two segments before trying to change rendition
  1107. assert.equal(segmentLoader.bandwidth, 80000, 'bandwidth is correct');
  1108. assert.equal(mediaChanges.length, 2, 'did not change media');
  1109. assert.equal(segmentRequest.uri.substring(segmentRequest.uri.length - 4),
  1110. '1.ts',
  1111. 'requested second segment');
  1112. // 1ms for the cached segment response
  1113. this.clock.tick(1);
  1114. // 11 bytes in 1ms = 88 kbps
  1115. this.requests[0].response = new Uint8Array(11).buffer;
  1116. this.requests.shift().respond(200, null, '');
  1117. segmentLoader.mediaSource_.sourceBuffers[0].trigger('updateend');
  1118. this.clock.tick(1);
  1119. // Media may be changed, but it should be changed to the same media. In the future, this
  1120. // can safely not be changed.
  1121. assert.equal(segmentLoader.bandwidth, 88000, 'bandwidth is correct');
  1122. assert.equal(mediaChanges.length, 3, 'changed media');
  1123. assert.equal(mediaChanges[2].uri, 'media.m3u8', 'media remains unchanged');
  1124. segmentRequest = this.requests[0];
  1125. assert.equal(segmentRequest.uri.substring(segmentRequest.uri.length - 4),
  1126. '2.ts',
  1127. 'requested third segment');
  1128. assert.equal(this.env.log.warn.callCount, 1, 'logged a warning');
  1129. this.env.log.warn.callCount = 0;
  1130. });
  1131. QUnit.test('updates the duration after switching playlists', function(assert) {
  1132. let selectedPlaylist = false;
  1133. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  1134. this.masterPlaylistController.bandwidth = 1e20;
  1135. // master
  1136. this.standardXHRResponse(this.requests[0]);
  1137. // media
  1138. this.standardXHRResponse(this.requests[1]);
  1139. this.masterPlaylistController.selectPlaylist = () => {
  1140. selectedPlaylist = true;
  1141. // this duration should be overwritten by the playlist change
  1142. this.masterPlaylistController.mediaSource.duration = 0;
  1143. this.masterPlaylistController.mediaSource.readyState = 'open';
  1144. return this.masterPlaylistController.masterPlaylistLoader_.master.playlists[1];
  1145. };
  1146. // 1ms has passed to upload 1kb
  1147. // that gives us a bandwidth of 1024 / 1 * 8 * 1000 = 8192000
  1148. this.clock.tick(1);
  1149. this.masterPlaylistController.mainSegmentLoader_.mediaIndex = 0;
  1150. // segment 0
  1151. this.standardXHRResponse(this.requests[2]);
  1152. this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
  1153. // media1
  1154. this.standardXHRResponse(this.requests[3]);
  1155. assert.ok(selectedPlaylist, 'selected playlist');
  1156. assert.ok(this.masterPlaylistController.mediaSource.duration !== 0,
  1157. 'updates the duration');
  1158. // verify stats
  1159. assert.equal(this.player.tech_.hls.stats.bandwidth, 8192000, 'Live stream');
  1160. assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
  1161. assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
  1162. 1024,
  1163. '1024 bytes downloaded');
  1164. });
  1165. QUnit.test('playlist selection uses systemBandwidth', function(assert) {
  1166. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  1167. this.player.width(1000);
  1168. this.player.height(900);
  1169. // master
  1170. this.standardXHRResponse(this.requests[0]);
  1171. // media
  1172. this.standardXHRResponse(this.requests[1]);
  1173. assert.ok(/media3\.m3u8/i.test(this.requests[1].url), 'Selected the highest rendition');
  1174. // 1ms has passed to upload 1kb
  1175. // that gives us a bandwidth of 1024 / 1 * 8 * 1000 = 8192000
  1176. this.clock.tick(1);
  1177. this.masterPlaylistController.mainSegmentLoader_.mediaIndex = 0;
  1178. // segment 0
  1179. this.standardXHRResponse(this.requests[2]);
  1180. // 20ms have passed to upload 1kb
  1181. // that gives us a throughput of 1024 / 20 * 8 * 1000 = 409600
  1182. this.clock.tick(20);
  1183. this.masterPlaylistController.mediaSource.sourceBuffers[0].trigger('updateend');
  1184. // systemBandwidth is 1 / (1 / 8192000 + 1 / 409600) = ~390095
  1185. // media1
  1186. this.standardXHRResponse(this.requests[3]);
  1187. assert.ok(/media\.m3u8/i.test(this.requests[3].url), 'Selected the rendition < 390095');
  1188. assert.ok(this.masterPlaylistController.mediaSource.duration !== 0,
  1189. 'updates the duration');
  1190. // verify stats
  1191. assert.equal(this.player.tech_.hls.stats.bandwidth, 8192000, 'Live stream');
  1192. assert.equal(this.player.tech_.hls.stats.mediaRequests, 1, '1 segment request');
  1193. assert.equal(this.player.tech_.hls.stats.mediaBytesTransferred,
  1194. 1024,
  1195. '1024 bytes downloaded');
  1196. });
  1197. QUnit.test('removes request timeout when segment timesout on lowest rendition',
  1198. function(assert) {
  1199. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  1200. // master
  1201. this.standardXHRResponse(this.requests[0]);
  1202. // media
  1203. this.standardXHRResponse(this.requests[1]);
  1204. assert.equal(this.masterPlaylistController.requestOptions_.timeout,
  1205. this.masterPlaylistController.masterPlaylistLoader_.targetDuration * 1.5 *
  1206. 1000,
  1207. 'default request timeout');
  1208. assert.ok(!Playlist.isLowestEnabledRendition(
  1209. this.masterPlaylistController.masterPlaylistLoader_.master,
  1210. this.masterPlaylistController.masterPlaylistLoader_.media()),
  1211. 'not on lowest rendition');
  1212. // Cause segment to timeout to force player into lowest rendition
  1213. this.requests[2].timedout = true;
  1214. // Downloading segment should cause media change and timeout removal
  1215. // segment 0
  1216. this.standardXHRResponse(this.requests[2]);
  1217. // Download new segment after media change
  1218. this.standardXHRResponse(this.requests[3]);
  1219. assert.ok(Playlist.isLowestEnabledRendition(
  1220. this.masterPlaylistController.masterPlaylistLoader_.master,
  1221. this.masterPlaylistController.masterPlaylistLoader_.media()),
  1222. 'on lowest rendition');
  1223. assert.equal(this.masterPlaylistController.requestOptions_.timeout, 0,
  1224. 'request timeout 0');
  1225. });
  1226. QUnit.test('removes request timeout when the source is a media playlist and not master',
  1227. function(assert) {
  1228. this.requests.length = 0;
  1229. this.player.src({
  1230. src: 'manifest/media.m3u8',
  1231. type: 'application/vnd.apple.mpegurl'
  1232. });
  1233. this.clock.tick(1);
  1234. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1235. // media
  1236. this.standardXHRResponse(this.requests.shift());
  1237. assert.equal(this.masterPlaylistController.requestOptions_.timeout, 0,
  1238. 'request timeout set to 0 when loading a non master playlist');
  1239. });
  1240. QUnit.test('seekable uses the intersection of alternate audio and combined tracks',
  1241. function(assert) {
  1242. let origSeekable = Playlist.seekable;
  1243. let mpc = this.masterPlaylistController;
  1244. let mainMedia = {};
  1245. let audioMedia = {};
  1246. let mainTimeRanges = [];
  1247. let audioTimeRanges = [];
  1248. let assertTimeRangesEqual = (left, right, message) => {
  1249. if (left.length === 0 && right.length === 0) {
  1250. return;
  1251. }
  1252. assert.equal(left.length, 1, message);
  1253. assert.equal(right.length, 1, message);
  1254. assert.equal(left.start(0), right.start(0), message);
  1255. assert.equal(left.end(0), right.end(0), message);
  1256. };
  1257. this.masterPlaylistController.masterPlaylistLoader_.media = () => mainMedia;
  1258. this.masterPlaylistController.syncController_.getExpiredTime = () => 0;
  1259. Playlist.seekable = (media) => {
  1260. if (media === mainMedia) {
  1261. return videojs.createTimeRanges(mainTimeRanges);
  1262. }
  1263. return videojs.createTimeRanges(audioTimeRanges);
  1264. };
  1265. assertTimeRangesEqual(mpc.seekable(),
  1266. videojs.createTimeRanges(),
  1267. 'empty when main empty');
  1268. mainTimeRanges = [[0, 10]];
  1269. mpc.seekable_ = videojs.createTimeRanges();
  1270. mpc.onSyncInfoUpdate_();
  1271. assertTimeRangesEqual(mpc.seekable(),
  1272. videojs.createTimeRanges([[0, 10]]),
  1273. 'main when no audio');
  1274. mpc.mediaTypes_.AUDIO.activePlaylistLoader = {
  1275. media: () => audioMedia,
  1276. dispose() {},
  1277. expired_: 0
  1278. };
  1279. mainTimeRanges = [];
  1280. mpc.seekable_ = videojs.createTimeRanges();
  1281. mpc.onSyncInfoUpdate_();
  1282. assertTimeRangesEqual(mpc.seekable(),
  1283. videojs.createTimeRanges(),
  1284. 'empty when both empty');
  1285. mainTimeRanges = [[0, 10]];
  1286. mpc.seekable_ = videojs.createTimeRanges();
  1287. mpc.onSyncInfoUpdate_();
  1288. assertTimeRangesEqual(mpc.seekable(),
  1289. videojs.createTimeRanges(),
  1290. 'empty when audio empty');
  1291. mainTimeRanges = [];
  1292. audioTimeRanges = [[0, 10]];
  1293. mpc.seekable_ = videojs.createTimeRanges();
  1294. mpc.onSyncInfoUpdate_();
  1295. assertTimeRangesEqual(mpc.seekable(),
  1296. videojs.createTimeRanges(),
  1297. 'empty when main empty');
  1298. mainTimeRanges = [[0, 10]];
  1299. audioTimeRanges = [[0, 10]];
  1300. mpc.seekable_ = videojs.createTimeRanges();
  1301. mpc.onSyncInfoUpdate_();
  1302. assertTimeRangesEqual(mpc.seekable(),
  1303. videojs.createTimeRanges([[0, 10]]),
  1304. 'ranges equal');
  1305. mainTimeRanges = [[5, 10]];
  1306. mpc.seekable_ = videojs.createTimeRanges();
  1307. mpc.onSyncInfoUpdate_();
  1308. assertTimeRangesEqual(mpc.seekable(),
  1309. videojs.createTimeRanges([[5, 10]]),
  1310. 'main later start');
  1311. mainTimeRanges = [[0, 10]];
  1312. audioTimeRanges = [[5, 10]];
  1313. mpc.seekable_ = videojs.createTimeRanges();
  1314. mpc.onSyncInfoUpdate_();
  1315. assertTimeRangesEqual(mpc.seekable(),
  1316. videojs.createTimeRanges([[5, 10]]),
  1317. 'audio later start');
  1318. mainTimeRanges = [[0, 9]];
  1319. audioTimeRanges = [[0, 10]];
  1320. mpc.seekable_ = videojs.createTimeRanges();
  1321. mpc.onSyncInfoUpdate_();
  1322. assertTimeRangesEqual(mpc.seekable(),
  1323. videojs.createTimeRanges([[0, 9]]),
  1324. 'main earlier end');
  1325. mainTimeRanges = [[0, 10]];
  1326. audioTimeRanges = [[0, 9]];
  1327. mpc.seekable_ = videojs.createTimeRanges();
  1328. mpc.onSyncInfoUpdate_();
  1329. assertTimeRangesEqual(mpc.seekable(),
  1330. videojs.createTimeRanges([[0, 9]]),
  1331. 'audio earlier end');
  1332. mainTimeRanges = [[1, 10]];
  1333. audioTimeRanges = [[0, 9]];
  1334. mpc.seekable_ = videojs.createTimeRanges();
  1335. mpc.onSyncInfoUpdate_();
  1336. assertTimeRangesEqual(mpc.seekable(),
  1337. videojs.createTimeRanges([[1, 9]]),
  1338. 'main later start, audio earlier end');
  1339. mainTimeRanges = [[0, 9]];
  1340. audioTimeRanges = [[1, 10]];
  1341. mpc.seekable_ = videojs.createTimeRanges();
  1342. mpc.onSyncInfoUpdate_();
  1343. assertTimeRangesEqual(mpc.seekable(),
  1344. videojs.createTimeRanges([[1, 9]]),
  1345. 'audio later start, main earlier end');
  1346. mainTimeRanges = [[2, 9]];
  1347. mpc.seekable_ = videojs.createTimeRanges();
  1348. mpc.onSyncInfoUpdate_();
  1349. assertTimeRangesEqual(mpc.seekable(),
  1350. videojs.createTimeRanges([[2, 9]]),
  1351. 'main later start, main earlier end');
  1352. mainTimeRanges = [[1, 10]];
  1353. audioTimeRanges = [[2, 9]];
  1354. mpc.seekable_ = videojs.createTimeRanges();
  1355. mpc.onSyncInfoUpdate_();
  1356. assertTimeRangesEqual(mpc.seekable(),
  1357. videojs.createTimeRanges([[2, 9]]),
  1358. 'audio later start, audio earlier end');
  1359. mainTimeRanges = [[1, 10]];
  1360. audioTimeRanges = [[11, 20]];
  1361. mpc.seekable_ = videojs.createTimeRanges();
  1362. mpc.onSyncInfoUpdate_();
  1363. assertTimeRangesEqual(mpc.seekable(),
  1364. videojs.createTimeRanges([[1, 10]]),
  1365. 'no intersection, audio later');
  1366. mainTimeRanges = [[11, 20]];
  1367. audioTimeRanges = [[1, 10]];
  1368. mpc.seekable_ = videojs.createTimeRanges();
  1369. mpc.onSyncInfoUpdate_();
  1370. assertTimeRangesEqual(mpc.seekable(),
  1371. videojs.createTimeRanges([[11, 20]]),
  1372. 'no intersection, main later');
  1373. Playlist.seekable = origSeekable;
  1374. });
  1375. QUnit.test('syncInfoUpdate triggers seekablechanged when seekable is updated',
  1376. function(assert) {
  1377. let origSeekable = Playlist.seekable;
  1378. let mpc = this.masterPlaylistController;
  1379. let tech = this.player.tech_;
  1380. let mainTimeRanges = [];
  1381. let media = {};
  1382. let seekablechanged = 0;
  1383. tech.on('seekablechanged', () => seekablechanged++);
  1384. Playlist.seekable = () => {
  1385. return videojs.createTimeRanges(mainTimeRanges);
  1386. };
  1387. this.masterPlaylistController.masterPlaylistLoader_.media = () => media;
  1388. this.masterPlaylistController.syncController_.getExpiredTime = () => 0;
  1389. mainTimeRanges = [[0, 10]];
  1390. mpc.seekable_ = videojs.createTimeRanges();
  1391. mpc.onSyncInfoUpdate_();
  1392. assert.equal(seekablechanged, 1, 'seekablechanged triggered');
  1393. Playlist.seekable = origSeekable;
  1394. });
  1395. QUnit.test('calls to update cues on new media', function(assert) {
  1396. let origHlsOptions = videojs.options.hls;
  1397. videojs.options.hls = {
  1398. useCueTags: true
  1399. };
  1400. this.player = createPlayer();
  1401. this.player.src({
  1402. src: 'manifest/media.m3u8',
  1403. type: 'application/vnd.apple.mpegurl'
  1404. });
  1405. this.clock.tick(1);
  1406. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1407. let callCount = 0;
  1408. this.masterPlaylistController.updateAdCues_ = (media) => callCount++;
  1409. // master
  1410. this.standardXHRResponse(this.requests.shift());
  1411. assert.equal(callCount, 0, 'no call to update cues on master');
  1412. // media
  1413. this.standardXHRResponse(this.requests.shift());
  1414. assert.equal(callCount, 1, 'calls to update cues on first media');
  1415. this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist');
  1416. assert.equal(callCount, 2, 'calls to update cues on subsequent media');
  1417. videojs.options.hls = origHlsOptions;
  1418. });
  1419. QUnit.test('calls to update cues on media when no master', function(assert) {
  1420. this.requests.length = 0;
  1421. this.player.src({
  1422. src: 'manifest/media.m3u8',
  1423. type: 'application/vnd.apple.mpegurl'
  1424. });
  1425. this.clock.tick(1);
  1426. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1427. this.masterPlaylistController.useCueTags_ = true;
  1428. let callCount = 0;
  1429. this.masterPlaylistController.updateAdCues_ = (media) => callCount++;
  1430. // media
  1431. this.standardXHRResponse(this.requests.shift());
  1432. assert.equal(callCount, 1, 'calls to update cues on first media');
  1433. this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist');
  1434. assert.equal(callCount, 2, 'calls to update cues on subsequent media');
  1435. });
  1436. QUnit.test('respects useCueTags option', function(assert) {
  1437. let origHlsOptions = videojs.options.hls;
  1438. let hlsPlaylistCueTagsEvents = 0;
  1439. videojs.options.hls = {
  1440. useCueTags: true
  1441. };
  1442. this.player = createPlayer();
  1443. this.player.tech_.on('usage', (event) => {
  1444. if (event.name === 'hls-playlist-cue-tags') {
  1445. hlsPlaylistCueTagsEvents++;
  1446. }
  1447. });
  1448. this.player.src({
  1449. src: 'manifest/media.m3u8',
  1450. type: 'application/vnd.apple.mpegurl'
  1451. });
  1452. this.clock.tick(1);
  1453. this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1454. this.standardXHRResponse(this.requests.shift());
  1455. this.standardXHRResponse(this.requests.shift());
  1456. assert.equal(hlsPlaylistCueTagsEvents, 1, 'cue tags event has been triggered once');
  1457. assert.ok(this.masterPlaylistController.cueTagsTrack_,
  1458. 'creates cueTagsTrack_ if useCueTags is truthy');
  1459. assert.equal(this.masterPlaylistController.cueTagsTrack_.label,
  1460. 'ad-cues',
  1461. 'cueTagsTrack_ has label of ad-cues');
  1462. assert.equal(this.player.textTracks()[0], this.masterPlaylistController.cueTagsTrack_,
  1463. 'adds cueTagsTrack as a text track if useCueTags is truthy');
  1464. videojs.options.hls = origHlsOptions;
  1465. });
  1466. QUnit.test('correctly sets alternate audio track kinds', function(assert) {
  1467. this.requests.length = 0;
  1468. this.player = createPlayer();
  1469. this.player.src({
  1470. src: 'manifest/alternate-audio-accessibility.m3u8',
  1471. type: 'application/vnd.apple.mpegurl'
  1472. });
  1473. this.clock.tick(1);
  1474. // master
  1475. this.standardXHRResponse(this.requests.shift());
  1476. // media - required for loadedmetadata
  1477. this.standardXHRResponse(this.requests.shift());
  1478. const audioTracks = this.player.tech_.audioTracks();
  1479. assert.equal(audioTracks.length, 4, 'added 4 audio tracks');
  1480. assert.equal(audioTracks[0].id, 'English', 'contains english track');
  1481. assert.equal(audioTracks[0].kind, 'main', 'english track\'s kind is "main"');
  1482. assert.equal(audioTracks[1].id,
  1483. 'English Descriptions',
  1484. 'contains english descriptions track');
  1485. assert.equal(audioTracks[1].kind,
  1486. 'main-desc',
  1487. 'english descriptions track\'s kind is "main-desc"');
  1488. assert.equal(audioTracks[2].id, 'Français', 'contains french track');
  1489. assert.equal(audioTracks[2].kind,
  1490. 'alternative',
  1491. 'french track\'s kind is "alternative"');
  1492. assert.equal(audioTracks[3].id, 'Espanol', 'contains spanish track');
  1493. assert.equal(audioTracks[3].kind,
  1494. 'alternative',
  1495. 'spanish track\'s kind is "alternative"');
  1496. });
  1497. QUnit.test('trigger events when video and audio is demuxed by default', function(assert) {
  1498. let hlsDemuxedEvents = 0;
  1499. this.requests.length = 0;
  1500. this.player = createPlayer();
  1501. this.player.src({
  1502. src: 'manifest/multipleAudioGroups.m3u8',
  1503. type: 'application/vnd.apple.mpegurl'
  1504. });
  1505. this.player.tech_.on('usage', (event) => {
  1506. if (event.name === 'hls-demuxed') {
  1507. hlsDemuxedEvents++;
  1508. }
  1509. });
  1510. openMediaSource(this.player, this.clock);
  1511. // master
  1512. this.standardXHRResponse(this.requests.shift());
  1513. // media
  1514. this.standardXHRResponse(this.requests.shift());
  1515. assert.equal(hlsDemuxedEvents, 1, 'video and audio is demuxed by default');
  1516. });
  1517. QUnit.test('trigger events when an AES is detected', function(assert) {
  1518. let hlsAesEvents = 0;
  1519. let isAesCopy = Hls.Playlist.isAes;
  1520. Hls.Playlist.isAes = (media) => {
  1521. return true;
  1522. };
  1523. this.player.tech_.on('usage', (event) => {
  1524. if (event.name === 'hls-aes') {
  1525. hlsAesEvents++;
  1526. }
  1527. });
  1528. // master
  1529. this.standardXHRResponse(this.requests.shift());
  1530. // media
  1531. this.standardXHRResponse(this.requests.shift());
  1532. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  1533. assert.equal(hlsAesEvents, 1, 'an AES HLS stream is detected');
  1534. Hls.Playlist.isAes = isAesCopy;
  1535. });
  1536. QUnit.test('trigger events when an fMP4 stream is detected', function(assert) {
  1537. let hlsFmp4Events = 0;
  1538. let isFmp4Copy = Hls.Playlist.isFmp4;
  1539. Hls.Playlist.isFmp4 = (media) => {
  1540. return true;
  1541. };
  1542. this.player.tech_.on('usage', (event) => {
  1543. if (event.name === 'hls-fmp4') {
  1544. hlsFmp4Events++;
  1545. }
  1546. });
  1547. // master
  1548. this.standardXHRResponse(this.requests.shift());
  1549. // media
  1550. this.standardXHRResponse(this.requests.shift());
  1551. this.masterPlaylistController.mediaSource.trigger('sourceopen');
  1552. assert.equal(hlsFmp4Events, 1, 'an fMP4 stream is detected');
  1553. Hls.Playlist.isFmp4 = isFmp4Copy;
  1554. });
  1555. QUnit.test('adds only CEA608 closed-caption tracks when a master playlist is loaded',
  1556. function(assert) {
  1557. this.requests.length = 0;
  1558. this.player = createPlayer();
  1559. this.player.src({
  1560. src: 'manifest/master-captions.m3u8',
  1561. type: 'application/vnd.apple.mpegurl'
  1562. });
  1563. // wait for async player.src to complete
  1564. this.clock.tick(1);
  1565. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1566. assert.equal(this.player.textTracks().length, 1, 'one text track to start');
  1567. assert.equal(this.player.textTracks()[0].label,
  1568. 'segment-metadata',
  1569. 'only segment-metadata text track');
  1570. // master, contains media groups for captions
  1571. this.standardXHRResponse(this.requests.shift());
  1572. // we wait for loadedmetadata before setting caption tracks, so we need to wait for a
  1573. // media playlist
  1574. assert.equal(this.player.textTracks().length, 1, 'only one text track after master');
  1575. // media
  1576. this.standardXHRResponse(this.requests.shift());
  1577. const master = masterPlaylistController.masterPlaylistLoader_.master;
  1578. const caps = master.mediaGroups['CLOSED-CAPTIONS'].CCs;
  1579. const capsArr = Object.keys(caps).map(key => Object.assign({name: key}, caps[key]));
  1580. const addedCaps = masterPlaylistController.mediaTypes_['CLOSED-CAPTIONS'].groups.CCs
  1581. .map(cap => Object.assign({name: cap.id}, cap));
  1582. assert.equal(capsArr.length, 4, '4 closed-caption tracks defined in playlist');
  1583. assert.equal(addedCaps.length, 2, '2 CEA608 tracks added internally');
  1584. assert.equal(addedCaps[0].instreamId, 'CC1', 'first 608 track is CC1');
  1585. assert.equal(addedCaps[1].instreamId, 'CC3', 'second 608 track is CC3');
  1586. const textTracks = this.player.textTracks();
  1587. assert.equal(textTracks.length, 3, '2 text tracks were added');
  1588. assert.equal(textTracks[1].mode, 'disabled', 'track starts disabled');
  1589. assert.equal(textTracks[2].mode, 'disabled', 'track starts disabled');
  1590. assert.equal(textTracks[1].id, addedCaps[0].instreamId,
  1591. 'text track 1\'s id is CC\'s instreamId');
  1592. assert.equal(textTracks[2].id, addedCaps[1].instreamId,
  1593. 'text track 2\'s id is CC\'s instreamId');
  1594. assert.equal(textTracks[1].label, addedCaps[0].name,
  1595. 'text track 1\'s label is CC\'s name');
  1596. assert.equal(textTracks[2].label, addedCaps[1].name,
  1597. 'text track 2\'s label is CC\'s name');
  1598. });
  1599. QUnit.test('adds subtitle tracks when a media playlist is loaded', function(assert) {
  1600. let hlsWebvttEvents = 0;
  1601. this.requests.length = 0;
  1602. this.player = createPlayer();
  1603. this.player.src({
  1604. src: 'manifest/master-subtitles.m3u8',
  1605. type: 'application/vnd.apple.mpegurl'
  1606. });
  1607. this.clock.tick(1);
  1608. this.player.tech_.on('usage', (event) => {
  1609. if (event.name === 'hls-webvtt') {
  1610. hlsWebvttEvents++;
  1611. }
  1612. });
  1613. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1614. assert.equal(hlsWebvttEvents, 0, 'there is no webvtt detected');
  1615. assert.equal(this.player.textTracks().length, 1, 'one text track to start');
  1616. assert.equal(this.player.textTracks()[0].label,
  1617. 'segment-metadata',
  1618. 'only segment-metadata text track');
  1619. // master, contains media groups for subtitles
  1620. this.standardXHRResponse(this.requests.shift());
  1621. // we wait for loadedmetadata before setting subtitle tracks, so we need to wait for a
  1622. // media playlist
  1623. assert.equal(this.player.textTracks().length, 1, 'only one text track after master');
  1624. // media
  1625. this.standardXHRResponse(this.requests.shift());
  1626. const master = masterPlaylistController.masterPlaylistLoader_.master;
  1627. const subs = master.mediaGroups.SUBTITLES.subs;
  1628. const subsArr = Object.keys(subs).map(key => subs[key]);
  1629. assert.equal(subsArr.length, 4, 'got 4 subtitles');
  1630. assert.equal(subsArr.filter(sub => sub.forced === false).length, 2, '2 forced');
  1631. assert.equal(subsArr.filter(sub => sub.forced === true).length, 2, '2 non-forced');
  1632. const textTracks = this.player.textTracks();
  1633. assert.equal(textTracks.length, 3, 'non-forced text tracks were added');
  1634. assert.equal(textTracks[1].mode, 'disabled', 'track starts disabled');
  1635. assert.equal(textTracks[2].mode, 'disabled', 'track starts disabled');
  1636. assert.equal(hlsWebvttEvents, 1, 'there is webvtt detected in the rendition');
  1637. // change source to make sure tracks are cleaned up
  1638. this.player.src({
  1639. src: 'http://example.com/media.mp4',
  1640. type: 'video/mp4'
  1641. });
  1642. this.clock.tick(1);
  1643. assert.equal(this.player.textTracks().length, 0, 'text tracks cleaned');
  1644. });
  1645. QUnit.test('switches off subtitles on subtitle errors', function(assert) {
  1646. this.requests.length = 0;
  1647. this.player = createPlayer();
  1648. this.player.src({
  1649. src: 'manifest/master-subtitles.m3u8',
  1650. type: 'application/vnd.apple.mpegurl'
  1651. });
  1652. this.clock.tick(1);
  1653. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1654. // sets up listener for text track changes
  1655. masterPlaylistController.trigger('sourceopen');
  1656. // master, contains media groups for subtitles
  1657. this.standardXHRResponse(this.requests.shift());
  1658. // media
  1659. this.standardXHRResponse(this.requests.shift());
  1660. const textTracks = this.player.textTracks();
  1661. assert.equal(this.requests.length, 0, 'no outstanding requests');
  1662. // enable first subtitle text track
  1663. assert.notEqual(textTracks[0].kind, 'subtitles', 'kind is not subtitles');
  1664. assert.equal(textTracks[1].kind, 'subtitles', 'kind is subtitles');
  1665. textTracks[1].mode = 'showing';
  1666. assert.equal(this.requests.length, 1, 'made a request');
  1667. assert.equal(textTracks[1].mode, 'showing', 'text track still showing');
  1668. // request failed
  1669. this.requests.shift().respond(404, null, '');
  1670. assert.equal(textTracks[1].mode, 'disabled', 'disabled text track');
  1671. assert.equal(this.env.log.warn.callCount, 1, 'logged a warning');
  1672. this.env.log.warn.callCount = 0;
  1673. assert.equal(this.requests.length, 0, 'no outstanding requests');
  1674. // re-enable first text track
  1675. textTracks[1].mode = 'showing';
  1676. assert.equal(this.requests.length, 1, 'made a request');
  1677. assert.equal(textTracks[1].mode, 'showing', 'text track still showing');
  1678. this.requests.shift().respond(200, null, `
  1679. #EXTM3U
  1680. #EXT-X-TARGETDURATION:10
  1681. #EXT-X-MEDIA-SEQUENCE:0
  1682. #EXTINF:10
  1683. 0.webvtt
  1684. #EXT-X-ENDLIST
  1685. `);
  1686. const syncController = masterPlaylistController.subtitleSegmentLoader_.syncController_;
  1687. // required for the vtt request to be made
  1688. syncController.timestampOffsetForTimeline = () => 0;
  1689. this.clock.tick(1);
  1690. assert.equal(this.requests.length, 1, 'made a request');
  1691. assert.ok(this.requests[0].url.endsWith('0.webvtt'), 'made a webvtt request');
  1692. assert.equal(textTracks[1].mode, 'showing', 'text track still showing');
  1693. this.requests.shift().respond(404, null, '');
  1694. assert.equal(textTracks[1].mode, 'disabled', 'disabled text track');
  1695. assert.equal(this.env.log.warn.callCount, 1, 'logged a warning');
  1696. this.env.log.warn.callCount = 0;
  1697. });
  1698. QUnit.test('pauses subtitle segment loader on tech errors', function(assert) {
  1699. this.requests.length = 0;
  1700. this.player = createPlayer();
  1701. this.player.src({
  1702. src: 'manifest/master-subtitles.m3u8',
  1703. type: 'application/vnd.apple.mpegurl'
  1704. });
  1705. this.clock.tick(1);
  1706. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1707. // sets up listener for text track changes
  1708. masterPlaylistController.trigger('sourceopen');
  1709. // master, contains media groups for subtitles
  1710. this.standardXHRResponse(this.requests.shift());
  1711. // media
  1712. this.standardXHRResponse(this.requests.shift());
  1713. const textTracks = this.player.textTracks();
  1714. // enable first subtitle text track
  1715. assert.notEqual(textTracks[0].kind, 'subtitles', 'kind is not subtitles');
  1716. assert.equal(textTracks[1].kind, 'subtitles', 'kind is subtitles');
  1717. textTracks[1].mode = 'showing';
  1718. let pauseCount = 0;
  1719. masterPlaylistController.subtitleSegmentLoader_.pause = () => pauseCount++;
  1720. this.player.tech_.trigger('error');
  1721. assert.equal(pauseCount, 1, 'paused subtitle segment loader');
  1722. });
  1723. QUnit.test('disposes subtitle loaders on dispose', function(assert) {
  1724. this.requests.length = 0;
  1725. this.player = createPlayer();
  1726. this.player.src({
  1727. src: 'manifest/master-subtitles.m3u8',
  1728. type: 'application/vnd.apple.mpegurl'
  1729. });
  1730. this.clock.tick(1);
  1731. let masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1732. assert.notOk(masterPlaylistController.mediaTypes_.SUBTITLES.activePlaylistLoader,
  1733. 'does not start with a subtitle playlist loader');
  1734. assert.ok(masterPlaylistController.subtitleSegmentLoader_,
  1735. 'starts with a subtitle segment loader');
  1736. let segmentLoaderDisposeCount = 0;
  1737. masterPlaylistController.subtitleSegmentLoader_.dispose =
  1738. () => segmentLoaderDisposeCount++;
  1739. masterPlaylistController.dispose();
  1740. assert.equal(segmentLoaderDisposeCount, 1, 'disposed the subtitle segment loader');
  1741. this.requests.length = 0;
  1742. this.player = createPlayer();
  1743. this.player.src({
  1744. src: 'manifest/master-subtitles.m3u8',
  1745. type: 'application/vnd.apple.mpegurl'
  1746. });
  1747. this.clock.tick(1);
  1748. masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1749. // sets up listener for text track changes
  1750. masterPlaylistController.trigger('sourceopen');
  1751. // master, contains media groups for subtitles
  1752. this.standardXHRResponse(this.requests.shift());
  1753. // media
  1754. this.standardXHRResponse(this.requests.shift());
  1755. const textTracks = this.player.textTracks();
  1756. // enable first subtitle text track
  1757. assert.notEqual(textTracks[0].kind, 'subtitles', 'kind is not subtitles');
  1758. assert.equal(textTracks[1].kind, 'subtitles', 'kind is subtitles');
  1759. textTracks[1].mode = 'showing';
  1760. assert.ok(masterPlaylistController.mediaTypes_.SUBTITLES.activePlaylistLoader,
  1761. 'has a subtitle playlist loader');
  1762. assert.ok(masterPlaylistController.subtitleSegmentLoader_,
  1763. 'has a subtitle segment loader');
  1764. let playlistLoaderDisposeCount = 0;
  1765. segmentLoaderDisposeCount = 0;
  1766. masterPlaylistController.mediaTypes_.SUBTITLES.activePlaylistLoader.dispose =
  1767. () => playlistLoaderDisposeCount++;
  1768. masterPlaylistController.subtitleSegmentLoader_.dispose =
  1769. () => segmentLoaderDisposeCount++;
  1770. masterPlaylistController.dispose();
  1771. assert.equal(playlistLoaderDisposeCount, 1, 'disposed the subtitle playlist loader');
  1772. assert.equal(segmentLoaderDisposeCount, 1, 'disposed the subtitle segment loader');
  1773. });
  1774. QUnit.test('subtitle segment loader resets on seeks', function(assert) {
  1775. this.requests.length = 0;
  1776. this.player = createPlayer();
  1777. this.player.src({
  1778. src: 'manifest/master-subtitles.m3u8',
  1779. type: 'application/vnd.apple.mpegurl'
  1780. });
  1781. this.clock.tick(1);
  1782. const masterPlaylistController = this.player.tech_.hls.masterPlaylistController_;
  1783. // sets up listener for text track changes
  1784. masterPlaylistController.trigger('sourceopen');
  1785. // master, contains media groups for subtitles
  1786. this.standardXHRResponse(this.requests.shift());
  1787. // media
  1788. this.standardXHRResponse(this.requests.shift());
  1789. const textTracks = this.player.textTracks();
  1790. // enable first subtitle text track
  1791. assert.notEqual(textTracks[0].kind, 'subtitles', 'kind is not subtitles');
  1792. assert.equal(textTracks[1].kind, 'subtitles', 'kind is subtitles');
  1793. textTracks[1].mode = 'showing';
  1794. let resetCount = 0;
  1795. let abortCount = 0;
  1796. let loadCount = 0;
  1797. masterPlaylistController.subtitleSegmentLoader_.resetEverything = () => resetCount++;
  1798. masterPlaylistController.subtitleSegmentLoader_.abort = () => abortCount++;
  1799. masterPlaylistController.subtitleSegmentLoader_.load = () => loadCount++;
  1800. this.player.pause();
  1801. masterPlaylistController.setCurrentTime(5);
  1802. assert.equal(resetCount, 1, 'reset subtitle segment loader');
  1803. assert.equal(abortCount, 1, 'aborted subtitle segment loader');
  1804. assert.equal(loadCount, 1, 'called load on subtitle segment loader');
  1805. this.player.play();
  1806. resetCount = 0;
  1807. abortCount = 0;
  1808. loadCount = 0;
  1809. masterPlaylistController.setCurrentTime(10);
  1810. assert.equal(resetCount, 1, 'reset subtitle segment loader');
  1811. assert.equal(abortCount, 1, 'aborted subtitle segment loader');
  1812. assert.equal(loadCount, 1, 'called load on subtitle segment loader');
  1813. });
  1814. QUnit.test('calculates dynamic GOAL_BUFFER_LENGTH', function(assert) {
  1815. const configOld = {
  1816. GOAL_BUFFER_LENGTH: Config.GOAL_BUFFER_LENGTH,
  1817. MAX_GOAL_BUFFER_LENGTH: Config.MAX_GOAL_BUFFER_LENGTH,
  1818. GOAL_BUFFER_LENGTH_RATE: Config.GOAL_BUFFER_LENGTH_RATE
  1819. };
  1820. const mpc = this.masterPlaylistController;
  1821. let currentTime = 0;
  1822. Config.GOAL_BUFFER_LENGTH = 30;
  1823. Config.MAX_GOAL_BUFFER_LENGTH = 60;
  1824. Config.GOAL_BUFFER_LENGTH_RATE = 0.5;
  1825. mpc.tech_.currentTime = () => currentTime;
  1826. assert.equal(mpc.goalBufferLength(), 30, 'dynamic GBL uses starting value at time 0');
  1827. currentTime = 10;
  1828. assert.equal(mpc.goalBufferLength(), 35, 'dynamic GBL increases by currentTime * rate');
  1829. currentTime = 60;
  1830. assert.equal(mpc.goalBufferLength(), 60, 'dynamic GBL uses max value');
  1831. currentTime = 70;
  1832. assert.equal(mpc.goalBufferLength(), 60, 'dynamic GBL continues to use max value');
  1833. // restore config
  1834. Object.keys(configOld).forEach((key) => Config[key] = configOld[key]);
  1835. });
  1836. QUnit.test('calculates dynamic BUFFER_LOW_WATER_LINE', function(assert) {
  1837. const configOld = {
  1838. BUFFER_LOW_WATER_LINE: Config.BUFFER_LOW_WATER_LINE,
  1839. MAX_BUFFER_LOW_WATER_LINE: Config.MAX_BUFFER_LOW_WATER_LINE,
  1840. BUFFER_LOW_WATER_LINE_RATE: Config.BUFFER_LOW_WATER_LINE_RATE
  1841. };
  1842. const mpc = this.masterPlaylistController;
  1843. let currentTime = 0;
  1844. Config.BUFFER_LOW_WATER_LINE = 0;
  1845. Config.MAX_BUFFER_LOW_WATER_LINE = 30;
  1846. Config.BUFFER_LOW_WATER_LINE_RATE = 0.5;
  1847. mpc.tech_.currentTime = () => currentTime;
  1848. assert.equal(mpc.bufferLowWaterLine(), 0, 'dynamic BLWL uses starting value at time 0');
  1849. currentTime = 10;
  1850. assert.equal(mpc.bufferLowWaterLine(), 5,
  1851. 'dynamic BLWL increases by currentTime * rate');
  1852. currentTime = 60;
  1853. assert.equal(mpc.bufferLowWaterLine(), 30, 'dynamic BLWL uses max value');
  1854. currentTime = 70;
  1855. assert.equal(mpc.bufferLowWaterLine(), 30, 'dynamic BLWL continues to use max value');
  1856. // restore config
  1857. Object.keys(configOld).forEach((key) => Config[key] = configOld[key]);
  1858. });
  1859. QUnit.test('Exception in play promise should be caught', function(assert) {
  1860. const mpc = this.masterPlaylistController;
  1861. mpc.setupSourceBuffers = () => true;
  1862. mpc.tech_ = {
  1863. autoplay: () => true,
  1864. play: () => new Promise(function(resolve, reject) {
  1865. reject(new DOMException());
  1866. })
  1867. };
  1868. mpc.handleSourceOpen_();
  1869. assert.ok(true, 'rejects dom exception');
  1870. });
  1871. QUnit.module('Codec to MIME Type Conversion');
  1872. const testMimeTypes = function(assert, isFMP4) {
  1873. let container = isFMP4 ? 'mp4' : 'mp2t';
  1874. let videoMime = `video/${container}`;
  1875. let audioMime = `audio/${container}`;
  1876. // no MAAT
  1877. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1878. generateMedia(false, true, false, false, isFMP4)),
  1879. [`${videoMime}; codecs="avc1.4d400d, mp4a.40.2"`],
  1880. `no MAAT, container: ${container}, codecs: none`);
  1881. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1882. generateMedia(false, true, true, false, isFMP4)),
  1883. [`${videoMime}; codecs="avc1.deadbeef"`],
  1884. `no MAAT, container: ${container}, codecs: video`);
  1885. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1886. generateMedia(false, true, false, true, isFMP4)),
  1887. [`${audioMime}; codecs="mp4a.40.E"`],
  1888. `no MAAT, container: ${container}, codecs: audio`);
  1889. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1890. generateMedia(false, true, true, true, isFMP4)),
  1891. [`${videoMime}; codecs="avc1.deadbeef, mp4a.40.E"`],
  1892. `no MAAT, container: ${container}, codecs: video, audio`);
  1893. // MAAT, not muxed
  1894. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1895. generateMedia(true, false, false, false, isFMP4)),
  1896. [`${videoMime}; codecs="avc1.4d400d"`,
  1897. `${audioMime}; codecs="mp4a.40.2"`],
  1898. `MAAT, demuxed, container: ${container}, codecs: none`);
  1899. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1900. generateMedia(true, false, true, false, isFMP4)),
  1901. [`${videoMime}; codecs="avc1.deadbeef"`,
  1902. `${audioMime}; codecs="mp4a.40.2"`],
  1903. `MAAT, demuxed, container: ${container}, codecs: video`);
  1904. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1905. generateMedia(true, false, false, true, isFMP4)),
  1906. [`${videoMime}; codecs="mp4a.40.E"`,
  1907. `${audioMime}; codecs="mp4a.40.E"`],
  1908. `MAAT, demuxed, container: ${container}, codecs: audio`);
  1909. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1910. generateMedia(true, false, true, true, isFMP4)),
  1911. [`${videoMime}; codecs="avc1.deadbeef"`,
  1912. `${audioMime}; codecs="mp4a.40.E"`],
  1913. `MAAT, demuxed, container: ${container}, codecs: video, audio`);
  1914. // MAAT, muxed
  1915. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1916. generateMedia(true, true, false, false, isFMP4)),
  1917. [`${videoMime}; codecs="avc1.4d400d, mp4a.40.2"`,
  1918. `${audioMime}; codecs="mp4a.40.2"`],
  1919. `MAAT, muxed, container: ${container}, codecs: none`);
  1920. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1921. generateMedia(true, true, true, false, isFMP4)),
  1922. [`${videoMime}; codecs="avc1.deadbeef, mp4a.40.2"`,
  1923. `${audioMime}; codecs="mp4a.40.2"`],
  1924. `MAAT, muxed, container: ${container}, codecs: video`);
  1925. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1926. generateMedia(true, true, false, true, isFMP4)),
  1927. [`${videoMime}; codecs="mp4a.40.E"`,
  1928. `${audioMime}; codecs="mp4a.40.E"`],
  1929. `MAAT, muxed, container: ${container}, codecs: audio`);
  1930. assert.deepEqual(mimeTypesForPlaylist_.apply(null,
  1931. generateMedia(true, true, true, true, isFMP4)),
  1932. [`${videoMime}; codecs="avc1.deadbeef, mp4a.40.E"`,
  1933. `${audioMime}; codecs="mp4a.40.E"`],
  1934. `MAAT, muxed, container: ${container}, codecs: video, audio`);
  1935. };
  1936. QUnit.test('recognizes muxed codec configurations', function(assert) {
  1937. testMimeTypes(assert, false);
  1938. testMimeTypes(assert, true);
  1939. });
  1940. QUnit.module('Map Legacy AVC Codec');
  1941. QUnit.test('maps legacy AVC codecs', function(assert) {
  1942. assert.equal(mapLegacyAvcCodecs_('avc1.deadbeef'),
  1943. 'avc1.deadbeef',
  1944. 'does nothing for non legacy pattern');
  1945. assert.equal(mapLegacyAvcCodecs_('avc1.dead.beef, mp4a.something'),
  1946. 'avc1.dead.beef, mp4a.something',
  1947. 'does nothing for non legacy pattern');
  1948. assert.equal(mapLegacyAvcCodecs_('avc1.dead.beef,mp4a.something'),
  1949. 'avc1.dead.beef,mp4a.something',
  1950. 'does nothing for non legacy pattern');
  1951. assert.equal(mapLegacyAvcCodecs_('mp4a.something,avc1.dead.beef'),
  1952. 'mp4a.something,avc1.dead.beef',
  1953. 'does nothing for non legacy pattern');
  1954. assert.equal(mapLegacyAvcCodecs_('mp4a.something, avc1.dead.beef'),
  1955. 'mp4a.something, avc1.dead.beef',
  1956. 'does nothing for non legacy pattern');
  1957. assert.equal(mapLegacyAvcCodecs_('avc1.42001e'),
  1958. 'avc1.42001e',
  1959. 'does nothing for non legacy pattern');
  1960. assert.equal(mapLegacyAvcCodecs_('avc1.4d0020,mp4a.40.2'),
  1961. 'avc1.4d0020,mp4a.40.2',
  1962. 'does nothing for non legacy pattern');
  1963. assert.equal(mapLegacyAvcCodecs_('mp4a.40.2,avc1.4d0020'),
  1964. 'mp4a.40.2,avc1.4d0020',
  1965. 'does nothing for non legacy pattern');
  1966. assert.equal(mapLegacyAvcCodecs_('mp4a.40.40'),
  1967. 'mp4a.40.40',
  1968. 'does nothing for non video codecs');
  1969. assert.equal(mapLegacyAvcCodecs_('avc1.66.30'),
  1970. 'avc1.42001e',
  1971. 'translates legacy video codec alone');
  1972. assert.equal(mapLegacyAvcCodecs_('avc1.66.30, mp4a.40.2'),
  1973. 'avc1.42001e, mp4a.40.2',
  1974. 'translates legacy video codec when paired with audio');
  1975. assert.equal(mapLegacyAvcCodecs_('mp4a.40.2, avc1.66.30'),
  1976. 'mp4a.40.2, avc1.42001e',
  1977. 'translates video codec when specified second');
  1978. });