454617f1 by David LaPalomento

Resolve network path segment URLs. Check currentSrc for m3u8s to load on init.

Figure out an absolute path to segment URLs that are specified like "/path/0.ts". If the currentSrc URL ends in ".m3u8" attempt to load it during init. Re-organize XHR mocking in test cases to capture multiple request URLs.
1 parent 5d6531d2
......@@ -23,18 +23,24 @@ var
mediaSource = new videojs.MediaSource(),
segmentParser = new videojs.hls.SegmentParser(),
player = this,
extname,
url,
segmentXhr,
fillBuffer,
selectPlaylist;
extname = (/[^#?]*(?:\/[^#?]*\.([^#?]*))/).exec(player.currentSrc());
if (typeof options === 'string') {
url = options;
} else if (options) {
url = options.url;
} else if (extname && extname[1] === 'm3u8') {
// if the currentSrc looks like an m3u8, attempt to use it
url = player.currentSrc();
} else {
// do nothing until the plugin is initialized with a valid URL
videojs.log('hls: no valid playlist URL specified');
return;
}
......@@ -94,8 +100,22 @@ var
}
segmentUri = segment.uri;
if (!(/^([A-z]*:)?\/\//).test(segmentUri)) {
// the segment URI is relative to the manifest
if ((/^\/[^\/]/).test(segmentUri)) {
// the segment is specified with a network path,
// e.g. "/01.ts"
(function() {
// use an anchor to resolve the manifest URL to an absolute path
// this method should work back to IE6:
// http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
var resolver = document.createElement('div');
resolver.innerHTML = '<a href="' + url + '"></a>';
segmentUri = (/^[A-z]*:\/\/[^\/]*/).exec(resolver.firstChild.href)[0] +
segmentUri;
})();
} else if (!(/^([A-z]*:)?\/\//).test(segmentUri)) {
// the segment is specified with a relative path,
// e.g. "../01.ts" or "path/to/01.ts"
segmentUri = url.split('/').slice(0, -1).concat(segmentUri).join('/');
}
......@@ -109,7 +129,7 @@ var
player.hls.segmentXhrTime = (+new Date()) - startTime;
player.hls.bandwidth = segmentXhr.response.byteLength / player.hls.segmentXhrTime;
// transmux the segment data from M2TS to FLV
// transmux the segment data from MP2T to FLV
segmentParser.parseSegmentBinaryData(new Uint8Array(segmentXhr.response));
while (segmentParser.tagsAvailable()) {
player.hls.sourceBuffer.appendBuffer(segmentParser.getNextTag().bytes,
......
{
"allowCache": true,
"targetDuration": 10,
"mediaSequence": 0,
"playlistType": "VOD",
"segments": [{
"duration": 10,
"uri": "/00001.ts"
}, {
"duration": 10,
"uri": "/subdir/00002.ts"
}, {
"duration": 10,
"uri": "/00003.ts"
}, {
"duration": 10,
"uri": "/00004.ts"
}]
}
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:10
#EXTINF:10,
/00001.ts
#EXTINF:10,
/subdir/00002.ts
#EXTINF:10,
/00003.ts
#EXTINF:10,
/00004.ts
#ZEN-TOTAL-DURATION:57.9911
#EXT-X-ENDLIST
......@@ -26,17 +26,23 @@ var
oldFlashSupported,
oldXhr,
oldSourceBuffer,
xhrParams;
xhrUrls;
module('HLS', {
setup: function() {
// force Flash support in phantomjs
// mock out Flash feature for phantomjs
oldFlashSupported = videojs.Flash.isSupported;
videojs.Flash.isSupported = function() {
return true;
};
oldSourceBuffer = window.videojs.SourceBuffer;
window.videojs.SourceBuffer = function() {
this.appendBuffer = function() {};
};
var video = document.createElement('video');
// create the test player
var video = document.createElement('video');
document.querySelector('#qunit-fixture').appendChild(video);
player = videojs(video, {
flash: {
......@@ -51,34 +57,29 @@ module('HLS', {
// make XHR synchronous
oldXhr = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
this.open = function() {
xhrParams = arguments;
this.open = function(method, url) {
xhrUrls.push(url);
};
this.send = function() {
// if the request URL looks like one of the test manifests, grab the
// contents off the global object
var manifestName = (/.*\/(.*)\.m3u8/).exec(xhrParams[1]);
var manifestName = (/.*\/(.*)\.m3u8/).exec(xhrUrls.slice(-1)[0]);
if (manifestName) {
manifestName = manifestName[1];
}
this.responseText = window.manifests[manifestName || xhrParams[1]];
this.responseText = window.manifests[manifestName || xhrUrls.slice(-1)[0]];
this.response = new Uint8Array([1]).buffer;
this.readyState = 4;
this.onreadystatechange();
};
};
// mock out SourceBuffer since it won't be available in phantomjs
oldSourceBuffer = window.videojs.SourceBuffer;
window.videojs.SourceBuffer = function() {
this.appendBuffer = function() {};
};
xhrUrls = [];
},
teardown: function() {
videojs.Flash.isSupported = oldFlashSupported;
window.XMLHttpRequest = oldXhr;
window.videojs.SourceBuffer = oldSourceBuffer;
window.XMLHttpRequest = oldXhr;
}
});
......@@ -115,23 +116,31 @@ test('starts downloading a segment on loadedmetadata', function() {
type: 'sourceopen'
});
strictEqual(xhrParams[1], 'manifest/00001.ts', 'the first segment is requested');
strictEqual(xhrUrls[1], 'manifest/00001.ts', 'the first segment is requested');
});
test('recognizes absolute URIs and requests them unmodified', function() {
player.hls('manifest/absoluteUris.m3u8');
player.buffered = function() {
return videojs.createTimeRange(0, 0);
};
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
strictEqual(xhrParams[1],
strictEqual(xhrUrls[1],
'http://example.com/00001.ts',
'the first segment is requested');
});
test('recognizes domain-relative URLs', function() {
player.hls('manifest/domainUris.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
strictEqual(xhrUrls[1],
window.location.origin + '/00001.ts',
'the first segment is requested');
});
test('re-initializes the plugin for each source', function() {
var firstInit, secondInit;
player.hls('manifest/master.m3u8');
......@@ -166,10 +175,9 @@ test('does not download the next segment if the buffer is full', function() {
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
xhrParams = null;
player.trigger('timeupdate');
strictEqual(xhrParams, null, 'no segment request was made');
strictEqual(xhrUrls.length, 1, 'no segment request was made');
});
test('downloads the next segment if the buffer is getting low', function() {
......@@ -177,17 +185,17 @@ test('downloads the next segment if the buffer is getting low', function() {
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
strictEqual(xhrUrls.length, 2, 'did not make a request');
player.currentTime = function() {
return 15;
};
player.buffered = function() {
return videojs.createTimeRange(0, 19.999);
};
xhrParams = null;
player.trigger('timeupdate');
ok(xhrParams, 'made a request');
strictEqual(xhrParams[1], 'manifest/00002.ts', 'made segment request');
strictEqual(xhrUrls.length, 3, 'made a request');
strictEqual(xhrUrls[2], 'manifest/00002.ts', 'made segment request');
});
test('stops downloading segments at the end of the playlist', function() {
......@@ -195,11 +203,11 @@ test('stops downloading segments at the end of the playlist', function() {
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
xhrParams = null;
xhrUrls = [];
player.hls.currentMediaIndex = 4;
player.trigger('timeupdate');
strictEqual(xhrParams, null, 'no request is made');
strictEqual(xhrUrls.length, 0, 'no request is made');
});
test('only makes one segment request at a time', function() {
......@@ -222,6 +230,45 @@ test('only makes one segment request at a time', function() {
strictEqual(1, openedXhrs, 'only one XHR is made');
});
test('uses the currentSrc if no options are provided and it ends in ".m3u8"', function() {
var url = 'http://example.com/services/mobile/streaming/index/master.m3u8?videoId=1824650741001';
player.src(url);
player.hls();
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
strictEqual(url, xhrUrls[0], 'currentSrc is used');
});
test('ignores currentSrc if it doesn\'t have the "m3u8" extension', function() {
player.src('basdfasdfasdfliel//.m3u9');
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(xhrUrls.length, 0, 'no request is made');
player.src('');
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(xhrUrls.length, 0, 'no request is made');
player.src('http://example.com/movie.mp4?q=why.m3u8');
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(xhrUrls.length, 0, 'no request is made');
player.src('http://example.m3u8/movie.mp4');
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(xhrUrls.length, 0, 'no request is made');
player.src('//example.com/movie.mp4#http://tricky.com/master.m3u8');
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(xhrUrls.length, 0, 'no request is made');
});
module('segment controller', {
setup: function() {
segmentController = new window.videojs.hls.SegmentController();
......