WIP: playlist-loader
Showing
3 changed files
with
206 additions
and
0 deletions
src/playlist-loader.js
0 → 100644
1 | /** | ||
2 | * A state machine that manages the loading, caching, and updating of | ||
3 | * M3U8 playlists. | ||
4 | */ | ||
5 | (function(window) { | ||
6 | 'use strict'; | ||
7 | var | ||
8 | |||
9 | /* XXX COPIED REMOVE ME */ | ||
10 | /** | ||
11 | * Constructs a new URI by interpreting a path relative to another | ||
12 | * URI. | ||
13 | * @param basePath {string} a relative or absolute URI | ||
14 | * @param path {string} a path part to combine with the base | ||
15 | * @return {string} a URI that is equivalent to composing `base` | ||
16 | * with `path` | ||
17 | * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue | ||
18 | */ | ||
19 | resolveUrl = function(basePath, path) { | ||
20 | // use the base element to get the browser to handle URI resolution | ||
21 | var | ||
22 | oldBase = document.querySelector('base'), | ||
23 | docHead = document.querySelector('head'), | ||
24 | a = document.createElement('a'), | ||
25 | base = oldBase, | ||
26 | oldHref, | ||
27 | result; | ||
28 | |||
29 | // prep the document | ||
30 | if (oldBase) { | ||
31 | oldHref = oldBase.href; | ||
32 | } else { | ||
33 | base = docHead.appendChild(document.createElement('base')); | ||
34 | } | ||
35 | |||
36 | base.href = basePath; | ||
37 | a.href = path; | ||
38 | result = a.href; | ||
39 | |||
40 | // clean up | ||
41 | if (oldBase) { | ||
42 | oldBase.href = oldHref; | ||
43 | } else { | ||
44 | docHead.removeChild(base); | ||
45 | } | ||
46 | return result; | ||
47 | }, | ||
48 | |||
49 | PlaylistLoader = function(url) { | ||
50 | var | ||
51 | loader = this, | ||
52 | request; | ||
53 | if (!url) { | ||
54 | throw new Error('A non-empty playlist URL is required'); | ||
55 | } | ||
56 | loader.state = 'HAVE_NOTHING'; | ||
57 | request = new window.XMLHttpRequest(); | ||
58 | request.open('GET', url); | ||
59 | request.onreadystatechange = function() { | ||
60 | var parser = new videojs.m3u8.Parser(); | ||
61 | parser.push(this.responseText); | ||
62 | |||
63 | if (parser.manifest.playlists) { | ||
64 | loader.master = parser.manifest; | ||
65 | } else { | ||
66 | // infer a master playlist if none was previously requested | ||
67 | loader.master = { | ||
68 | playlists: [parser.manifest] | ||
69 | }; | ||
70 | } | ||
71 | loader.state = 'HAVE_MASTER'; | ||
72 | return; | ||
73 | }; | ||
74 | request.send(null); | ||
75 | }; | ||
76 | |||
77 | window.videojs.hls.PlaylistLoader = PlaylistLoader; | ||
78 | })(window); |
test/playlist-loader_test.js
0 → 100644
1 | (function(window) { | ||
2 | 'use strict'; | ||
3 | var | ||
4 | oldXhr, | ||
5 | requests, | ||
6 | videojs = window.videojs; | ||
7 | |||
8 | module('Playlist Loader', { | ||
9 | setup: function() { | ||
10 | oldXhr = window.XMLHttpRequest; | ||
11 | requests = []; | ||
12 | |||
13 | window.XMLHttpRequest = function() { | ||
14 | this.open = function(method, url) { | ||
15 | this.method = method; | ||
16 | this.url = url; | ||
17 | }; | ||
18 | this.send = function() { | ||
19 | requests.push(this); | ||
20 | }; | ||
21 | this.respond = function(response) { | ||
22 | this.responseText = response; | ||
23 | this.readyState = 4; | ||
24 | this.onreadystatechange(); | ||
25 | }; | ||
26 | }; | ||
27 | this.send = function() { | ||
28 | }; | ||
29 | }, | ||
30 | teardown: function() { | ||
31 | window.XMLHttpRequest = oldXhr; | ||
32 | } | ||
33 | }); | ||
34 | |||
35 | test('throws if the playlist url is empty or undefined', function() { | ||
36 | throws(function() { | ||
37 | videojs.hls.PlaylistLoader(); | ||
38 | }, 'requires an argument'); | ||
39 | throws(function() { | ||
40 | videojs.hls.PlaylistLoader(''); | ||
41 | }, 'does not accept the empty string'); | ||
42 | }); | ||
43 | |||
44 | test('starts without any metadata', function() { | ||
45 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | ||
46 | strictEqual(loader.state, 'HAVE_NOTHING', 'no metadata has loaded yet'); | ||
47 | }); | ||
48 | |||
49 | test('requests the initial playlist immediately', function() { | ||
50 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | ||
51 | strictEqual(requests.length, 1, 'made a request'); | ||
52 | strictEqual(requests[0].url, 'master.m3u8', 'requested the initial playlist'); | ||
53 | }); | ||
54 | |||
55 | test('moves to HAVE_MASTER after loading a master playlist', function() { | ||
56 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | ||
57 | requests.pop().respond('#EXTM3U\n' + | ||
58 | '#EXT-X-STREAM-INF:\n' + | ||
59 | 'media.m3u8\n'); | ||
60 | ok(loader.master, 'the master playlist is available'); | ||
61 | strictEqual(loader.state, 'HAVE_MASTER', 'the state is correct'); | ||
62 | }); | ||
63 | |||
64 | test('jumps to HAVE_METADATA when initialized with a media playlist', function() { | ||
65 | var loader = new videojs.hls.PlaylistLoader('media.m3u8'); | ||
66 | requests.pop().respond('#EXTM3U\n' + | ||
67 | '#EXTINF:10,\n' + | ||
68 | '0.ts\n' + | ||
69 | '#EXT-X-ENDLIST\n'); | ||
70 | ok(loader.master, 'infers a master playlist'); | ||
71 | ok(loader.media, 'sets the media playlist'); | ||
72 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); | ||
73 | strictEqual(0, requests.length, 'no more requests are made'); | ||
74 | }); | ||
75 | |||
76 | test('jumps to HAVE_METADATA when initialized with a live media playlist', function() { | ||
77 | var loader = new videojs.hls.PlaylistLoader('media.m3u8'); | ||
78 | requests.pop().respond('#EXTM3U\n' + | ||
79 | '#EXTINF:10,\n' + | ||
80 | '0.ts\n'); | ||
81 | ok(loader.master, 'infers a master playlist'); | ||
82 | ok(loader.media, 'sets the media playlist'); | ||
83 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); | ||
84 | }); | ||
85 | |||
86 | test('moves to HAVE_METADATA after loading a media playlist', function() { | ||
87 | var loader = new videojs.hls.PlaylistLoader('master.m3u8'); | ||
88 | requests.pop().respond('#EXTM3U\n' + | ||
89 | '#EXT-X-STREAM-INF:\n' + | ||
90 | 'media.m3u8\n' + | ||
91 | 'alt.m3u8\n'); | ||
92 | strictEqual(requests.length, 1, 'requests the media playlist'); | ||
93 | strictEqual(requests[0].method, 'GET', 'GETs the media playlist'); | ||
94 | strictEqual(requests[0].url, 'media.m3u8', 'requests the first playlist'); | ||
95 | |||
96 | requests.pop().response('#EXTM3U\n' + | ||
97 | '#EXTINF:10,\n' + | ||
98 | '0.ts\n'); | ||
99 | ok(loader.master, 'sets the master playlist'); | ||
100 | ok(loader.media, 'sets the media playlist'); | ||
101 | strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct'); | ||
102 | }); | ||
103 | |||
104 | test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() { | ||
105 | var loader = new videojs.hls.PlaylistLoader('live.m3u8'); | ||
106 | requests.pop().response('#EXTM3U\n' + | ||
107 | '#EXTINF:10,\n' + | ||
108 | '0.ts\n'); | ||
109 | loader.refreshMedia(); | ||
110 | strictEqual(loader.state, 'HAVE_CURRENT_METADATA', 'the state is correct'); | ||
111 | strictEqual(requests.length, 1, 'requested playlist'); | ||
112 | strictEqual(requests[0].url, 'live.m3u8', 'refreshes the media playlist'); | ||
113 | }); | ||
114 | |||
115 | test('returns to HAVE_METADATA after refreshing the playlist', function() { | ||
116 | var loader = new videojs.hls.PlaylistLoader('live.m3u8'); | ||
117 | requests.pop().response('#EXTM3U\n' + | ||
118 | '#EXTINF:10,\n' + | ||
119 | '0.ts\n'); | ||
120 | loader.refreshMedia(); | ||
121 | requests.pop().response('#EXTM3U\n' + | ||
122 | '#EXTINF:10,\n' + | ||
123 | '1.ts\n'); | ||
124 | strictEqual(loader.state, 'HAVE_CURRENT_METADATA', 'the state is correct'); | ||
125 | }); | ||
126 | })(window); |
... | @@ -28,6 +28,7 @@ | ... | @@ -28,6 +28,7 @@ |
28 | <!-- M3U8 --> | 28 | <!-- M3U8 --> |
29 | <script src="../src/stream.js"></script> | 29 | <script src="../src/stream.js"></script> |
30 | <script src="../src/m3u8/m3u8-parser.js"></script> | 30 | <script src="../src/m3u8/m3u8-parser.js"></script> |
31 | <script src="../src/playlist-loader.js"></script> | ||
31 | <!-- M3U8 TEST DATA --> | 32 | <!-- M3U8 TEST DATA --> |
32 | <script src="../tmp/manifests.js"></script> | 33 | <script src="../tmp/manifests.js"></script> |
33 | <script src="../tmp/expected.js"></script> | 34 | <script src="../tmp/expected.js"></script> |
... | @@ -51,6 +52,7 @@ | ... | @@ -51,6 +52,7 @@ |
51 | <script src="exp-golomb_test.js"></script> | 52 | <script src="exp-golomb_test.js"></script> |
52 | <script src="flv-tag_test.js"></script> | 53 | <script src="flv-tag_test.js"></script> |
53 | <script src="m3u8_test.js"></script> | 54 | <script src="m3u8_test.js"></script> |
55 | <script src="playlist-loader_test.js"></script> | ||
54 | </head> | 56 | </head> |
55 | <body> | 57 | <body> |
56 | <div id="qunit"></div> | 58 | <div id="qunit"></div> | ... | ... |
-
Please register or sign in to post a comment