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.
Showing
8 changed files
with
103 additions
and
18 deletions
... | @@ -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); | ... | ... |
This diff is collapsed.
Click to expand it.
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 | ... | ... |
test/manifest/brightcove.json
0 → 100644
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 | } |
test/manifest/brightcove.m3u8
0 → 100644
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'); | ... | ... |
-
Please register or sign in to post a comment