sync-controller.test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. import QUnit from 'qunit';
  2. import {
  3. default as SyncController,
  4. syncPointStrategies as strategies } from '../src/sync-controller.js';
  5. import { playlistWithDuration } from './test-helpers.js';
  6. function getStrategy(name) {
  7. for (let i = 0; i < strategies.length; i++) {
  8. if (strategies[i].name === name) {
  9. return strategies[i];
  10. }
  11. }
  12. throw new Error('No sync-strategy named "${name}" was found!');
  13. }
  14. QUnit.module('SyncController', {
  15. beforeEach() {
  16. this.syncController = new SyncController();
  17. }
  18. });
  19. QUnit.test('returns correct sync point for VOD strategy', function(assert) {
  20. let playlist = playlistWithDuration(40);
  21. let duration = 40;
  22. let timeline = 0;
  23. let vodStrategy = getStrategy('VOD');
  24. let syncPoint = vodStrategy.run(this.syncController, playlist, duration, timeline);
  25. assert.deepEqual(syncPoint, { time: 0, segmentIndex: 0 }, 'sync point found for vod');
  26. duration = Infinity;
  27. syncPoint = vodStrategy.run(this.syncController, playlist, duration, timeline);
  28. assert.equal(syncPoint, null, 'no syncpoint found for non vod ');
  29. });
  30. QUnit.test('returns correct sync point for ProgramDateTime strategy', function(assert) {
  31. let strategy = getStrategy('ProgramDateTime');
  32. let datetime = new Date(2012, 11, 12, 12, 12, 12);
  33. let playlist = playlistWithDuration(40);
  34. let timeline = 0;
  35. let duration = Infinity;
  36. let syncPoint;
  37. syncPoint = strategy.run(this.syncController, playlist, duration, timeline);
  38. assert.equal(syncPoint, null, 'no syncpoint when datetimeToDisplayTime not set');
  39. playlist.dateTimeObject = datetime;
  40. this.syncController.setDateTimeMapping(playlist);
  41. let newPlaylist = playlistWithDuration(40);
  42. syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline);
  43. assert.equal(syncPoint, null, 'no syncpoint when datetimeObject not set on playlist');
  44. newPlaylist.dateTimeObject = new Date(2012, 11, 12, 12, 12, 22);
  45. syncPoint = strategy.run(this.syncController, newPlaylist, duration, timeline);
  46. assert.deepEqual(syncPoint, {
  47. time: 10,
  48. segmentIndex: 0
  49. }, 'syncpoint found for ProgramDateTime set');
  50. });
  51. QUnit.test('returns correct sync point for Segment strategy', function(assert) {
  52. let strategy = getStrategy('Segment');
  53. let playlist = {
  54. segments: [
  55. { timeline: 0 },
  56. { timeline: 0 },
  57. { timeline: 1, start: 10 },
  58. { timeline: 1, start: 20 },
  59. { timeline: 1 },
  60. { timeline: 1 },
  61. { timeline: 1, start: 50 },
  62. { timeline: 1, start: 60 }
  63. ]
  64. };
  65. let currentTimeline;
  66. let syncPoint;
  67. currentTimeline = 0;
  68. syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 0);
  69. assert.equal(syncPoint, null, 'no syncpoint for timeline 0');
  70. currentTimeline = 1;
  71. syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 30);
  72. assert.deepEqual(syncPoint, { time: 20, segmentIndex: 3 },
  73. 'closest sync point found');
  74. syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 40);
  75. assert.deepEqual(syncPoint, { time: 50, segmentIndex: 6 },
  76. 'closest sync point found');
  77. syncPoint = strategy.run(this.syncController, playlist, 80, currentTimeline, 50);
  78. assert.deepEqual(syncPoint, { time: 50, segmentIndex: 6 },
  79. 'exact sync point found');
  80. });
  81. QUnit.test('returns correct sync point for Discontinuity strategy', function(assert) {
  82. let strategy = getStrategy('Discontinuity');
  83. let playlist = {
  84. targetDuration: 10,
  85. discontinuitySequence: 2,
  86. discontinuityStarts: [2, 5],
  87. segments: [
  88. { timeline: 2, start: 20, end: 30, duration: 10 },
  89. { timeline: 2, start: 30, end: 40, duration: 10 },
  90. { timeline: 3, start: 40, end: 50, duration: 10, discontinuity: true },
  91. { timeline: 3, start: 50, end: 60, duration: 10 },
  92. { timeline: 3, start: 60, end: 70, duration: 10 },
  93. { timeline: 4, start: 70, end: 80, duration: 10, discontinuity: true },
  94. { timeline: 4, start: 80, end: 90, duration: 10 },
  95. { timeline: 4, start: 90, end: 100, duration: 10 }
  96. ]
  97. };
  98. let segmentInfo = {
  99. playlist,
  100. segment: playlist.segments[2],
  101. mediaIndex: 2
  102. };
  103. let currentTimeline = 3;
  104. let syncPoint;
  105. syncPoint = strategy.run(this.syncController, playlist, 100, currentTimeline, 0);
  106. assert.equal(syncPoint, null, 'no sync point when no discontinuities saved');
  107. this.syncController.saveDiscontinuitySyncInfo_(segmentInfo);
  108. syncPoint = strategy.run(this.syncController, playlist, 100, currentTimeline, 55);
  109. assert.deepEqual(syncPoint, { time: 40, segmentIndex: 2 },
  110. 'found sync point for timeline 3');
  111. segmentInfo.mediaIndex = 6;
  112. segmentInfo.segment = playlist.segments[6];
  113. currentTimeline = 4;
  114. this.syncController.saveDiscontinuitySyncInfo_(segmentInfo);
  115. syncPoint = strategy.run(this.syncController, playlist, 100, currentTimeline, 90);
  116. assert.deepEqual(syncPoint, { time: 70, segmentIndex: 5 },
  117. 'found sync point for timeline 4');
  118. });
  119. QUnit.test('returns correct sync point for Playlist strategy', function(assert) {
  120. let strategy = getStrategy('Playlist');
  121. let playlist = { mediaSequence: 100 };
  122. let syncPoint;
  123. syncPoint = strategy.run(this.syncController, playlist, 40, 0);
  124. assert.equal(syncPoint, null, 'no sync point if no sync info');
  125. playlist.mediaSequence = 102;
  126. playlist.syncInfo = { time: 10, mediaSequence: 100};
  127. syncPoint = strategy.run(this.syncController, playlist, 40, 0);
  128. assert.deepEqual(syncPoint, { time: 10, segmentIndex: -2 },
  129. 'found sync point in playlist');
  130. });
  131. QUnit.test('saves expired info onto new playlist for sync point', function(assert) {
  132. let oldPlaylist = playlistWithDuration(50);
  133. let newPlaylist = playlistWithDuration(50);
  134. oldPlaylist.mediaSequence = 100;
  135. newPlaylist.mediaSequence = 103;
  136. oldPlaylist.segments[0].start = 390;
  137. oldPlaylist.segments[1].start = 400;
  138. this.syncController.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  139. assert.deepEqual(newPlaylist.syncInfo, { mediaSequence: 101, time: 400 },
  140. 'saved correct info for expired segment onto new playlist');
  141. });
  142. QUnit.test('Correctly updates time mapping and discontinuity info when probing segments',
  143. function(assert) {
  144. let syncCon = this.syncController;
  145. let playlist = playlistWithDuration(60);
  146. playlist.discontinuityStarts = [3];
  147. playlist.discontinuitySequence = 0;
  148. playlist.segments[3].discontinuity = true;
  149. playlist.segments.forEach((segment, i) => {
  150. if (i >= playlist.discontinuityStarts[0]) {
  151. segment.timeline = 1;
  152. } else {
  153. segment.timeline = 0;
  154. }
  155. });
  156. syncCon.probeTsSegment_ = function(segmentInfo) {
  157. return {
  158. // offset segment timing to make things interesting
  159. start: segmentInfo.mediaIndex * 10 + 5 + (6 * segmentInfo.timeline),
  160. end: segmentInfo.mediaIndex * 10 + 10 + 5 + (6 * segmentInfo.timeline)
  161. };
  162. };
  163. let segment = playlist.segments[0];
  164. let segmentInfo = {
  165. mediaIndex: 0,
  166. playlist,
  167. timeline: 0,
  168. timestampOffset: 0,
  169. startOfSegment: 0,
  170. segment
  171. };
  172. syncCon.probeSegmentInfo(segmentInfo);
  173. assert.ok(syncCon.timelines[0], 'created mapping object for timeline 0');
  174. assert.deepEqual(syncCon.timelines[0], { time: 0, mapping: -5 },
  175. 'mapping object correct');
  176. assert.equal(segment.start, 0, 'correctly calculated segment start');
  177. assert.equal(segment.end, 10, 'correctly calculated segment end');
  178. assert.ok(syncCon.discontinuities[1], 'created discontinuity info for timeline 1');
  179. assert.deepEqual(syncCon.discontinuities[1], { time: 30, accuracy: 3 },
  180. 'discontinuity sync info correct');
  181. segmentInfo.timestampOffset = null;
  182. segmentInfo.startOfSegment = 10;
  183. segmentInfo.mediaIndex = 1;
  184. segment = playlist.segments[1];
  185. segmentInfo.segment = segment;
  186. syncCon.probeSegmentInfo(segmentInfo);
  187. assert.equal(segment.start, 10, 'correctly calculated segment start');
  188. assert.equal(segment.end, 20, 'correctly calculated segment end');
  189. assert.deepEqual(syncCon.discontinuities[1], { time: 30, accuracy: 2 },
  190. 'discontinuity sync info correctly updated with new accuracy');
  191. segmentInfo.timestampOffset = 30;
  192. segmentInfo.startOfSegment = 30;
  193. segmentInfo.mediaIndex = 3;
  194. segmentInfo.timeline = 1;
  195. segment = playlist.segments[3];
  196. segmentInfo.segment = segment;
  197. syncCon.probeSegmentInfo(segmentInfo);
  198. assert.ok(syncCon.timelines[1], 'created mapping object for timeline 1');
  199. assert.deepEqual(syncCon.timelines[1], { time: 30, mapping: -11 },
  200. 'mapping object correct');
  201. assert.equal(segment.start, 30, 'correctly calculated segment start');
  202. assert.equal(segment.end, 40, 'correctly calculated segment end');
  203. assert.deepEqual(syncCon.discontinuities[1], { time: 30, accuracy: 0 },
  204. 'discontinuity sync info correctly updated with new accuracy');
  205. });
  206. QUnit.test('Correctly calculates expired time', function(assert) {
  207. let playlist = {
  208. targetDuration: 10,
  209. mediaSequence: 100,
  210. discontinuityStarts: [],
  211. syncInfo: {
  212. time: 50,
  213. mediaSequence: 95
  214. },
  215. segments: [
  216. {
  217. duration: 10,
  218. uri: '0.ts'
  219. },
  220. {
  221. duration: 10,
  222. uri: '1.ts'
  223. },
  224. {
  225. duration: 10,
  226. uri: '2.ts'
  227. },
  228. {
  229. duration: 10,
  230. uri: '3.ts'
  231. },
  232. {
  233. duration: 10,
  234. uri: '4.ts'
  235. }
  236. ]
  237. };
  238. let expired = this.syncController.getExpiredTime(playlist, Infinity);
  239. assert.equal(expired, 100, 'estimated expired time using segmentSync');
  240. playlist = {
  241. targetDuration: 10,
  242. discontinuityStarts: [],
  243. mediaSequence: 100,
  244. segments: [
  245. {
  246. duration: 10,
  247. uri: '0.ts'
  248. },
  249. {
  250. duration: 10,
  251. uri: '1.ts',
  252. start: 108.5,
  253. end: 118.4
  254. },
  255. {
  256. duration: 10,
  257. uri: '2.ts'
  258. },
  259. {
  260. duration: 10,
  261. uri: '3.ts'
  262. },
  263. {
  264. duration: 10,
  265. uri: '4.ts'
  266. }
  267. ]
  268. };
  269. expired = this.syncController.getExpiredTime(playlist, Infinity);
  270. assert.equal(expired, 98.5, 'estimated expired time using segmentSync');
  271. playlist = {
  272. discontinuityStarts: [],
  273. targetDuration: 10,
  274. mediaSequence: 100,
  275. syncInfo: {
  276. time: 50,
  277. mediaSequence: 95
  278. },
  279. segments: [
  280. {
  281. duration: 10,
  282. uri: '0.ts'
  283. },
  284. {
  285. duration: 10,
  286. uri: '1.ts',
  287. start: 108.5,
  288. end: 118.5
  289. },
  290. {
  291. duration: 10,
  292. uri: '2.ts'
  293. },
  294. {
  295. duration: 10,
  296. uri: '3.ts'
  297. },
  298. {
  299. duration: 10,
  300. uri: '4.ts'
  301. }
  302. ]
  303. };
  304. expired = this.syncController.getExpiredTime(playlist, Infinity);
  305. assert.equal(expired, 98.5, 'estimated expired time using segmentSync');
  306. playlist = {
  307. targetDuration: 10,
  308. discontinuityStarts: [],
  309. mediaSequence: 100,
  310. syncInfo: {
  311. time: 90.8,
  312. mediaSequence: 99
  313. },
  314. segments: [
  315. {
  316. duration: 10,
  317. uri: '0.ts'
  318. },
  319. {
  320. duration: 10,
  321. uri: '1.ts'
  322. },
  323. {
  324. duration: 10,
  325. uri: '2.ts',
  326. start: 118.5,
  327. end: 128.5
  328. },
  329. {
  330. duration: 10,
  331. uri: '3.ts'
  332. },
  333. {
  334. duration: 10,
  335. uri: '4.ts'
  336. }
  337. ]
  338. };
  339. expired = this.syncController.getExpiredTime(playlist, Infinity);
  340. assert.equal(expired, 100.8, 'estimated expired time using segmentSync');
  341. playlist = {
  342. targetDuration: 10,
  343. discontinuityStarts: [],
  344. mediaSequence: 100,
  345. endList: true,
  346. segments: [
  347. {
  348. duration: 10,
  349. uri: '0.ts'
  350. },
  351. {
  352. duration: 10,
  353. uri: '1.ts'
  354. },
  355. {
  356. duration: 10,
  357. uri: '2.ts'
  358. },
  359. {
  360. duration: 10,
  361. uri: '3.ts'
  362. },
  363. {
  364. duration: 10,
  365. uri: '4.ts'
  366. }
  367. ]
  368. };
  369. expired = this.syncController.getExpiredTime(playlist, 50);
  370. assert.equal(expired, 0, 'estimated expired time using segmentSync');
  371. playlist = {
  372. targetDuration: 10,
  373. discontinuityStarts: [],
  374. mediaSequence: 100,
  375. endList: true,
  376. segments: [
  377. {
  378. start: 0.006,
  379. duration: 10,
  380. uri: '0.ts',
  381. end: 9.982
  382. },
  383. {
  384. duration: 10,
  385. uri: '1.ts'
  386. },
  387. {
  388. duration: 10,
  389. uri: '2.ts'
  390. },
  391. {
  392. duration: 10,
  393. uri: '3.ts'
  394. },
  395. {
  396. duration: 10,
  397. uri: '4.ts'
  398. }
  399. ]
  400. };
  401. expired = this.syncController.getExpiredTime(playlist, 50);
  402. assert.equal(expired, 0, 'estimated expired time using segmentSync');
  403. });