50a78066 by David LaPalomento

Merge pull request #62 from videojs/tech2

HLS Tech
2 parents fafd0563 d30df95f
......@@ -45,9 +45,6 @@ module.exports = function(grunt) {
dest: 'dist/videojs.hls.min.js'
}
},
qunit: {
files: ['test/**/*.html', '!test/perf.html', '!test/muxer/**']
},
jshint: {
gruntfile: {
options: {
......@@ -93,11 +90,11 @@ module.exports = function(grunt) {
},
src: {
files: '<%= jshint.src.src %>',
tasks: ['jshint:src', 'qunit']
tasks: ['jshint:src', 'test']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'qunit']
tasks: ['jshint:test', 'test']
}
},
concurrent: {
......@@ -194,7 +191,6 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-connect');
......@@ -255,7 +251,7 @@ module.exports = function(grunt) {
['clean',
'jshint',
'manifests-to-js',
'qunit',
'test',
'concat',
'uglify']);
......
......@@ -31,7 +31,7 @@
<!-- bipbop -->
<!-- <script src="test/tsSegment.js"></script> -->
<!-- bunnies -->
<script src="test/tsSegment-bc.js"></script>
<!--<script src="test/tsSegment-bc.js"></script>-->
<style>
body {
......@@ -63,10 +63,12 @@
<script>
videojs.options.flash.swf = 'node_modules/video.js/dist/video-js/video-js.swf';
// initialize the player
var player = videojs('video');
var player = videojs('video', {
techOrder: ['hls']
});
// initialize the plugin
player.hls();
//player.hls()
</script>
</body>
</html>
......
......@@ -19,7 +19,6 @@
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.6.0",
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-qunit": "~0.2.0",
"grunt-contrib-uglify": "~0.2.0",
"grunt-contrib-watch": "~0.4.0",
"grunt-karma": "~0.6.2",
......
......@@ -8,7 +8,7 @@
(function(window) {
var
FlvTag = window.videojs.hls.FlvTag,
FlvTag = window.videojs.Hls.FlvTag,
adtsSampleingRates = [
96000, 88200,
64000, 48000,
......@@ -17,7 +17,7 @@ var
16000, 12000
];
window.videojs.hls.AacStream = function() {
window.videojs.Hls.AacStream = function() {
var
next_pts, // :uint
pts_offset, // :int
......
......@@ -29,5 +29,5 @@
}
};
window.videojs.hls.utils = module;
window.videojs.Hls.utils = module;
})(this);
......
......@@ -4,7 +4,7 @@
* Parser for exponential Golomb codes, a variable-bitwidth number encoding
* scheme used by h264.
*/
window.videojs.hls.ExpGolomb = function(workingData) {
window.videojs.Hls.ExpGolomb = function(workingData) {
var
// the number of bytes left to examine in workingData
workingBytesAvailable = workingData.byteLength,
......
(function(window) {
window.videojs = window.videojs || {};
window.videojs.hls = window.videojs.hls || {};
window.videojs.Hls = window.videojs.Hls || {};
var hls = window.videojs.hls;
var hls = window.videojs.Hls;
// (type:uint, extraData:Boolean = false) extends ByteArray
hls.FlvTag = function(type, extraData) {
......
......@@ -8,8 +8,8 @@
(function(window) {
var
ExpGolomb = window.videojs.hls.ExpGolomb,
FlvTag = window.videojs.hls.FlvTag,
ExpGolomb = window.videojs.Hls.ExpGolomb,
FlvTag = window.videojs.Hls.FlvTag,
H264ExtraData = function() {
this.sps = []; // :Array
......@@ -234,7 +234,7 @@
* an h264 stream. Exactly one byte.
*/
// incomplete, see Table 7.1 of ITU-T H.264 for 12-32
window.videojs.hls.NALUnitType = NALUnitType = {
window.videojs.Hls.NALUnitType = NALUnitType = {
unspecified: 0,
slice_layer_without_partitioning_rbsp_non_idr: 1,
slice_data_partition_a_layer_rbsp: 2,
......@@ -249,7 +249,7 @@
end_of_stream_rbsp: 11
};
window.videojs.hls.H264Stream = function() {
window.videojs.Hls.H264Stream = function() {
var
next_pts, // :uint;
next_dts, // :uint;
......
......@@ -32,7 +32,7 @@
}
return result;
},
Stream = videojs.hls.Stream,
Stream = videojs.Hls.Stream,
LineStream,
ParseStream,
Parser;
......
......@@ -5,8 +5,8 @@
(function(window, videojs) {
'use strict';
var
resolveUrl = videojs.hls.resolveUrl,
xhr = videojs.hls.xhr,
resolveUrl = videojs.Hls.resolveUrl,
xhr = videojs.Hls.xhr,
/**
* Returns a new master playlist that is the result of merging an
......@@ -51,6 +51,7 @@
var
loader = this,
media,
mediaUpdateTimeout,
request,
haveMetadata = function(error, xhr, url) {
......@@ -88,7 +89,7 @@
// refresh live playlists after a target duration passes
if (!loader.media().endList) {
window.setTimeout(function() {
mediaUpdateTimeout = window.setTimeout(function() {
loader.trigger('mediaupdatetimeout');
}, refreshDelay);
}
......@@ -104,6 +105,13 @@
loader.state = 'HAVE_NOTHING';
loader.dispose = function() {
if (request) {
request.abort();
}
window.clearTimeout(mediaUpdateTimeout);
};
/**
* When called without any arguments, returns the currently
* active media playlist. When called with a single argument,
......@@ -213,7 +221,9 @@
haveMetadata(error,
this,
parser.manifest.playlists[0].uri);
if (!error) {
loader.trigger('loadedmetadata');
}
});
return loader.trigger('loadedplaylist');
}
......@@ -231,7 +241,7 @@
return loader.trigger('loadedmetadata');
});
};
PlaylistLoader.prototype = new videojs.hls.Stream();
PlaylistLoader.prototype = new videojs.Hls.Stream();
videojs.hls.PlaylistLoader = PlaylistLoader;
videojs.Hls.PlaylistLoader = PlaylistLoader;
})(window, window.videojs);
......
(function(window) {
var
videojs = window.videojs,
FlvTag = videojs.hls.FlvTag,
H264Stream = videojs.hls.H264Stream,
AacStream = videojs.hls.AacStream,
FlvTag = videojs.Hls.FlvTag,
H264Stream = videojs.Hls.H264Stream,
AacStream = videojs.Hls.AacStream,
MP2T_PACKET_LENGTH,
STREAM_TYPES;
......@@ -11,7 +11,7 @@
* An object that incrementally transmuxes MPEG2 Trasport Stream
* chunks into an FLV.
*/
videojs.hls.SegmentParser = function() {
videojs.Hls.SegmentParser = function() {
var
self = this,
parseTSPacket,
......@@ -432,8 +432,8 @@
};
// MPEG2-TS constants
videojs.hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188;
videojs.hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = {
videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188;
videojs.Hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = {
h264: 0x1b,
adts: 0x0f
};
......
......@@ -65,5 +65,5 @@
});
};
videojs.hls.Stream = Stream;
videojs.Hls.Stream = Stream;
})(window.videojs);
......
......@@ -8,28 +8,6 @@
(function(window, videojs, document, undefined) {
videojs.hls = {
/**
* Whether the browser has built-in HLS support.
*/
supportsNativeHls: (function() {
var
video = document.createElement('video'),
xMpegUrl,
vndMpeg;
// native HLS is definitely not supported if HTML5 video isn't
if (!videojs.Html5.isSupported()) {
return false;
}
xMpegUrl = video.canPlayType('application/x-mpegURL');
vndMpeg = video.canPlayType('application/vnd.apple.mpegURL');
return (/probably|maybe/).test(xMpegUrl) ||
(/probably|maybe/).test(vndMpeg);
})()
};
var
// the desired length of video to maintain in the buffer, in seconds
......@@ -95,60 +73,7 @@ var
}
},
/**
* Creates and sends an XMLHttpRequest.
* @param options {string | object} if this argument is a string, it
* is intrepreted as a URL and a simple GET request is
* inititated. If it is an object, it should contain a `url`
* property that indicates the URL to request and optionally a
* `method` which is the type of HTTP request to send.
* @param callback (optional) {function} a function to call when the
* request completes. If the request was not successful, the first
* argument will be falsey.
* @return {object} the XMLHttpRequest that was initiated.
*/
xhr = videojs.hls.xhr = function(url, callback) {
var
options = {
method: 'GET'
},
request;
if (typeof callback !== 'function') {
callback = function() {};
}
if (typeof url === 'object') {
options = videojs.util.mergeOptions(options, url);
url = options.url;
}
request = new window.XMLHttpRequest();
request.open(options.method, url);
if (options.responseType) {
request.responseType = options.responseType;
}
if (options.withCredentials) {
request.withCredentials = true;
}
request.onreadystatechange = function() {
// wait until the request completes
if (this.readyState !== 4) {
return;
}
// request error
if (this.status >= 400 || this.status === 0) {
return callback.call(this, true, url);
}
return callback.call(this, false, url);
};
request.send(null);
return request;
},
xhr,
/**
* TODO - Document this great feature.
......@@ -228,6 +153,12 @@ var
var
duration = 0,
segment,
i;
if (!playlist) {
return 0;
}
i = (playlist.segments || []).length;
// if present, use the duration specified in the playlist
......@@ -247,134 +178,17 @@ var
return duration;
},
/**
* 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 = videojs.hls.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;
resolveUrl,
// 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;
},
/**
* Initializes the HLS plugin.
* @param options {mixed} the URL to an HLS playlist
*/
init = function(options) {
initSource = function(player, mediaSource, srcUrl) {
var
mediaSource = new videojs.MediaSource(),
segmentParser = new videojs.hls.SegmentParser(),
player = this,
srcUrl,
segmentParser = new videojs.Hls.SegmentParser(),
segmentXhr,
settings,
settings = videojs.util.mergeOptions({}, player.options().hls),
fillBuffer,
updateDuration;
// if the video element supports HLS natively, do nothing
if (videojs.hls.supportsNativeHls) {
return;
}
settings = videojs.util.mergeOptions({}, options);
srcUrl = (function() {
var
extname,
i = 0,
j = 0,
src = player.el().querySelector('.vjs-tech').src,
sources = player.options().sources,
techName,
length = sources.length;
// use the URL specified in options if one was provided
if (typeof options === 'string') {
return options;
} else if (options && options.url) {
return options.url;
}
// src attributes take precedence over source children
if (src) {
// assume files with the m3u8 extension are HLS
extname = (/[^#?]*(?:\/[^#?]*\.([^#?]*))/).exec(src);
if (extname && extname[1] === 'm3u8') {
return src;
}
return;
}
// find the first playable source
for (; i < length; i++) {
// ignore sources without a specified type
if (!sources[i].type) {
continue;
}
// do nothing if the source is handled by one of the standard techs
for (j in player.options().techOrder) {
techName = player.options().techOrder[j];
techName = techName[0].toUpperCase() + techName.substring(1);
if (videojs[techName].canPlaySource({ type: sources[i].type })) {
return;
}
}
// use the plugin if the MIME type specifies HLS
if ((/application\/x-mpegURL/).test(sources[i].type) ||
(/application\/vnd\.apple\.mpegURL/).test(sources[i].type)) {
return sources[i].src;
}
}
})();
if (!srcUrl) {
// do nothing until the plugin is initialized with a valid URL
videojs.log('hls: no valid playlist URL specified');
return;
}
// expose the HLS plugin state
player.hls.readyState = function() {
if (!player.hls.media) {
return 0; // HAVE_NOTHING
}
return 1; // HAVE_METADATA
};
player.on('seeking', function() {
var currentTime = player.currentTime();
......@@ -397,16 +211,8 @@ var
* Update the player duration
*/
updateDuration = function(playlist) {
var tech;
// update the duration
player.duration(totalDuration(playlist));
// tell the flash tech of the new duration
tech = player.el().querySelector('.vjs-tech');
if(tech.vjs_setProperty) {
tech.vjs_setProperty('duration', player.duration());
}
// manually fire the duration change
player.trigger('durationchange');
};
/**
......@@ -502,7 +308,8 @@ var
}
// if no segments are available, do nothing
if (!player.hls.playlists.media().segments) {
if (player.hls.playlists.state === "HAVE_NOTHING" ||
!player.hls.playlists.media().segments) {
return;
}
......@@ -609,7 +416,7 @@ var
player.hls.mediaIndex = 0;
player.hls.playlists =
new videojs.hls.PlaylistLoader(srcUrl, settings.withCredentials);
new videojs.Hls.PlaylistLoader(srcUrl, settings.withCredentials);
player.hls.playlists.on('loadedmetadata', function() {
oldMediaPlaylist = player.hls.playlists.media();
......@@ -638,28 +445,160 @@ var
oldMediaPlaylist = updatedPlaylist;
});
});
player.src([{
};
var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
videojs.Hls = videojs.Flash.extend({
init: function(player, options, ready) {
var
source = options.source,
settings = player.options();
player.hls = this;
delete options.source;
options.swf = settings.flash.swf;
videojs.Flash.call(this, player, options, ready);
options.source = source;
videojs.Hls.prototype.src.call(this, options.source && options.source.src);
}
});
videojs.Hls.prototype.src = function(src) {
var
player = this.player(),
mediaSource,
source;
if (src) {
mediaSource = new videojs.MediaSource();
source = {
src: videojs.URL.createObjectURL(mediaSource),
type: "video/flv"
}]);
};
this.mediaSource = mediaSource;
initSource(player, mediaSource, src);
this.ready(function() {
this.el().vjs_src(source.src);
});
}
};
if (player.options().autoplay) {
player.play();
videojs.Hls.prototype.duration = function() {
var playlists = this.playlists;
if (playlists) {
return totalDuration(playlists.media());
}
return 0;
};
videojs.Hls.prototype.dispose = function() {
if (this.playlists) {
this.playlists.dispose();
}
videojs.Flash.prototype.dispose.call(this);
};
videojs.Hls.isSupported = function() {
return videojs.Flash.isSupported() && videojs.MediaSource;
};
videojs.Hls.canPlaySource = function(srcObj) {
return mpegurlRE.test(srcObj.type) || videojs.Flash.canPlaySource.call(this, srcObj);
};
/**
* Creates and sends an XMLHttpRequest.
* @param options {string | object} if this argument is a string, it
* is intrepreted as a URL and a simple GET request is
* inititated. If it is an object, it should contain a `url`
* property that indicates the URL to request and optionally a
* `method` which is the type of HTTP request to send.
* @param callback (optional) {function} a function to call when the
* request completes. If the request was not successful, the first
* argument will be falsey.
* @return {object} the XMLHttpRequest that was initiated.
*/
xhr = videojs.Hls.xhr = function(url, callback) {
var
options = {
method: 'GET'
},
request;
if (typeof callback !== 'function') {
callback = function() {};
}
if (typeof url === 'object') {
options = videojs.util.mergeOptions(options, url);
url = options.url;
}
};
videojs.plugin('hls', function() {
if (typeof Uint8Array === 'undefined') {
request = new window.XMLHttpRequest();
request.open(options.method, url);
if (options.responseType) {
request.responseType = options.responseType;
}
if (options.withCredentials) {
request.withCredentials = true;
}
request.onreadystatechange = function() {
// wait until the request completes
if (this.readyState !== 4) {
return;
}
var initialize = function() {
return function() {
this.hls = initialize();
init.apply(this, arguments);
};
// request error
if (this.status >= 400 || this.status === 0) {
return callback.call(this, true, url);
}
return callback.call(this, false, url);
};
initialize().apply(this, arguments);
});
request.send(null);
return request;
};
/**
* 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 = videojs.Hls.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;
};
})(window, window.videojs, document);
......
......@@ -21,7 +21,7 @@
*/
var
buffer,
ExpGolomb = window.videojs.hls.ExpGolomb,
ExpGolomb = window.videojs.Hls.ExpGolomb,
expGolomb;
module('Exponential Golomb coding');
......
......@@ -19,7 +19,7 @@
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var FlvTag = window.videojs.hls.FlvTag;
var FlvTag = window.videojs.Hls.FlvTag;
module('FLV tag');
......
......@@ -2,12 +2,12 @@
module('H264 Stream');
var
nalUnitTypes = window.videojs.hls.NALUnitType,
FlvTag = window.videojs.hls.FlvTag;
nalUnitTypes = window.videojs.Hls.NALUnitType,
FlvTag = window.videojs.Hls.FlvTag;
test('metadata is generated for IDRs after a full NAL unit is written', function() {
var
h264Stream = new videojs.hls.H264Stream(),
h264Stream = new videojs.Hls.H264Stream(),
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
......@@ -62,7 +62,7 @@ test('metadata is generated for IDRs after a full NAL unit is written', function
test('starting PTS values can be negative', function() {
var
h264Stream = new videojs.hls.H264Stream(),
h264Stream = new videojs.Hls.H264Stream(),
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
......
......@@ -127,7 +127,7 @@
original.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var parser = new videojs.hls.SegmentParser(),
var parser = new videojs.Hls.SegmentParser(),
tags = [parser.getFlvHeader()],
tag,
hex,
......@@ -164,7 +164,7 @@
}
hex = '<pre>'
hex += videojs.hls.utils.hexDump(data);
hex += videojs.Hls.utils.hexDump(data);
hex += '</pre>'
vjsOutput.innerHTML = hex;
......@@ -201,7 +201,7 @@
}
// output the hex dump
hex += videojs.hls.utils.hexDump(bytes);
hex += videojs.Hls.utils.hexDump(bytes);
hex += '</pre>';
workingOutput.innerHTML = hex;
});
......
......@@ -36,26 +36,26 @@
test('throws if the playlist url is empty or undefined', function() {
throws(function() {
videojs.hls.PlaylistLoader();
videojs.Hls.PlaylistLoader();
}, 'requires an argument');
throws(function() {
videojs.hls.PlaylistLoader('');
videojs.Hls.PlaylistLoader('');
}, 'does not accept the empty string');
});
test('starts without any metadata', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
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() {
new videojs.hls.PlaylistLoader('master.m3u8');
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');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:\n' +
......@@ -67,7 +67,7 @@
test('jumps to HAVE_METADATA when initialized with a media playlist', function() {
var
loadedmetadatas = 0,
loader = new videojs.hls.PlaylistLoader('media.m3u8');
loader = new videojs.Hls.PlaylistLoader('media.m3u8');
loader.on('loadedmetadata', function() {
loadedmetadatas++;
});
......@@ -85,7 +85,7 @@
});
test('jumps to HAVE_METADATA when initialized with a live media playlist', function() {
var loader = new videojs.hls.PlaylistLoader('media.m3u8');
var loader = new videojs.Hls.PlaylistLoader('media.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
......@@ -99,7 +99,7 @@
var
loadedPlaylist = 0,
loadedMetadata = 0,
loader = new videojs.hls.PlaylistLoader('master.m3u8');
loader = new videojs.Hls.PlaylistLoader('master.m3u8');
loader.on('loadedplaylist', function() {
loadedPlaylist++;
});
......@@ -131,7 +131,7 @@
});
test('moves to HAVE_CURRENT_METADATA when refreshing the playlist', function() {
var loader = new videojs.hls.PlaylistLoader('live.m3u8');
var loader = new videojs.Hls.PlaylistLoader('live.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
......@@ -145,7 +145,7 @@
});
test('returns to HAVE_METADATA after refreshing the playlist', function() {
var loader = new videojs.hls.PlaylistLoader('live.m3u8');
var loader = new videojs.Hls.PlaylistLoader('live.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXTINF:10,\n' +
......@@ -161,7 +161,7 @@
test('emits an error when an initial playlist request fails', function() {
var
errors = [],
loader = new videojs.hls.PlaylistLoader('master.m3u8');
loader = new videojs.Hls.PlaylistLoader('master.m3u8');
loader.on('error', function() {
errors.push(loader.error);
......@@ -175,7 +175,7 @@
test('errors when an initial media playlist request fails', function() {
var
errors = [],
loader = new videojs.hls.PlaylistLoader('master.m3u8');
loader = new videojs.Hls.PlaylistLoader('master.m3u8');
loader.on('error', function() {
errors.push(loader.error);
......@@ -197,7 +197,7 @@
// http://tools.ietf.org/html/draft-pantos-http-live-streaming-12#section-6.3.4
test('halves the refresh timeout if a playlist is unchanged' +
'since the last reload', function() {
new videojs.hls.PlaylistLoader('live.m3u8');
new videojs.Hls.PlaylistLoader('live.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
......@@ -218,7 +218,7 @@
});
test('media-sequence updates are considered a playlist change', function() {
new videojs.hls.PlaylistLoader('live.m3u8');
new videojs.Hls.PlaylistLoader('live.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
......@@ -238,7 +238,7 @@
test('emits an error if a media refresh fails', function() {
var
errors = 0,
loader = new videojs.hls.PlaylistLoader('live.m3u8');
loader = new videojs.Hls.PlaylistLoader('live.m3u8');
loader.on('error', function() {
errors++;
......@@ -256,7 +256,7 @@
});
test('switches media playlists when requested', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
......@@ -284,7 +284,7 @@
});
test('can switch media playlists based on URI', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
......@@ -312,7 +312,7 @@
});
test('aborts in-flight playlist refreshes when switching', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
......@@ -331,7 +331,7 @@
});
test('switching to the active playlist is a no-op', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
......@@ -350,7 +350,7 @@
});
test('throws an error if a media switch is initiated too early', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
throws(function() {
loader.media('high.m3u8');
......@@ -368,7 +368,7 @@
});
test('throws an error if a switch to an unrecognized playlist is requested', function() {
var loader = new videojs.hls.PlaylistLoader('master.m3u8');
var loader = new videojs.Hls.PlaylistLoader('master.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-STREAM-INF:BANDWIDTH=1\n' +
......@@ -378,4 +378,31 @@
loader.media('unrecognized.m3u8');
}, 'throws an error');
});
test('dispose cancels the refresh timeout', function() {
var loader = new videojs.Hls.PlaylistLoader('live.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXTINF:10,\n' +
'0.ts\n');
loader.dispose();
// a lot of time passes...
clock.tick(15 * 1000);
strictEqual(requests.length, 0, 'no refresh request was made');
});
test('dispose aborts pending refresh requests', function() {
var loader = new videojs.Hls.PlaylistLoader('live.m3u8');
requests.pop().respond(200, null,
'#EXTM3U\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXTINF:10,\n' +
'0.ts\n');
clock.tick(10 * 1000);
loader.dispose();
ok(requests[0].aborted, 'refresh request aborted');
});
})(window);
......
......@@ -39,7 +39,7 @@
module('segment parser', {
setup: function() {
parser = new window.videojs.hls.SegmentParser();
parser = new window.videojs.Hls.SegmentParser();
}
});
......@@ -168,11 +168,11 @@
result = result.concat(makePsi(settings));
// ensure the resulting packet is the correct size
result.length = window.videojs.hls.SegmentParser.MP2T_PACKET_LENGTH;
result.length = window.videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH;
return result;
},
h264Type = window.videojs.hls.SegmentParser.STREAM_TYPES.h264,
adtsType = window.videojs.hls.SegmentParser.STREAM_TYPES.adts;
h264Type = window.videojs.Hls.SegmentParser.STREAM_TYPES.h264,
adtsType = window.videojs.Hls.SegmentParser.STREAM_TYPES.adts;
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
......
(function(window, videojs, undefined) {
'use strict';
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
......@@ -22,15 +23,37 @@
var
player,
oldFlashSupported,
oldMediaSourceOpen,
oldSegmentParser,
oldSetTimeout,
oldSourceBuffer,
oldSupportsNativeHls,
xhrUrls,
oldFlashSupported,
requests,
xhr,
createPlayer = function(options) {
var tech, video, player;
video = document.createElement('video');
document.querySelector('#qunit-fixture').appendChild(video);
player = videojs(video, {
flash: {
swf: ''
},
techOrder: ['hls'],
hls: options || {}
});
player.buffered = function() {
return videojs.createTimeRange(0, 0);
};
tech = player.el().querySelector('.vjs-tech');
tech.vjs_getProperty = function() {};
tech.vjs_src = function() {};
videojs.Flash.onReady(tech.id);
return player;
},
standardXHRResponse = function(request) {
if (!request.url) {
return;
......@@ -82,37 +105,23 @@ var
module('HLS', {
setup: function() {
oldMediaSourceOpen = videojs.MediaSource.open;
videojs.MediaSource.open = function() {};
// mock out Flash features for phantomjs
oldFlashSupported = videojs.Flash.isSupported;
videojs.Flash.isSupported = function() {
return true;
};
oldSourceBuffer = window.videojs.SourceBuffer;
window.videojs.SourceBuffer = function() {
this.appendBuffer = function() {};
this.abort = function() {};
};
// force native HLS to be ignored
oldSupportsNativeHls = videojs.hls.supportsNativeHls;
videojs.hls.supportsNativeHls = false;
// create the test player
var video = document.createElement('video');
document.querySelector('#qunit-fixture').appendChild(video);
player = videojs(video, {
flash: {
swf: '../node_modules/video.js/dist/video-js/video-js.swf'
},
techOrder: ['flash']
});
player.buffered = function() {
return videojs.createTimeRange(0, 0);
};
// store functionality that some tests need to mock
oldSegmentParser = videojs.hls.SegmentParser;
oldSegmentParser = videojs.Hls.SegmentParser;
oldSetTimeout = window.setTimeout;
// fake XHRs
......@@ -121,13 +130,16 @@ module('HLS', {
xhr.onCreate = function(xhr) {
requests.push(xhr);
};
xhrUrls = [];
// create the test player
player = createPlayer();
},
teardown: function() {
player.dispose();
videojs.Flash.isSupported = oldFlashSupported;
videojs.hls.supportsNativeHls = oldSupportsNativeHls;
videojs.hls.SegmentParser = oldSegmentParser;
videojs.MediaSource.open = oldMediaSourceOpen;
videojs.Hls.SegmentParser = oldSegmentParser;
videojs.SourceBuffer = oldSourceBuffer;
window.setTimeout = oldSetTimeout;
xhr.restore();
......@@ -140,8 +152,11 @@ test('starts playing if autoplay is specified', function() {
plays++;
};
player.options().autoplay = true;
player.hls('manifest/playlist.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/playlist.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -155,9 +170,12 @@ test('creates a PlaylistLoader on init', function() {
loadedmetadata = true;
});
player.hls('manifest/playlist.m3u8');
ok(!player.hls.playlists, 'waits for sourceopen to create the loader');
videojs.mediaSources[player.currentSrc()].trigger({
ok(!player.hls.playlists, 'waits for set src to create the loader');
player.src({
src:'manifest/playlist.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
standardXHRResponse(requests[0]);
......@@ -178,8 +196,11 @@ test('sets the duration if one is available on the playlist', function() {
}
calls++;
};
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -197,8 +218,11 @@ test('calculates the duration if needed', function() {
}
durations.push(duration);
};
player.hls('http://example.com/manifest/missingExtinf.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/manifest/missingExtinf.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -210,11 +234,14 @@ test('calculates the duration if needed', function() {
});
test('starts downloading a segment on loadedmetadata', function() {
player.hls('manifest/media.m3u8');
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.buffered = function() {
return videojs.createTimeRange(0, 0);
};
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -228,8 +255,11 @@ test('starts downloading a segment on loadedmetadata', function() {
});
test('recognizes absolute URIs and requests them unmodified', function() {
player.hls('manifest/absoluteUris.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/absoluteUris.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -241,8 +271,11 @@ test('recognizes absolute URIs and requests them unmodified', function() {
});
test('recognizes domain-relative URLs', function() {
player.hls('manifest/domainUris.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/domainUris.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -253,14 +286,31 @@ test('recognizes domain-relative URLs', function() {
'the first segment is requested');
});
test('re-initializes the plugin for each source', function() {
var firstInit, secondInit;
player.hls('manifest/master.m3u8');
firstInit = player.hls;
player.hls('manifest/master.m3u8');
secondInit = player.hls;
test('re-initializes the tech for each source', function() {
var firstPlaylists, secondPlaylists, firstMSE, secondMSE;
notStrictEqual(firstInit, secondInit, 'the plugin object is replaced');
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
firstPlaylists = player.hls.playlists;
firstMSE = player.hls.mediaSource;
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
secondPlaylists = player.hls.playlists;
secondMSE = player.hls.mediaSource;
notStrictEqual(firstPlaylists, secondPlaylists, 'the playlist object is not reused');
notStrictEqual(firstMSE, secondMSE, 'the media source object is not reused');
});
test('triggers an error when a master playlist request errors', function() {
......@@ -268,8 +318,11 @@ test('triggers an error when a master playlist request errors', function() {
player.on('error', function() {
error = player.hls.error;
});
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
requests.pop().respond(500);
......@@ -279,8 +332,11 @@ test('triggers an error when a master playlist request errors', function() {
});
test('downloads media playlists after loading the master', function() {
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -309,8 +365,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read
};
this.send = function() {};
};
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
player.trigger('timeupdate');
......@@ -320,8 +379,11 @@ test('timeupdates do not check to fill the buffer until a media playlist is read
});
test('calculates the bandwidth after downloading a segment', function() {
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -337,12 +399,15 @@ test('calculates the bandwidth after downloading a segment', function() {
test('selects a playlist after segment downloads', function() {
var calls = 0;
player.hls('manifest/master.m3u8');
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.selectPlaylist = function() {
calls++;
return player.hls.playlists.master.playlists[0];
};
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -367,8 +432,11 @@ test('selects a playlist after segment downloads', function() {
test('moves to the next segment if there is a network error', function() {
var mediaIndex;
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -386,7 +454,10 @@ test('updates the duration after switching playlists', function() {
var
calls = 0,
selectedPlaylist = false;
player.hls('manifest/master.m3u8');
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.selectPlaylist = function() {
selectedPlaylist = true;
return player.hls.playlists.master.playlists[1];
......@@ -400,7 +471,7 @@ test('updates the duration after switching playlists', function() {
calls++;
}
};
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -418,8 +489,11 @@ test('downloads additional playlists if required', function() {
playlist = {
uri: 'media3.m3u8'
};
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -456,8 +530,11 @@ test('downloads additional playlists if required', function() {
test('selects a playlist below the current bandwidth', function() {
var playlist;
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -478,8 +555,11 @@ test('selects a playlist below the current bandwidth', function() {
test('raises the minimum bitrate for a stream proportionially', function() {
var playlist;
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -500,8 +580,11 @@ test('raises the minimum bitrate for a stream proportionially', function() {
test('uses the lowest bitrate if no other is suitable', function() {
var playlist;
player.hls('manifest/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -520,9 +603,12 @@ test('uses the lowest bitrate if no other is suitable', function() {
test('selects the correct rendition by player dimensions', function() {
var playlist;
player.hls('manifest/master.m3u8');
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -550,14 +636,17 @@ test('selects the correct rendition by player dimensions', function() {
test('does not download the next segment if the buffer is full', function() {
player.hls('manifest/media.m3u8');
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.currentTime = function() {
return 15;
};
player.buffered = function() {
return videojs.createTimeRange(0, 20);
};
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -569,8 +658,11 @@ test('does not download the next segment if the buffer is full', function() {
});
test('downloads the next segment if the buffer is getting low', function() {
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -597,8 +689,11 @@ test('downloads the next segment if the buffer is getting low', function() {
});
test('stops downloading segments at the end of the playlist', function() {
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
standardXHRResponse(requests[0]);
......@@ -606,13 +701,16 @@ test('stops downloading segments at the end of the playlist', function() {
player.hls.mediaIndex = 4;
player.trigger('timeupdate');
strictEqual(xhrUrls.length, 0, 'no request is made');
strictEqual(requests.length, 0, 'no request is made');
});
test('only makes one segment request at a time', function() {
var openedXhrs = 0;
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
xhr.restore();
......@@ -634,68 +732,12 @@ test('only makes one segment request at a time', function() {
xhr = sinon.useFakeXMLHttpRequest();
});
test('uses the src attribute if no options are provided and it ends in ".m3u8"', function() {
var url = 'http://example.com/services/mobile/streaming/index/master.m3u8?videoId=1824650741001';
player.el().querySelector('.vjs-tech').src = url;
player.hls();
videojs.mediaSources[player.currentSrc()].trigger({
type: 'sourceopen'
});
strictEqual(requests[0].url, url, 'currentSrc is used');
});
test('ignores src attribute if it doesn\'t have the "m3u8" extension', function() {
var tech = player.el().querySelector('.vjs-tech');
tech.src = 'basdfasdfasdfliel//.m3u9';
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(requests.length, 0, 'no request is made');
tech.src = '';
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(requests.length, 0, 'no request is made');
tech.src = 'http://example.com/movie.mp4?q=why.m3u8';
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(requests.length, 0, 'no request is made');
tech.src = 'http://example.m3u8/movie.mp4';
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(requests.length, 0, 'no request is made');
tech.src = '//example.com/movie.mp4#http://tricky.com/master.m3u8';
player.hls();
ok(!(player.currentSrc() in videojs.mediaSources), 'no media source is created');
strictEqual(requests.length, 0, 'no request is made');
});
test('activates if the first playable source is HLS', function() {
var video;
document.querySelector('#qunit-fixture').innerHTML =
'<video controls>' +
'<source type="slartibartfast$%" src="movie.slarti">' +
'<source type="application/x-mpegURL" src="movie.m3u8">' +
'<source type="video/mp4" src="movie.mp4">' +
'</video>';
video = document.querySelector('#qunit-fixture video');
player = videojs(video, {
flash: {
swf: '../node_modules/video.js/dist/video-js/video-js.swf'
},
techOrder: ['flash']
});
player.hls();
ok(player.currentSrc() in videojs.mediaSources, 'media source created');
});
test('cancels outstanding XHRs when seeking', function() {
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
standardXHRResponse(requests[0]);
......@@ -721,7 +763,7 @@ test('cancels outstanding XHRs when seeking', function() {
test('flushes the parser after each segment', function() {
var flushes = 0;
// mock out the segment parser
videojs.hls.SegmentParser = function() {
videojs.Hls.SegmentParser = function() {
this.getFlvHeader = function() {
return [];
};
......@@ -732,8 +774,11 @@ test('flushes the parser after each segment', function() {
this.tagsAvailable = function() {};
};
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -749,7 +794,7 @@ test('drops tags before the target timestamp when seeking', function() {
bytes = [];
// mock out the parser and source buffer
videojs.hls.SegmentParser = mockSegmentParser(tags);
videojs.Hls.SegmentParser = mockSegmentParser(tags);
window.videojs.SourceBuffer = function() {
this.appendBuffer = function(chunk) {
bytes.push(chunk);
......@@ -764,8 +809,11 @@ test('drops tags before the target timestamp when seeking', function() {
// push a tag into the buffer
tags.push({ pts: 0, bytes: 0 });
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
standardXHRResponse(requests[0]);
......@@ -803,7 +851,7 @@ test('clears pending buffer updates when seeking', function() {
tags = [{ pts: 0, bytes: 0 }];
// mock out the parser and source buffer
videojs.hls.SegmentParser = mockSegmentParser(tags);
videojs.Hls.SegmentParser = mockSegmentParser(tags);
window.videojs.SourceBuffer = function() {
this.appendBuffer = function(chunk) {
bytes.push(chunk);
......@@ -818,8 +866,11 @@ test('clears pending buffer updates when seeking', function() {
};
// queue up a tag to be pushed into the buffer (but don't push it yet!)
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -846,8 +897,11 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
player.on('error', function() {
errorTriggered = true;
});
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
requests.pop().respond(404);
......@@ -862,9 +916,12 @@ test('playlist 404 should trigger MEDIA_ERR_NETWORK', function() {
});
test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
player.hls('manifest/media.m3u8');
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -875,9 +932,12 @@ test('segment 404 should trigger MEDIA_ERR_NETWORK', function () {
});
test('segment 500 should trigger MEDIA_ERR_ABORTED', function () {
player.hls('manifest/media.m3u8');
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -887,17 +947,12 @@ test('segment 500 should trigger MEDIA_ERR_ABORTED', function () {
equal(4, player.hls.error.code, 'Player error code should be set to MediaError.MEDIA_ERR_ABORTED');
});
test('has no effect if native HLS is available', function() {
videojs.hls.supportsNativeHls = true;
player.hls('http://example.com/manifest/master.m3u8');
ok(!(player.currentSrc() in videojs.mediaSources),
'no media source was opened');
});
test('duration is Infinity for live playlists', function() {
player.hls('http://example.com/manifest/missingEndlist.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/manifest/missingEndlist.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -912,8 +967,11 @@ test('does not reload playlists with an endlist tag', function() {
window.setTimeout = function(callback, timeout) {
callbacks.push({ callback: callback, timeout: timeout });
};
player.hls('manifest/media.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'manifest/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -921,8 +979,11 @@ test('does not reload playlists with an endlist tag', function() {
});
test('updates the media index when a playlist reloads', function() {
player.hls('http://example.com/live-updating.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/live-updating.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -964,8 +1025,11 @@ test('mediaIndex is zero before the first segment loads', function() {
this.open = function() {};
this.send = function() {};
};
player.hls('http://example.com/first-seg-load.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/first-seg-load.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -973,8 +1037,11 @@ test('mediaIndex is zero before the first segment loads', function() {
});
test('reloads out-of-date live playlists when switching variants', function() {
player.hls('http://example.com/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
......@@ -1014,29 +1081,40 @@ test('does not reload master playlists', function() {
callbacks.push(callback);
};
player.hls('http://example.com/master.m3u8');
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
strictEqual(callbacks.length, 0, 'no reload scheduled');
strictEqual(callbacks.length,
0, 'no reload scheduled');
});
test('if withCredentials option is used, withCredentials is set on the XHR object', function() {
player.hls({
url: 'http://example.com/media.m3u8',
player.dispose();
player = createPlayer({
withCredentials: true
});
videojs.mediaSources[player.currentSrc()].trigger({
player.src({
src: 'http://example.com/media.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
ok(requests[0].withCredentials, "with credentials should be set to true if that option is passed in");
});
test('does not break if the playlist has no segments', function() {
player.hls('manifest/master.m3u8');
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
try {
videojs.mediaSources[player.currentSrc()].trigger({
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
requests[0].respond(200, null,
......@@ -1051,4 +1129,22 @@ test('does not break if the playlist has no segments', function() {
strictEqual(requests.length, 1, 'no requests for non-existent segments were queued');
});
test('disposes the playlist loader', function() {
var disposes = 0, player;
player = createPlayer();
player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
player.hls.mediaSource.trigger({
type: 'sourceopen'
});
player.hls.playlists.dispose = function() {
disposes++;
};
player.dispose();
strictEqual(disposes, 1, 'disposed playlist loader');
});
})(window, window.videojs);
......