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