277bea49 by jrivera

Expose the ability to set an `beforeRequest` function on the xhr object in order…

… to modify the options used to create the request object
* Make Xhr a factory function that returns a unique instance of the xhr function and expose an instance of it on each player's xhr object
* Keep the returned function is backward compatible with the previous xhr
* Add a `beforeRequest` function to the XHR that allows you to override options before the request
* The `beforeRequest` function can be specified on the global `videojs.Hls.xhr` function and it'll be used for all players unless overridden on a per-player level
1 parent 6fe9fa2f
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
6 * 6 *
7 */ 7 */
8 import resolveUrl from './resolve-url'; 8 import resolveUrl from './resolve-url';
9 import XhrModule from './xhr';
10 import {mergeOptions} from 'video.js'; 9 import {mergeOptions} from 'video.js';
11 import Stream from './stream'; 10 import Stream from './stream';
12 import m3u8 from './m3u8'; 11 import m3u8 from './m3u8';
...@@ -86,7 +85,7 @@ const updateSegments = function(original, update, offset) { ...@@ -86,7 +85,7 @@ const updateSegments = function(original, update, offset) {
86 }; 85 };
87 86
88 export default class PlaylistLoader extends Stream { 87 export default class PlaylistLoader extends Stream {
89 constructor(srcUrl, withCredentials) { 88 constructor(srcUrl, hls, withCredentials) {
90 super(); 89 super();
91 let loader = this; 90 let loader = this;
92 let dispose; 91 let dispose;
...@@ -95,6 +94,8 @@ export default class PlaylistLoader extends Stream { ...@@ -95,6 +94,8 @@ export default class PlaylistLoader extends Stream {
95 let playlistRequestError; 94 let playlistRequestError;
96 let haveMetadata; 95 let haveMetadata;
97 96
97 this.hls_ = hls;
98
98 // a flag that disables "expired time"-tracking this setting has 99 // a flag that disables "expired time"-tracking this setting has
99 // no effect when not playing a live stream 100 // no effect when not playing a live stream
100 this.trackExpiredTime_ = false; 101 this.trackExpiredTime_ = false;
...@@ -261,7 +262,7 @@ export default class PlaylistLoader extends Stream { ...@@ -261,7 +262,7 @@ export default class PlaylistLoader extends Stream {
261 } 262 }
262 263
263 // request the new playlist 264 // request the new playlist
264 request = XhrModule({ 265 request = this.hls_.xhr({
265 uri: resolveUrl(loader.master.uri, playlist.uri), 266 uri: resolveUrl(loader.master.uri, playlist.uri),
266 withCredentials 267 withCredentials
267 }, function(error, request) { 268 }, function(error, request) {
...@@ -298,7 +299,7 @@ export default class PlaylistLoader extends Stream { ...@@ -298,7 +299,7 @@ export default class PlaylistLoader extends Stream {
298 } 299 }
299 300
300 loader.state = 'HAVE_CURRENT_METADATA'; 301 loader.state = 'HAVE_CURRENT_METADATA';
301 request = XhrModule({ 302 request = this.hls_.xhr({
302 uri: resolveUrl(loader.master.uri, loader.media().uri), 303 uri: resolveUrl(loader.master.uri, loader.media().uri),
303 withCredentials 304 withCredentials
304 }, function(error, request) { 305 }, function(error, request) {
...@@ -310,7 +311,7 @@ export default class PlaylistLoader extends Stream { ...@@ -310,7 +311,7 @@ export default class PlaylistLoader extends Stream {
310 }); 311 });
311 312
312 // request the specified URL 313 // request the specified URL
313 request = XhrModule({ 314 request = this.hls_.xhr({
314 uri: srcUrl, 315 uri: srcUrl,
315 withCredentials 316 withCredentials
316 }, function(error, req) { 317 }, function(error, req) {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
5 */ 5 */
6 import PlaylistLoader from './playlist-loader'; 6 import PlaylistLoader from './playlist-loader';
7 import Playlist from './playlist'; 7 import Playlist from './playlist';
8 import xhr from './xhr'; 8 import xhrFactory from './xhr';
9 import {Decrypter, AsyncStream, decrypt} from './decrypter'; 9 import {Decrypter, AsyncStream, decrypt} from './decrypter';
10 import utils from './bin-utils'; 10 import utils from './bin-utils';
11 import {MediaSource, URL} from 'videojs-contrib-media-sources'; 11 import {MediaSource, URL} from 'videojs-contrib-media-sources';
...@@ -20,7 +20,7 @@ const Hls = { ...@@ -20,7 +20,7 @@ const Hls = {
20 AsyncStream, 20 AsyncStream,
21 decrypt, 21 decrypt,
22 utils, 22 utils,
23 xhr 23 xhr: xhrFactory()
24 }; 24 };
25 25
26 // the desired length of video to maintain in the buffer, in seconds 26 // the desired length of video to maintain in the buffer, in seconds
...@@ -416,6 +416,7 @@ export default class HlsHandler extends Component { ...@@ -416,6 +416,7 @@ export default class HlsHandler extends Component {
416 this.options_.withCredentials = videojs.options.hls.withCredentials; 416 this.options_.withCredentials = videojs.options.hls.withCredentials;
417 } 417 }
418 this.playlists = new Hls.PlaylistLoader(this.source_.src, 418 this.playlists = new Hls.PlaylistLoader(this.source_.src,
419 this.tech_.hls,
419 this.options_.withCredentials); 420 this.options_.withCredentials);
420 421
421 this.tech_.one('canplay', this.setupFirstPlay.bind(this)); 422 this.tech_.one('canplay', this.setupFirstPlay.bind(this));
...@@ -1224,7 +1225,7 @@ export default class HlsHandler extends Component { ...@@ -1224,7 +1225,7 @@ export default class HlsHandler extends Component {
1224 } 1225 }
1225 1226
1226 // request the next segment 1227 // request the next segment
1227 this.segmentXhr_ = Hls.xhr({ 1228 this.segmentXhr_ = this.tech_.hls.xhr({
1228 uri: segmentInfo.uri, 1229 uri: segmentInfo.uri,
1229 responseType: 'arraybuffer', 1230 responseType: 'arraybuffer',
1230 withCredentials: this.source_.withCredentials, 1231 withCredentials: this.source_.withCredentials,
...@@ -1504,7 +1505,7 @@ export default class HlsHandler extends Component { ...@@ -1504,7 +1505,7 @@ export default class HlsHandler extends Component {
1504 1505
1505 // request the key if the retry limit hasn't been reached 1506 // request the key if the retry limit hasn't been reached
1506 if (!key.bytes && !keyFailed(key)) { 1507 if (!key.bytes && !keyFailed(key)) {
1507 this.keyXhr_ = Hls.xhr({ 1508 this.keyXhr_ = this.tech_.hls.xhr({
1508 uri: this.playlistUriToUrl(key.uri), 1509 uri: this.playlistUriToUrl(key.uri),
1509 responseType: 'arraybuffer', 1510 responseType: 'arraybuffer',
1510 withCredentials: settings.withCredentials 1511 withCredentials: settings.withCredentials
...@@ -1563,6 +1564,14 @@ const HlsSourceHandler = function(mode) { ...@@ -1563,6 +1564,14 @@ const HlsSourceHandler = function(mode) {
1563 source, 1564 source,
1564 mode 1565 mode
1565 }); 1566 });
1567
1568 tech.hls.xhr = xhrFactory();
1569 // Use a global `before` function if specified on videojs.Hls.xhr
1570 // but still allow for a per-player override
1571 if (videojs.Hls.xhr.beforeRequest) {
1572 tech.hls.xhr.beforeRequest = videojs.Hls.xhr.beforeRequest;
1573 }
1574
1566 tech.hls.src(source.src); 1575 tech.hls.src(source.src);
1567 return tech.hls; 1576 return tech.hls;
1568 }, 1577 },
......
...@@ -2,12 +2,25 @@ ...@@ -2,12 +2,25 @@
2 * A wrapper for videojs.xhr that tracks bandwidth. 2 * A wrapper for videojs.xhr that tracks bandwidth.
3 */ 3 */
4 import {xhr as videojsXHR, mergeOptions} from 'video.js'; 4 import {xhr as videojsXHR, mergeOptions} from 'video.js';
5 const xhr = function(options, callback) { 5
6 const xhrFactory = function() {
7 const xhr = function XhrFunction(options, callback) {
6 // Add a default timeout for all hls requests 8 // Add a default timeout for all hls requests
7 options = mergeOptions({ 9 options = mergeOptions({
8 timeout: 45e3 10 timeout: 45e3
9 }, options); 11 }, options);
10 12
13 // Allow an optional user-specified function to modify the option
14 // object before we construct the xhr request
15 if (XhrFunction.beforeRequest &&
16 typeof XhrFunction.beforeRequest === 'function') {
17 let newOptions = XhrFunction.beforeRequest(options);
18
19 if (newOptions) {
20 options = newOptions;
21 }
22 }
23
11 let request = videojsXHR(options, function(error, response) { 24 let request = videojsXHR(options, function(error, response) {
12 if (!error && request.response) { 25 if (!error && request.response) {
13 request.responseTime = (new Date()).getTime(); 26 request.responseTime = (new Date()).getTime();
...@@ -44,6 +57,9 @@ const xhr = function(options, callback) { ...@@ -44,6 +57,9 @@ const xhr = function(options, callback) {
44 57
45 request.requestTime = (new Date()).getTime(); 58 request.requestTime = (new Date()).getTime();
46 return request; 59 return request;
60 };
61
62 return xhr;
47 }; 63 };
48 64
49 export default xhr; 65 export default xhrFactory;
......
...@@ -3363,6 +3363,79 @@ QUnit.test('selectPlaylist does not fail if getComputedStyle returns null', func ...@@ -3363,6 +3363,79 @@ QUnit.test('selectPlaylist does not fail if getComputedStyle returns null', func
3363 window.getComputedStyle = oldGetComputedStyle; 3363 window.getComputedStyle = oldGetComputedStyle;
3364 }); 3364 });
3365 3365
3366 QUnit.test('Allows specifying the beforeRequest functionon the player', function() {
3367 let beforeRequestCalled = false;
3368
3369 this.player.ready(function() {
3370 this.hls.xhr.beforeRequest = function() {
3371 beforeRequestCalled = true;
3372 };
3373 });
3374 this.player.src({
3375 src: 'master.m3u8',
3376 type: 'application/vnd.apple.mpegurl'
3377 });
3378
3379 openMediaSource(this.player, this.clock);
3380 // master
3381 standardXHRResponse(this.requests.shift());
3382 // media
3383 standardXHRResponse(this.requests.shift());
3384
3385 QUnit.ok(beforeRequestCalled, 'beforeRequest was called');
3386 });
3387
3388 QUnit.test('Allows specifying the beforeRequest function globally', function() {
3389 let beforeRequestCalled = false;
3390
3391 videojs.Hls.xhr.beforeRequest = function() {
3392 beforeRequestCalled = true;
3393 };
3394
3395 this.player.src({
3396 src: 'master.m3u8',
3397 type: 'application/vnd.apple.mpegurl'
3398 });
3399
3400 QUnit.ok(beforeRequestCalled, 'beforeRequest was called');
3401
3402 delete videojs.Hls.xhr.beforeRequest;
3403 });
3404
3405 QUnit.test('Allows overriding the global beforeRequest function', function() {
3406 let beforeGlobalRequestCalled = 0;
3407 let beforeLocalRequestCalled = 0;
3408
3409 videojs.Hls.xhr.beforeRequest = function() {
3410 beforeGlobalRequestCalled++;
3411 };
3412
3413 this.player.src({
3414 src: 'master.m3u8',
3415 type: 'application/vnd.apple.mpegurl'
3416 });
3417 this.player.ready(function() {
3418 this.hls.xhr.beforeRequest = function() {
3419 beforeLocalRequestCalled++;
3420 };
3421 });
3422
3423 openMediaSource(this.player, this.clock);
3424 // master
3425 standardXHRResponse(this.requests.shift());
3426 // media
3427 standardXHRResponse(this.requests.shift());
3428 // ts
3429 standardXHRResponse(this.requests.shift());
3430
3431 QUnit.equal(beforeLocalRequestCalled, 2, 'local beforeRequest was called twice ' +
3432 'for the media playlist and media');
3433 QUnit.equal(beforeGlobalRequestCalled, 1, 'global beforeRequest was called once ' +
3434 'for the master playlist');
3435
3436 delete videojs.Hls.xhr.beforeRequest;
3437 });
3438
3366 QUnit.module('Buffer Inspection'); 3439 QUnit.module('Buffer Inspection');
3367 QUnit.test('detects time range end-point changed by updates', function() { 3440 QUnit.test('detects time range end-point changed by updates', function() {
3368 let edge; 3441 let edge;
......