playback-watcher.test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. import videojs from 'video.js';
  2. import QUnit from 'qunit';
  3. import {
  4. useFakeEnvironment,
  5. useFakeMediaSource,
  6. createPlayer,
  7. openMediaSource,
  8. standardXHRResponse
  9. } from './test-helpers.js';
  10. import PlaybackWatcher from '../src/playback-watcher';
  11. let monitorCurrentTime_;
  12. QUnit.module('PlaybackWatcher', {
  13. beforeEach(assert) {
  14. this.env = useFakeEnvironment(assert);
  15. this.requests = this.env.requests;
  16. this.mse = useFakeMediaSource();
  17. this.clock = this.env.clock;
  18. this.old = {};
  19. // setup a player
  20. this.player = createPlayer();
  21. this.player.autoplay(true);
  22. },
  23. afterEach() {
  24. this.env.restore();
  25. this.mse.restore();
  26. this.player.dispose();
  27. }
  28. });
  29. QUnit.test('skips over gap in firefox with waiting event', function(assert) {
  30. let hlsGapSkipEvents = 0;
  31. this.player.autoplay(true);
  32. this.player.tech_.on('usage', (event) => {
  33. if (event.name === 'hls-gap-skip') {
  34. hlsGapSkipEvents++;
  35. }
  36. });
  37. // create a buffer with a gap between 10 & 20 seconds
  38. this.player.tech_.buffered = function() {
  39. return videojs.createTimeRanges([[0, 10], [20, 30]]);
  40. };
  41. // set an arbitrary source
  42. this.player.src({
  43. src: 'master.m3u8',
  44. type: 'application/vnd.apple.mpegurl'
  45. });
  46. // start playback normally
  47. this.player.tech_.triggerReady();
  48. this.clock.tick(1);
  49. standardXHRResponse(this.requests.shift());
  50. openMediaSource(this.player, this.clock);
  51. this.player.tech_.trigger('canplay');
  52. this.player.tech_.trigger('play');
  53. this.player.tech_.trigger('playing');
  54. this.clock.tick(1);
  55. assert.equal(hlsGapSkipEvents, 0, 'there is no skipped gap');
  56. // seek to 10 seconds and wait 12 seconds
  57. this.player.currentTime(10);
  58. this.player.tech_.trigger('waiting');
  59. this.clock.tick(12000);
  60. // check that player jumped the gap
  61. assert.equal(Math.round(this.player.currentTime()),
  62. 20, 'Player seeked over gap after timer');
  63. assert.equal(hlsGapSkipEvents, 1, 'there is one skipped gap');
  64. });
  65. QUnit.test('skips over gap in chrome without waiting event', function(assert) {
  66. let hlsGapSkipEvents = 0;
  67. this.player.autoplay(true);
  68. this.player.tech_.on('usage', (event) => {
  69. if (event.name === 'hls-gap-skip') {
  70. hlsGapSkipEvents++;
  71. }
  72. });
  73. // create a buffer with a gap between 10 & 20 seconds
  74. this.player.tech_.buffered = function() {
  75. return videojs.createTimeRanges([[0, 10], [20, 30]]);
  76. };
  77. // set an arbitrary source
  78. this.player.src({
  79. src: 'master.m3u8',
  80. type: 'application/vnd.apple.mpegurl'
  81. });
  82. // start playback normally
  83. this.player.tech_.triggerReady();
  84. this.clock.tick(1);
  85. standardXHRResponse(this.requests.shift());
  86. openMediaSource(this.player, this.clock);
  87. this.player.tech_.trigger('canplay');
  88. this.player.tech_.trigger('play');
  89. this.player.tech_.trigger('playing');
  90. this.clock.tick(1);
  91. assert.equal(hlsGapSkipEvents, 0, 'there is no skipped gap');
  92. // seek to 10 seconds & simulate chrome waiting event
  93. this.player.currentTime(10);
  94. this.clock.tick(4000);
  95. // checks that player doesn't seek before timer expires
  96. assert.equal(this.player.currentTime(), 10, 'Player doesnt seek over gap pre-timer');
  97. this.clock.tick(10000);
  98. // check that player jumped the gap
  99. assert.equal(Math.round(this.player.currentTime()),
  100. 20, 'Player seeked over gap after timer');
  101. assert.equal(hlsGapSkipEvents, 1, 'there is one skipped gap');
  102. });
  103. QUnit.test('skips over gap in Chrome due to video underflow', function(assert) {
  104. let hlsVideoUnderflowEvents = 0;
  105. this.player.autoplay(true);
  106. this.player.tech_.on('usage', (event) => {
  107. if (event.name === 'hls-video-underflow') {
  108. hlsVideoUnderflowEvents++;
  109. }
  110. });
  111. this.player.tech_.buffered = () => {
  112. return videojs.createTimeRanges([[0, 10], [10.1, 20]]);
  113. };
  114. // set an arbitrary source
  115. this.player.src({
  116. src: 'master.m3u8',
  117. type: 'application/vnd.apple.mpegurl'
  118. });
  119. // start playback normally
  120. this.player.tech_.triggerReady();
  121. this.clock.tick(1);
  122. standardXHRResponse(this.requests.shift());
  123. openMediaSource(this.player, this.clock);
  124. this.player.tech_.trigger('play');
  125. this.player.tech_.trigger('playing');
  126. this.clock.tick(1);
  127. assert.equal(hlsVideoUnderflowEvents, 0, 'no video underflow event got triggered');
  128. this.player.currentTime(13);
  129. let seeks = [];
  130. this.player.tech_.setCurrentTime = (time) => {
  131. seeks.push(time);
  132. };
  133. this.player.tech_.trigger('waiting');
  134. assert.equal(seeks.length, 1, 'one seek');
  135. assert.equal(seeks[0], 13, 'player seeked to current time');
  136. assert.equal(hlsVideoUnderflowEvents, 1, 'triggered a video underflow event');
  137. });
  138. QUnit.test('seek to live point if we fall off the end of a live playlist',
  139. function(assert) {
  140. // set an arbitrary live source
  141. this.player.src({
  142. src: 'liveStart30sBefore.m3u8',
  143. type: 'application/vnd.apple.mpegurl'
  144. });
  145. // start playback normally
  146. this.player.tech_.triggerReady();
  147. this.clock.tick(1);
  148. standardXHRResponse(this.requests.shift());
  149. openMediaSource(this.player, this.clock);
  150. this.player.tech_.trigger('play');
  151. this.player.tech_.trigger('playing');
  152. this.clock.tick(1);
  153. this.player.currentTime(0);
  154. let seeks = [];
  155. this.player.tech_.setCurrentTime = (time) => {
  156. seeks.push(time);
  157. };
  158. this.player.tech_.hls.playbackWatcher_.seekable = () => {
  159. return videojs.createTimeRanges([[1, 45]]);
  160. };
  161. this.player.tech_.trigger('waiting');
  162. assert.equal(seeks.length, 1, 'one seek');
  163. assert.equal(seeks[0], 45, 'player seeked to live point');
  164. });
  165. QUnit.test('seeks to current time when stuck inside buffered region', function(assert) {
  166. // set an arbitrary live source
  167. this.player.src({
  168. src: 'liveStart30sBefore.m3u8',
  169. type: 'application/vnd.apple.mpegurl'
  170. });
  171. // start playback normally
  172. this.player.tech_.triggerReady();
  173. this.clock.tick(1);
  174. standardXHRResponse(this.requests.shift());
  175. openMediaSource(this.player, this.clock);
  176. this.player.tech_.trigger('canplay');
  177. this.player.tech_.trigger('play');
  178. this.player.tech_.trigger('playing');
  179. this.clock.tick(1);
  180. this.player.currentTime(10);
  181. let seeks = [];
  182. this.player.tech_.setCurrentTime = (time) => {
  183. seeks.push(time);
  184. };
  185. this.player.tech_.seeking = () => false;
  186. this.player.tech_.buffered = () => videojs.createTimeRanges([[0, 30]]);
  187. this.player.tech_.seekable = () => videojs.createTimeRanges([[0, 30]]);
  188. this.player.tech_.paused = () => false;
  189. // Playback watcher loop runs on a 250ms clock
  190. this.clock.tick(250);
  191. // Loop has run through once, `lastRecordedTime` should have been recorded
  192. // and `consecutiveUpdates` set to 0 to begin count
  193. assert.equal(this.player.tech_.hls.playbackWatcher_.lastRecordedTime, 10,
  194. 'Playback Watcher stored current time');
  195. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 0,
  196. 'consecutiveUpdates set to 0');
  197. // Playback watcher loop runs on a 250ms clock
  198. this.clock.tick(250);
  199. // Loop should increment consecutive updates until it is >= 5
  200. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 1,
  201. 'consecutiveUpdates incremented');
  202. // Playback watcher loop runs on a 250ms clock
  203. this.clock.tick(250);
  204. // Loop should increment consecutive updates until it is >= 5
  205. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 2,
  206. 'consecutiveUpdates incremented');
  207. // Playback watcher loop runs on a 250ms clock
  208. this.clock.tick(250);
  209. // Loop should increment consecutive updates until it is >= 5
  210. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 3,
  211. 'consecutiveUpdates incremented');
  212. // Playback watcher loop runs on a 250ms clock
  213. this.clock.tick(250);
  214. // Loop should increment consecutive updates until it is >= 5
  215. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 4,
  216. 'consecutiveUpdates incremented');
  217. // Playback watcher loop runs on a 250ms clock
  218. this.clock.tick(250);
  219. // Loop should increment consecutive updates until it is >= 5
  220. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 5,
  221. 'consecutiveUpdates incremented');
  222. // Playback watcher loop runs on a 250ms clock
  223. this.clock.tick(250);
  224. // Loop should see consecutive updates >= 5, call `waiting_`
  225. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 0,
  226. 'consecutiveUpdates reset');
  227. // Playback watcher seeked to currentTime in `waiting_` to correct the `unknownwaiting`
  228. assert.equal(seeks.length, 1, 'one seek');
  229. assert.equal(seeks[0], 10, 'player seeked to currentTime');
  230. });
  231. QUnit.test('does not seek to current time when stuck near edge of buffered region',
  232. function(assert) {
  233. // set an arbitrary live source
  234. this.player.src({
  235. src: 'liveStart30sBefore.m3u8',
  236. type: 'application/vnd.apple.mpegurl'
  237. });
  238. // start playback normally
  239. this.player.tech_.triggerReady();
  240. this.clock.tick(1);
  241. standardXHRResponse(this.requests.shift());
  242. openMediaSource(this.player, this.clock);
  243. this.player.tech_.trigger('canplay');
  244. this.player.tech_.trigger('play');
  245. this.player.tech_.trigger('playing');
  246. this.clock.tick(1);
  247. this.player.currentTime(29.98);
  248. let seeks = [];
  249. this.player.tech_.setCurrentTime = (time) => {
  250. seeks.push(time);
  251. };
  252. this.player.tech_.seeking = () => false;
  253. this.player.tech_.buffered = () => videojs.createTimeRanges([[0, 30]]);
  254. this.player.tech_.seekable = () => videojs.createTimeRanges([[0, 30]]);
  255. this.player.tech_.paused = () => false;
  256. // Playback watcher loop runs on a 250ms clock
  257. this.clock.tick(250);
  258. // Loop has run through once, `lastRecordedTime` should have been recorded
  259. // and `consecutiveUpdates` set to 0 to begin count
  260. assert.equal(this.player.tech_.hls.playbackWatcher_.lastRecordedTime, 29.98,
  261. 'Playback Watcher stored current time');
  262. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 0,
  263. 'consecutiveUpdates set to 0');
  264. // Playback watcher loop runs on a 250ms clock
  265. this.clock.tick(250);
  266. // Loop has run through a second time, should detect that currentTime hasn't made
  267. // progress while at the end of the buffer. Since the currentTime is at the end of the
  268. // buffer, `consecutiveUpdates` should not be incremented
  269. assert.equal(this.player.tech_.hls.playbackWatcher_.lastRecordedTime, 29.98,
  270. 'Playback Watcher stored current time');
  271. assert.equal(this.player.tech_.hls.playbackWatcher_.consecutiveUpdates, 0,
  272. 'consecutiveUpdates should still be 0');
  273. // no corrective seek
  274. assert.equal(seeks.length, 0, 'no seek');
  275. });
  276. QUnit.test('fires notifications when activated', function(assert) {
  277. let buffered = [[]];
  278. let seekable = [[]];
  279. let currentTime = 0;
  280. let hlsLiveResyncEvents = 0;
  281. let hlsVideoUnderflowEvents = 0;
  282. let playbackWatcher;
  283. this.player.src({
  284. src: 'liveStart30sBefore.m3u8',
  285. type: 'application/vnd.apple.mpegurl'
  286. });
  287. this.player.tech_.triggerReady();
  288. this.clock.tick(1);
  289. this.player.tech_.currentTime = function() {
  290. return currentTime;
  291. };
  292. this.player.tech_.buffered = function() {
  293. return {
  294. length: buffered.length,
  295. start(i) {
  296. return buffered[i][0];
  297. },
  298. end(i) {
  299. return buffered[i][1];
  300. }
  301. };
  302. };
  303. playbackWatcher = this.player.tech_.hls.playbackWatcher_;
  304. playbackWatcher.seekable = function() {
  305. return {
  306. length: seekable.length,
  307. start(i) {
  308. return seekable[i][0];
  309. },
  310. end(i) {
  311. return seekable[i][1];
  312. }
  313. };
  314. };
  315. this.player.tech_.on('usage', (event) => {
  316. if (event.name === 'hls-live-resync') {
  317. hlsLiveResyncEvents++;
  318. }
  319. if (event.name === 'hls-video-underflow') {
  320. hlsVideoUnderflowEvents++;
  321. }
  322. });
  323. currentTime = 19;
  324. seekable[0] = [20, 30];
  325. playbackWatcher.waiting_();
  326. assert.equal(hlsLiveResyncEvents, 1, 'triggered a liveresync event');
  327. currentTime = 12;
  328. seekable[0] = [0, 100];
  329. buffered = [[0, 9], [10, 20]];
  330. playbackWatcher.waiting_();
  331. assert.equal(hlsVideoUnderflowEvents, 1, 'triggered a videounderflow event');
  332. assert.equal(hlsLiveResyncEvents, 1, 'did not trigger an additional liveresync event');
  333. });
  334. QUnit.test('fixes bad seeks', function(assert) {
  335. // set an arbitrary live source
  336. this.player.src({
  337. src: 'liveStart30sBefore.m3u8',
  338. type: 'application/vnd.apple.mpegurl'
  339. });
  340. // start playback normally
  341. this.player.tech_.triggerReady();
  342. this.clock.tick(1);
  343. standardXHRResponse(this.requests.shift());
  344. openMediaSource(this.player, this.clock);
  345. this.player.tech_.trigger('play');
  346. this.player.tech_.trigger('playing');
  347. this.clock.tick(1);
  348. let playbackWatcher = this.player.tech_.hls.playbackWatcher_;
  349. let seeks = [];
  350. let seekable;
  351. let seeking;
  352. let currentTime;
  353. playbackWatcher.seekable = () => seekable;
  354. playbackWatcher.tech_ = {
  355. off: () => {},
  356. seeking: () => seeking,
  357. setCurrentTime: (time) => {
  358. seeks.push(time);
  359. },
  360. currentTime: () => currentTime
  361. };
  362. currentTime = 50;
  363. seekable = videojs.createTimeRanges([[1, 45]]);
  364. seeking = false;
  365. assert.ok(!playbackWatcher.fixesBadSeeks_(), 'does nothing when not seeking');
  366. assert.equal(seeks.length, 0, 'did not seek');
  367. seeking = true;
  368. assert.ok(playbackWatcher.fixesBadSeeks_(), 'acts when seek past seekable range');
  369. assert.equal(seeks.length, 1, 'seeked');
  370. assert.equal(seeks[0], 45, 'player seeked to live point');
  371. currentTime = 0;
  372. assert.ok(playbackWatcher.fixesBadSeeks_(), 'acts when seek before seekable range');
  373. assert.equal(seeks.length, 2, 'seeked');
  374. assert.equal(seeks[1], 1.1, 'player seeked to start of the live window');
  375. currentTime = 30;
  376. assert.ok(!playbackWatcher.fixesBadSeeks_(), 'does nothing when time within range');
  377. assert.equal(seeks.length, 2, 'did not seek');
  378. });
  379. QUnit.test('corrects seek outside of seekable', function(assert) {
  380. // set an arbitrary live source
  381. this.player.src({
  382. src: 'liveStart30sBefore.m3u8',
  383. type: 'application/vnd.apple.mpegurl'
  384. });
  385. // start playback normally
  386. this.player.tech_.triggerReady();
  387. this.clock.tick(1);
  388. standardXHRResponse(this.requests.shift());
  389. openMediaSource(this.player, this.clock);
  390. this.player.tech_.trigger('play');
  391. this.player.tech_.trigger('playing');
  392. this.clock.tick(1);
  393. let playbackWatcher = this.player.tech_.hls.playbackWatcher_;
  394. let seeks = [];
  395. let seekable;
  396. let seeking;
  397. let currentTime;
  398. playbackWatcher.seekable = () => seekable;
  399. playbackWatcher.tech_ = {
  400. off: () => {},
  401. seeking: () => seeking,
  402. setCurrentTime: (time) => {
  403. seeks.push(time);
  404. },
  405. currentTime: () => currentTime,
  406. // mocked out
  407. paused: () => false,
  408. buffered: () => videojs.createTimeRanges()
  409. };
  410. // waiting
  411. currentTime = 50;
  412. seekable = videojs.createTimeRanges([[1, 45]]);
  413. seeking = true;
  414. this.player.tech_.trigger('waiting');
  415. assert.equal(seeks.length, 1, 'seeked');
  416. assert.equal(seeks[0], 45, 'player seeked to live point');
  417. currentTime = 0;
  418. this.player.tech_.trigger('waiting');
  419. assert.equal(seeks.length, 2, 'seeked');
  420. assert.equal(seeks[1], 1.1, 'player seeked to start of the live window');
  421. // inside of seekable range
  422. currentTime = 10;
  423. this.player.tech_.trigger('waiting');
  424. assert.equal(seeks.length, 2, 'did not seek');
  425. currentTime = 50;
  426. // if we're not seeking, the case shouldn't be handled here
  427. seeking = false;
  428. this.player.tech_.trigger('waiting');
  429. assert.equal(seeks.length, 2, 'did not seek');
  430. // no check for 0 with seeking false because that should be handled by live falloff
  431. // checkCurrentTime
  432. seeking = true;
  433. currentTime = 50;
  434. playbackWatcher.checkCurrentTime_();
  435. assert.equal(seeks.length, 3, 'seeked');
  436. assert.equal(seeks[2], 45, 'player seeked to live point');
  437. currentTime = 0;
  438. playbackWatcher.checkCurrentTime_();
  439. assert.equal(seeks.length, 4, 'seeked');
  440. assert.equal(seeks[3], 1.1, 'player seeked to live point');
  441. currentTime = 10;
  442. playbackWatcher.checkCurrentTime_();
  443. assert.equal(seeks.length, 4, 'did not seek');
  444. seeking = false;
  445. currentTime = 50;
  446. playbackWatcher.checkCurrentTime_();
  447. assert.equal(seeks.length, 4, 'did not seek');
  448. currentTime = 0;
  449. playbackWatcher.checkCurrentTime_();
  450. assert.equal(seeks.length, 4, 'did not seek');
  451. });
  452. QUnit.test('calls fixesBadSeeks_ on seekablechanged', function(assert) {
  453. // set an arbitrary live source
  454. this.player.src({
  455. src: 'liveStart30sBefore.m3u8',
  456. type: 'application/vnd.apple.mpegurl'
  457. });
  458. // start playback normally
  459. this.player.tech_.triggerReady();
  460. this.clock.tick(1);
  461. standardXHRResponse(this.requests.shift());
  462. openMediaSource(this.player, this.clock);
  463. this.player.tech_.trigger('play');
  464. this.player.tech_.trigger('playing');
  465. this.clock.tick(1);
  466. let playbackWatcher = this.player.tech_.hls.playbackWatcher_;
  467. let fixesBadSeeks_ = 0;
  468. playbackWatcher.fixesBadSeeks_ = () => fixesBadSeeks_++;
  469. this.player.tech_.trigger('seekablechanged');
  470. assert.equal(fixesBadSeeks_, 1, 'fixesBadSeeks_ was called');
  471. });
  472. QUnit.module('PlaybackWatcher isolated functions', {
  473. beforeEach() {
  474. monitorCurrentTime_ = PlaybackWatcher.prototype.monitorCurrentTime_;
  475. PlaybackWatcher.prototype.monitorCurrentTime_ = () => {};
  476. this.playbackWatcher = new PlaybackWatcher({
  477. tech: {
  478. on: () => {},
  479. off: () => {}
  480. }
  481. });
  482. },
  483. afterEach() {
  484. this.playbackWatcher.dispose();
  485. PlaybackWatcher.prototype.monitorCurrentTime_ = monitorCurrentTime_;
  486. }
  487. });
  488. QUnit.test('skips gap from video underflow', function(assert) {
  489. assert.equal(
  490. this.playbackWatcher.gapFromVideoUnderflow_(videojs.createTimeRanges(), 0),
  491. null,
  492. 'returns null when buffer is empty');
  493. assert.equal(
  494. this.playbackWatcher.gapFromVideoUnderflow_(videojs.createTimeRanges([[0, 10]]), 13),
  495. null,
  496. 'returns null when there is only a previous buffer');
  497. assert.equal(
  498. this.playbackWatcher.gapFromVideoUnderflow_(
  499. videojs.createTimeRanges([[0, 10], [10.1, 20]]), 15),
  500. null,
  501. 'returns null when gap is too far from current time');
  502. assert.equal(
  503. this.playbackWatcher.gapFromVideoUnderflow_(
  504. videojs.createTimeRanges([[0, 10], [10.1, 20]]), 9.9),
  505. null,
  506. 'returns null when gap is after current time');
  507. assert.equal(
  508. this.playbackWatcher.gapFromVideoUnderflow_(
  509. videojs.createTimeRanges([[0, 10.1], [10.2, 20]]), 12.1),
  510. null,
  511. 'returns null when time is less than or equal to 2 seconds ahead');
  512. assert.equal(
  513. this.playbackWatcher.gapFromVideoUnderflow_(
  514. videojs.createTimeRanges([[0, 10], [10.1, 20]]), 14.1),
  515. null,
  516. 'returns null when time is greater than or equal to 4 seconds ahead');
  517. assert.deepEqual(
  518. this.playbackWatcher.gapFromVideoUnderflow_(
  519. videojs.createTimeRanges([[0, 10], [10.1, 20]]), 12.2),
  520. {start: 10, end: 10.1},
  521. 'returns gap when gap is small and time is greater than 2 seconds ahead in a buffer');
  522. assert.deepEqual(
  523. this.playbackWatcher.gapFromVideoUnderflow_(
  524. videojs.createTimeRanges([[0, 10], [10.1, 20]]), 13),
  525. {start: 10, end: 10.1},
  526. 'returns gap when gap is small and time is 3 seconds ahead in a buffer');
  527. assert.deepEqual(
  528. this.playbackWatcher.gapFromVideoUnderflow_(
  529. videojs.createTimeRanges([[0, 10], [10.1, 20]]), 13.9),
  530. {start: 10, end: 10.1},
  531. 'returns gap when gap is small and time is less than 4 seconds ahead in a buffer');
  532. // In a case where current time is outside of the buffered range, something odd must've
  533. // happened, but we should still allow the player to try to continue from that spot.
  534. assert.deepEqual(
  535. this.playbackWatcher.gapFromVideoUnderflow_(
  536. videojs.createTimeRanges([[0, 10], [10.1, 12.9]]), 13),
  537. {start: 10, end: 10.1},
  538. 'returns gap even when current time is not in buffered range');
  539. });
  540. QUnit.test('detects live window falloff', function(assert) {
  541. let beforeSeekableWindow_ =
  542. this.playbackWatcher.beforeSeekableWindow_.bind(this.playbackWatcher);
  543. assert.ok(
  544. beforeSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 10),
  545. 'true if playlist live and current time before seekable');
  546. assert.ok(
  547. !beforeSeekableWindow_(videojs.createTimeRanges([]), 10),
  548. 'false if no seekable range');
  549. assert.ok(
  550. !beforeSeekableWindow_(videojs.createTimeRanges([[0, 10]]), -1),
  551. 'false if seekable range starts at 0');
  552. assert.ok(
  553. !beforeSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 11),
  554. 'false if current time at seekable start');
  555. assert.ok(
  556. !beforeSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 20),
  557. 'false if current time at seekable end');
  558. assert.ok(
  559. !beforeSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 15),
  560. 'false if current time within seekable range');
  561. assert.ok(
  562. !beforeSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 21),
  563. 'false if current time past seekable range');
  564. assert.ok(
  565. beforeSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 0),
  566. 'true if current time is 0 and earlier than seekable range');
  567. });
  568. QUnit.test('detects beyond seekable window', function(assert) {
  569. let afterSeekableWindow_ =
  570. this.playbackWatcher.afterSeekableWindow_.bind(this.playbackWatcher);
  571. assert.ok(
  572. !afterSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 10.8),
  573. 'false if before seekable range');
  574. assert.ok(
  575. afterSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 20.2),
  576. 'true if after seekable range');
  577. assert.ok(
  578. !afterSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 10.9),
  579. 'false if within starting seekable range buffer');
  580. assert.ok(
  581. !afterSeekableWindow_(videojs.createTimeRanges([[11, 20]]), 20.1),
  582. 'false if within ending seekable range buffer');
  583. assert.ok(
  584. !afterSeekableWindow_(videojs.createTimeRanges(), 10),
  585. 'false if no seekable range');
  586. assert.ok(
  587. !afterSeekableWindow_(videojs.createTimeRanges([[0, 10]]), -0.2),
  588. 'false if current time is negative');
  589. assert.ok(
  590. !afterSeekableWindow_(videojs.createTimeRanges([[0, 10]]), 5),
  591. 'false if within seekable range');
  592. assert.ok(
  593. !afterSeekableWindow_(videojs.createTimeRanges([[0, 10]]), 0),
  594. 'false if within seekable range');
  595. assert.ok(
  596. !afterSeekableWindow_(videojs.createTimeRanges([[0, 10]]), 10),
  597. 'false if within seekable range');
  598. });