Fix request timeouts (#770)
* Removed segment timeout when on the lowest enabled rendition * Move timeout check to only run when needed
Showing
7 changed files
with
170 additions
and
93 deletions
... | @@ -59,6 +59,10 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -59,6 +59,10 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
59 | this.hls_ = tech.hls; | 59 | this.hls_ = tech.hls; |
60 | this.mode_ = mode; | 60 | this.mode_ = mode; |
61 | this.audioTracks_ = []; | 61 | this.audioTracks_ = []; |
62 | this.requestOptions_ = { | ||
63 | withCredentials: this.withCredentials, | ||
64 | timeout: null | ||
65 | }; | ||
62 | 66 | ||
63 | this.mediaSource = new videojs.MediaSource({ mode }); | 67 | this.mediaSource = new videojs.MediaSource({ mode }); |
64 | this.mediaSource.on('audioinfo', (e) => this.trigger(e)); | 68 | this.mediaSource.on('audioinfo', (e) => this.trigger(e)); |
... | @@ -69,7 +73,6 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -69,7 +73,6 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
69 | hls: this.hls_, | 73 | hls: this.hls_, |
70 | mediaSource: this.mediaSource, | 74 | mediaSource: this.mediaSource, |
71 | currentTime: this.tech_.currentTime.bind(this.tech_), | 75 | currentTime: this.tech_.currentTime.bind(this.tech_), |
72 | withCredentials: this.withCredentials, | ||
73 | seekable: () => this.seekable(), | 76 | seekable: () => this.seekable(), |
74 | seeking: () => this.tech_.seeking(), | 77 | seeking: () => this.tech_.seeking(), |
75 | setCurrentTime: (a) => this.tech_.setCurrentTime(a), | 78 | setCurrentTime: (a) => this.tech_.setCurrentTime(a), |
... | @@ -90,11 +93,14 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -90,11 +93,14 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
90 | 93 | ||
91 | this.masterPlaylistLoader_.on('loadedmetadata', () => { | 94 | this.masterPlaylistLoader_.on('loadedmetadata', () => { |
92 | let media = this.masterPlaylistLoader_.media(); | 95 | let media = this.masterPlaylistLoader_.media(); |
96 | let requestTimeout = (this.masterPlaylistLoader_.targetDuration * 1.5) * 1000; | ||
97 | |||
98 | this.requestOptions_.timeout = requestTimeout; | ||
93 | 99 | ||
94 | // if this isn't a live video and preload permits, start | 100 | // if this isn't a live video and preload permits, start |
95 | // downloading segments | 101 | // downloading segments |
96 | if (media.endList && this.tech_.preload() !== 'none') { | 102 | if (media.endList && this.tech_.preload() !== 'none') { |
97 | this.mainSegmentLoader_.playlist(media); | 103 | this.mainSegmentLoader_.playlist(media, this.requestOptions_); |
98 | this.mainSegmentLoader_.expired(this.masterPlaylistLoader_.expired_); | 104 | this.mainSegmentLoader_.expired(this.masterPlaylistLoader_.expired_); |
99 | this.mainSegmentLoader_.load(); | 105 | this.mainSegmentLoader_.load(); |
100 | } | 106 | } |
... | @@ -122,7 +128,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -122,7 +128,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
122 | // that the segments have changed in some way and use that to | 128 | // that the segments have changed in some way and use that to |
123 | // update the SegmentLoader instead of doing it twice here and | 129 | // update the SegmentLoader instead of doing it twice here and |
124 | // on `mediachange` | 130 | // on `mediachange` |
125 | this.mainSegmentLoader_.playlist(updatedPlaylist); | 131 | this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); |
126 | this.mainSegmentLoader_.expired(this.masterPlaylistLoader_.expired_); | 132 | this.mainSegmentLoader_.expired(this.masterPlaylistLoader_.expired_); |
127 | this.updateDuration(); | 133 | this.updateDuration(); |
128 | 134 | ||
... | @@ -143,14 +149,23 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -143,14 +149,23 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
143 | 149 | ||
144 | this.masterPlaylistLoader_.on('mediachange', () => { | 150 | this.masterPlaylistLoader_.on('mediachange', () => { |
145 | let media = this.masterPlaylistLoader_.media(); | 151 | let media = this.masterPlaylistLoader_.media(); |
152 | let requestTimeout = (this.masterPlaylistLoader_.targetDuration * 1.5) * 1000; | ||
146 | 153 | ||
147 | this.mainSegmentLoader_.abort(); | 154 | this.mainSegmentLoader_.abort(); |
148 | 155 | ||
156 | // If we don't have any more available playlists, we don't want to | ||
157 | // timeout the request. | ||
158 | if (this.masterPlaylistLoader_.isLowestEnabledRendition_()) { | ||
159 | this.requestOptions_.timeout = 0; | ||
160 | } else { | ||
161 | this.requestOptions_.timeout = requestTimeout; | ||
162 | } | ||
163 | |||
149 | // TODO: Create a new event on the PlaylistLoader that signals | 164 | // TODO: Create a new event on the PlaylistLoader that signals |
150 | // that the segments have changed in some way and use that to | 165 | // that the segments have changed in some way and use that to |
151 | // update the SegmentLoader instead of doing it twice here and | 166 | // update the SegmentLoader instead of doing it twice here and |
152 | // on `loadedplaylist` | 167 | // on `loadedplaylist` |
153 | this.mainSegmentLoader_.playlist(media); | 168 | this.mainSegmentLoader_.playlist(media, this.requestOptions_); |
154 | this.mainSegmentLoader_.expired(this.masterPlaylistLoader_.expired_); | 169 | this.mainSegmentLoader_.expired(this.masterPlaylistLoader_.expired_); |
155 | this.mainSegmentLoader_.load(); | 170 | this.mainSegmentLoader_.load(); |
156 | 171 | ||
... | @@ -340,7 +355,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -340,7 +355,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
340 | let media = this.audioPlaylistLoader_.media(); | 355 | let media = this.audioPlaylistLoader_.media(); |
341 | /* eslint-enable no-shadow */ | 356 | /* eslint-enable no-shadow */ |
342 | 357 | ||
343 | this.audioSegmentLoader_.playlist(media); | 358 | this.audioSegmentLoader_.playlist(media, this.requestOptions_); |
344 | this.addMimeType_(this.audioSegmentLoader_, 'mp4a.40.2', media); | 359 | this.addMimeType_(this.audioSegmentLoader_, 'mp4a.40.2', media); |
345 | 360 | ||
346 | // if the video is already playing, or if this isn't a live video and preload | 361 | // if the video is already playing, or if this isn't a live video and preload |
... | @@ -370,7 +385,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { | ... | @@ -370,7 +385,7 @@ export default class MasterPlaylistController extends videojs.EventTarget { |
370 | return; | 385 | return; |
371 | } | 386 | } |
372 | 387 | ||
373 | this.audioSegmentLoader_.playlist(updatedPlaylist); | 388 | this.audioSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); |
374 | }); | 389 | }); |
375 | 390 | ||
376 | this.audioPlaylistLoader_.on('error', () => { | 391 | this.audioPlaylistLoader_.on('error', () => { | ... | ... |
... | @@ -177,6 +177,7 @@ const PlaylistLoader = function(srcUrl, hls, withCredentials) { | ... | @@ -177,6 +177,7 @@ const PlaylistLoader = function(srcUrl, hls, withCredentials) { |
177 | // merge this playlist into the master | 177 | // merge this playlist into the master |
178 | update = updateMaster(loader.master, parser.manifest); | 178 | update = updateMaster(loader.master, parser.manifest); |
179 | refreshDelay = (parser.manifest.targetDuration || 10) * 1000; | 179 | refreshDelay = (parser.manifest.targetDuration || 10) * 1000; |
180 | loader.targetDuration = parser.manifest.targetDuration; | ||
180 | if (update) { | 181 | if (update) { |
181 | loader.master = update; | 182 | loader.master = update; |
182 | loader.updateMediaPlaylist_(parser.manifest); | 183 | loader.updateMediaPlaylist_(parser.manifest); |
... | @@ -227,6 +228,44 @@ const PlaylistLoader = function(srcUrl, hls, withCredentials) { | ... | @@ -227,6 +228,44 @@ const PlaylistLoader = function(srcUrl, hls, withCredentials) { |
227 | } | 228 | } |
228 | }; | 229 | }; |
229 | 230 | ||
231 | /** | ||
232 | * Returns the number of enabled playlists on the master playlist object | ||
233 | * | ||
234 | * @return {Number} number of eneabled playlists | ||
235 | */ | ||
236 | loader.enabledPlaylists_ = function() { | ||
237 | return loader.master.playlists.filter((element, index, array) => { | ||
238 | return !element.excludeUntil || element.excludeUntil <= Date.now(); | ||
239 | }).length; | ||
240 | }; | ||
241 | |||
242 | /** | ||
243 | * Returns whether the current playlist is the lowest rendition | ||
244 | * | ||
245 | * @return {Boolean} true if on lowest rendition | ||
246 | */ | ||
247 | loader.isLowestEnabledRendition_ = function() { | ||
248 | if (!loader.media()) { | ||
249 | return false; | ||
250 | } | ||
251 | |||
252 | let currentPlaylist = loader.media().attributes.BANDWIDTH; | ||
253 | |||
254 | return !(loader.master.playlists.filter((element, index, array) => { | ||
255 | let enabled = typeof element.excludeUntil === 'undefined' || | ||
256 | element.excludeUntil <= Date.now(); | ||
257 | |||
258 | if (!enabled) { | ||
259 | return false; | ||
260 | } | ||
261 | |||
262 | let item = element.attributes.BANDWIDTH; | ||
263 | |||
264 | return item <= currentPlaylist; | ||
265 | |||
266 | }).length > 1); | ||
267 | }; | ||
268 | |||
230 | /** | 269 | /** |
231 | * When called without any arguments, returns the currently | 270 | * When called without any arguments, returns the currently |
232 | * active media playlist. When called with a single argument, | 271 | * active media playlist. When called with a single argument, | ... | ... |
... | @@ -140,7 +140,6 @@ export default class SegmentLoader extends videojs.EventTarget { | ... | @@ -140,7 +140,6 @@ export default class SegmentLoader extends videojs.EventTarget { |
140 | this.seeking_ = settings.seeking; | 140 | this.seeking_ = settings.seeking; |
141 | this.setCurrentTime_ = settings.setCurrentTime; | 141 | this.setCurrentTime_ = settings.setCurrentTime; |
142 | this.mediaSource_ = settings.mediaSource; | 142 | this.mediaSource_ = settings.mediaSource; |
143 | this.withCredentials_ = settings.withCredentials; | ||
144 | this.checkBufferTimeout_ = null; | 143 | this.checkBufferTimeout_ = null; |
145 | this.error_ = void 0; | 144 | this.error_ = void 0; |
146 | this.expired_ = 0; | 145 | this.expired_ = 0; |
... | @@ -150,6 +149,7 @@ export default class SegmentLoader extends videojs.EventTarget { | ... | @@ -150,6 +149,7 @@ export default class SegmentLoader extends videojs.EventTarget { |
150 | this.pendingSegment_ = null; | 149 | this.pendingSegment_ = null; |
151 | this.sourceUpdater_ = null; | 150 | this.sourceUpdater_ = null; |
152 | this.hls_ = settings.hls; | 151 | this.hls_ = settings.hls; |
152 | this.xhrOptions_ = null; | ||
153 | } | 153 | } |
154 | 154 | ||
155 | /** | 155 | /** |
... | @@ -238,8 +238,10 @@ export default class SegmentLoader extends videojs.EventTarget { | ... | @@ -238,8 +238,10 @@ export default class SegmentLoader extends videojs.EventTarget { |
238 | * | 238 | * |
239 | * @param {PlaylistLoader} media the playlist to set on the segment loader | 239 | * @param {PlaylistLoader} media the playlist to set on the segment loader |
240 | */ | 240 | */ |
241 | playlist(media) { | 241 | playlist(media, options = {}) { |
242 | this.playlist_ = media; | 242 | this.playlist_ = media; |
243 | this.xhrOptions_ = options; | ||
244 | |||
243 | // if we were unpaused but waiting for a playlist, start | 245 | // if we were unpaused but waiting for a playlist, start |
244 | // buffering now | 246 | // buffering now |
245 | if (this.sourceUpdater_ && | 247 | if (this.sourceUpdater_ && |
... | @@ -506,7 +508,6 @@ export default class SegmentLoader extends videojs.EventTarget { | ... | @@ -506,7 +508,6 @@ export default class SegmentLoader extends videojs.EventTarget { |
506 | */ | 508 | */ |
507 | loadSegment_(segmentInfo) { | 509 | loadSegment_(segmentInfo) { |
508 | let segment; | 510 | let segment; |
509 | let requestTimeout; | ||
510 | let keyXhr; | 511 | let keyXhr; |
511 | let segmentXhr; | 512 | let segmentXhr; |
512 | let seekable = this.seekable_(); | 513 | let seekable = this.seekable_(); |
... | @@ -534,27 +535,25 @@ export default class SegmentLoader extends videojs.EventTarget { | ... | @@ -534,27 +535,25 @@ export default class SegmentLoader extends videojs.EventTarget { |
534 | } | 535 | } |
535 | 536 | ||
536 | segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; | 537 | segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; |
537 | // Set xhr timeout to 150% of the segment duration to allow us | ||
538 | // some time to switch renditions in the event of a catastrophic | ||
539 | // decrease in network performance or a server issue. | ||
540 | requestTimeout = (segment.duration * 1.5) * 1000; | ||
541 | 538 | ||
542 | if (segment.key) { | 539 | if (segment.key) { |
543 | keyXhr = this.hls_.xhr({ | 540 | let keyRequestOptions = videojs.mergeOptions(this.xhrOptions_, { |
544 | uri: segment.key.resolvedUri, | 541 | uri: segment.key.resolvedUri, |
545 | responseType: 'arraybuffer', | 542 | responseType: 'arraybuffer' |
546 | withCredentials: this.withCredentials_, | 543 | }); |
547 | timeout: requestTimeout | 544 | |
548 | }, this.handleResponse_.bind(this)); | 545 | keyXhr = this.hls_.xhr(keyRequestOptions, this.handleResponse_.bind(this)); |
549 | } | 546 | } |
547 | |||
550 | this.pendingSegment_ = segmentInfo; | 548 | this.pendingSegment_ = segmentInfo; |
551 | segmentXhr = this.hls_.xhr({ | 549 | |
550 | let segmentRequestOptions = videojs.mergeOptions(this.xhrOptions_, { | ||
552 | uri: segmentInfo.uri, | 551 | uri: segmentInfo.uri, |
553 | responseType: 'arraybuffer', | 552 | responseType: 'arraybuffer', |
554 | withCredentials: this.withCredentials_, | ||
555 | timeout: requestTimeout, | ||
556 | headers: segmentXhrHeaders(segment) | 553 | headers: segmentXhrHeaders(segment) |
557 | }, this.handleResponse_.bind(this)); | 554 | }); |
555 | |||
556 | segmentXhr = this.hls_.xhr(segmentRequestOptions, this.handleResponse_.bind(this)); | ||
558 | 557 | ||
559 | this.xhr_ = { | 558 | this.xhr_ = { |
560 | keyXhr, | 559 | keyXhr, | ... | ... |
... | @@ -484,6 +484,40 @@ QUnit.test('updates the duration after switching playlists', function() { | ... | @@ -484,6 +484,40 @@ QUnit.test('updates the duration after switching playlists', function() { |
484 | '16 bytes downloaded'); | 484 | '16 bytes downloaded'); |
485 | }); | 485 | }); |
486 | 486 | ||
487 | QUnit.test('removes request timeout when segment timesout on lowest rendition', | ||
488 | function() { | ||
489 | this.masterPlaylistController.mediaSource.trigger('sourceopen'); | ||
490 | |||
491 | // master | ||
492 | standardXHRResponse(this.requests[0]); | ||
493 | // media | ||
494 | standardXHRResponse(this.requests[1]); | ||
495 | |||
496 | QUnit.equal(this.masterPlaylistController.requestOptions_.timeout, | ||
497 | this.masterPlaylistController.masterPlaylistLoader_.targetDuration * 1.5 * | ||
498 | 1000, | ||
499 | 'default request timeout'); | ||
500 | |||
501 | QUnit.ok(!this.masterPlaylistController | ||
502 | .masterPlaylistLoader_ | ||
503 | .isLowestEnabledRendition_(), 'Not lowest rendition'); | ||
504 | |||
505 | // Cause segment to timeout to force player into lowest rendition | ||
506 | this.requests[2].timedout = true; | ||
507 | |||
508 | // Downloading segment should cause media change and timeout removal | ||
509 | // segment 0 | ||
510 | standardXHRResponse(this.requests[2]); | ||
511 | // Download new segment after media change | ||
512 | standardXHRResponse(this.requests[3]); | ||
513 | |||
514 | QUnit.ok(this.masterPlaylistController | ||
515 | .masterPlaylistLoader_.isLowestEnabledRendition_(), 'On lowest rendition'); | ||
516 | |||
517 | QUnit.equal(this.masterPlaylistController.requestOptions_.timeout, 0, | ||
518 | 'request timeout 0'); | ||
519 | }); | ||
520 | |||
487 | QUnit.test('seekable uses the intersection of alternate audio and combined tracks', | 521 | QUnit.test('seekable uses the intersection of alternate audio and combined tracks', |
488 | function() { | 522 | function() { |
489 | let origSeekable = Playlist.seekable; | 523 | let origSeekable = Playlist.seekable; | ... | ... |
... | @@ -121,6 +121,48 @@ QUnit.test('resolves relative media playlist URIs', function() { | ... | @@ -121,6 +121,48 @@ QUnit.test('resolves relative media playlist URIs', function() { |
121 | 'resolved media URI'); | 121 | 'resolved media URI'); |
122 | }); | 122 | }); |
123 | 123 | ||
124 | QUnit.test('playlist loader returns the correct amount of enabled playlists', function() { | ||
125 | let loader = new PlaylistLoader('master.m3u8', this.fakeHls); | ||
126 | |||
127 | loader.load(); | ||
128 | |||
129 | this.requests.shift().respond(200, null, | ||
130 | '#EXTM3U\n' + | ||
131 | '#EXT-X-STREAM-INF:\n' + | ||
132 | 'video1/media.m3u8\n' + | ||
133 | '#EXT-X-STREAM-INF:\n' + | ||
134 | 'video2/media.m3u8\n'); | ||
135 | QUnit.equal(loader.enabledPlaylists_(), 2, 'Returned initial amount of playlists'); | ||
136 | loader.master.playlists[0].excludeUntil = Date.now() + 100000; | ||
137 | this.clock.tick(1000); | ||
138 | QUnit.equal(loader.enabledPlaylists_(), 1, 'Returned one less playlist'); | ||
139 | }); | ||
140 | |||
141 | QUnit.test('playlist loader detects if we are on lowest rendition', function() { | ||
142 | let loader = new PlaylistLoader('master.m3u8', this.fakeHls); | ||
143 | |||
144 | loader.load(); | ||
145 | this.requests.shift().respond(200, null, | ||
146 | '#EXTM3U\n' + | ||
147 | '#EXT-X-STREAM-INF:\n' + | ||
148 | 'video1/media.m3u8\n' + | ||
149 | '#EXT-X-STREAM-INF:\n' + | ||
150 | 'video2/media.m3u8\n'); | ||
151 | loader.media = function() { | ||
152 | return {attributes: {BANDWIDTH: 10}}; | ||
153 | }; | ||
154 | |||
155 | loader.master.playlists = [{attributes: {BANDWIDTH: 10}}, | ||
156 | {attributes: {BANDWIDTH: 20}}]; | ||
157 | QUnit.ok(loader.isLowestEnabledRendition_(), 'Detected on lowest rendition'); | ||
158 | |||
159 | loader.media = function() { | ||
160 | return {attributes: {BANDWIDTH: 20}}; | ||
161 | }; | ||
162 | |||
163 | QUnit.ok(!loader.isLowestEnabledRendition_(), 'Detected not on lowest rendition'); | ||
164 | }); | ||
165 | |||
124 | QUnit.test('recognizes absolute URIs and requests them unmodified', function() { | 166 | QUnit.test('recognizes absolute URIs and requests them unmodified', function() { |
125 | let loader = new PlaylistLoader('manifest/media.m3u8', this.fakeHls); | 167 | let loader = new PlaylistLoader('manifest/media.m3u8', this.fakeHls); |
126 | 168 | ... | ... |
... | @@ -647,77 +647,6 @@ QUnit.test('live playlists do not trigger ended', function() { | ... | @@ -647,77 +647,6 @@ QUnit.test('live playlists do not trigger ended', function() { |
647 | QUnit.equal(loader.mediaRequests, 1, '1 request'); | 647 | QUnit.equal(loader.mediaRequests, 1, '1 request'); |
648 | }); | 648 | }); |
649 | 649 | ||
650 | QUnit.test('respects the global withCredentials option', function() { | ||
651 | let hlsOptions = videojs.options.hls; | ||
652 | |||
653 | videojs.options.hls = { | ||
654 | withCredentials: true | ||
655 | }; | ||
656 | loader = new SegmentLoader({ | ||
657 | hls: this.fakeHls, | ||
658 | currentTime() { | ||
659 | return currentTime; | ||
660 | }, | ||
661 | seekable: () => this.seekable, | ||
662 | mediaSource | ||
663 | }); | ||
664 | loader.playlist(playlistWithDuration(10, {isEncrypted: true})); | ||
665 | loader.mimeType(this.mimeType); | ||
666 | loader.load(); | ||
667 | |||
668 | QUnit.equal(this.requests[0].url, '0-key.php', 'requested the first segment\'s key'); | ||
669 | QUnit.ok(this.requests[0].withCredentials, 'key request used withCredentials'); | ||
670 | QUnit.equal(this.requests[1].url, '0.ts', 'requested the first segment'); | ||
671 | QUnit.ok(this.requests[1].withCredentials, 'segment request used withCredentials'); | ||
672 | videojs.options.hls = hlsOptions; | ||
673 | }); | ||
674 | |||
675 | QUnit.test('respects the withCredentials option', function() { | ||
676 | loader = new SegmentLoader({ | ||
677 | hls: this.fakeHls, | ||
678 | currentTime() { | ||
679 | return currentTime; | ||
680 | }, | ||
681 | seekable: () => this.seekable, | ||
682 | mediaSource, | ||
683 | withCredentials: true | ||
684 | }); | ||
685 | loader.playlist(playlistWithDuration(10, {isEncrypted: true})); | ||
686 | loader.mimeType(this.mimeType); | ||
687 | loader.load(); | ||
688 | |||
689 | QUnit.equal(this.requests[0].url, '0-key.php', 'requested the first segment\'s key'); | ||
690 | QUnit.ok(this.requests[0].withCredentials, 'key request used withCredentials'); | ||
691 | QUnit.equal(this.requests[1].url, '0.ts', 'requested the first segment'); | ||
692 | QUnit.ok(this.requests[1].withCredentials, 'segment request used withCredentials'); | ||
693 | }); | ||
694 | |||
695 | QUnit.test('the withCredentials option overrides the global', function() { | ||
696 | let hlsOptions = videojs.options.hls; | ||
697 | |||
698 | videojs.options.hls = { | ||
699 | withCredentials: true | ||
700 | }; | ||
701 | loader = new SegmentLoader({ | ||
702 | hls: this.fakeHls, | ||
703 | currentTime() { | ||
704 | return currentTime; | ||
705 | }, | ||
706 | mediaSource, | ||
707 | seekable: () => this.seekable, | ||
708 | withCredentials: false | ||
709 | }); | ||
710 | loader.playlist(playlistWithDuration(10, {isEncrypted: true})); | ||
711 | loader.mimeType(this.mimeType); | ||
712 | loader.load(); | ||
713 | |||
714 | QUnit.equal(this.requests[0].url, '0-key.php', 'requested the first segment\'s key'); | ||
715 | QUnit.ok(!this.requests[0].withCredentials, 'overrode key request withCredentials'); | ||
716 | QUnit.equal(this.requests[1].url, '0.ts', 'requested the first segment'); | ||
717 | QUnit.ok(!this.requests[1].withCredentials, 'overrode segment request withCredentials'); | ||
718 | videojs.options.hls = hlsOptions; | ||
719 | }); | ||
720 | |||
721 | QUnit.test('remains ready if there are no segments', function() { | 650 | QUnit.test('remains ready if there are no segments', function() { |
722 | loader.playlist(playlistWithDuration(0)); | 651 | loader.playlist(playlistWithDuration(0)); |
723 | loader.mimeType(this.mimeType); | 652 | loader.mimeType(this.mimeType); | ... | ... |
... | @@ -1219,6 +1219,25 @@ QUnit.test('if withCredentials global option is used, withCredentials is set on | ... | @@ -1219,6 +1219,25 @@ QUnit.test('if withCredentials global option is used, withCredentials is set on |
1219 | videojs.options.hls = hlsOptions; | 1219 | videojs.options.hls = hlsOptions; |
1220 | }); | 1220 | }); |
1221 | 1221 | ||
1222 | QUnit.test('the withCredentials option overrides the global default', function() { | ||
1223 | let hlsOptions = videojs.options.hls; | ||
1224 | |||
1225 | this.player.dispose(); | ||
1226 | videojs.options.hls = { | ||
1227 | withCredentials: true | ||
1228 | }; | ||
1229 | this.player = createPlayer(); | ||
1230 | this.player.src({ | ||
1231 | src: 'http://example.com/media.m3u8', | ||
1232 | type: 'application/vnd.apple.mpegurl', | ||
1233 | withCredentials: false | ||
1234 | }); | ||
1235 | openMediaSource(this.player, this.clock); | ||
1236 | QUnit.ok(!this.requests[0].withCredentials, | ||
1237 | 'with credentials should be set to false if if overrode global option'); | ||
1238 | videojs.options.hls = hlsOptions; | ||
1239 | }); | ||
1240 | |||
1222 | QUnit.test('if mode global option is used, mode is set to global option', function() { | 1241 | QUnit.test('if mode global option is used, mode is set to global option', function() { |
1223 | let hlsOptions = videojs.options.hls; | 1242 | let hlsOptions = videojs.options.hls; |
1224 | 1243 | ... | ... |
-
Please register or sign in to post a comment