906e1f23 by David LaPalomento

WIP: playlist-loader

1 parent 58952618
/**
* A state machine that manages the loading, caching, and updating of
* M3U8 playlists.
*/
(function(window) {
'use strict';
var
/* XXX COPIED REMOVE ME */
/**
* Constructs a new URI by interpreting a path relative to another
* URI.
* @param basePath {string} a relative or absolute URI
* @param path {string} a path part to combine with the base
* @return {string} a URI that is equivalent to composing `base`
* with `path`
* @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
*/
resolveUrl = function(basePath, path) {
// use the base element to get the browser to handle URI resolution
var
oldBase = document.querySelector('base'),
docHead = document.querySelector('head'),
a = document.createElement('a'),
base = oldBase,
oldHref,
result;
// prep the document
if (oldBase) {
oldHref = oldBase.href;
} else {
base = docHead.appendChild(document.createElement('base'));
}
base.href = basePath;
a.href = path;
result = a.href;
// clean up
if (oldBase) {
oldBase.href = oldHref;
} else {
docHead.removeChild(base);
}
return result;
},
PlaylistLoader = function(url) {
var
loader = this,
request;
if (!url) {
throw new Error('A non-empty playlist URL is required');
}
loader.state = 'HAVE_NOTHING';
request = new window.XMLHttpRequest();
request.open('GET', url);
request.onreadystatechange = function() {
var parser = new videojs.m3u8.Parser();
parser.push(this.responseText);
if (parser.manifest.playlists) {
loader.master = parser.manifest;
} else {
// infer a master playlist if none was previously requested
loader.master = {
playlists: [parser.manifest]
};
}
loader.state = 'HAVE_MASTER';
return;
};
request.send(null);
};
window.videojs.hls.PlaylistLoader = PlaylistLoader;
})(window);
(function(window) {
'use strict';
var
oldXhr,
requests,
videojs = window.videojs;
module('Playlist Loader', {
setup: function() {
oldXhr = window.XMLHttpRequest;
requests = [];
window.XMLHttpRequest = function() {
this.open = function(method, url) {
this.method = method;
this.url = url;
};
this.send = function() {
requests.push(this);
};
this.respond = function(response) {
this.responseText = response;
this.readyState = 4;
this.onreadystatechange();
};
};
this.send = function() {
};
},
teardown: function() {
window.XMLHttpRequest = oldXhr;
}
});
test('throws if the playlist url is empty or undefined', function() {
throws(function() {
videojs.hls.PlaylistLoader();
}, 'requires an argument');
throws(function() {
videojs.hls.PlaylistLoader('');
}, 'does not accept the empty string');
});
test('starts without any metadata', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
strictEqual(loader.state, 'HAVE_NOTHING', 'no metadata has loaded yet');
});
test('requests the initial playlist immediately', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
strictEqual(requests.length, 1, 'made a request');
strictEqual(requests[0].url, 'master.m3u8', 'requested the initial playlist');
});
test('moves to HAVE_MASTER after loading a master playlist', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
requests.pop().respond('#EXTM3U\n' +
'#EXT-X-STREAM-INF:\n' +
'media.m3u8\n');
ok(loader.master, 'the master playlist is available');
strictEqual(loader.state, 'HAVE_MASTER', 'the state is correct');
});
test('jumps to HAVE_METADATA when initialized with a media playlist', function() {
var loader = new videojs.hls.PlaylistLoader('media.m3u8');
requests.pop().respond('#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n' +
'#EXT-X-ENDLIST\n');
ok(loader.master, 'infers a master playlist');
ok(loader.media, 'sets the media playlist');
strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct');
strictEqual(0, requests.length, 'no more requests are made');
});
test('jumps to HAVE_METADATA when initialized with a live media playlist', function() {
var loader = new videojs.hls.PlaylistLoader('media.m3u8');
requests.pop().respond('#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n');
ok(loader.master, 'infers a master playlist');
ok(loader.media, 'sets the media playlist');
strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct');
});
test('moves to HAVE_METADATA after loading a media playlist', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
requests.pop().respond('#EXTM3U\n' +
'#EXT-X-STREAM-INF:\n' +
'media.m3u8\n' +
'alt.m3u8\n');
strictEqual(requests.length, 1, 'requests the media playlist');
strictEqual(requests[0].method, 'GET', 'GETs the media playlist');
strictEqual(requests[0].url, 'media.m3u8', 'requests the first playlist');
requests.pop().response('#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n');
ok(loader.master, 'sets the master playlist');
ok(loader.media, 'sets the media playlist');
strictEqual(loader.state, 'HAVE_METADATA', 'the state is correct');
});
test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() {
var loader = new videojs.hls.PlaylistLoader('live.m3u8');
requests.pop().response('#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n');
loader.refreshMedia();
strictEqual(loader.state, 'HAVE_CURRENT_METADATA', 'the state is correct');
strictEqual(requests.length, 1, 'requested playlist');
strictEqual(requests[0].url, 'live.m3u8', 'refreshes the media playlist');
});
test('returns to HAVE_METADATA after refreshing the playlist', function() {
var loader = new videojs.hls.PlaylistLoader('live.m3u8');
requests.pop().response('#EXTM3U\n' +
'#EXTINF:10,\n' +
'0.ts\n');
loader.refreshMedia();
requests.pop().response('#EXTM3U\n' +
'#EXTINF:10,\n' +
'1.ts\n');
strictEqual(loader.state, 'HAVE_CURRENT_METADATA', 'the state is correct');
});
})(window);
......@@ -28,6 +28,7 @@
<!-- M3U8 -->
<script src="../src/stream.js"></script>
<script src="../src/m3u8/m3u8-parser.js"></script>
<script src="../src/playlist-loader.js"></script>
<!-- M3U8 TEST DATA -->
<script src="../tmp/manifests.js"></script>
<script src="../tmp/expected.js"></script>
......@@ -51,6 +52,7 @@
<script src="exp-golomb_test.js"></script>
<script src="flv-tag_test.js"></script>
<script src="m3u8_test.js"></script>
<script src="playlist-loader_test.js"></script>
</head>
<body>
<div id="qunit"></div>
......