reload-source-on-error.test.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import QUnit from 'qunit';
  2. import videojs from 'video.js';
  3. import sinon from 'sinon';
  4. import reloadSourceOnError from '../src/reload-source-on-error';
  5. QUnit.module('ReloadSourceOnError', {
  6. beforeEach() {
  7. this.clock = sinon.useFakeTimers();
  8. // setup a player
  9. this.player = new videojs.EventTarget();
  10. this.player.currentValues = {
  11. currentTime: 10,
  12. duration: 12
  13. };
  14. this.player.ready = (callback) => {
  15. callback.call(this.player);
  16. };
  17. this.tech = {
  18. currentSource_: {
  19. src: 'thisisasource.m3u8',
  20. type: 'doesn\'t/matter'
  21. }
  22. };
  23. this.player.tech = () => {
  24. return this.tech;
  25. };
  26. this.player.duration = () => {
  27. return this.player.currentValues.duration;
  28. };
  29. this.player.src = (source) => {
  30. this.player.currentValues.currentTime = 0;
  31. this.player.src.calledWith.push(source);
  32. };
  33. this.player.src.calledWith = [];
  34. this.player.currentTime = (time) => {
  35. if (time) {
  36. this.player.currentTime.calledWith.push(time);
  37. this.player.currentValues.currentTime = time;
  38. }
  39. return this.player.currentValues.currentTime;
  40. };
  41. this.player.currentTime.calledWith = [];
  42. this.player.play = () => {
  43. this.player.play.called++;
  44. };
  45. this.player.play.called = 0;
  46. this.player.reloadSourceOnError = reloadSourceOnError;
  47. this.clock.tick(60 * 1000);
  48. this.oldLog = videojs.log.error;
  49. this.errors = [];
  50. videojs.log.error = (...args) => {
  51. this.errors.push(...args);
  52. };
  53. },
  54. afterEach() {
  55. this.clock.restore();
  56. videojs.log.error = this.oldLog;
  57. }
  58. });
  59. QUnit.test('triggers on player error', function(assert) {
  60. this.player.reloadSourceOnError();
  61. this.player.trigger('error', -2);
  62. assert.equal(this.player.src.calledWith.length, 1, 'player.src was only called once');
  63. assert.deepEqual(this.player.src.calledWith[0],
  64. this.tech.currentSource_,
  65. 'player.src was called with player.currentSource');
  66. });
  67. QUnit.test('seeks to currentTime in VOD', function(assert) {
  68. this.player.reloadSourceOnError();
  69. this.player.trigger('error', -2);
  70. this.player.trigger('loadedmetadata');
  71. assert.equal(this.player.currentTime.calledWith.length,
  72. 1,
  73. 'player.currentTime was only called once');
  74. assert.deepEqual(this.player.currentTime.calledWith[0],
  75. 10,
  76. 'player.currentTime was called with the right value');
  77. });
  78. QUnit.test('doesn\'t seek to currentTime in live', function(assert) {
  79. this.player.reloadSourceOnError();
  80. this.player.currentValues.duration = Infinity;
  81. this.player.trigger('error', -2);
  82. this.player.trigger('loadedmetadata');
  83. assert.equal(this.player.currentTime.calledWith.length,
  84. 0,
  85. 'player.currentTime was not called');
  86. assert.deepEqual(this.player.currentTime(), 0, 'player.currentTime is still zero');
  87. });
  88. QUnit.test('by default, only allows a retry once every 30 seconds', function(assert) {
  89. let hlsErrorReloadInitializedEvents = 0;
  90. let hlsErrorReloadEvents = 0;
  91. let hlsErrorReloadCanceledEvents = 0;
  92. this.player.on('usage', (event) => {
  93. if (event.name === 'hls-error-reload-initialized') {
  94. hlsErrorReloadInitializedEvents++;
  95. }
  96. });
  97. this.player.on('usage', (event) => {
  98. if (event.name === 'hls-error-reload') {
  99. hlsErrorReloadEvents++;
  100. }
  101. });
  102. this.player.on('usage', (event) => {
  103. if (event.name === 'hls-error-reload-canceled') {
  104. hlsErrorReloadCanceledEvents++;
  105. }
  106. });
  107. assert.equal(hlsErrorReloadInitializedEvents, 0, 'the plugin has not been initialized');
  108. assert.equal(hlsErrorReloadEvents, 0, 'no source was set');
  109. assert.equal(hlsErrorReloadCanceledEvents, 0,
  110. 'reload canceled event has not been triggered');
  111. this.player.reloadSourceOnError();
  112. this.player.trigger('error', -2);
  113. this.player.trigger('loadedmetadata');
  114. assert.equal(hlsErrorReloadInitializedEvents, 1, 'the plugin has been initialized');
  115. assert.equal(hlsErrorReloadEvents, 1, 'src was set after an error caused the reload');
  116. assert.equal(this.player.src.calledWith.length, 1, 'player.src was only called once');
  117. // Advance 59 seconds
  118. this.clock.tick(59 * 1000);
  119. this.player.trigger('error', -2);
  120. this.player.trigger('loadedmetadata');
  121. assert.equal(this.player.src.calledWith.length, 2, 'player.src was called twice');
  122. // Advance 29 seconds
  123. this.clock.tick(29 * 1000);
  124. this.player.trigger('error', -2);
  125. this.player.trigger('loadedmetadata');
  126. assert.equal(hlsErrorReloadCanceledEvents, 1,
  127. 'did not reload the source because not enough time has elapsed');
  128. assert.equal(this.player.src.calledWith.length, 2, 'player.src was called twice');
  129. });
  130. QUnit.test('allows you to override the default retry interval', function(assert) {
  131. this.player.reloadSourceOnError({
  132. errorInterval: 60
  133. });
  134. this.player.trigger('error', -2);
  135. this.player.trigger('loadedmetadata');
  136. assert.equal(this.player.src.calledWith.length, 1, 'player.src was only called once');
  137. // Advance 59 seconds
  138. this.clock.tick(59 * 1000);
  139. this.player.trigger('error', -2);
  140. this.player.trigger('loadedmetadata');
  141. assert.equal(this.player.src.calledWith.length, 1, 'player.src was only called once');
  142. });
  143. QUnit.test('the plugin cleans up after it\'s previous incarnation when called again',
  144. function(assert) {
  145. this.player.reloadSourceOnError();
  146. this.player.reloadSourceOnError();
  147. this.player.trigger('error', -2);
  148. assert.equal(this.player.src.calledWith.length, 1, 'player.src was only called once');
  149. });
  150. QUnit.test('allows you to provide a getSource function', function(assert) {
  151. const newSource = {
  152. src: 'newsource.m3u8',
  153. type: 'this/matters'
  154. };
  155. this.player.reloadSourceOnError({
  156. getSource: (next) => {
  157. return next(newSource);
  158. }
  159. });
  160. this.player.trigger('error', -2);
  161. assert.equal(this.player.src.calledWith.length, 1, 'player.src was only called once');
  162. assert.deepEqual(this.player.src.calledWith[0],
  163. newSource,
  164. 'player.src was called with return value of options.getSource()');
  165. });
  166. QUnit.test('errors if getSource is not a function', function(assert) {
  167. this.player.reloadSourceOnError({
  168. getSource: 'totally not a function'
  169. });
  170. this.player.trigger('error', -2);
  171. assert.equal(this.player.src.calledWith.length, 0, 'player.src was never called');
  172. assert.equal(this.errors.length, 1, 'videojs.log.error was called once');
  173. });
  174. QUnit.test('should not set source if getSource returns null or undefined',
  175. function(assert) {
  176. this.player.reloadSourceOnError({
  177. getSource: () => undefined
  178. });
  179. this.player.trigger('error', -2);
  180. assert.equal(this.player.src.calledWith.length, 0, 'player.src was never called');
  181. this.player.reloadSourceOnError({
  182. getSource: () => null
  183. });
  184. this.player.trigger('error', -2);
  185. assert.equal(this.player.src.calledWith.length, 0, 'player.src was never called');
  186. });