html.test.js 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959
  1. import document from 'global/document';
  2. import window from 'global/window';
  3. import QUnit from 'qunit';
  4. import sinon from 'sinon';
  5. import videojs from 'video.js';
  6. import HtmlMediaSource from '../src/html-media-source';
  7. import {
  8. gopsSafeToAlignWith,
  9. updateGopBuffer,
  10. removeGopBuffer
  11. } from '../src/virtual-source-buffer';
  12. // we disable this because browserify needs to include these files
  13. // but the exports are not important
  14. /* eslint-disable no-unused-vars */
  15. import {MediaSource, URL} from '../src/videojs-contrib-media-sources.js';
  16. /* eslint-disable no-unused-vars */
  17. QUnit.module('videojs-contrib-media-sources - HTML', {
  18. beforeEach() {
  19. this.fixture = document.getElementById('qunit-fixture');
  20. this.video = document.createElement('video');
  21. this.fixture.appendChild(this.video);
  22. this.source = document.createElement('source');
  23. this.player = videojs(this.video);
  24. // add a fake source so that we can get this.player_ on sourceopen
  25. this.url = 'fake.ts';
  26. this.source.src = this.url;
  27. this.video.appendChild(this.source);
  28. // Mock the environment's timers because certain things - particularly
  29. // player readiness - are asynchronous in video.js 5.
  30. this.clock = sinon.useFakeTimers();
  31. this.oldMediaSource = window.MediaSource || window.WebKitMediaSource;
  32. window.MediaSource = videojs.extend(videojs.EventTarget, {
  33. constructor() {
  34. this.isNative = true;
  35. this.sourceBuffers = [];
  36. this.duration = NaN;
  37. },
  38. addSourceBuffer(type) {
  39. let buffer = new (videojs.extend(videojs.EventTarget, {
  40. type,
  41. appendBuffer() {}
  42. }))();
  43. this.sourceBuffers.push(buffer);
  44. return buffer;
  45. }
  46. });
  47. window.MediaSource.isTypeSupported = function(mime) {
  48. return true;
  49. };
  50. window.WebKitMediaSource = window.MediaSource;
  51. },
  52. afterEach() {
  53. this.clock.restore();
  54. this.player.dispose();
  55. window.MediaSource = this.oldMediaSource;
  56. window.WebKitMediaSource = window.MediaSource;
  57. }
  58. });
  59. QUnit.test('constructs a native MediaSource', function() {
  60. QUnit.ok(
  61. new videojs.MediaSource().nativeMediaSource_.isNative,
  62. 'constructed a MediaSource'
  63. );
  64. });
  65. const createDataMessage = function(type, typedArray, extraObject) {
  66. let message = {
  67. data: {
  68. action: 'data',
  69. segment: {
  70. type,
  71. data: typedArray.buffer,
  72. initSegment: {
  73. data: typedArray.buffer,
  74. byteOffset: typedArray.byteOffset,
  75. byteLength: typedArray.byteLength
  76. }
  77. },
  78. byteOffset: typedArray.byteOffset,
  79. byteLength: typedArray.byteLength
  80. }
  81. };
  82. return Object.keys(extraObject || {}).reduce(function(obj, key) {
  83. obj.data.segment[key] = extraObject[key];
  84. return obj;
  85. }, message);
  86. };
  87. // Create a WebWorker-style message that signals the transmuxer is done
  88. const doneMessage = {
  89. data: {
  90. action: 'done'
  91. }
  92. };
  93. // send fake data to the transmuxer to trigger the creation of the
  94. // native source buffers
  95. const initializeNativeSourceBuffers = function(sourceBuffer) {
  96. // initialize an audio source buffer
  97. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', new Uint8Array(1)));
  98. // initialize a video source buffer
  99. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', new Uint8Array(1)));
  100. // instruct the transmuxer to flush the "data" it has buffered so
  101. // far
  102. sourceBuffer.transmuxer_.onmessage(doneMessage);
  103. };
  104. QUnit.test('creates mp4 source buffers for mp2t segments', function() {
  105. let mediaSource = new videojs.MediaSource();
  106. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  107. initializeNativeSourceBuffers(sourceBuffer);
  108. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  109. QUnit.equal(
  110. mediaSource.videoBuffer_.type,
  111. 'video/mp4;codecs="avc1.4d400d"',
  112. 'video buffer has the default codec'
  113. );
  114. QUnit.ok(mediaSource.audioBuffer_, 'created an audio buffer');
  115. QUnit.equal(
  116. mediaSource.audioBuffer_.type,
  117. 'audio/mp4;codecs="mp4a.40.2"',
  118. 'audio buffer has the default codec'
  119. );
  120. QUnit.equal(mediaSource.sourceBuffers.length, 1, 'created one virtual buffer');
  121. QUnit.equal(
  122. mediaSource.sourceBuffers[0],
  123. sourceBuffer,
  124. 'returned the virtual buffer'
  125. );
  126. QUnit.ok(sourceBuffer.transmuxer_, 'created a transmuxer');
  127. });
  128. QUnit.test(
  129. 'the terminate is called on the transmuxer when the media source is killed',
  130. function() {
  131. let mediaSource = new videojs.MediaSource();
  132. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  133. let terminates = 0;
  134. sourceBuffer.transmuxer_ = {
  135. terminate() {
  136. terminates++;
  137. }
  138. };
  139. mediaSource.trigger('sourceclose');
  140. QUnit.equal(terminates, 1, 'called terminate on transmux web worker');
  141. });
  142. QUnit.test('duration is faked when playing a live stream', function() {
  143. let mediaSource = new videojs.MediaSource();
  144. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  145. mediaSource.duration = Infinity;
  146. mediaSource.nativeMediaSource_.duration = 100;
  147. QUnit.equal(mediaSource.nativeMediaSource_.duration, 100,
  148. 'native duration was not set to infinity');
  149. QUnit.equal(mediaSource.duration, Infinity,
  150. 'the MediaSource wrapper pretends it has an infinite duration');
  151. });
  152. QUnit.test(
  153. 'duration uses the underlying MediaSource\'s duration when not live', function() {
  154. let mediaSource = new videojs.MediaSource();
  155. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  156. mediaSource.duration = 100;
  157. mediaSource.nativeMediaSource_.duration = 120;
  158. QUnit.equal(mediaSource.duration, 120,
  159. 'the MediaSource wrapper returns the native duration');
  160. });
  161. QUnit.test('abort on the fake source buffer calls abort on the real ones', function() {
  162. let mediaSource = new videojs.MediaSource();
  163. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  164. let messages = [];
  165. let aborts = 0;
  166. initializeNativeSourceBuffers(sourceBuffer);
  167. sourceBuffer.transmuxer_.postMessage = function(message) {
  168. messages.push(message);
  169. };
  170. sourceBuffer.bufferUpdating_ = true;
  171. sourceBuffer.videoBuffer_.abort = function() {
  172. aborts++;
  173. };
  174. sourceBuffer.audioBuffer_.abort = function() {
  175. aborts++;
  176. };
  177. sourceBuffer.abort();
  178. QUnit.equal(aborts, 2, 'called abort on both');
  179. QUnit.equal(
  180. sourceBuffer.bufferUpdating_,
  181. false,
  182. 'set updating to false'
  183. );
  184. QUnit.equal(messages.length, 1, 'has one message');
  185. QUnit.equal(messages[0].action, 'reset', 'reset called on transmuxer');
  186. });
  187. QUnit.test(
  188. 'calling remove deletes cues and invokes remove on any extant source buffers',
  189. function() {
  190. let mediaSource = new videojs.MediaSource();
  191. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  192. let removedCue = [];
  193. let removes = 0;
  194. initializeNativeSourceBuffers(sourceBuffer);
  195. sourceBuffer.inbandTextTracks_ = {
  196. CC1: {
  197. removeCue(cue) {
  198. removedCue.push(cue);
  199. this.cues.splice(this.cues.indexOf(cue), 1);
  200. },
  201. cues: [
  202. {startTime: 10, endTime: 20, text: 'delete me'},
  203. {startTime: 0, endTime: 2, text: 'save me'}
  204. ]
  205. }
  206. };
  207. mediaSource.videoBuffer_.remove = function(start, end) {
  208. if (start === 3 && end === 10) {
  209. removes++;
  210. }
  211. };
  212. mediaSource.audioBuffer_.remove = function(start, end) {
  213. if (start === 3 && end === 10) {
  214. removes++;
  215. }
  216. };
  217. sourceBuffer.remove(3, 10);
  218. QUnit.equal(removes, 2, 'called remove on both sourceBuffers');
  219. QUnit.equal(
  220. sourceBuffer.inbandTextTracks_.CC1.cues.length,
  221. 1,
  222. 'one cue remains after remove'
  223. );
  224. QUnit.equal(
  225. removedCue[0].text,
  226. 'delete me',
  227. 'the cue that overlapped the remove region was removed'
  228. );
  229. });
  230. QUnit.test(
  231. 'calling remove property handles absence of cues (null)',
  232. function() {
  233. let mediaSource = new videojs.MediaSource();
  234. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  235. initializeNativeSourceBuffers(sourceBuffer);
  236. sourceBuffer.inbandTextTracks_ = {
  237. CC1: {
  238. cues: null
  239. }
  240. };
  241. mediaSource.videoBuffer_.remove = function(start, end) {
  242. // pass
  243. };
  244. mediaSource.audioBuffer_.remove = function(start, end) {
  245. // pass
  246. };
  247. // this call should not raise an exception
  248. sourceBuffer.remove(3, 10);
  249. QUnit.equal(
  250. sourceBuffer.inbandTextTracks_.CC1.cues,
  251. null,
  252. 'cues are still null'
  253. );
  254. });
  255. QUnit.test('removing doesn\'t happen with audio disabled', function() {
  256. let mediaSource = new videojs.MediaSource();
  257. let muxedBuffer = mediaSource.addSourceBuffer('video/mp2t');
  258. // creating this audio buffer disables audio in the muxed one
  259. let audioBuffer = mediaSource.addSourceBuffer('audio/mp2t; codecs="mp4a.40.2"');
  260. let removedCue = [];
  261. let removes = 0;
  262. initializeNativeSourceBuffers(muxedBuffer);
  263. muxedBuffer.inbandTextTracks_ = {
  264. CC1: {
  265. removeCue(cue) {
  266. removedCue.push(cue);
  267. this.cues.splice(this.cues.indexOf(cue), 1);
  268. },
  269. cues: [
  270. {startTime: 10, endTime: 20, text: 'delete me'},
  271. {startTime: 0, endTime: 2, text: 'save me'}
  272. ]
  273. }
  274. };
  275. mediaSource.videoBuffer_.remove = function(start, end) {
  276. if (start === 3 && end === 10) {
  277. removes++;
  278. }
  279. };
  280. mediaSource.audioBuffer_.remove = function(start, end) {
  281. if (start === 3 && end === 10) {
  282. removes++;
  283. }
  284. };
  285. muxedBuffer.remove(3, 10);
  286. QUnit.equal(removes, 1, 'called remove on only one source buffer');
  287. QUnit.equal(muxedBuffer.inbandTextTracks_.CC1.cues.length,
  288. 1,
  289. 'one cue remains after remove');
  290. QUnit.equal(removedCue[0].text,
  291. 'delete me',
  292. 'the cue that overlapped the remove region was removed');
  293. });
  294. QUnit.test('readyState delegates to the native implementation', function() {
  295. let mediaSource = new HtmlMediaSource();
  296. QUnit.equal(
  297. mediaSource.readyState,
  298. mediaSource.nativeMediaSource_.readyState,
  299. 'readyStates are equal'
  300. );
  301. mediaSource.nativeMediaSource_.readyState = 'nonsense stuff';
  302. QUnit.equal(
  303. mediaSource.readyState,
  304. mediaSource.nativeMediaSource_.readyState,
  305. 'readyStates are equal'
  306. );
  307. });
  308. QUnit.test('addSeekableRange_ throws an error for media with known duration', function() {
  309. let mediaSource = new videojs.MediaSource();
  310. mediaSource.duration = 100;
  311. QUnit.throws(function() {
  312. mediaSource.addSeekableRange_(0, 100);
  313. }, 'cannot add seekable range');
  314. });
  315. QUnit.test('addSeekableRange_ adds to the native MediaSource duration', function() {
  316. let mediaSource = new videojs.MediaSource();
  317. mediaSource.duration = Infinity;
  318. mediaSource.addSeekableRange_(120, 240);
  319. QUnit.equal(mediaSource.nativeMediaSource_.duration, 240, 'set native duration');
  320. QUnit.equal(mediaSource.duration, Infinity, 'emulated duration');
  321. mediaSource.addSeekableRange_(120, 220);
  322. QUnit.equal(mediaSource.nativeMediaSource_.duration, 240, 'ignored the smaller range');
  323. QUnit.equal(mediaSource.duration, Infinity, 'emulated duration');
  324. });
  325. QUnit.test('appendBuffer error triggers on the player', function() {
  326. let mediaSource = new videojs.MediaSource();
  327. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  328. let error = false;
  329. mediaSource.player_ = this.player;
  330. initializeNativeSourceBuffers(sourceBuffer);
  331. sourceBuffer.videoBuffer_.appendBuffer = () => {
  332. throw new Error();
  333. };
  334. this.player.on('error', () => error = true);
  335. // send fake data to the source buffer from the transmuxer to append to native buffer
  336. // initializeNativeSourceBuffers does the same thing to trigger the creation of
  337. // native source buffers.
  338. let fakeTransmuxerMessage = initializeNativeSourceBuffers;
  339. fakeTransmuxerMessage(sourceBuffer);
  340. this.clock.tick(1);
  341. QUnit.ok(error, 'error triggered on player');
  342. });
  343. QUnit.test('transmuxes mp2t segments', function() {
  344. let mp2tSegments = [];
  345. let mp4Segments = [];
  346. let data = new Uint8Array(1);
  347. let mediaSource;
  348. let sourceBuffer;
  349. mediaSource = new videojs.MediaSource();
  350. sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  351. sourceBuffer.transmuxer_.postMessage = function(segment) {
  352. if (segment.action === 'push') {
  353. let buffer = new Uint8Array(segment.data, segment.byteOffset, segment.byteLength);
  354. mp2tSegments.push(buffer);
  355. }
  356. };
  357. sourceBuffer.concatAndAppendSegments_ = function(segmentObj, destinationBuffer) {
  358. mp4Segments.push(segmentObj);
  359. };
  360. sourceBuffer.appendBuffer(data);
  361. QUnit.equal(mp2tSegments.length, 1, 'transmuxed one segment');
  362. QUnit.equal(mp2tSegments[0].length, 1, 'did not alter the segment');
  363. QUnit.equal(mp2tSegments[0][0], data[0], 'did not alter the segment');
  364. // an init segment
  365. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', new Uint8Array(1)));
  366. // a media segment
  367. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', new Uint8Array(1)));
  368. // Segments are concatenated
  369. QUnit.equal(
  370. mp4Segments.length,
  371. 0,
  372. 'segments are not appended until after the `done` message'
  373. );
  374. // send `done` message
  375. sourceBuffer.transmuxer_.onmessage(doneMessage);
  376. // Segments are concatenated
  377. QUnit.equal(mp4Segments.length, 2, 'appended the segments');
  378. });
  379. QUnit.test(
  380. 'handles typed-arrays that are subsets of their underlying buffer',
  381. function() {
  382. let mp2tSegments = [];
  383. let mp4Segments = [];
  384. let dataBuffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
  385. let data = dataBuffer.subarray(5, 7);
  386. let mediaSource;
  387. let sourceBuffer;
  388. mediaSource = new videojs.MediaSource();
  389. sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  390. sourceBuffer.transmuxer_.postMessage = function(segment) {
  391. if (segment.action === 'push') {
  392. let buffer = new Uint8Array(segment.data, segment.byteOffset, segment.byteLength);
  393. mp2tSegments.push(buffer);
  394. }
  395. };
  396. sourceBuffer.concatAndAppendSegments_ = function(segmentObj, destinationBuffer) {
  397. mp4Segments.push(segmentObj.segments[0]);
  398. };
  399. sourceBuffer.appendBuffer(data);
  400. QUnit.equal(mp2tSegments.length, 1, 'emitted the fragment');
  401. QUnit.equal(
  402. mp2tSegments[0].length,
  403. 2,
  404. 'correctly handled a typed-array that is a subset'
  405. );
  406. QUnit.equal(mp2tSegments[0][0], 5, 'fragment contains the correct first byte');
  407. QUnit.equal(mp2tSegments[0][1], 6, 'fragment contains the correct second byte');
  408. // an init segment
  409. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', data));
  410. // Segments are concatenated
  411. QUnit.equal(
  412. mp4Segments.length,
  413. 0,
  414. 'segments are not appended until after the `done` message'
  415. );
  416. // send `done` message
  417. sourceBuffer.transmuxer_.onmessage(doneMessage);
  418. // Segments are concatenated
  419. QUnit.equal(mp4Segments.length, 1, 'emitted the fragment');
  420. QUnit.equal(
  421. mp4Segments[0].length,
  422. 2,
  423. 'correctly handled a typed-array that is a subset'
  424. );
  425. QUnit.equal(mp4Segments[0][0], 5, 'fragment contains the correct first byte');
  426. QUnit.equal(mp4Segments[0][1], 6, 'fragment contains the correct second byte');
  427. });
  428. QUnit.test(
  429. 'only appends audio init segment for first segment or on audio/media changes',
  430. function() {
  431. let mp4Segments = [];
  432. let initBuffer = new Uint8Array([0, 1]);
  433. let dataBuffer = new Uint8Array([2, 3]);
  434. let mediaSource;
  435. let sourceBuffer;
  436. mediaSource = new videojs.MediaSource();
  437. sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  438. sourceBuffer.audioDisabled_ = false;
  439. mediaSource.player_ = this.player;
  440. mediaSource.url_ = this.url;
  441. mediaSource.trigger('sourceopen');
  442. sourceBuffer.concatAndAppendSegments_ = function(segmentObj, destinationBuffer) {
  443. let segment = segmentObj.segments.reduce((seg, arr) => seg.concat(Array.from(arr)),
  444. []);
  445. mp4Segments.push(segment);
  446. };
  447. QUnit.ok(sourceBuffer.appendAudioInitSegment_, 'will append init segment next');
  448. // an init segment
  449. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', dataBuffer, {
  450. initSegment: {
  451. data: initBuffer.buffer,
  452. byteOffset: initBuffer.byteOffset,
  453. byteLength: initBuffer.byteLength
  454. }
  455. }));
  456. // Segments are concatenated
  457. QUnit.equal(
  458. mp4Segments.length,
  459. 0,
  460. 'segments are not appended until after the `done` message'
  461. );
  462. // send `done` message
  463. sourceBuffer.transmuxer_.onmessage(doneMessage);
  464. // Segments are concatenated
  465. QUnit.equal(mp4Segments.length, 1, 'emitted the fragment');
  466. // Contains init segment on first segment
  467. QUnit.equal(mp4Segments[0][0], 0, 'fragment contains the correct first byte');
  468. QUnit.equal(mp4Segments[0][1], 1, 'fragment contains the correct second byte');
  469. QUnit.equal(mp4Segments[0][2], 2, 'fragment contains the correct third byte');
  470. QUnit.equal(mp4Segments[0][3], 3, 'fragment contains the correct fourth byte');
  471. QUnit.ok(!sourceBuffer.appendAudioInitSegment_, 'will not append init segment next');
  472. dataBuffer = new Uint8Array([4, 5]);
  473. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', dataBuffer, {
  474. initSegment: {
  475. data: initBuffer.buffer,
  476. byteOffset: initBuffer.byteOffset,
  477. byteLength: initBuffer.byteLength
  478. }
  479. }));
  480. sourceBuffer.transmuxer_.onmessage(doneMessage);
  481. QUnit.equal(mp4Segments.length, 2, 'emitted the fragment');
  482. // does not contain init segment on next segment
  483. QUnit.equal(mp4Segments[1][0], 4, 'fragment contains the correct first byte');
  484. QUnit.equal(mp4Segments[1][1], 5, 'fragment contains the correct second byte');
  485. // audio track change
  486. this.player.audioTracks().trigger('change');
  487. sourceBuffer.audioDisabled_ = false;
  488. QUnit.ok(sourceBuffer.appendAudioInitSegment_, 'audio change sets appendAudioInitSegment_');
  489. dataBuffer = new Uint8Array([6, 7]);
  490. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', dataBuffer, {
  491. initSegment: {
  492. data: initBuffer.buffer,
  493. byteOffset: initBuffer.byteOffset,
  494. byteLength: initBuffer.byteLength
  495. }
  496. }));
  497. sourceBuffer.transmuxer_.onmessage(doneMessage);
  498. QUnit.equal(mp4Segments.length, 3, 'emitted the fragment');
  499. // contains init segment after audio track change
  500. QUnit.equal(mp4Segments[2][0], 0, 'fragment contains the correct first byte');
  501. QUnit.equal(mp4Segments[2][1], 1, 'fragment contains the correct second byte');
  502. QUnit.equal(mp4Segments[2][2], 6, 'fragment contains the correct third byte');
  503. QUnit.equal(mp4Segments[2][3], 7, 'fragment contains the correct fourth byte');
  504. QUnit.ok(!sourceBuffer.appendAudioInitSegment_, 'will not append init segment next');
  505. dataBuffer = new Uint8Array([8, 9]);
  506. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', dataBuffer, {
  507. initSegment: {
  508. data: initBuffer.buffer,
  509. byteOffset: initBuffer.byteOffset,
  510. byteLength: initBuffer.byteLength
  511. }
  512. }));
  513. sourceBuffer.transmuxer_.onmessage(doneMessage);
  514. QUnit.equal(mp4Segments.length, 4, 'emitted the fragment');
  515. // does not contain init segment in next segment
  516. QUnit.equal(mp4Segments[3][0], 8, 'fragment contains the correct first byte');
  517. QUnit.equal(mp4Segments[3][1], 9, 'fragment contains the correct second byte');
  518. QUnit.ok(!sourceBuffer.appendAudioInitSegment_, 'will not append init segment next');
  519. // rendition switch
  520. this.player.trigger('mediachange');
  521. QUnit.ok(sourceBuffer.appendAudioInitSegment_, 'media change sets appendAudioInitSegment_');
  522. dataBuffer = new Uint8Array([10, 11]);
  523. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', dataBuffer, {
  524. initSegment: {
  525. data: initBuffer.buffer,
  526. byteOffset: initBuffer.byteOffset,
  527. byteLength: initBuffer.byteLength
  528. }
  529. }));
  530. sourceBuffer.transmuxer_.onmessage(doneMessage);
  531. QUnit.equal(mp4Segments.length, 5, 'emitted the fragment');
  532. // contains init segment after audio track change
  533. QUnit.equal(mp4Segments[4][0], 0, 'fragment contains the correct first byte');
  534. QUnit.equal(mp4Segments[4][1], 1, 'fragment contains the correct second byte');
  535. QUnit.equal(mp4Segments[4][2], 10, 'fragment contains the correct third byte');
  536. QUnit.equal(mp4Segments[4][3], 11, 'fragment contains the correct fourth byte');
  537. QUnit.ok(!sourceBuffer.appendAudioInitSegment_, 'will not append init segment next');
  538. });
  539. QUnit.test(
  540. 'appends video init segment for every segment',
  541. function() {
  542. let mp4Segments = [];
  543. let initBuffer = new Uint8Array([0, 1]);
  544. let dataBuffer = new Uint8Array([2, 3]);
  545. let mediaSource;
  546. let sourceBuffer;
  547. mediaSource = new videojs.MediaSource();
  548. sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  549. mediaSource.player_ = this.player;
  550. mediaSource.url_ = this.url;
  551. mediaSource.trigger('sourceopen');
  552. sourceBuffer.concatAndAppendSegments_ = function(segmentObj, destinationBuffer) {
  553. let segment = segmentObj.segments.reduce((seg, arr) => seg.concat(Array.from(arr)),
  554. []);
  555. mp4Segments.push(segment);
  556. };
  557. // an init segment
  558. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', dataBuffer, {
  559. initSegment: {
  560. data: initBuffer.buffer,
  561. byteOffset: initBuffer.byteOffset,
  562. byteLength: initBuffer.byteLength
  563. }
  564. }));
  565. // Segments are concatenated
  566. QUnit.equal(
  567. mp4Segments.length,
  568. 0,
  569. 'segments are not appended until after the `done` message'
  570. );
  571. // send `done` message
  572. sourceBuffer.transmuxer_.onmessage(doneMessage);
  573. // Segments are concatenated
  574. QUnit.equal(mp4Segments.length, 1, 'emitted the fragment');
  575. // Contains init segment on first segment
  576. QUnit.equal(mp4Segments[0][0], 0, 'fragment contains the correct first byte');
  577. QUnit.equal(mp4Segments[0][1], 1, 'fragment contains the correct second byte');
  578. QUnit.equal(mp4Segments[0][2], 2, 'fragment contains the correct third byte');
  579. QUnit.equal(mp4Segments[0][3], 3, 'fragment contains the correct fourth byte');
  580. dataBuffer = new Uint8Array([4, 5]);
  581. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', dataBuffer, {
  582. initSegment: {
  583. data: initBuffer.buffer,
  584. byteOffset: initBuffer.byteOffset,
  585. byteLength: initBuffer.byteLength
  586. }
  587. }));
  588. sourceBuffer.transmuxer_.onmessage(doneMessage);
  589. QUnit.equal(mp4Segments.length, 2, 'emitted the fragment');
  590. QUnit.equal(mp4Segments[1][0], 0, 'fragment contains the correct first byte');
  591. QUnit.equal(mp4Segments[1][1], 1, 'fragment contains the correct second byte');
  592. QUnit.equal(mp4Segments[1][2], 4, 'fragment contains the correct third byte');
  593. QUnit.equal(mp4Segments[1][3], 5, 'fragment contains the correct fourth byte');
  594. dataBuffer = new Uint8Array([6, 7]);
  595. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', dataBuffer, {
  596. initSegment: {
  597. data: initBuffer.buffer,
  598. byteOffset: initBuffer.byteOffset,
  599. byteLength: initBuffer.byteLength
  600. }
  601. }));
  602. sourceBuffer.transmuxer_.onmessage(doneMessage);
  603. QUnit.equal(mp4Segments.length, 3, 'emitted the fragment');
  604. // contains init segment after audio track change
  605. QUnit.equal(mp4Segments[2][0], 0, 'fragment contains the correct first byte');
  606. QUnit.equal(mp4Segments[2][1], 1, 'fragment contains the correct second byte');
  607. QUnit.equal(mp4Segments[2][2], 6, 'fragment contains the correct third byte');
  608. QUnit.equal(mp4Segments[2][3], 7, 'fragment contains the correct fourth byte');
  609. });
  610. QUnit.test('handles empty codec string value', function() {
  611. let mediaSource = new videojs.MediaSource();
  612. let sourceBuffer =
  613. mediaSource.addSourceBuffer('video/mp2t; codecs=""');
  614. initializeNativeSourceBuffers(sourceBuffer);
  615. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  616. QUnit.equal(
  617. mediaSource.videoBuffer_.type,
  618. 'video/mp4;codecs="avc1.4d400d"',
  619. 'video buffer has the default codec'
  620. );
  621. QUnit.ok(mediaSource.audioBuffer_, 'created an audio buffer');
  622. QUnit.equal(
  623. mediaSource.audioBuffer_.type,
  624. 'audio/mp4;codecs="mp4a.40.2"',
  625. 'audio buffer has the default codec'
  626. );
  627. QUnit.equal(mediaSource.sourceBuffers.length, 1, 'created one virtual buffer');
  628. QUnit.equal(
  629. mediaSource.sourceBuffers[0],
  630. sourceBuffer,
  631. 'returned the virtual buffer'
  632. );
  633. });
  634. QUnit.test('can create an audio buffer by itself', function() {
  635. let mediaSource = new videojs.MediaSource();
  636. let sourceBuffer =
  637. mediaSource.addSourceBuffer('video/mp2t; codecs="mp4a.40.2"');
  638. initializeNativeSourceBuffers(sourceBuffer);
  639. QUnit.ok(!mediaSource.videoBuffer_, 'did not create a video buffer');
  640. QUnit.ok(mediaSource.audioBuffer_, 'created an audio buffer');
  641. QUnit.equal(
  642. mediaSource.audioBuffer_.type,
  643. 'audio/mp4;codecs="mp4a.40.2"',
  644. 'audio buffer has the default codec'
  645. );
  646. QUnit.equal(mediaSource.sourceBuffers.length, 1, 'created one virtual buffer');
  647. QUnit.equal(
  648. mediaSource.sourceBuffers[0],
  649. sourceBuffer,
  650. 'returned the virtual buffer'
  651. );
  652. });
  653. QUnit.test('can create an video buffer by itself', function() {
  654. let mediaSource = new videojs.MediaSource();
  655. let sourceBuffer =
  656. mediaSource.addSourceBuffer('video/mp2t; codecs="avc1.4d400d"');
  657. initializeNativeSourceBuffers(sourceBuffer);
  658. QUnit.ok(!mediaSource.audioBuffer_, 'did not create an audio buffer');
  659. QUnit.ok(mediaSource.videoBuffer_, 'created an video buffer');
  660. QUnit.equal(
  661. mediaSource.videoBuffer_.type,
  662. 'video/mp4;codecs="avc1.4d400d"',
  663. 'video buffer has the codec that was passed'
  664. );
  665. QUnit.equal(mediaSource.sourceBuffers.length, 1, 'created one virtual buffer');
  666. QUnit.equal(
  667. mediaSource.sourceBuffers[0],
  668. sourceBuffer,
  669. 'returned the virtual buffer'
  670. );
  671. });
  672. QUnit.test('handles invalid codec string', function() {
  673. let mediaSource = new videojs.MediaSource();
  674. let sourceBuffer =
  675. mediaSource.addSourceBuffer('video/mp2t; codecs="nope"');
  676. initializeNativeSourceBuffers(sourceBuffer);
  677. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  678. QUnit.equal(
  679. mediaSource.videoBuffer_.type,
  680. 'video/mp4;codecs="avc1.4d400d"',
  681. 'video buffer has the default codec'
  682. );
  683. QUnit.ok(mediaSource.audioBuffer_, 'created an audio buffer');
  684. QUnit.equal(
  685. mediaSource.audioBuffer_.type,
  686. 'audio/mp4;codecs="mp4a.40.2"',
  687. 'audio buffer has the default codec'
  688. );
  689. QUnit.equal(mediaSource.sourceBuffers.length, 1, 'created one virtual buffer');
  690. QUnit.equal(
  691. mediaSource.sourceBuffers[0],
  692. sourceBuffer,
  693. 'returned the virtual buffer'
  694. );
  695. });
  696. QUnit.test('handles codec strings in reverse order', function() {
  697. let mediaSource = new videojs.MediaSource();
  698. let sourceBuffer =
  699. mediaSource.addSourceBuffer('video/mp2t; codecs="mp4a.40.5,avc1.64001f"');
  700. initializeNativeSourceBuffers(sourceBuffer);
  701. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  702. QUnit.equal(
  703. mediaSource.videoBuffer_.type,
  704. 'video/mp4;codecs="avc1.64001f"',
  705. 'video buffer has the passed codec'
  706. );
  707. QUnit.ok(mediaSource.audioBuffer_, 'created an audio buffer');
  708. QUnit.equal(
  709. mediaSource.audioBuffer_.type,
  710. 'audio/mp4;codecs="mp4a.40.5"',
  711. 'audio buffer has the passed codec'
  712. );
  713. QUnit.equal(mediaSource.sourceBuffers.length, 1, 'created one virtual buffer');
  714. QUnit.equal(
  715. mediaSource.sourceBuffers[0],
  716. sourceBuffer,
  717. 'returned the virtual buffer'
  718. );
  719. QUnit.ok(sourceBuffer.transmuxer_, 'created a transmuxer');
  720. });
  721. QUnit.test('forwards codec strings to native buffers when specified', function() {
  722. let mediaSource = new videojs.MediaSource();
  723. let sourceBuffer =
  724. mediaSource.addSourceBuffer('video/mp2t; codecs="avc1.64001f,mp4a.40.5"');
  725. initializeNativeSourceBuffers(sourceBuffer);
  726. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  727. QUnit.equal(mediaSource.videoBuffer_.type,
  728. 'video/mp4;codecs="avc1.64001f"',
  729. 'passed the video codec along');
  730. QUnit.ok(mediaSource.audioBuffer_, 'created a video buffer');
  731. QUnit.equal(mediaSource.audioBuffer_.type,
  732. 'audio/mp4;codecs="mp4a.40.5"',
  733. 'passed the audio codec along');
  734. });
  735. QUnit.test('parses old-school apple codec strings to the modern standard', function() {
  736. let mediaSource = new videojs.MediaSource();
  737. let sourceBuffer =
  738. mediaSource.addSourceBuffer('video/mp2t; codecs="avc1.100.31,mp4a.40.5"');
  739. initializeNativeSourceBuffers(sourceBuffer);
  740. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  741. QUnit.equal(mediaSource.videoBuffer_.type,
  742. 'video/mp4;codecs="avc1.64001f"',
  743. 'passed the video codec along');
  744. QUnit.ok(mediaSource.audioBuffer_, 'created a video buffer');
  745. QUnit.equal(mediaSource.audioBuffer_.type,
  746. 'audio/mp4;codecs="mp4a.40.5"',
  747. 'passed the audio codec along');
  748. });
  749. QUnit.test('specifies reasonable codecs if none are specified', function() {
  750. let mediaSource = new videojs.MediaSource();
  751. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  752. initializeNativeSourceBuffers(sourceBuffer);
  753. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  754. QUnit.equal(mediaSource.videoBuffer_.type,
  755. 'video/mp4;codecs="avc1.4d400d"',
  756. 'passed the video codec along');
  757. QUnit.ok(mediaSource.audioBuffer_, 'created a video buffer');
  758. QUnit.equal(mediaSource.audioBuffer_.type,
  759. 'audio/mp4;codecs="mp4a.40.2"',
  760. 'passed the audio codec along');
  761. });
  762. QUnit.test('virtual buffers are updating if either native buffer is', function() {
  763. let mediaSource = new videojs.MediaSource();
  764. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  765. initializeNativeSourceBuffers(sourceBuffer);
  766. mediaSource.videoBuffer_.updating = true;
  767. mediaSource.audioBuffer_.updating = false;
  768. QUnit.equal(sourceBuffer.updating, true, 'virtual buffer is updating');
  769. mediaSource.audioBuffer_.updating = true;
  770. QUnit.equal(sourceBuffer.updating, true, 'virtual buffer is updating');
  771. mediaSource.videoBuffer_.updating = false;
  772. QUnit.equal(sourceBuffer.updating, true, 'virtual buffer is updating');
  773. mediaSource.audioBuffer_.updating = false;
  774. QUnit.equal(sourceBuffer.updating, false, 'virtual buffer is not updating');
  775. });
  776. QUnit.test(
  777. 'virtual buffers have a position buffered if both native buffers do',
  778. function() {
  779. let mediaSource = new videojs.MediaSource();
  780. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  781. initializeNativeSourceBuffers(sourceBuffer);
  782. mediaSource.videoBuffer_.buffered = videojs.createTimeRanges([
  783. [0, 10],
  784. [20, 30]
  785. ]);
  786. mediaSource.audioBuffer_.buffered = videojs.createTimeRanges([
  787. [0, 7],
  788. [11, 15],
  789. [16, 40]
  790. ]);
  791. QUnit.equal(sourceBuffer.buffered.length, 2, 'two buffered ranges');
  792. QUnit.equal(sourceBuffer.buffered.start(0), 0, 'first starts at zero');
  793. QUnit.equal(sourceBuffer.buffered.end(0), 7, 'first ends at seven');
  794. QUnit.equal(sourceBuffer.buffered.start(1), 20, 'second starts at twenty');
  795. QUnit.equal(sourceBuffer.buffered.end(1), 30, 'second ends at 30');
  796. });
  797. QUnit.test('disabled audio does not affect buffered property', function() {
  798. let mediaSource = new videojs.MediaSource();
  799. let muxedBuffer = mediaSource.addSourceBuffer('video/mp2t');
  800. // creating a separate audio buffer disables audio on the muxed one
  801. let audioBuffer = mediaSource.addSourceBuffer('audio/mp2t; codecs="mp4a.40.2"');
  802. initializeNativeSourceBuffers(muxedBuffer);
  803. mediaSource.videoBuffer_.buffered = videojs.createTimeRanges([[1, 10]]);
  804. mediaSource.audioBuffer_.buffered = videojs.createTimeRanges([[2, 11]]);
  805. QUnit.equal(audioBuffer.buffered.length, 1, 'one buffered range');
  806. QUnit.equal(audioBuffer.buffered.start(0), 2, 'starts at two');
  807. QUnit.equal(audioBuffer.buffered.end(0), 11, 'ends at eleven');
  808. QUnit.equal(muxedBuffer.buffered.length, 1, 'one buffered range');
  809. QUnit.equal(muxedBuffer.buffered.start(0), 1, 'starts at one');
  810. QUnit.equal(muxedBuffer.buffered.end(0), 10, 'ends at ten');
  811. });
  812. QUnit.test('sets transmuxer baseMediaDecodeTime on appends', function() {
  813. let mediaSource = new videojs.MediaSource();
  814. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  815. let resets = [];
  816. sourceBuffer.transmuxer_.postMessage = function(message) {
  817. if (message.action === 'setTimestampOffset') {
  818. resets.push(message.timestampOffset);
  819. }
  820. };
  821. sourceBuffer.timestampOffset = 42;
  822. QUnit.equal(
  823. resets.length,
  824. 1,
  825. 'reset called'
  826. );
  827. QUnit.equal(
  828. resets[0],
  829. 42,
  830. 'set the baseMediaDecodeTime based on timestampOffset'
  831. );
  832. });
  833. QUnit.test('aggregates source buffer update events', function() {
  834. let mediaSource = new videojs.MediaSource();
  835. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  836. let updates = 0;
  837. let updateends = 0;
  838. let updatestarts = 0;
  839. initializeNativeSourceBuffers(sourceBuffer);
  840. mediaSource.player_ = this.player;
  841. sourceBuffer.addEventListener('updatestart', function() {
  842. updatestarts++;
  843. });
  844. sourceBuffer.addEventListener('update', function() {
  845. updates++;
  846. });
  847. sourceBuffer.addEventListener('updateend', function() {
  848. updateends++;
  849. });
  850. QUnit.equal(updatestarts, 0, 'no updatestarts before a `done` message is received');
  851. QUnit.equal(updates, 0, 'no updates before a `done` message is received');
  852. QUnit.equal(updateends, 0, 'no updateends before a `done` message is received');
  853. // the video buffer begins updating first:
  854. sourceBuffer.videoBuffer_.updating = true;
  855. sourceBuffer.audioBuffer_.updating = false;
  856. sourceBuffer.videoBuffer_.trigger('updatestart');
  857. QUnit.equal(updatestarts, 1, 'aggregated updatestart');
  858. sourceBuffer.audioBuffer_.updating = true;
  859. sourceBuffer.audioBuffer_.trigger('updatestart');
  860. QUnit.equal(updatestarts, 1, 'aggregated updatestart');
  861. // the audio buffer finishes first:
  862. sourceBuffer.audioBuffer_.updating = false;
  863. sourceBuffer.videoBuffer_.updating = true;
  864. sourceBuffer.audioBuffer_.trigger('update');
  865. QUnit.equal(updates, 0, 'waited for the second update');
  866. sourceBuffer.videoBuffer_.updating = false;
  867. sourceBuffer.videoBuffer_.trigger('update');
  868. QUnit.equal(updates, 1, 'aggregated update');
  869. // audio finishes first:
  870. sourceBuffer.videoBuffer_.updating = true;
  871. sourceBuffer.audioBuffer_.updating = false;
  872. sourceBuffer.audioBuffer_.trigger('updateend');
  873. QUnit.equal(updateends, 0, 'waited for the second updateend');
  874. sourceBuffer.videoBuffer_.updating = false;
  875. sourceBuffer.videoBuffer_.trigger('updateend');
  876. QUnit.equal(updateends, 1, 'aggregated updateend');
  877. });
  878. QUnit.test('translates caption events into WebVTT cues', function() {
  879. let mediaSource = new videojs.MediaSource();
  880. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  881. let types = [];
  882. let hls608 = 0;
  883. mediaSource.player_ = {
  884. addRemoteTextTrack(options) {
  885. types.push(options.kind);
  886. return {
  887. track: {
  888. kind: options.kind,
  889. label: options.label,
  890. cues: [],
  891. addCue(cue) {
  892. this.cues.push(cue);
  893. }
  894. }
  895. };
  896. },
  897. textTracks() {
  898. return {
  899. getTrackById() {}
  900. };
  901. },
  902. remoteTextTracks() {
  903. },
  904. tech_: new videojs.EventTarget()
  905. };
  906. mediaSource.player_.tech_.on('usage', (event) => {
  907. if (event.name === 'hls-608') {
  908. hls608++;
  909. }
  910. });
  911. sourceBuffer.timestampOffset = 10;
  912. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', new Uint8Array(1), {
  913. captions: [{
  914. startTime: 1,
  915. endTime: 3,
  916. text: 'This is an in-band caption in CC1',
  917. stream: 'CC1'
  918. }],
  919. captionStreams: {CC1: true}
  920. }));
  921. sourceBuffer.transmuxer_.onmessage(doneMessage);
  922. let cues = sourceBuffer.inbandTextTracks_.CC1.cues;
  923. QUnit.equal(hls608, 1, 'one hls-608 event was triggered');
  924. QUnit.equal(types.length, 1, 'created one text track');
  925. QUnit.equal(types[0], 'captions', 'the type was captions');
  926. QUnit.equal(cues.length, 1, 'created one cue');
  927. QUnit.equal(cues[0].text, 'This is an in-band caption in CC1', 'included the text');
  928. QUnit.equal(cues[0].startTime, 11, 'started at eleven');
  929. QUnit.equal(cues[0].endTime, 13, 'ended at thirteen');
  930. });
  931. QUnit.test('captions use existing tracks with id equal to CC#', function() {
  932. let mediaSource = new videojs.MediaSource();
  933. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  934. let addTrackCalled = 0;
  935. let tracks = {
  936. CC1: {
  937. kind: 'captions',
  938. label: 'CC1',
  939. id: 'CC1',
  940. cues: [],
  941. addCue(cue) {
  942. this.cues.push(cue);
  943. }
  944. },
  945. CC2: {
  946. kind: 'captions',
  947. label: 'CC2',
  948. id: 'CC2',
  949. cues: [],
  950. addCue(cue) {
  951. this.cues.push(cue);
  952. }
  953. }
  954. };
  955. mediaSource.player_ = {
  956. addRemoteTextTrack(options) {
  957. addTrackCalled++;
  958. },
  959. textTracks() {
  960. return {
  961. getTrackById(id) {
  962. return tracks[id];
  963. }
  964. };
  965. },
  966. remoteTextTracks() {
  967. },
  968. tech_: new videojs.EventTarget()
  969. };
  970. sourceBuffer.timestampOffset = 10;
  971. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', new Uint8Array(1), {
  972. captions: [{
  973. stream: 'CC1',
  974. startTime: 1,
  975. endTime: 3,
  976. text: 'This is an in-band caption in CC1'
  977. }, {
  978. stream: 'CC2',
  979. startTime: 1,
  980. endTime: 3,
  981. text: 'This is an in-band caption in CC2'
  982. }],
  983. captionStreams: {CC1: true, CC2: true}
  984. }));
  985. sourceBuffer.transmuxer_.onmessage(doneMessage);
  986. let cues = sourceBuffer.inbandTextTracks_.CC1.cues;
  987. QUnit.equal(addTrackCalled, 0, 'no tracks were created');
  988. QUnit.equal(tracks.CC1.cues.length, 1, 'CC1 contains 1 cue');
  989. QUnit.equal(tracks.CC2.cues.length, 1, 'CC2 contains 1 cue');
  990. QUnit.equal(tracks.CC1.cues[0].text, 'This is an in-band caption in CC1', 'CC1 contains the right cue');
  991. QUnit.equal(tracks.CC2.cues[0].text, 'This is an in-band caption in CC2', 'CC2 contains the right cue');
  992. });
  993. QUnit.test('translates metadata events into WebVTT cues', function() {
  994. let mediaSource = new videojs.MediaSource();
  995. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  996. mediaSource.duration = Infinity;
  997. mediaSource.nativeMediaSource_.duration = 60;
  998. let types = [];
  999. let metadata = [{
  1000. cueTime: 2,
  1001. frames: [{
  1002. url: 'This is a url tag'
  1003. }, {
  1004. value: 'This is a text tag'
  1005. }]
  1006. }, {
  1007. cueTime: 12,
  1008. frames: [{
  1009. data: 'This is a priv tag'
  1010. }]
  1011. }];
  1012. metadata.dispatchType = 0x10;
  1013. mediaSource.player_ = {
  1014. addRemoteTextTrack(options) {
  1015. types.push(options.kind);
  1016. return {
  1017. track: {
  1018. kind: options.kind,
  1019. label: options.label,
  1020. cues: [],
  1021. addCue(cue) {
  1022. this.cues.push(cue);
  1023. }
  1024. }
  1025. };
  1026. },
  1027. remoteTextTracks() {
  1028. }
  1029. };
  1030. sourceBuffer.timestampOffset = 10;
  1031. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', new Uint8Array(1), {
  1032. metadata
  1033. }));
  1034. sourceBuffer.transmuxer_.onmessage(doneMessage);
  1035. QUnit.equal(
  1036. sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType,
  1037. 16,
  1038. 'in-band metadata track dispatch type correctly set'
  1039. );
  1040. let cues = sourceBuffer.metadataTrack_.cues;
  1041. QUnit.equal(types.length, 1, 'created one text track');
  1042. QUnit.equal(types[0], 'metadata', 'the type was metadata');
  1043. QUnit.equal(cues.length, 3, 'created three cues');
  1044. QUnit.equal(cues[0].text, 'This is a url tag', 'included the text');
  1045. QUnit.equal(cues[0].startTime, 12, 'started at twelve');
  1046. QUnit.equal(cues[0].endTime, 22, 'ended at StartTime of next cue(22)');
  1047. QUnit.equal(cues[1].text, 'This is a text tag', 'included the text');
  1048. QUnit.equal(cues[1].startTime, 12, 'started at twelve');
  1049. QUnit.equal(cues[1].endTime, 22, 'ended at the startTime of next cue(22)');
  1050. QUnit.equal(cues[2].text, 'This is a priv tag', 'included the text');
  1051. QUnit.equal(cues[2].startTime, 22, 'started at twenty two');
  1052. QUnit.equal(cues[2].endTime, Number.MAX_VALUE, 'ended at the maximum value');
  1053. mediaSource.duration = 100;
  1054. mediaSource.trigger('sourceended');
  1055. QUnit.equal(cues[2].endTime, mediaSource.duration, 'sourceended is fired');
  1056. });
  1057. QUnit.test('does not wrap mp4 source buffers', function() {
  1058. let mediaSource = new videojs.MediaSource();
  1059. mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d');
  1060. mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
  1061. QUnit.equal(
  1062. mediaSource.sourceBuffers.length,
  1063. mediaSource.nativeMediaSource_.sourceBuffers.length,
  1064. 'did not need virtual buffers'
  1065. );
  1066. QUnit.equal(mediaSource.sourceBuffers.length, 2, 'created native buffers');
  1067. });
  1068. QUnit.test('can get activeSourceBuffers', function() {
  1069. let mediaSource = new videojs.MediaSource();
  1070. // although activeSourceBuffers should technically be a SourceBufferList, we are
  1071. // returning it as an array, and users may expect it to behave as such
  1072. QUnit.ok(Array.isArray(mediaSource.activeSourceBuffers));
  1073. });
  1074. QUnit.test('active source buffers are updated on each buffer\'s updateend',
  1075. function() {
  1076. let mediaSource = new videojs.MediaSource();
  1077. let updateCallCount = 0;
  1078. let sourceBuffer;
  1079. mediaSource.updateActiveSourceBuffers_ = () => {
  1080. updateCallCount++;
  1081. };
  1082. sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  1083. mediaSource.player_ = this.player;
  1084. mediaSource.url_ = this.url;
  1085. mediaSource.trigger('sourceopen');
  1086. QUnit.equal(updateCallCount, 0,
  1087. 'active source buffers not updated on adding source buffer');
  1088. mediaSource.player_.audioTracks().trigger('addtrack');
  1089. QUnit.equal(updateCallCount, 1,
  1090. 'active source buffers updated after addtrack');
  1091. sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  1092. QUnit.equal(updateCallCount, 1,
  1093. 'active source buffers not updated on adding second source buffer');
  1094. mediaSource.player_.audioTracks().trigger('removetrack');
  1095. QUnit.equal(updateCallCount, 2,
  1096. 'active source buffers updated after removetrack');
  1097. mediaSource.player_.audioTracks().trigger('change');
  1098. QUnit.equal(updateCallCount, 3,
  1099. 'active source buffers updated after change');
  1100. });
  1101. QUnit.test('combined buffer is the only active buffer when main track enabled',
  1102. function() {
  1103. let mediaSource = new videojs.MediaSource();
  1104. let sourceBufferAudio;
  1105. let sourceBufferCombined;
  1106. let audioTracks = [{
  1107. enabled: true,
  1108. kind: 'main',
  1109. label: 'main'
  1110. }, {
  1111. enabled: false,
  1112. kind: 'alternative',
  1113. label: 'English (UK)'
  1114. }];
  1115. this.player.audioTracks = () => audioTracks;
  1116. mediaSource.player_ = this.player;
  1117. sourceBufferCombined = mediaSource.addSourceBuffer('video/m2pt');
  1118. sourceBufferCombined.videoCodec_ = true;
  1119. sourceBufferCombined.audioCodec_ = true;
  1120. sourceBufferAudio = mediaSource.addSourceBuffer('video/m2pt');
  1121. sourceBufferAudio.videoCodec_ = false;
  1122. sourceBufferAudio.audioCodec_ = true;
  1123. mediaSource.updateActiveSourceBuffers_();
  1124. QUnit.equal(mediaSource.activeSourceBuffers.length, 1,
  1125. 'active source buffers starts with one source buffer');
  1126. QUnit.equal(mediaSource.activeSourceBuffers[0], sourceBufferCombined,
  1127. 'active source buffers starts with combined source buffer');
  1128. });
  1129. QUnit.test('combined & audio buffers are active when alternative track enabled',
  1130. function() {
  1131. let mediaSource = new videojs.MediaSource();
  1132. let sourceBufferAudio;
  1133. let sourceBufferCombined;
  1134. let audioTracks = [{
  1135. enabled: false,
  1136. kind: 'main',
  1137. label: 'main'
  1138. }, {
  1139. enabled: true,
  1140. kind: 'alternative',
  1141. label: 'English (UK)'
  1142. }];
  1143. this.player.audioTracks = () => audioTracks;
  1144. mediaSource.player_ = this.player;
  1145. sourceBufferCombined = mediaSource.addSourceBuffer('video/m2pt');
  1146. sourceBufferCombined.videoCodec_ = true;
  1147. sourceBufferCombined.audioCodec_ = true;
  1148. sourceBufferAudio = mediaSource.addSourceBuffer('video/m2pt');
  1149. sourceBufferAudio.videoCodec_ = false;
  1150. sourceBufferAudio.audioCodec_ = true;
  1151. mediaSource.updateActiveSourceBuffers_();
  1152. QUnit.equal(mediaSource.activeSourceBuffers.length, 2,
  1153. 'active source buffers includes both source buffers');
  1154. // maintains same order as source buffers were created
  1155. QUnit.equal(mediaSource.activeSourceBuffers[0], sourceBufferCombined,
  1156. 'active source buffers starts with combined source buffer');
  1157. QUnit.equal(mediaSource.activeSourceBuffers[1], sourceBufferAudio,
  1158. 'active source buffers ends with audio source buffer');
  1159. });
  1160. QUnit.test('video only & audio only buffers are always active',
  1161. function() {
  1162. let mediaSource = new videojs.MediaSource();
  1163. let sourceBufferAudio;
  1164. let sourceBufferCombined;
  1165. let audioTracks = [{
  1166. enabled: false,
  1167. kind: 'main',
  1168. label: 'main'
  1169. }, {
  1170. enabled: true,
  1171. kind: 'alternative',
  1172. label: 'English (UK)'
  1173. }];
  1174. this.player.audioTracks = () => audioTracks;
  1175. mediaSource.player_ = this.player;
  1176. sourceBufferCombined = mediaSource.addSourceBuffer('video/m2pt');
  1177. sourceBufferCombined.videoCodec_ = true;
  1178. sourceBufferCombined.audioCodec_ = false;
  1179. sourceBufferAudio = mediaSource.addSourceBuffer('video/m2pt');
  1180. sourceBufferAudio.videoCodec_ = false;
  1181. sourceBufferAudio.audioCodec_ = true;
  1182. mediaSource.updateActiveSourceBuffers_();
  1183. QUnit.equal(mediaSource.activeSourceBuffers.length, 2,
  1184. 'active source buffers includes both source buffers');
  1185. // maintains same order as source buffers were created
  1186. QUnit.equal(mediaSource.activeSourceBuffers[0], sourceBufferCombined,
  1187. 'active source buffers starts with combined source buffer');
  1188. QUnit.equal(mediaSource.activeSourceBuffers[1], sourceBufferAudio,
  1189. 'active source buffers ends with audio source buffer');
  1190. audioTracks[0].enabled = true;
  1191. audioTracks[1].enabled = false;
  1192. mediaSource.updateActiveSourceBuffers_();
  1193. QUnit.equal(mediaSource.activeSourceBuffers.length, 2,
  1194. 'active source buffers includes both source buffers');
  1195. // maintains same order as source buffers were created
  1196. QUnit.equal(mediaSource.activeSourceBuffers[0], sourceBufferCombined,
  1197. 'active source buffers starts with combined source buffer');
  1198. QUnit.equal(mediaSource.activeSourceBuffers[1], sourceBufferAudio,
  1199. 'active source buffers ends with audio source buffer');
  1200. });
  1201. QUnit.test('Single buffer always active. Audio disabled depends on audio codec',
  1202. function() {
  1203. let mediaSource = new videojs.MediaSource();
  1204. let audioTracks = [{
  1205. enabled: true,
  1206. kind: 'main',
  1207. label: 'main'
  1208. }];
  1209. this.player.audioTracks = () => audioTracks;
  1210. mediaSource.player_ = this.player;
  1211. let sourceBuffer = mediaSource.addSourceBuffer('video/m2pt');
  1212. // video only
  1213. sourceBuffer.videoCodec_ = true;
  1214. sourceBuffer.audioCodec_ = false;
  1215. mediaSource.updateActiveSourceBuffers_();
  1216. QUnit.equal(mediaSource.activeSourceBuffers.length, 1, 'sourceBuffer is active');
  1217. QUnit.ok(mediaSource.activeSourceBuffers[0].audioDisabled_,
  1218. 'audio is disabled on video only active sourceBuffer');
  1219. // audio only
  1220. sourceBuffer.videoCodec_ = false;
  1221. sourceBuffer.audioCodec_ = true;
  1222. mediaSource.updateActiveSourceBuffers_();
  1223. QUnit.equal(mediaSource.activeSourceBuffers.length, 1, 'sourceBuffer is active');
  1224. QUnit.notOk(mediaSource.activeSourceBuffers[0].audioDisabled_,
  1225. 'audio not disabled on audio only active sourceBuffer');
  1226. });
  1227. QUnit.test('video segments with info trigger videooinfo event', function() {
  1228. let data = new Uint8Array(1);
  1229. let infoEvents = [];
  1230. let mediaSource = new videojs.MediaSource();
  1231. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  1232. let info = {width: 100};
  1233. let newinfo = {width: 225};
  1234. mediaSource.on('videoinfo', (e) => infoEvents.push(e));
  1235. // send an audio segment with info, then send done
  1236. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', data, {info}));
  1237. sourceBuffer.transmuxer_.onmessage(doneMessage);
  1238. QUnit.equal(infoEvents.length, 1, 'video info should trigger');
  1239. QUnit.deepEqual(infoEvents[0].info, info, 'video info = muxed info');
  1240. // send an audio segment with info, then send done
  1241. sourceBuffer.transmuxer_.onmessage(createDataMessage('video', data, {info: newinfo}));
  1242. sourceBuffer.transmuxer_.onmessage(doneMessage);
  1243. QUnit.equal(infoEvents.length, 2, 'video info should trigger');
  1244. QUnit.deepEqual(infoEvents[1].info, newinfo, 'video info = muxed info');
  1245. });
  1246. QUnit.test('audio segments with info trigger audioinfo event', function() {
  1247. let data = new Uint8Array(1);
  1248. let infoEvents = [];
  1249. let mediaSource = new videojs.MediaSource();
  1250. let sourceBuffer = mediaSource.addSourceBuffer('video/mp2t');
  1251. let info = {width: 100};
  1252. let newinfo = {width: 225};
  1253. mediaSource.on('audioinfo', (e) => infoEvents.push(e));
  1254. // send an audio segment with info, then send done
  1255. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', data, {info}));
  1256. sourceBuffer.transmuxer_.onmessage(doneMessage);
  1257. QUnit.equal(infoEvents.length, 1, 'audio info should trigger');
  1258. QUnit.deepEqual(infoEvents[0].info, info, 'audio info = muxed info');
  1259. // send an audio segment with info, then send done
  1260. sourceBuffer.transmuxer_.onmessage(createDataMessage('audio', data, {info: newinfo}));
  1261. sourceBuffer.transmuxer_.onmessage(doneMessage);
  1262. QUnit.equal(infoEvents.length, 2, 'audio info should trigger');
  1263. QUnit.deepEqual(infoEvents[1].info, newinfo, 'audio info = muxed info');
  1264. });
  1265. QUnit.test('creates native SourceBuffers immediately if a second ' +
  1266. 'VirtualSourceBuffer is created', function() {
  1267. let mediaSource = new videojs.MediaSource();
  1268. let sourceBuffer =
  1269. mediaSource.addSourceBuffer('video/mp2t; codecs="avc1.64001f,mp4a.40.5"');
  1270. let sourceBuffer2 =
  1271. mediaSource.addSourceBuffer('video/mp2t; codecs="mp4a.40.5"');
  1272. QUnit.ok(mediaSource.videoBuffer_, 'created a video buffer');
  1273. QUnit.equal(
  1274. mediaSource.videoBuffer_.type,
  1275. 'video/mp4;codecs="avc1.64001f"',
  1276. 'video buffer has the specified codec'
  1277. );
  1278. QUnit.ok(mediaSource.audioBuffer_, 'created an audio buffer');
  1279. QUnit.equal(
  1280. mediaSource.audioBuffer_.type,
  1281. 'audio/mp4;codecs="mp4a.40.5"',
  1282. 'audio buffer has the specified codec'
  1283. );
  1284. QUnit.equal(mediaSource.sourceBuffers.length, 2, 'created two virtual buffers');
  1285. QUnit.equal(
  1286. mediaSource.sourceBuffers[0],
  1287. sourceBuffer,
  1288. 'returned the virtual buffer');
  1289. QUnit.equal(
  1290. mediaSource.sourceBuffers[1],
  1291. sourceBuffer2,
  1292. 'returned the virtual buffer');
  1293. QUnit.equal(
  1294. sourceBuffer.audioDisabled_,
  1295. true,
  1296. 'first source buffer\'s audio is automatically disabled');
  1297. QUnit.ok(
  1298. sourceBuffer2.audioBuffer_,
  1299. 'second source buffer has an audio source buffer');
  1300. });
  1301. QUnit.module('VirtualSourceBuffer - Isolated Functions');
  1302. QUnit.test('gopsSafeToAlignWith returns correct list', function() {
  1303. // gopsSafeToAlignWith uses a 3 second safetyNet so that gops very close to the playhead
  1304. // are not considered safe to append to
  1305. const safetyNet = 3;
  1306. const pts = (time) => Math.ceil(time * 90000);
  1307. let mapping = 0;
  1308. let currentTime = 0;
  1309. let buffer = [];
  1310. let player;
  1311. let actual;
  1312. let expected;
  1313. expected = [];
  1314. actual = gopsSafeToAlignWith(buffer, player, mapping);
  1315. QUnit.deepEqual(actual, expected, 'empty array when player is undefined');
  1316. player = { currentTime: () => currentTime };
  1317. actual = gopsSafeToAlignWith(buffer, player, mapping);
  1318. QUnit.deepEqual(actual, expected, 'empty array when buffer is empty');
  1319. buffer = expected = [
  1320. { pts: pts(currentTime + safetyNet + 1) },
  1321. { pts: pts(currentTime + safetyNet + 2) },
  1322. { pts: pts(currentTime + safetyNet + 3) }
  1323. ];
  1324. actual = gopsSafeToAlignWith(buffer, player, mapping);
  1325. QUnit.deepEqual(actual, expected,
  1326. 'entire buffer considered safe when all gops come after currentTime + safetyNet');
  1327. buffer = [
  1328. { pts: pts(currentTime + safetyNet) },
  1329. { pts: pts(currentTime + safetyNet + 1) },
  1330. { pts: pts(currentTime + safetyNet + 2) }
  1331. ];
  1332. expected = [
  1333. { pts: pts(currentTime + safetyNet + 1) },
  1334. { pts: pts(currentTime + safetyNet + 2) }
  1335. ];
  1336. actual = gopsSafeToAlignWith(buffer, player, mapping);
  1337. QUnit.deepEqual(actual, expected, 'safetyNet comparison is not inclusive');
  1338. currentTime = 10;
  1339. mapping = -5;
  1340. buffer = [
  1341. { pts: pts(currentTime - mapping + safetyNet - 2) },
  1342. { pts: pts(currentTime - mapping + safetyNet - 1) },
  1343. { pts: pts(currentTime - mapping + safetyNet) },
  1344. { pts: pts(currentTime - mapping + safetyNet + 1) },
  1345. { pts: pts(currentTime - mapping + safetyNet + 2) }
  1346. ];
  1347. expected = [
  1348. { pts: pts(currentTime - mapping + safetyNet + 1) },
  1349. { pts: pts(currentTime - mapping + safetyNet + 2) }
  1350. ];
  1351. actual = gopsSafeToAlignWith(buffer, player, mapping);
  1352. QUnit.deepEqual(actual, expected, 'uses mapping to shift currentTime');
  1353. currentTime = 20;
  1354. expected = [];
  1355. actual = gopsSafeToAlignWith(buffer, player, mapping);
  1356. QUnit.deepEqual(actual, expected,
  1357. 'empty array when no gops in buffer come after currentTime');
  1358. });
  1359. QUnit.test('updateGopBuffer correctly processes new gop information', function() {
  1360. let buffer = [];
  1361. let gops = [];
  1362. let replace = true;
  1363. let actual;
  1364. let expected;
  1365. buffer = expected = [{ pts: 100 }, { pts: 200 }];
  1366. actual = updateGopBuffer(buffer, gops, replace);
  1367. QUnit.deepEqual(actual, expected, 'returns buffer when no new gops');
  1368. gops = expected = [{ pts: 300 }, { pts: 400 }];
  1369. actual = updateGopBuffer(buffer, gops, replace);
  1370. QUnit.deepEqual(actual, expected, 'returns only new gops when replace is true');
  1371. replace = false;
  1372. buffer = [];
  1373. gops = [{ pts: 100 }];
  1374. expected = [{ pts: 100 }];
  1375. actual = updateGopBuffer(buffer, gops, replace);
  1376. QUnit.deepEqual(actual, expected, 'appends new gops to empty buffer');
  1377. buffer = [{ pts: 100 }, { pts: 200 }];
  1378. gops = [{ pts: 300 }, { pts: 400 }];
  1379. expected = [{ pts: 100 }, { pts: 200 }, { pts: 300 }, { pts: 400 }];
  1380. actual = updateGopBuffer(buffer, gops, replace);
  1381. QUnit.deepEqual(actual, expected, 'appends new gops at end of buffer when no overlap');
  1382. buffer = [{ pts: 100 }, { pts: 200 }, { pts: 300 }, { pts: 400 }];
  1383. gops = [{ pts: 250 }, { pts: 300 }, { pts: 350 }];
  1384. expected = [{ pts: 100 }, { pts: 200 }, { pts: 250 }, { pts: 300 }, { pts: 350 }];
  1385. actual = updateGopBuffer(buffer, gops, replace);
  1386. QUnit.deepEqual(actual, expected,
  1387. 'slices buffer at point of overlap and appends new gops');
  1388. buffer = [{ pts: 100 }, { pts: 200 }, { pts: 300 }, { pts: 400 }];
  1389. gops = [{ pts: 200 }, { pts: 300 }, { pts: 350 }];
  1390. expected = [{ pts: 100 }, { pts: 200 }, { pts: 300 }, { pts: 350 }];
  1391. actual = updateGopBuffer(buffer, gops, replace);
  1392. QUnit.deepEqual(actual, expected, 'overlap slice is inclusive');
  1393. buffer = [{ pts: 300 }, { pts: 400 }, { pts: 500 }, { pts: 600 }];
  1394. gops = [{ pts: 100 }, { pts: 200 }, { pts: 250 }];
  1395. expected = [{ pts: 100 }, { pts: 200 }, { pts: 250 }];
  1396. actual = updateGopBuffer(buffer, gops, replace);
  1397. QUnit.deepEqual(actual, expected,
  1398. 'completely replaces buffer with new gops when all gops come before buffer');
  1399. });
  1400. QUnit.test('removeGopBuffer correctly removes range from buffer', function() {
  1401. const pts = (time) => Math.ceil(time * 90000);
  1402. let buffer = [];
  1403. let start = 0;
  1404. let end = 0;
  1405. let mapping = -5;
  1406. let actual;
  1407. let expected;
  1408. expected = [];
  1409. actual = removeGopBuffer(buffer, start, end, mapping);
  1410. QUnit.deepEqual(actual, expected, 'returns empty array when buffer empty');
  1411. start = 0;
  1412. end = 8;
  1413. buffer = expected = [
  1414. { pts: pts(10 - mapping) },
  1415. { pts: pts(11 - mapping) },
  1416. { pts: pts(12 - mapping) },
  1417. { pts: pts(15 - mapping) },
  1418. { pts: pts(18 - mapping) },
  1419. { pts: pts(20 - mapping) }
  1420. ];
  1421. actual = removeGopBuffer(buffer, start, end, mapping);
  1422. QUnit.deepEqual(actual, expected,
  1423. 'no removal when remove range comes before start of buffer');
  1424. start = 22;
  1425. end = 30;
  1426. buffer = [
  1427. { pts: pts(10 - mapping) },
  1428. { pts: pts(11 - mapping) },
  1429. { pts: pts(12 - mapping) },
  1430. { pts: pts(15 - mapping) },
  1431. { pts: pts(18 - mapping) },
  1432. { pts: pts(20 - mapping) }
  1433. ];
  1434. expected = [
  1435. { pts: pts(10 - mapping) },
  1436. { pts: pts(11 - mapping) },
  1437. { pts: pts(12 - mapping) },
  1438. { pts: pts(15 - mapping) },
  1439. { pts: pts(18 - mapping) }
  1440. ];
  1441. actual = removeGopBuffer(buffer, start, end, mapping);
  1442. QUnit.deepEqual(actual, expected,
  1443. 'removes last gop when remove range is after end of buffer');
  1444. start = 0;
  1445. end = 10;
  1446. buffer = [
  1447. { pts: pts(10 - mapping) },
  1448. { pts: pts(11 - mapping) },
  1449. { pts: pts(12 - mapping) },
  1450. { pts: pts(15 - mapping) },
  1451. { pts: pts(18 - mapping) },
  1452. { pts: pts(20 - mapping) }
  1453. ];
  1454. expected = [
  1455. { pts: pts(11 - mapping) },
  1456. { pts: pts(12 - mapping) },
  1457. { pts: pts(15 - mapping) },
  1458. { pts: pts(18 - mapping) },
  1459. { pts: pts(20 - mapping) }
  1460. ];
  1461. actual = removeGopBuffer(buffer, start, end, mapping);
  1462. QUnit.deepEqual(actual, expected, 'clamps start range to begining of buffer');
  1463. start = 0;
  1464. end = 12;
  1465. buffer = [
  1466. { pts: pts(10 - mapping) },
  1467. { pts: pts(11 - mapping) },
  1468. { pts: pts(12 - mapping) },
  1469. { pts: pts(15 - mapping) },
  1470. { pts: pts(18 - mapping) },
  1471. { pts: pts(20 - mapping) }
  1472. ];
  1473. expected = [
  1474. { pts: pts(15 - mapping) },
  1475. { pts: pts(18 - mapping) },
  1476. { pts: pts(20 - mapping) }
  1477. ];
  1478. actual = removeGopBuffer(buffer, start, end, mapping);
  1479. QUnit.deepEqual(actual, expected, 'clamps start range to begining of buffer');
  1480. start = 0;
  1481. end = 14;
  1482. buffer = [
  1483. { pts: pts(10 - mapping) },
  1484. { pts: pts(11 - mapping) },
  1485. { pts: pts(12 - mapping) },
  1486. { pts: pts(15 - mapping) },
  1487. { pts: pts(18 - mapping) },
  1488. { pts: pts(20 - mapping) }
  1489. ];
  1490. expected = [
  1491. { pts: pts(15 - mapping) },
  1492. { pts: pts(18 - mapping) },
  1493. { pts: pts(20 - mapping) }
  1494. ];
  1495. actual = removeGopBuffer(buffer, start, end, mapping);
  1496. QUnit.deepEqual(actual, expected, 'clamps start range to begining of buffer');
  1497. start = 15;
  1498. end = 30;
  1499. buffer = [
  1500. { pts: pts(10 - mapping) },
  1501. { pts: pts(11 - mapping) },
  1502. { pts: pts(12 - mapping) },
  1503. { pts: pts(15 - mapping) },
  1504. { pts: pts(18 - mapping) },
  1505. { pts: pts(20 - mapping) }
  1506. ];
  1507. expected = [
  1508. { pts: pts(10 - mapping) },
  1509. { pts: pts(11 - mapping) },
  1510. { pts: pts(12 - mapping) }
  1511. ];
  1512. actual = removeGopBuffer(buffer, start, end, mapping);
  1513. QUnit.deepEqual(actual, expected, 'clamps end range to end of buffer');
  1514. start = 17;
  1515. end = 30;
  1516. buffer = [
  1517. { pts: pts(10 - mapping) },
  1518. { pts: pts(11 - mapping) },
  1519. { pts: pts(12 - mapping) },
  1520. { pts: pts(15 - mapping) },
  1521. { pts: pts(18 - mapping) },
  1522. { pts: pts(20 - mapping) }
  1523. ];
  1524. expected = [
  1525. { pts: pts(10 - mapping) },
  1526. { pts: pts(11 - mapping) },
  1527. { pts: pts(12 - mapping) }
  1528. ];
  1529. actual = removeGopBuffer(buffer, start, end, mapping);
  1530. QUnit.deepEqual(actual, expected, 'clamps end range to end of buffer');
  1531. start = 20;
  1532. end = 30;
  1533. buffer = [
  1534. { pts: pts(10 - mapping) },
  1535. { pts: pts(11 - mapping) },
  1536. { pts: pts(12 - mapping) },
  1537. { pts: pts(15 - mapping) },
  1538. { pts: pts(18 - mapping) },
  1539. { pts: pts(20 - mapping) }
  1540. ];
  1541. expected = [
  1542. { pts: pts(10 - mapping) },
  1543. { pts: pts(11 - mapping) },
  1544. { pts: pts(12 - mapping) },
  1545. { pts: pts(15 - mapping) },
  1546. { pts: pts(18 - mapping) }
  1547. ];
  1548. actual = removeGopBuffer(buffer, start, end, mapping);
  1549. QUnit.deepEqual(actual, expected, 'clamps end range to end of buffer');
  1550. buffer = [
  1551. { pts: pts(10 - mapping) },
  1552. { pts: pts(11 - mapping) },
  1553. { pts: pts(12 - mapping) },
  1554. { pts: pts(15 - mapping) },
  1555. { pts: pts(18 - mapping) },
  1556. { pts: pts(20 - mapping) }
  1557. ];
  1558. start = 12;
  1559. end = 15;
  1560. expected = [
  1561. { pts: pts(10 - mapping) },
  1562. { pts: pts(11 - mapping) },
  1563. { pts: pts(18 - mapping) },
  1564. { pts: pts(20 - mapping) }
  1565. ];
  1566. actual = removeGopBuffer(buffer, start, end, mapping);
  1567. QUnit.deepEqual(actual, expected, 'removes gops that remove range intersects with');
  1568. buffer = [
  1569. { pts: pts(10 - mapping) },
  1570. { pts: pts(11 - mapping) },
  1571. { pts: pts(12 - mapping) },
  1572. { pts: pts(15 - mapping) },
  1573. { pts: pts(18 - mapping) },
  1574. { pts: pts(20 - mapping) }
  1575. ];
  1576. start = 12;
  1577. end = 14;
  1578. expected = [
  1579. { pts: pts(10 - mapping) },
  1580. { pts: pts(11 - mapping) },
  1581. { pts: pts(15 - mapping) },
  1582. { pts: pts(18 - mapping) },
  1583. { pts: pts(20 - mapping) }
  1584. ];
  1585. actual = removeGopBuffer(buffer, start, end, mapping);
  1586. QUnit.deepEqual(actual, expected, 'removes gops that remove range intersects with');
  1587. buffer = [
  1588. { pts: pts(10 - mapping) },
  1589. { pts: pts(11 - mapping) },
  1590. { pts: pts(12 - mapping) },
  1591. { pts: pts(15 - mapping) },
  1592. { pts: pts(18 - mapping) },
  1593. { pts: pts(20 - mapping) }
  1594. ];
  1595. start = 13;
  1596. end = 14;
  1597. expected = [
  1598. { pts: pts(10 - mapping) },
  1599. { pts: pts(11 - mapping) },
  1600. { pts: pts(15 - mapping) },
  1601. { pts: pts(18 - mapping) },
  1602. { pts: pts(20 - mapping) }
  1603. ];
  1604. actual = removeGopBuffer(buffer, start, end, mapping);
  1605. QUnit.deepEqual(actual, expected, 'removes gops that remove range intersects with');
  1606. buffer = [
  1607. { pts: pts(10 - mapping) },
  1608. { pts: pts(11 - mapping) },
  1609. { pts: pts(12 - mapping) },
  1610. { pts: pts(15 - mapping) },
  1611. { pts: pts(18 - mapping) },
  1612. { pts: pts(20 - mapping) }
  1613. ];
  1614. start = 13;
  1615. end = 15;
  1616. expected = [
  1617. { pts: pts(10 - mapping) },
  1618. { pts: pts(11 - mapping) },
  1619. { pts: pts(18 - mapping) },
  1620. { pts: pts(20 - mapping) }
  1621. ];
  1622. actual = removeGopBuffer(buffer, start, end, mapping);
  1623. QUnit.deepEqual(actual, expected, 'removes gops that remove range intersects with');
  1624. buffer = [
  1625. { pts: pts(10 - mapping) },
  1626. { pts: pts(11 - mapping) },
  1627. { pts: pts(12 - mapping) },
  1628. { pts: pts(15 - mapping) },
  1629. { pts: pts(18 - mapping) },
  1630. { pts: pts(20 - mapping) }
  1631. ];
  1632. start = 12;
  1633. end = 17;
  1634. expected = [
  1635. { pts: pts(10 - mapping) },
  1636. { pts: pts(11 - mapping) },
  1637. { pts: pts(18 - mapping) },
  1638. { pts: pts(20 - mapping) }
  1639. ];
  1640. actual = removeGopBuffer(buffer, start, end, mapping);
  1641. QUnit.deepEqual(actual, expected, 'removes gops that remove range intersects with');
  1642. buffer = [
  1643. { pts: pts(10 - mapping) },
  1644. { pts: pts(11 - mapping) },
  1645. { pts: pts(12 - mapping) },
  1646. { pts: pts(15 - mapping) },
  1647. { pts: pts(18 - mapping) },
  1648. { pts: pts(20 - mapping) }
  1649. ];
  1650. start = 13;
  1651. end = 16;
  1652. expected = [
  1653. { pts: pts(10 - mapping) },
  1654. { pts: pts(11 - mapping) },
  1655. { pts: pts(18 - mapping) },
  1656. { pts: pts(20 - mapping) }
  1657. ];
  1658. actual = removeGopBuffer(buffer, start, end, mapping);
  1659. QUnit.deepEqual(actual, expected, 'removes gops that remove range intersects with');
  1660. start = 10;
  1661. end = 20;
  1662. buffer = [
  1663. { pts: pts(10 - mapping) },
  1664. { pts: pts(11 - mapping) },
  1665. { pts: pts(12 - mapping) },
  1666. { pts: pts(15 - mapping) },
  1667. { pts: pts(18 - mapping) },
  1668. { pts: pts(20 - mapping) }
  1669. ];
  1670. expected = [];
  1671. actual = removeGopBuffer(buffer, start, end, mapping);
  1672. QUnit.deepEqual(actual, expected,
  1673. 'removes entire buffer when buffer inside remove range');
  1674. start = 0;
  1675. end = 30;
  1676. buffer = [
  1677. { pts: pts(10 - mapping) },
  1678. { pts: pts(11 - mapping) },
  1679. { pts: pts(12 - mapping) },
  1680. { pts: pts(15 - mapping) },
  1681. { pts: pts(18 - mapping) },
  1682. { pts: pts(20 - mapping) }
  1683. ];
  1684. expected = [];
  1685. actual = removeGopBuffer(buffer, start, end, mapping);
  1686. QUnit.deepEqual(actual, expected,
  1687. 'removes entire buffer when buffer inside remove range');
  1688. });