50efd48c by David LaPalomento

Basic support for master playlists

If a master playlist has been downloaded, immediately fetch the default variant playlist and start buffering it. This matches HLS network activity in Safari on OS X which also seems to lazily load the non-default variant streams. Consolidate relative URL resolution and use a solution involving the `base` element to take advantage of browser logic for URL composition. Update test cases to expect absolute URLs for XHRs after the initial manifest request.
1 parent 98a64cbe
......@@ -412,7 +412,7 @@
this.manifest.totalDuration = calculatedDuration;
this.trigger('info', {
message: 'updating total duration to use a calculated value'
})
});
}
}
})[entry.tagType] || noop).call(self);
......
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000
prog_index.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000
prog_index1.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000
prog_index2.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000
prog_index3.m3u8
......
{
"allowCache": true,
"playlists": [{
"attributes": {
"PROGRAM-ID": 1,
"BANDWIDTH": 240000,
"RESOLUTION": {
"width": 396,
"height": 224
}
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001"
}, {
"attributes": {
"PROGRAM-ID": 1,
"BANDWIDTH": 40000
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001"
}, {
"attributes": {
"PROGRAM-ID": 1,
"BANDWIDTH": 440000,
"RESOLUTION": {
"width": 396,
"height": 224
}
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001"
}, {
"attributes": {
"PROGRAM-ID": 1,
"BANDWIDTH": 1928000,
"RESOLUTION": {
"width": 960,
"height": 540
}
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
}]
}
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001
......@@ -10,13 +10,13 @@
"height": 224
}
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001"
"uri": "media.m3u8"
}, {
"attributes": {
"PROGRAM-ID": 1,
"BANDWIDTH": 40000
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001"
"uri": "media.m3u8"
}, {
"attributes": {
"PROGRAM-ID": 1,
......@@ -26,7 +26,7 @@
"height": 224
}
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001"
"uri": "media.m3u8"
}, {
"attributes": {
"PROGRAM-ID": 1,
......@@ -36,6 +36,6 @@
"height": 540
}
},
"uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
"uri": "media.m3u8"
}]
}
......
# A simple master playlist with multiple variant streams
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001
media.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001
media.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001
media.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540
http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001
media.m3u8
......
......@@ -63,7 +63,7 @@ module('HLS', {
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(xhrUrls.slice(-1)[0]);
var manifestName = (/(?:.*\/)?(.*)\.m3u8/).exec(xhrUrls.slice(-1)[0]);
if (manifestName) {
manifestName = manifestName[1];
}
......@@ -99,10 +99,11 @@ test('loads the specified manifest URL on init', function() {
});
ok(loadedmanifest, 'loadedmanifest fires');
ok(loadedmetadata, 'loadedmetadata fires');
ok(player.hls.manifest, 'the manifest is available');
ok(player.hls.manifest.segments, 'the segment entries are parsed');
strictEqual(player.hls.manifest,
player.hls.currentPlaylist,
ok(player.hls.master, 'a master is inferred');
ok(player.hls.media, 'the manifest is available');
ok(player.hls.media.segments, 'the segment entries are parsed');
strictEqual(player.hls.master.playlists[0],
player.hls.media,
'the playlist is selected');
strictEqual(player.hls.readyState(), 1, 'the readyState is HAVE_METADATA');
});
......@@ -116,7 +117,11 @@ test('starts downloading a segment on loadedmetadata', function() {
type: 'sourceopen'
});
strictEqual(xhrUrls[1], 'manifest/00001.ts', 'the first segment is requested');
strictEqual(xhrUrls[1],
window.location.origin +
window.location.pathname.split('/').slice(0, -1).join('/') +
'/manifest/00001.ts',
'the first segment is requested');
});
test('recognizes absolute URIs and requests them unmodified', function() {
......@@ -151,6 +156,26 @@ test('re-initializes the plugin for each source', function() {
notStrictEqual(firstInit, secondInit, 'the plugin object is replaced');
});
test('downloads media playlists after loading the master', function() {
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
strictEqual(xhrUrls.length, 3, 'three requests were made');
strictEqual(xhrUrls[0], 'manifest/master.m3u8', 'master playlist requested');
strictEqual(xhrUrls[1],
window.location.origin +
window.location.pathname.split('/').slice(0, -1).join('/') +
'/manifest/media.m3u8',
'media playlist requested');
strictEqual(xhrUrls[2],
window.location.origin +
window.location.pathname.split('/').slice(0, -1).join('/') +
'/manifest/00001.ts',
'first segment requested');
});
test('calculates the bandwidth after downloading a segment', function() {
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
......@@ -195,7 +220,11 @@ test('downloads the next segment if the buffer is getting low', function() {
player.trigger('timeupdate');
strictEqual(xhrUrls.length, 3, 'made a request');
strictEqual(xhrUrls[2], 'manifest/00002.ts', 'made segment request');
strictEqual(xhrUrls[2],
window.location.origin +
window.location.pathname.split('/').slice(0, -1).join('/') +
'/manifest/00002.ts',
'made segment request');
});
test('stops downloading segments at the end of the playlist', function() {
......@@ -204,7 +233,7 @@ test('stops downloading segments at the end of the playlist', function() {
type: 'sourceopen'
});
xhrUrls = [];
player.hls.currentMediaIndex = 4;
player.hls.mediaIndex = 4;
player.trigger('timeupdate');
strictEqual(xhrUrls.length, 0, 'no request is made');
......