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 @@ ...@@ -412,7 +412,7 @@
412 this.manifest.totalDuration = calculatedDuration; 412 this.manifest.totalDuration = calculatedDuration;
413 this.trigger('info', { 413 this.trigger('info', {
414 message: 'updating total duration to use a calculated value' 414 message: 'updating total duration to use a calculated value'
415 }) 415 });
416 } 416 }
417 } 417 }
418 })[entry.tagType] || noop).call(self); 418 })[entry.tagType] || noop).call(self);
......
1 #EXTM3U 1 #EXTM3U
2 #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000 2 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000
3 prog_index.m3u8 3 prog_index.m3u8
4 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000
5 prog_index1.m3u8
6 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000
7 prog_index2.m3u8
8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000
9 prog_index3.m3u8
......
1 {
2 "allowCache": true,
3 "playlists": [{
4 "attributes": {
5 "PROGRAM-ID": 1,
6 "BANDWIDTH": 240000,
7 "RESOLUTION": {
8 "width": 396,
9 "height": 224
10 }
11 },
12 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001"
13 }, {
14 "attributes": {
15 "PROGRAM-ID": 1,
16 "BANDWIDTH": 40000
17 },
18 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001"
19 }, {
20 "attributes": {
21 "PROGRAM-ID": 1,
22 "BANDWIDTH": 440000,
23 "RESOLUTION": {
24 "width": 396,
25 "height": 224
26 }
27 },
28 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001"
29 }, {
30 "attributes": {
31 "PROGRAM-ID": 1,
32 "BANDWIDTH": 1928000,
33 "RESOLUTION": {
34 "width": 960,
35 "height": 540
36 }
37 },
38 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001"
39 }]
40 }
1 #EXTM3U
2 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224
3 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001
4 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000
5 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001
6 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224
7 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001
8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540
9 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001
...@@ -10,13 +10,13 @@ ...@@ -10,13 +10,13 @@
10 "height": 224 10 "height": 224
11 } 11 }
12 }, 12 },
13 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001" 13 "uri": "media.m3u8"
14 }, { 14 }, {
15 "attributes": { 15 "attributes": {
16 "PROGRAM-ID": 1, 16 "PROGRAM-ID": 1,
17 "BANDWIDTH": 40000 17 "BANDWIDTH": 40000
18 }, 18 },
19 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001" 19 "uri": "media.m3u8"
20 }, { 20 }, {
21 "attributes": { 21 "attributes": {
22 "PROGRAM-ID": 1, 22 "PROGRAM-ID": 1,
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
26 "height": 224 26 "height": 224
27 } 27 }
28 }, 28 },
29 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001" 29 "uri": "media.m3u8"
30 }, { 30 }, {
31 "attributes": { 31 "attributes": {
32 "PROGRAM-ID": 1, 32 "PROGRAM-ID": 1,
...@@ -36,6 +36,6 @@ ...@@ -36,6 +36,6 @@
36 "height": 540 36 "height": 540
37 } 37 }
38 }, 38 },
39 "uri": "http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001" 39 "uri": "media.m3u8"
40 }] 40 }]
41 } 41 }
......
1 # A simple master playlist with multiple variant streams
1 #EXTM3U 2 #EXTM3U
2 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224 3 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224
3 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001 4 media.m3u8
4 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000 5 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000
5 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001 6 media.m3u8
6 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224 7 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224
7 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001 8 media.m3u8
8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540 9 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540
9 http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001 10 media.m3u8
......
...@@ -63,7 +63,7 @@ module('HLS', { ...@@ -63,7 +63,7 @@ module('HLS', {
63 this.send = function() { 63 this.send = function() {
64 // if the request URL looks like one of the test manifests, grab the 64 // if the request URL looks like one of the test manifests, grab the
65 // contents off the global object 65 // contents off the global object
66 var manifestName = (/.*\/(.*)\.m3u8/).exec(xhrUrls.slice(-1)[0]); 66 var manifestName = (/(?:.*\/)?(.*)\.m3u8/).exec(xhrUrls.slice(-1)[0]);
67 if (manifestName) { 67 if (manifestName) {
68 manifestName = manifestName[1]; 68 manifestName = manifestName[1];
69 } 69 }
...@@ -99,10 +99,11 @@ test('loads the specified manifest URL on init', function() { ...@@ -99,10 +99,11 @@ test('loads the specified manifest URL on init', function() {
99 }); 99 });
100 ok(loadedmanifest, 'loadedmanifest fires'); 100 ok(loadedmanifest, 'loadedmanifest fires');
101 ok(loadedmetadata, 'loadedmetadata fires'); 101 ok(loadedmetadata, 'loadedmetadata fires');
102 ok(player.hls.manifest, 'the manifest is available'); 102 ok(player.hls.master, 'a master is inferred');
103 ok(player.hls.manifest.segments, 'the segment entries are parsed'); 103 ok(player.hls.media, 'the manifest is available');
104 strictEqual(player.hls.manifest, 104 ok(player.hls.media.segments, 'the segment entries are parsed');
105 player.hls.currentPlaylist, 105 strictEqual(player.hls.master.playlists[0],
106 player.hls.media,
106 'the playlist is selected'); 107 'the playlist is selected');
107 strictEqual(player.hls.readyState(), 1, 'the readyState is HAVE_METADATA'); 108 strictEqual(player.hls.readyState(), 1, 'the readyState is HAVE_METADATA');
108 }); 109 });
...@@ -116,7 +117,11 @@ test('starts downloading a segment on loadedmetadata', function() { ...@@ -116,7 +117,11 @@ test('starts downloading a segment on loadedmetadata', function() {
116 type: 'sourceopen' 117 type: 'sourceopen'
117 }); 118 });
118 119
119 strictEqual(xhrUrls[1], 'manifest/00001.ts', 'the first segment is requested'); 120 strictEqual(xhrUrls[1],
121 window.location.origin +
122 window.location.pathname.split('/').slice(0, -1).join('/') +
123 '/manifest/00001.ts',
124 'the first segment is requested');
120 }); 125 });
121 126
122 test('recognizes absolute URIs and requests them unmodified', function() { 127 test('recognizes absolute URIs and requests them unmodified', function() {
...@@ -151,6 +156,26 @@ test('re-initializes the plugin for each source', function() { ...@@ -151,6 +156,26 @@ test('re-initializes the plugin for each source', function() {
151 notStrictEqual(firstInit, secondInit, 'the plugin object is replaced'); 156 notStrictEqual(firstInit, secondInit, 'the plugin object is replaced');
152 }); 157 });
153 158
159 test('downloads media playlists after loading the master', function() {
160 player.hls('manifest/master.m3u8');
161 videojs.mediaSources[player.currentSrc()].trigger({
162 type: 'sourceopen'
163 });
164
165 strictEqual(xhrUrls.length, 3, 'three requests were made');
166 strictEqual(xhrUrls[0], 'manifest/master.m3u8', 'master playlist requested');
167 strictEqual(xhrUrls[1],
168 window.location.origin +
169 window.location.pathname.split('/').slice(0, -1).join('/') +
170 '/manifest/media.m3u8',
171 'media playlist requested');
172 strictEqual(xhrUrls[2],
173 window.location.origin +
174 window.location.pathname.split('/').slice(0, -1).join('/') +
175 '/manifest/00001.ts',
176 'first segment requested');
177 });
178
154 test('calculates the bandwidth after downloading a segment', function() { 179 test('calculates the bandwidth after downloading a segment', function() {
155 player.hls('manifest/media.m3u8'); 180 player.hls('manifest/media.m3u8');
156 videojs.mediaSources[player.currentSrc()].trigger({ 181 videojs.mediaSources[player.currentSrc()].trigger({
...@@ -195,7 +220,11 @@ test('downloads the next segment if the buffer is getting low', function() { ...@@ -195,7 +220,11 @@ test('downloads the next segment if the buffer is getting low', function() {
195 player.trigger('timeupdate'); 220 player.trigger('timeupdate');
196 221
197 strictEqual(xhrUrls.length, 3, 'made a request'); 222 strictEqual(xhrUrls.length, 3, 'made a request');
198 strictEqual(xhrUrls[2], 'manifest/00002.ts', 'made segment request'); 223 strictEqual(xhrUrls[2],
224 window.location.origin +
225 window.location.pathname.split('/').slice(0, -1).join('/') +
226 '/manifest/00002.ts',
227 'made segment request');
199 }); 228 });
200 229
201 test('stops downloading segments at the end of the playlist', function() { 230 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() { ...@@ -204,7 +233,7 @@ test('stops downloading segments at the end of the playlist', function() {
204 type: 'sourceopen' 233 type: 'sourceopen'
205 }); 234 });
206 xhrUrls = []; 235 xhrUrls = [];
207 player.hls.currentMediaIndex = 4; 236 player.hls.mediaIndex = 4;
208 player.trigger('timeupdate'); 237 player.trigger('timeupdate');
209 238
210 strictEqual(xhrUrls.length, 0, 'no request is made'); 239 strictEqual(xhrUrls.length, 0, 'no request is made');
......