You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

871 lines
32 KiB

  1. import QUnit from 'qunit';
  2. import {
  3. useFakeEnvironment
  4. } from './test-helpers.js';
  5. import * as MediaGroups from '../src/media-groups';
  6. import PlaylistLoader from '../src/playlist-loader';
  7. import DashPlaylistLoader from '../src/dash-playlist-loader';
  8. import noop from '../src/util/noop';
  9. QUnit.module('MediaGroups', {
  10. beforeEach(assert) {
  11. this.env = useFakeEnvironment(assert);
  12. this.clock = this.env.clock;
  13. this.requests = this.env.requests;
  14. },
  15. afterEach(assert) {
  16. this.env.restore();
  17. }
  18. });
  19. QUnit.test('createMediaTypes creates skeleton object for all supported media groups',
  20. function(assert) {
  21. const noopToString = noop.toString();
  22. const result = MediaGroups.createMediaTypes();
  23. assert.ok(result.AUDIO, 'created AUDIO media group object');
  24. assert.deepEqual(result.AUDIO.groups, {},
  25. 'created empty object for AUDIO groups');
  26. assert.deepEqual(result.AUDIO.tracks, {},
  27. 'created empty object for AUDIO tracks');
  28. assert.equal(result.AUDIO.activePlaylistLoader, null,
  29. 'AUDIO activePlaylistLoader is null');
  30. assert.equal(result.AUDIO.activeGroup.toString(), noopToString,
  31. 'created noop function for AUDIO activeGroup');
  32. assert.equal(result.AUDIO.activeTrack.toString(), noopToString,
  33. 'created noop function for AUDIO activeTrack');
  34. assert.equal(result.AUDIO.onGroupChanged.toString(), noopToString,
  35. 'created noop function for AUDIO onGroupChanged');
  36. assert.equal(result.AUDIO.onTrackChanged.toString(), noopToString,
  37. 'created noop function for AUDIO onTrackChanged');
  38. assert.ok(result.SUBTITLES, 'created SUBTITLES media group object');
  39. assert.deepEqual(result.SUBTITLES.groups, {},
  40. 'created empty object for SUBTITLES groups');
  41. assert.deepEqual(result.SUBTITLES.tracks, {},
  42. 'created empty object for SUBTITLES tracks');
  43. assert.equal(result.SUBTITLES.activePlaylistLoader, null,
  44. 'SUBTITLES activePlaylistLoader is null');
  45. assert.equal(result.SUBTITLES.activeGroup.toString(), noopToString,
  46. 'created noop function for SUBTITLES activeGroup');
  47. assert.equal(result.SUBTITLES.activeTrack.toString(), noopToString,
  48. 'created noop function for SUBTITLES activeTrack');
  49. assert.equal(result.SUBTITLES.onGroupChanged.toString(), noopToString,
  50. 'created noop function for SUBTITLES onGroupChanged');
  51. assert.equal(result.SUBTITLES.onTrackChanged.toString(), noopToString,
  52. 'created noop function for SUBTITLES onTrackChanged');
  53. assert.ok(result['CLOSED-CAPTIONS'], 'created CLOSED-CAPTIONS media group object');
  54. assert.deepEqual(result['CLOSED-CAPTIONS'].groups, {},
  55. 'created empty object for CLOSED-CAPTIONS groups');
  56. assert.deepEqual(result['CLOSED-CAPTIONS'].tracks, {},
  57. 'created empty object for CLOSED-CAPTIONS tracks');
  58. assert.equal(result['CLOSED-CAPTIONS'].activePlaylistLoader, null,
  59. 'CLOSED-CAPTIONS activePlaylistLoader is null');
  60. assert.equal(result['CLOSED-CAPTIONS'].activeGroup.toString(), noopToString,
  61. 'created noop function for CLOSED-CAPTIONS activeGroup');
  62. assert.equal(result['CLOSED-CAPTIONS'].activeTrack.toString(), noopToString,
  63. 'created noop function for CLOSED-CAPTIONS activeTrack');
  64. assert.equal(result['CLOSED-CAPTIONS'].onGroupChanged.toString(), noopToString,
  65. 'created noop function for CLOSED-CAPTIONS onGroupChanged');
  66. assert.equal(result['CLOSED-CAPTIONS'].onTrackChanged.toString(), noopToString,
  67. 'created noop function for CLOSED-CAPTIONS onTrackChanged');
  68. });
  69. QUnit.test('stopLoaders pauses segment loader and playlist loader when available',
  70. function(assert) {
  71. let segmentLoaderAbortCalls = 0;
  72. let segmentLoaderPauseCalls = 0;
  73. let playlistLoaderPauseCalls = 0;
  74. const segmentLoader = {
  75. abort: () => segmentLoaderAbortCalls++,
  76. pause: () => segmentLoaderPauseCalls++
  77. };
  78. const playlistLoader = {
  79. pause: () => playlistLoaderPauseCalls++
  80. };
  81. const mediaType = { activePlaylistLoader: null };
  82. MediaGroups.stopLoaders(segmentLoader, mediaType);
  83. assert.equal(segmentLoaderAbortCalls, 1, 'aborted segment loader');
  84. assert.equal(segmentLoaderPauseCalls, 1, 'paused segment loader');
  85. assert.equal(playlistLoaderPauseCalls, 0, 'no pause when no active playlist loader');
  86. mediaType.activePlaylistLoader = playlistLoader;
  87. MediaGroups.stopLoaders(segmentLoader, mediaType);
  88. assert.equal(segmentLoaderAbortCalls, 2, 'aborted segment loader');
  89. assert.equal(segmentLoaderPauseCalls, 2, 'paused segment loader');
  90. assert.equal(playlistLoaderPauseCalls, 1, 'pause active playlist loader');
  91. assert.equal(mediaType.activePlaylistLoader, null,
  92. 'clears active playlist loader for media group');
  93. });
  94. QUnit.test('startLoaders starts playlist loader when appropriate',
  95. function(assert) {
  96. let playlistLoaderLoadCalls = 0;
  97. let media = null;
  98. const playlistLoader = {
  99. load: () => playlistLoaderLoadCalls++,
  100. media: () => media
  101. };
  102. const mediaType = { activePlaylistLoader: null };
  103. MediaGroups.startLoaders(playlistLoader, mediaType);
  104. assert.equal(playlistLoaderLoadCalls, 1, 'called load on playlist loader');
  105. assert.strictEqual(mediaType.activePlaylistLoader, playlistLoader,
  106. 'set active playlist loader for media group');
  107. });
  108. QUnit.test('activeTrack returns the correct audio track', function(assert) {
  109. const type = 'AUDIO';
  110. const settings = { mediaTypes: MediaGroups.createMediaTypes() };
  111. const tracks = settings.mediaTypes[type].tracks;
  112. const activeTrack = MediaGroups.activeTrack[type](type, settings);
  113. assert.equal(activeTrack(), null, 'returns null when empty track list');
  114. tracks.track1 = { id: 'track1', enabled: false };
  115. tracks.track2 = { id: 'track2', enabled: false };
  116. tracks.track3 = { id: 'track3', enabled: false };
  117. assert.equal(activeTrack(), null, 'returns null when no active tracks');
  118. tracks.track3.enabled = true;
  119. assert.strictEqual(activeTrack(), tracks.track3, 'returns active track');
  120. tracks.track1.enabled = true;
  121. // video.js treats the first enabled track in the track list as the active track
  122. // so we want the same behavior here
  123. assert.strictEqual(activeTrack(), tracks.track1, 'returns first active track');
  124. tracks.track1.enabled = false;
  125. assert.strictEqual(activeTrack(), tracks.track3, 'returns active track');
  126. tracks.track3.enabled = false;
  127. assert.equal(activeTrack(), null, 'returns null when no active tracks');
  128. });
  129. QUnit.test('activeTrack returns the correct subtitle track', function(assert) {
  130. const type = 'SUBTITLES';
  131. const settings = { mediaTypes: MediaGroups.createMediaTypes() };
  132. const tracks = settings.mediaTypes[type].tracks;
  133. const activeTrack = MediaGroups.activeTrack[type](type, settings);
  134. assert.equal(activeTrack(), null, 'returns null when empty track list');
  135. tracks.track1 = { id: 'track1', mode: 'disabled' };
  136. tracks.track2 = { id: 'track2', mode: 'hidden' };
  137. tracks.track3 = { id: 'track3', mode: 'disabled' };
  138. assert.equal(activeTrack(), null, 'returns null when no active tracks');
  139. tracks.track3.mode = 'showing';
  140. assert.strictEqual(activeTrack(), tracks.track3, 'returns active track');
  141. tracks.track1.mode = 'showing';
  142. // video.js treats the first enabled track in the track list as the active track
  143. // so we want the same behavior here
  144. assert.strictEqual(activeTrack(), tracks.track1, 'returns first active track');
  145. tracks.track1.mode = 'disabled';
  146. assert.strictEqual(activeTrack(), tracks.track3, 'returns active track');
  147. tracks.track3.mode = 'hidden';
  148. assert.equal(activeTrack(), null, 'returns null when no active tracks');
  149. });
  150. QUnit.test('activeGroup returns the correct audio group', function(assert) {
  151. const type = 'AUDIO';
  152. let media = null;
  153. const settings = {
  154. mediaTypes: MediaGroups.createMediaTypes(),
  155. masterPlaylistLoader: {
  156. media: () => media
  157. }
  158. };
  159. const groups = settings.mediaTypes[type].groups;
  160. const tracks = settings.mediaTypes[type].tracks;
  161. const activeTrack = MediaGroups.activeTrack[type](type, settings);
  162. const activeGroup = MediaGroups.activeGroup(type, settings);
  163. assert.equal(activeGroup(), null, 'returns null when no media in masterPlaylistLoader');
  164. media = { attributes: { } };
  165. groups.main = [{ id: 'en' }, { id: 'fr' }];
  166. assert.strictEqual(activeGroup(), groups.main,
  167. 'defaults to main audio group when media does not specify audio group');
  168. groups.audio = [{ id: 'en'}, { id: 'fr' }];
  169. media.attributes.AUDIO = 'audio';
  170. assert.strictEqual(activeGroup(), groups.audio,
  171. 'returns list of variants in active audio group');
  172. tracks.en = { id: 'en', enabled: false };
  173. tracks.fr = { id: 'fr', enabled: false };
  174. assert.equal(activeGroup(activeTrack()), null,
  175. 'returns null when an active track is specified, but there is no active track');
  176. tracks.fr.enabled = true;
  177. assert.strictEqual(activeGroup(activeTrack()), groups.audio[1],
  178. 'returned the active group corresponding to the active track');
  179. });
  180. QUnit.test('activeGroup returns the correct subtitle group', function(assert) {
  181. const type = 'SUBTITLES';
  182. let media = null;
  183. const settings = {
  184. mediaTypes: MediaGroups.createMediaTypes(),
  185. masterPlaylistLoader: {
  186. media: () => media
  187. }
  188. };
  189. const groups = settings.mediaTypes[type].groups;
  190. const tracks = settings.mediaTypes[type].tracks;
  191. const activeTrack = MediaGroups.activeTrack[type](type, settings);
  192. const activeGroup = MediaGroups.activeGroup(type, settings);
  193. assert.equal(activeGroup(), null, 'returns null when no media in masterPlaylistLoader');
  194. media = { attributes: { } };
  195. // there is no default `main` group for subtitles like there is for audio
  196. assert.notOk(activeGroup(), 'returns null when media does not specify subtitle group');
  197. groups.subs = [{ id: 'en'}, { id: 'fr' }];
  198. media.attributes.SUBTITLES = 'subs';
  199. assert.strictEqual(activeGroup(), groups.subs,
  200. 'returns list of variants in active subtitle group');
  201. tracks.en = { id: 'en', mode: 'disabled' };
  202. tracks.fr = { id: 'fr', mode: 'disabled' };
  203. assert.equal(activeGroup(activeTrack()), null,
  204. 'returns null when an active track is specified, but there is no active track');
  205. tracks.fr.mode = 'showing';
  206. assert.strictEqual(activeGroup(activeTrack()), groups.subs[1],
  207. 'returned the active group corresponding to the active track');
  208. });
  209. QUnit.test('onGroupChanged updates active playlist loader and resyncs segment loader',
  210. function(assert) {
  211. let mainSegmentLoaderResetCalls = 0;
  212. let segmentLoaderResyncCalls = 0;
  213. let segmentLoaderPauseCalls = 0;
  214. const type = 'AUDIO';
  215. const media = { attributes: { AUDIO: 'main' } };
  216. const mainSegmentLoader = { resetEverything: () => mainSegmentLoaderResetCalls++ };
  217. const segmentLoader = {
  218. abort() {},
  219. pause: () => segmentLoaderPauseCalls++,
  220. load() {},
  221. playlist() {},
  222. resyncLoader: () => segmentLoaderResyncCalls++
  223. };
  224. const mockPlaylistLoader = () => {
  225. return {
  226. media: () => media,
  227. load() {},
  228. pause() {}
  229. };
  230. };
  231. const masterPlaylistLoader = mockPlaylistLoader();
  232. const settings = {
  233. segmentLoaders: {
  234. AUDIO: segmentLoader,
  235. main: mainSegmentLoader
  236. },
  237. mediaTypes: MediaGroups.createMediaTypes(),
  238. masterPlaylistLoader
  239. };
  240. const mediaType = settings.mediaTypes[type];
  241. const groups = mediaType.groups;
  242. const tracks = mediaType.tracks;
  243. groups.main = [
  244. { id: 'en', playlistLoader: null },
  245. { id: 'fr', playlistLoader: mockPlaylistLoader() },
  246. { id: 'es', playlistLoader: mockPlaylistLoader() }
  247. ];
  248. tracks.en = { id: 'en', enabled: false };
  249. tracks.fr = { id: 'fr', enabled: false };
  250. tracks.es = { id: 'es', enabled: false };
  251. mediaType.activeTrack = MediaGroups.activeTrack[type](type, settings);
  252. mediaType.activeGroup = MediaGroups.activeGroup(type, settings);
  253. const onGroupChanged = MediaGroups.onGroupChanged(type, settings);
  254. onGroupChanged();
  255. assert.equal(segmentLoaderPauseCalls, 1, 'loaders paused on group change');
  256. assert.equal(mainSegmentLoaderResetCalls, 0, 'no reset when no active group');
  257. assert.equal(segmentLoaderResyncCalls, 0, 'no resync when no active group');
  258. tracks.en.enabled = true;
  259. onGroupChanged();
  260. assert.equal(segmentLoaderPauseCalls, 2, 'loaders paused on group change');
  261. assert.equal(mainSegmentLoaderResetCalls, 0,
  262. 'no reset changing from no active playlist loader to group with no playlist loader');
  263. assert.equal(segmentLoaderResyncCalls, 0,
  264. 'no resync changing to group with no playlist loader');
  265. mediaType.activePlaylistLoader = groups.main[1].playlistLoader;
  266. onGroupChanged();
  267. assert.equal(segmentLoaderPauseCalls, 3, 'loaders paused on group change');
  268. assert.equal(mainSegmentLoaderResetCalls, 1,
  269. 'reset changing from active playlist loader to group with no playlist loader');
  270. assert.equal(segmentLoaderResyncCalls, 0,
  271. 'no resync changing to group with no playlist loader');
  272. tracks.en.enabled = false;
  273. tracks.fr.enabled = true;
  274. mediaType.activePlaylistLoader = groups.main[2].playlistLoader;
  275. onGroupChanged();
  276. assert.equal(segmentLoaderPauseCalls, 4, 'loaders paused on group change');
  277. assert.equal(mainSegmentLoaderResetCalls, 1,
  278. 'no reset changing to group with playlist loader');
  279. assert.equal(segmentLoaderResyncCalls, 1,
  280. 'resync changing to group with playlist loader');
  281. assert.strictEqual(mediaType.activePlaylistLoader, groups.main[1].playlistLoader,
  282. 'sets the correct active playlist loader');
  283. });
  284. QUnit.test('onTrackChanged updates active playlist loader and resets segment loader',
  285. function(assert) {
  286. let mainSegmentLoaderResetCalls = 0;
  287. let segmentLoaderResetCalls = 0;
  288. let segmentLoaderPauseCalls = 0;
  289. let segmentLoaderTrack;
  290. const type = 'AUDIO';
  291. const media = { attributes: { AUDIO: 'main' } };
  292. const mainSegmentLoader = { resetEverything: () => mainSegmentLoaderResetCalls++ };
  293. const segmentLoader = {
  294. abort() {},
  295. pause: () => segmentLoaderPauseCalls++,
  296. playlist() {},
  297. resetEverything: () => segmentLoaderResetCalls++
  298. };
  299. const mockPlaylistLoader = () => {
  300. return {
  301. media: () => media,
  302. load() {},
  303. pause() {}
  304. };
  305. };
  306. const masterPlaylistLoader = mockPlaylistLoader();
  307. const settings = {
  308. segmentLoaders: {
  309. AUDIO: segmentLoader,
  310. main: mainSegmentLoader
  311. },
  312. mediaTypes: MediaGroups.createMediaTypes(),
  313. masterPlaylistLoader
  314. };
  315. const mediaType = settings.mediaTypes[type];
  316. const groups = mediaType.groups;
  317. const tracks = mediaType.tracks;
  318. groups.main = [
  319. { id: 'en', playlistLoader: null },
  320. { id: 'fr', playlistLoader: mockPlaylistLoader() },
  321. { id: 'es', playlistLoader: mockPlaylistLoader() }
  322. ];
  323. tracks.en = { id: 'en', enabled: false };
  324. tracks.fr = { id: 'fr', enabled: false };
  325. tracks.es = { id: 'es', enabled: false };
  326. mediaType.activeTrack = MediaGroups.activeTrack[type](type, settings);
  327. mediaType.activeGroup = MediaGroups.activeGroup(type, settings);
  328. const onTrackChanged = MediaGroups.onTrackChanged(type, settings);
  329. onTrackChanged();
  330. assert.equal(segmentLoaderPauseCalls, 1, 'loaders paused on track change');
  331. assert.equal(mainSegmentLoaderResetCalls, 0, 'no main reset when no active group');
  332. assert.equal(segmentLoaderResetCalls, 0, 'no reset when no active group');
  333. tracks.en.enabled = true;
  334. onTrackChanged();
  335. assert.equal(segmentLoaderPauseCalls, 2, 'loaders paused on track change');
  336. assert.equal(mainSegmentLoaderResetCalls, 1,
  337. 'main reset changing to group with no playlist loader');
  338. assert.equal(segmentLoaderResetCalls, 0,
  339. 'no reset changing to group with no playlist loader');
  340. tracks.en.enabled = false;
  341. tracks.fr.enabled = true;
  342. mediaType.activePlaylistLoader = groups.main[1].playlistLoader;
  343. onTrackChanged();
  344. assert.equal(segmentLoaderPauseCalls, 3, 'loaders paused on track change');
  345. assert.equal(mainSegmentLoaderResetCalls, 1,
  346. 'no main reset changing to group with playlist loader');
  347. assert.equal(segmentLoaderResetCalls, 0,
  348. 'no reset when active group hasn\'t changed');
  349. assert.strictEqual(mediaType.activePlaylistLoader, groups.main[1].playlistLoader,
  350. 'sets the correct active playlist loader');
  351. mediaType.activePlaylistLoader = groups.main[2].playlistLoader;
  352. onTrackChanged();
  353. assert.equal(segmentLoaderPauseCalls, 4, 'loaders paused on track change');
  354. assert.equal(mainSegmentLoaderResetCalls, 1,
  355. 'no main reset changing to group with playlist loader');
  356. assert.equal(segmentLoaderResetCalls, 1,
  357. 'reset on track change');
  358. assert.strictEqual(mediaType.activePlaylistLoader, groups.main[1].playlistLoader,
  359. 'sets the correct active playlist loader');
  360. // setting the track on the segment loader only applies to the SUBTITLES case.
  361. // even though this test is testing type AUDIO, aside from this difference of setting
  362. // the track, the functionality between the types is the same.
  363. segmentLoader.track = (track) => segmentLoaderTrack = track;
  364. mediaType.activePlaylistLoader = groups.main[2].playlistLoader;
  365. onTrackChanged();
  366. assert.equal(segmentLoaderPauseCalls, 5, 'loaders paused on track change');
  367. assert.equal(mainSegmentLoaderResetCalls, 1,
  368. 'no main reset changing to group with playlist loader');
  369. assert.equal(segmentLoaderResetCalls, 2,
  370. 'reset on track change');
  371. assert.strictEqual(mediaType.activePlaylistLoader, groups.main[1].playlistLoader,
  372. 'sets the correct active playlist loader');
  373. assert.strictEqual(segmentLoaderTrack, tracks.fr,
  374. 'set the correct track on the segment loader');
  375. });
  376. QUnit.test('switches to default audio track when an error is encountered',
  377. function(assert) {
  378. let blacklistCurrentPlaylistCalls = 0;
  379. let onTrackChangedCalls = 0;
  380. const type = 'AUDIO';
  381. const segmentLoader = { abort() {}, pause() {} };
  382. const masterPlaylistLoader = {
  383. media() {
  384. return { attributes: { AUDIO: 'main' } };
  385. }
  386. };
  387. const settings = {
  388. segmentLoaders: { AUDIO: segmentLoader },
  389. mediaTypes: MediaGroups.createMediaTypes(),
  390. blacklistCurrentPlaylist: () => blacklistCurrentPlaylistCalls++,
  391. masterPlaylistLoader
  392. };
  393. const mediaType = settings.mediaTypes[type];
  394. const groups = mediaType.groups;
  395. const tracks = mediaType.tracks;
  396. mediaType.activeTrack = MediaGroups.activeTrack[type](type, settings);
  397. mediaType.activeGroup = MediaGroups.activeGroup(type, settings);
  398. mediaType.onTrackChanged = () => onTrackChangedCalls++;
  399. const onError = MediaGroups.onError[type](type, settings);
  400. groups.main = [ { id: 'en', default: true }, { id: 'fr'}, { id: 'es'} ];
  401. tracks.en = { id: 'en', enabed: false };
  402. tracks.fr = { id: 'fr', enabed: true };
  403. tracks.es = { id: 'es', enabed: false };
  404. onError();
  405. assert.equal(blacklistCurrentPlaylistCalls, 0, 'did not blacklist current playlist');
  406. assert.equal(onTrackChangedCalls, 1, 'called onTrackChanged after changing to default');
  407. assert.equal(tracks.en.enabled, true, 'enabled default track');
  408. assert.equal(tracks.fr.enabled, false, 'disabled active track');
  409. assert.equal(tracks.es.enabled, false, 'disabled track still disabled');
  410. assert.equal(this.env.log.warn.callCount, 1, 'logged a warning');
  411. this.env.log.warn.callCount = 0;
  412. onError();
  413. assert.equal(blacklistCurrentPlaylistCalls, 1, 'blacklist current playlist');
  414. assert.equal(onTrackChangedCalls, 1, 'did not call onTrackChanged after blacklist');
  415. assert.equal(tracks.en.enabled, true, 'default track still enabled');
  416. assert.equal(tracks.fr.enabled, false, 'disabled track still disabled');
  417. assert.equal(tracks.es.enabled, false, 'disabled track still disabled');
  418. assert.equal(this.env.log.warn.callCount, 0, 'no warning logged');
  419. });
  420. QUnit.test('disables subtitle track when an error is encountered', function(assert) {
  421. let onTrackChangedCalls = 0;
  422. const type = 'SUBTITLES';
  423. const segmentLoader = { abort() {}, pause() {} };
  424. const settings = {
  425. segmentLoaders: { SUBTITLES: segmentLoader },
  426. mediaTypes: MediaGroups.createMediaTypes()
  427. };
  428. const mediaType = settings.mediaTypes[type];
  429. const tracks = mediaType.tracks;
  430. mediaType.activeTrack = MediaGroups.activeTrack[type](type, settings);
  431. mediaType.onTrackChanged = () => onTrackChangedCalls++;
  432. const onError = MediaGroups.onError[type](type, settings);
  433. tracks.en = { id: 'en', mode: 'disabled' };
  434. tracks.fr = { id: 'fr', mode: 'disabled' };
  435. tracks.es = { id: 'es', mode: 'showing' };
  436. onError();
  437. assert.equal(onTrackChangedCalls, 1, 'called onTrackChanged after disabling track');
  438. assert.equal(tracks.en.mode, 'disabled', 'disabled track still disabled');
  439. assert.equal(tracks.fr.mode, 'disabled', 'disabled track still disabled');
  440. assert.equal(tracks.es.mode, 'disabled', 'disabled active track');
  441. assert.equal(this.env.log.warn.callCount, 1, 'logged a warning');
  442. this.env.log.warn.callCount = 0;
  443. });
  444. QUnit.test('setupListeners adds correct playlist loader listeners', function(assert) {
  445. const settings = {
  446. tech: {},
  447. requestOptions: {},
  448. segmentLoaders: {
  449. AUDIO: {},
  450. SUBTITLES: {}
  451. },
  452. mediaTypes: MediaGroups.createMediaTypes()
  453. };
  454. const listeners = [];
  455. const on = (event, cb) => listeners.push([event, cb]);
  456. const playlistLoader = { on };
  457. let type = 'SUBTITLES';
  458. MediaGroups.setupListeners[type](type, playlistLoader, settings);
  459. assert.equal(listeners.length, 3, 'setup 3 event listeners');
  460. assert.equal(listeners[0][0], 'loadedmetadata', 'setup loadedmetadata listener');
  461. assert.equal(typeof listeners[0][1], 'function', 'setup loadedmetadata listener');
  462. assert.equal(listeners[1][0], 'loadedplaylist', 'setup loadedmetadata listener');
  463. assert.equal(typeof listeners[1][1], 'function', 'setup loadedmetadata listener');
  464. assert.equal(listeners[2][0], 'error', 'setup loadedmetadata listener');
  465. assert.equal(typeof listeners[2][1], 'function', 'setup loadedmetadata listener');
  466. listeners.length = 0;
  467. type = 'AUDIO';
  468. MediaGroups.setupListeners[type](type, playlistLoader, settings);
  469. assert.equal(listeners.length, 3, 'setup 3 event listeners');
  470. assert.equal(listeners[0][0], 'loadedmetadata', 'setup loadedmetadata listener');
  471. assert.equal(typeof listeners[0][1], 'function', 'setup loadedmetadata listener');
  472. assert.equal(listeners[1][0], 'loadedplaylist', 'setup loadedmetadata listener');
  473. assert.equal(typeof listeners[1][1], 'function', 'setup loadedmetadata listener');
  474. assert.equal(listeners[2][0], 'error', 'setup loadedmetadata listener');
  475. assert.equal(typeof listeners[2][1], 'function', 'setup loadedmetadata listener');
  476. listeners.length = 0;
  477. MediaGroups.setupListeners[type](type, null, settings);
  478. assert.equal(listeners.length, 0, 'no event listeners setup when no playlist loader');
  479. });
  480. QUnit.module('MediaGroups - initialize', {
  481. beforeEach(assert) {
  482. this.mediaTypes = MediaGroups.createMediaTypes();
  483. this.master = {
  484. mediaGroups: {
  485. 'AUDIO': {},
  486. 'SUBTITLES': {},
  487. 'CLOSED-CAPTIONS': {}
  488. },
  489. playlists: []
  490. };
  491. this.settings = {
  492. mode: 'html5',
  493. hls: {},
  494. tech: {
  495. addRemoteTextTrack(track) {
  496. return { track };
  497. }
  498. },
  499. segmentLoaders: {
  500. AUDIO: { on() {} },
  501. SUBTITLES: { on() {} }
  502. },
  503. requestOptions: { withCredentials: false, timeout: 10 },
  504. master: this.master,
  505. mediaTypes: this.mediaTypes,
  506. blacklistCurrentPlaylist() {},
  507. sourceType: 'hls'
  508. };
  509. }
  510. });
  511. QUnit.test('initialize audio forces default track when no audio groups provided',
  512. function(assert) {
  513. const type = 'AUDIO';
  514. MediaGroups.initialize[type](type, this.settings);
  515. assert.deepEqual(this.master.mediaGroups[type],
  516. { main: { default: { default: true } } }, 'forced default audio group');
  517. assert.deepEqual(this.mediaTypes[type].groups,
  518. { main: [ { id: 'default', playlistLoader: null, default: true } ] },
  519. 'creates group properties and no playlist loader');
  520. assert.ok(this.mediaTypes[type].tracks.default, 'created default track');
  521. });
  522. QUnit.test('initialize audio correctly generates tracks and playlist loaders',
  523. function(assert) {
  524. const type = 'AUDIO';
  525. this.master.mediaGroups[type].aud1 = {
  526. en: { default: true, language: 'en' },
  527. fr: { default: false, language: 'fr', resolvedUri: 'aud1/fr.m3u8' }
  528. };
  529. this.master.mediaGroups[type].aud2 = {
  530. en: { default: true, language: 'en' },
  531. fr: { default: false, language: 'fr', resolvedUri: 'aud2/fr.m3u8' }
  532. };
  533. MediaGroups.initialize[type](type, this.settings);
  534. assert.notOk(this.master.mediaGroups[type].main, 'no default main group added');
  535. assert.deepEqual(this.mediaTypes[type].groups,
  536. {
  537. aud1: [
  538. { id: 'en', default: true, language: 'en', playlistLoader: null },
  539. { id: 'fr', default: false, language: 'fr', resolvedUri: 'aud1/fr.m3u8',
  540. // just so deepEqual passes since there is no other way to get the object
  541. // reference for the playlist loader. Assertions below will confirm that this is
  542. // not null.
  543. playlistLoader: this.mediaTypes[type].groups.aud1[1].playlistLoader }
  544. ],
  545. aud2: [
  546. { id: 'en', default: true, language: 'en', playlistLoader: null },
  547. { id: 'fr', default: false, language: 'fr', resolvedUri: 'aud2/fr.m3u8',
  548. // just so deepEqual passes since there is no other way to get the object
  549. // reference for the playlist loader. Assertions below will confirm that this is
  550. // not null.
  551. playlistLoader: this.mediaTypes[type].groups.aud2[1].playlistLoader }
  552. ]
  553. }, 'creates group properties');
  554. assert.ok(this.mediaTypes[type].groups.aud1[1].playlistLoader,
  555. 'playlistLoader created for non muxed audio group');
  556. assert.ok(this.mediaTypes[type].groups.aud2[1].playlistLoader,
  557. 'playlistLoader created for non muxed audio group');
  558. assert.ok(this.mediaTypes[type].tracks.en, 'created audio track');
  559. assert.ok(this.mediaTypes[type].tracks.fr, 'created audio track');
  560. });
  561. QUnit.test('initialize subtitles correctly generates tracks and playlist loaders',
  562. function(assert) {
  563. const type = 'SUBTITLES';
  564. this.master.mediaGroups[type].sub1 = {
  565. 'en': { language: 'en', resolvedUri: 'sub1/en.m3u8' },
  566. 'en-forced': { language: 'en', resolvedUri: 'sub1/en-forced.m3u8', forced: true },
  567. 'fr': { language: 'fr', resolvedUri: 'sub1/fr.m3u8' }
  568. };
  569. this.master.mediaGroups[type].sub2 = {
  570. 'en': { language: 'en', resolvedUri: 'sub2/en.m3u8' },
  571. 'en-forced': { language: 'en', resolvedUri: 'sub2/en-forced.m3u8', forced: true },
  572. 'fr': { language: 'fr', resolvedUri: 'sub2/fr.m3u8' }
  573. };
  574. MediaGroups.initialize[type](type, this.settings);
  575. assert.deepEqual(this.mediaTypes[type].groups,
  576. {
  577. sub1: [
  578. { id: 'en', language: 'en', resolvedUri: 'sub1/en.m3u8',
  579. playlistLoader: this.mediaTypes[type].groups.sub1[0].playlistLoader },
  580. { id: 'fr', language: 'fr', resolvedUri: 'sub1/fr.m3u8',
  581. playlistLoader: this.mediaTypes[type].groups.sub1[1].playlistLoader }
  582. ],
  583. sub2: [
  584. { id: 'en', language: 'en', resolvedUri: 'sub2/en.m3u8',
  585. playlistLoader: this.mediaTypes[type].groups.sub2[0].playlistLoader },
  586. { id: 'fr', language: 'fr', resolvedUri: 'sub2/fr.m3u8',
  587. playlistLoader: this.mediaTypes[type].groups.sub2[1].playlistLoader }
  588. ]
  589. }, 'creates group properties');
  590. assert.ok(this.mediaTypes[type].groups.sub1[0].playlistLoader,
  591. 'playlistLoader created');
  592. assert.ok(this.mediaTypes[type].groups.sub1[1].playlistLoader,
  593. 'playlistLoader created');
  594. assert.ok(this.mediaTypes[type].groups.sub2[0].playlistLoader,
  595. 'playlistLoader created');
  596. assert.ok(this.mediaTypes[type].groups.sub2[1].playlistLoader,
  597. 'playlistLoader created');
  598. assert.ok(this.mediaTypes[type].tracks.en, 'created text track');
  599. assert.ok(this.mediaTypes[type].tracks.fr, 'created text track');
  600. });
  601. QUnit.test('initialize closed-captions correctly generates tracks and NO loaders',
  602. function(assert) {
  603. const type = 'CLOSED-CAPTIONS';
  604. this.master.mediaGroups[type].CCs = {
  605. en608: { language: 'en', instreamId: 'CC1' },
  606. en708: { language: 'en', instreamId: 'SERVICE1' },
  607. fr608: { language: 'fr', instreamId: 'CC3' },
  608. fr708: { language: 'fr', instreamId: 'SERVICE3' }
  609. };
  610. MediaGroups.initialize[type](type, this.settings);
  611. assert.deepEqual(this.mediaTypes[type].groups,
  612. {
  613. CCs: [
  614. { id: 'en608', language: 'en', instreamId: 'CC1' },
  615. { id: 'fr608', language: 'fr', instreamId: 'CC3' }
  616. ]
  617. }, 'creates group properties');
  618. assert.ok(this.mediaTypes[type].tracks.en608, 'created text track');
  619. assert.ok(this.mediaTypes[type].tracks.fr608, 'created text track');
  620. });
  621. QUnit.test('initialize audio correctly uses HLS source type', function(assert) {
  622. this.master.mediaGroups.AUDIO.aud1 = {
  623. en: { default: true, language: 'en' },
  624. fr: { default: false, language: 'fr', resolvedUri: 'aud1/fr.m3u8' }
  625. };
  626. this.settings.sourceType = 'hls';
  627. MediaGroups.initialize.AUDIO('AUDIO', this.settings);
  628. assert.notOk(this.mediaTypes.AUDIO.groups.aud1[0].playlistLoader,
  629. 'no playlist loader because muxed (no URI)');
  630. assert.ok(this.mediaTypes.AUDIO.groups.aud1[1].playlistLoader instanceof PlaylistLoader,
  631. 'playlist loader is an HLS playlist loader');
  632. });
  633. QUnit.test('initialize audio correctly uses DASH source type', function(assert) {
  634. // allow async methods to resolve before next test
  635. const done = assert.async();
  636. this.master.mediaGroups.AUDIO.aud1 = {
  637. // playlists are resolved, no URI for DASH
  638. en: { default: true, language: 'en', playlists: [{}] },
  639. fr: { default: false, language: 'fr', playlists: [{}] }
  640. };
  641. this.settings.sourceType = 'dash';
  642. MediaGroups.initialize.AUDIO('AUDIO', this.settings);
  643. assert.ok(
  644. this.mediaTypes.AUDIO.groups.aud1[0].playlistLoader instanceof DashPlaylistLoader,
  645. 'playlist loader is a DASH playlist loader');
  646. assert.ok(
  647. this.mediaTypes.AUDIO.groups.aud1[1].playlistLoader instanceof DashPlaylistLoader,
  648. 'playlist loader is a DASH playlist loader');
  649. done();
  650. });
  651. QUnit.test('initialize audio does not create DASH playlist loader if no playlists',
  652. function(assert) {
  653. this.master.mediaGroups.AUDIO.aud1 = {
  654. en: { default: true, language: 'en' },
  655. fr: { default: false, language: 'fr' }
  656. };
  657. this.settings.sourceType = 'dash';
  658. MediaGroups.initialize.AUDIO('AUDIO', this.settings);
  659. assert.notOk(this.mediaTypes.AUDIO.groups.aud1[0].playlistLoader,
  660. 'no playlist loader when misconfigured');
  661. assert.notOk(this.mediaTypes.AUDIO.groups.aud1[1].playlistLoader,
  662. 'no playlist loader when misconfigured');
  663. });
  664. QUnit.test('initialize audio does not create playlist loader for alternate tracks with' +
  665. ' main stream as URI attribute', function(assert) {
  666. this.master.mediaGroups.AUDIO.aud1 = {
  667. en: { default: true, language: 'en', resolvedUri: 'main.m3u8' },
  668. fr: { default: false, language: 'fr', resolvedUri: 'audio/fr.m3u8' }
  669. };
  670. this.master.playlists = [{
  671. attributes: { AUDIO: 'aud1' },
  672. resolvedUri: 'main.m3u8'
  673. }];
  674. MediaGroups.initialize.AUDIO('AUDIO', this.settings);
  675. assert.notOk(this.mediaTypes.AUDIO.groups.aud1[0].resolvedUri,
  676. 'resolvedUri proeprty deleted');
  677. assert.notOk(this.mediaTypes.AUDIO.groups.aud1[0].playlistLoader,
  678. 'no playlist loader for alternate audio in main stream');
  679. assert.ok(this.mediaTypes.AUDIO.groups.aud1[1].playlistLoader instanceof PlaylistLoader,
  680. 'playlist loader for alternate audio not in main stream');
  681. });
  682. QUnit.test('initialize subtitles correctly uses HLS source type', function(assert) {
  683. this.master.mediaGroups.SUBTITLES.sub1 = {
  684. en: { language: 'en', resolvedUri: 'sub1/en.m3u8' },
  685. fr: { language: 'fr', resolvedUri: 'sub1/fr.m3u8' }
  686. };
  687. this.settings.sourceType = 'hls';
  688. MediaGroups.initialize.SUBTITLES('SUBTITLES', this.settings);
  689. assert.ok(
  690. this.mediaTypes.SUBTITLES.groups.sub1[0].playlistLoader instanceof PlaylistLoader,
  691. 'playlist loader is an HLS playlist loader');
  692. assert.ok(
  693. this.mediaTypes.SUBTITLES.groups.sub1[1].playlistLoader instanceof PlaylistLoader,
  694. 'playlist loader is an HLS playlist loader');
  695. });
  696. QUnit.test('initialize subtitles correctly uses DASH source type', function(assert) {
  697. // allow async methods to resolve before next test
  698. const done = assert.async();
  699. this.master.mediaGroups.SUBTITLES.sub1 = {
  700. // playlists are resolved, no URI for DASH
  701. en: { language: 'en', playlists: [{}] },
  702. fr: { language: 'fr', playlists: [{}] }
  703. };
  704. this.settings.sourceType = 'dash';
  705. MediaGroups.initialize.AUDIO('AUDIO', this.settings);
  706. MediaGroups.initialize.SUBTITLES('SUBTITLES', this.settings);
  707. assert.ok(
  708. this.mediaTypes.SUBTITLES.groups.sub1[0].playlistLoader instanceof DashPlaylistLoader,
  709. 'playlist loader is a DASH playlist loader');
  710. assert.ok(
  711. this.mediaTypes.SUBTITLES.groups.sub1[1].playlistLoader instanceof DashPlaylistLoader,
  712. 'playlist loader is a DASH playlist loader');
  713. done();
  714. });