a810eb63 by David LaPalomento

Merge pull request #4 from dlapalomento/feature/m3u8s

HLS playback support cleanup
2 parents 3b271282 884e8d3c
/node_modules/
*~
*.iml
......
......@@ -10,5 +10,11 @@
"unused": true,
"boss": true,
"eqnull": true,
"node": true
"node": true,
"camelcase": true,
"nonew": true,
"quotmark": "single",
"trailing": true,
"maxlen": 80
}
......
......@@ -20,7 +20,19 @@ module.exports = function(grunt) {
stripBanners: true
},
dist: {
src: ['src/*.js'],
src: ['src/video-js-hls.js',
'src/flv-tag.js',
'src/exp-golomb.js',
'src/h264-stream.js',
'src/aac-stream.js',
'src/segment-parser.js',
'src/segment-controller.js',
'src/m3u8/m3u8.js',
'src/m3u8/m3u8-tag-types.js',
'src/m3u8/m3u8-parser.js',
'src/manifest-controller.js',
'src/segment-controller.js',
'src/hls-playback-controller.js'],
dest: 'dist/videojs.hls.js'
},
},
......@@ -53,7 +65,7 @@ module.exports = function(grunt) {
options: {
jshintrc: 'test/.jshintrc'
},
src: ['test/**/*.js', '!test/tsSegment.js']
src: ['test/**/*.js', '!test/tsSegment.js', '!test/fixtures/*.js']
},
},
watch: {
......@@ -81,6 +93,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
// Default task.
grunt.registerTask('default', ['jshint', 'qunit', 'clean', 'concat', 'uglify']);
grunt.registerTask('default',
['jshint', 'qunit', 'clean', 'concat', 'uglify']);
};
......
......@@ -25,9 +25,11 @@
<!-- m3u8 handling -->
<script src="src/m3u8/m3u8.js"></script>
<script src="src/m3u8/m3u8-parser.js"></script>
<script src="src/m3u8/m3u8-tag-types.js"></script>
<script src="src/m3u8/m3u8-parser.js"></script>
<script src="src/manifest-controller.js"></script>
<script src="src/segment-controller.js"></script>
<script src="src/hls-playback-controller.js"></script>
<!-- example MPEG2-TS segments -->
<!-- bipbop -->
......@@ -48,51 +50,14 @@
// initialize the player
videojs.options.flash.swf = 'node_modules/videojs-media-sources/video-js-with-mse.swf';
video = videojs('video');
// create a media source
mediaSource = new videojs.MediaSource();
mediaSource.addEventListener('sourceopen', function(event){
var
parser = new videojs.hls.SegmentParser(),
sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
everything = [];
// feed parsed bytes into the player
everything.push(parser.getFlvHeader());
sourceBuffer.appendBuffer(everything[everything.length - 1], video);
parser.parseSegmentBinaryData(window.bcSegment);
while (parser.tagsAvailable()) {
everything.push(parser.getNextTag().bytes);
sourceBuffer.appendBuffer(everything[everything.length - 1], video);
}
parser.flushTags();
while (parser.tagsAvailable()) {
everything.push(parser.getNextTag().bytes);
sourceBuffer.appendBuffer(everything[everything.length - 1], video);
}
var iframe = document.createElement('iframe');
iframe.src = 'data:video/x-flv;base64,' + window.btoa((Array.prototype.map.call(everything.reduce(function(result, next) {
var array = new Uint8Array(result.byteLength + next.byteLength);
array.set(result);
array.set(next, result.length);
return array;
}), function(byte) {
return String.fromCharCode(byte);
})).join(''));
//console.log(iframe);
// document.body.appendChild(iframe);
}, false);
url = videojs.URL.createObjectURL(mediaSource);
video.src({
src: url,
type: "video/flv"
video = videojs('video',{},function(){
this.playbackController = new window.videojs.hls.HLSPlaybackController(this);
this.playbackController.loadManifest('test/fixtures/bipbop.m3u8', function(data) {
console.log(data);
});
});
</script>
</body>
</html>
......
......@@ -25,10 +25,6 @@ window.videojs.hls.ExpGolomb = function(workingData) {
return (8 * workingBytesAvailable) + workingBitsAvailable;
};
this.logStuff = function() {
console.log('bits', workingBitsAvailable, 'word', (workingWord >>> 0));
};
// ():void
this.loadWord = function() {
var
......
......@@ -47,7 +47,6 @@ hls.FlvTag = function(type, extraData) {
try {
this.bytes.set(bytes.subarray(offset, offset + length), this.position);
} catch(e) {
console.log(e);
throw e;
}
this.position += length;
......
......@@ -90,8 +90,6 @@
} else if (0 !== sps0[offset + 0]) {
offset += 1;
} else {
console.log('found emulation bytes');
rbsp.set([0x00, 0x00], rbspCount);
spsCount += 2;
rbspCount += 2;
......@@ -311,7 +309,6 @@
};
this.finishFrame = function() {
console.log('finish frame');
if (h264Frame) {
// Push SPS before EVERY IDR frame fo seeking
if (newExtraData.extraDataExists()) {
......
(function(window) {
var
ManifestController = window.videojs.hls.ManifestController,
SegmentController = window.videojs.hls.SegmentController,
MediaSource = window.videojs.MediaSource,
SegmentParser = window.videojs.hls.SegmentParser;
window.videojs.hls.HLSPlaybackController = function(player) {
var self = this;
self.player = player;
self.mediaSource = new MediaSource();
self.parser = new SegmentParser();
self.manifestLoaded = false;
self.currentSegment = 0;
// register external callbacks
self.rendition = function(rendition) {
self.currentRendition = rendition;
self.loadManifest(self.currentRendition.url, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update);
};
self.loadManifest = function(manifestUrl, onDataCallback) {
self.mediaSource.addEventListener('sourceopen', function() {
// feed parsed bytes into the player
self.sourceBuffer = self.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
self.parser = new SegmentParser();
self.sourceBuffer.appendBuffer(self.parser.getFlvHeader(), self.player);
if (onDataCallback) {
self.manifestLoadCompleteCallback = onDataCallback;
}
self.manifestController = new ManifestController();
self.manifestController.loadManifest(manifestUrl, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update);
}, false);
self.player.src({
src: window.videojs.URL.createObjectURL(self.mediaSource),
type: "video/flv"
});
};
self.onM3U8LoadComplete = function(m3u8) {
if (m3u8.invalidReasons.length === 0) {
if (m3u8.isPlaylist) {
self.currentPlaylist = m3u8;
self.rendition(self.currentPlaylist.playlistItems[0]);
} else {
self.currentManifest = m3u8;
self.manifestLoaded = true;
self.loadSegment(self.currentManifest.mediaItems[0]);
if (self.manifestLoadCompleteCallback) {
self.manifestLoadCompleteCallback(m3u8);
}
}
}
};
self.onM3U8LoadError = function() {};
self.onM3U8Update = function() {};
self.loadSegment = function(segment) {
self.segmentController = new SegmentController();
self.segmentController.loadSegment(segment.url, self.onSegmentLoadComplete, self.onSegmentLoadError);
};
self.onSegmentLoadComplete = function(segment) {
self.parser.parseSegmentBinaryData(segment.binaryData);
while (self.parser.tagsAvailable()) {
self.sourceBuffer.appendBuffer(self.parser.getNextTag().bytes, self.player);
}
if (self.currentSegment < self.currentManifest.mediaItems.length-1) {
self.loadNextSegment();
}
};
self.loadNextSegment = function() {
self.currentSegment++;
self.loadSegment(self.currentManifest.mediaItems[self.currentSegment]);
};
self.onSegmentLoadError = function() {};
};
})(this);
(function(window) {
var M3U8 = window.videojs.hls.M3U8;
window.videojs.hls.M3U8Parser = function() {
var self = this;
var tagTypes = window.videojs.hls.m3u8TagType;
var lines = [];
var data;
self.getTagType = function( lineData ) {
for ( var s in tagTypes )
{
if (lineData.indexOf(tagTypes[s]) == 0)
{
return tagTypes[s];
}
}
}
self.getTagValue = function ( lineData ) {
for ( var s in tagTypes )
{
if (lineData.indexOf(tagTypes[s]) == 0)
{
return lineData.substr(tagTypes[s].length);
}
}
}
self.parse = function( rawDataString ) {
data = new M3U8();
if( rawDataString != undefined && rawDataString.toString().length > 0 )
{
lines = rawDataString.split('\n');
lines.forEach(
function(value,index) {
switch( self.getTagType(value) )
{
case tagTypes.EXTM3U:
data.hasValidM3UTag = (index == 0);
if(!data.hasValidM3UTag)
{
data.invalidReasons.push("Invalid EXTM3U Tag");
}
break;
case tagTypes.DISCONTINUITY:
break;
case tagTypes.PLAYLIST_TYPE:
if(self.getTagValue(value) == "VOD" || self.getTagValue(value) == "EVENT")
{
data.playlistType = self.getTagValue(value);
data.isPlaylist = true;
} else {
data.invalidReasons.push("Invalid Playlist Type Value");
}
break;
case tagTypes.EXTINF:
var segment = {url: "unknown", byterange: -1, targetDuration: data.targetDuration };
if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE )
{
segment.byterange = self.getTagValue(lines[index+1]).split('@');
segment.url = lines[index+2];
} else
{
segment.url = lines[index+1];
}
data.mediaItems.push(segment);
break;
case tagTypes.STREAM_INF:
var rendition = {};
var attributes = value.substr(tagTypes.STREAM_INF.length).split(',');
attributes.forEach(function(attr_value,attr_index) {
if(isNaN(attr_value.split('=')[1])){
rendition[attr_value.split('=')[0].toLowerCase()] = attr_value.split('=')[1];
if(rendition[attr_value.split('=')[0].toLowerCase()].split('x').length = 2)
{
rendition.resolution = {
width: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[0]),
height: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[1])
}
}
} else {
rendition[attr_value.split('=')[0].toLowerCase()] = Number(attr_value.split('=')[1]);
}
});
if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE )
{
rendition.byterange = self.getTagValue(lines[index+1]).split('@');
rendition.url = lines[index+2];
} else
{
rendition.url = lines[index+1];
}
data.isPlaylist = true;
data.playlistItems.push(rendition);
break;
case tagTypes.TARGETDURATION:
data.targetDuration = Number(self.getTagValue(value).split(',')[0]);
break;
case tagTypes.ZEN_TOTAL_DURATION:
data.totalDuration = self.getTagValue(value);
break;
case tagTypes.VERSION:
data.version = Number(self.getTagValue(value));
break;
case tagTypes.MEDIA_SEQUENCE:
data.mediaSequence = parseInt(self.getTagValue(value));
break;
case tagTypes.ALLOW_CACHE:
if(self.getTagValue(value) == "YES" || self.getTagValue(value) == "NO")
{
data.allowCache = self.getTagValue(value);
} else {
data.invalidReasons.push("Invalid ALLOW_CACHE Value");
}
break;
case tagTypes.ENDLIST:
data.hasEndTag = true;
break;
}
}
)
} else {
data.invalidReasons.push("Empty Manifest");
}
return data;
};
var M3U8 = window.videojs.hls.M3U8;
window.videojs.hls.M3U8Parser = function() {
var
self = this,
tagTypes = window.videojs.hls.m3u8TagType,
lines = [],
data;
self.getTagType = function(lineData) {
for (var s in tagTypes) {
if (lineData.indexOf(tagTypes[s]) === 0) {
return tagTypes[s];
}
}
};
})(this);
\ No newline at end of file
self.getTagValue = function(lineData) {
for (var s in tagTypes) {
if (lineData.indexOf(tagTypes[s]) === 0) {
return lineData.substr(tagTypes[s].length);
}
}
};
self.parse = function(rawDataString) {
data = new M3U8();
if (self.directory) {
data.directory = self.directory;
}
if (rawDataString === undefined || rawDataString.length <= 0) {
data.invalidReasons.push("Empty Manifest");
return;
}
lines = rawDataString.split('\n');
lines.forEach(function(value,index) {
var segment, rendition, attributes;
switch (self.getTagType(value)) {
case tagTypes.EXTM3U:
data.hasValidM3UTag = (index === 0);
if (!data.hasValidM3UTag) {
data.invalidReasons.push("Invalid EXTM3U Tag");
}
break;
case tagTypes.DISCONTINUITY:
break;
case tagTypes.PLAYLIST_TYPE:
if (self.getTagValue(value) === "VOD" ||
self.getTagValue(value) === "EVENT") {
data.playlistType = self.getTagValue(value);
data.isPlaylist = true;
} else {
data.invalidReasons.push("Invalid Playlist Type Value");
}
break;
case tagTypes.EXTINF:
segment = {
url: "unknown",
byterange: -1,
targetDuration: data.targetDuration
};
if (self.getTagType(lines[index + 1]) === tagTypes.BYTERANGE) {
segment.byterange = self.getTagValue(lines[index + 1]).split('@');
segment.url = lines[index + 2];
} else {
segment.url = lines[index + 1];
}
if (segment.url.indexOf("http") === -1 && self.directory) {
if (data.directory[data.directory.length-1] === segment.url[0] &&
segment.url[0] === "/") {
segment.url = segment.url.substr(1);
}
segment.url = self.directory + segment.url;
}
data.mediaItems.push(segment);
break;
case tagTypes.STREAM_INF:
rendition = {};
attributes = value.substr(tagTypes.STREAM_INF.length).split(',');
attributes.forEach(function(attrValue) {
if (isNaN(attrValue.split('=')[1])) {
rendition[attrValue.split('=')[0].toLowerCase()] = attrValue.split('=')[1];
if (rendition[attrValue.split('=')[0].toLowerCase()].split('x').length === 2) {
rendition.resolution = {
width: parseInt(rendition[attrValue.split('=')[0].toLowerCase()].split('x')[0],10),
height: parseInt(rendition[attrValue.split('=')[0].toLowerCase()].split('x')[1],10)
};
}
} else {
rendition[attrValue.split('=')[0].toLowerCase()] = parseInt(attrValue.split('=')[1],10);
}
});
if (self.getTagType(lines[index + 1]) === tagTypes.BYTERANGE) {
rendition.byterange = self.getTagValue(lines[index + 1]).split('@');
rendition.url = lines[index + 2];
} else {
rendition.url = lines[index + 1];
}
data.isPlaylist = true;
data.playlistItems.push(rendition);
break;
case tagTypes.TARGETDURATION:
data.targetDuration = parseFloat(self.getTagValue(value).split(',')[0]);
break;
case tagTypes.ZEN_TOTAL_DURATION:
data.totalDuration = parseFloat(self.getTagValue(value));
break;
case tagTypes.VERSION:
data.version = parseFloat(self.getTagValue(value));
break;
case tagTypes.MEDIA_SEQUENCE:
data.mediaSequence = parseInt(self.getTagValue(value),10);
break;
case tagTypes.ALLOW_CACHE:
if (self.getTagValue(value) === "YES" || self.getTagValue(value) === "NO") {
data.allowCache = self.getTagValue(value);
} else {
data.invalidReasons.push("Invalid ALLOW_CACHE Value");
}
break;
case tagTypes.ENDLIST:
data.hasEndTag = true;
break;
}
});
return data;
};
};
})(this);
......
window.videojs.hls.m3u8TagType = {
/*
* Derived from V8: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08
*/
/**
* Identifies manifest as Extended M3U - must be present on first line!
*/
EXTM3U:"#EXTM3U",
/**
* Specifies duration.
* Syntax: #EXTINF:<duration>,<title>
* Example: #EXTINF:10,
*/
EXTINF:"#EXTINF:",
/**
* Indicates that a media segment is a sub-range of the resource identified by its media URI.
* Syntax: #EXT-X-BYTERANGE:<n>[@o]
*/
BYTERANGE:"#EXT-X-BYTERANGE:",
/**
* Specifies the maximum media segment duration - applies to entire manifest.
* Syntax: #EXT-X-TARGETDURATION:<s>
* Example: #EXT-X-TARGETDURATION:10
*/
TARGETDURATION:"#EXT-X-TARGETDURATION:",
/**
* Specifies the sequence number of the first URI in a manifest.
* Syntax: #EXT-X-MEDIA-SEQUENCE:<i>
* Example: #EXT-X-MEDIA-SEQUENCE:50
*/
MEDIA_SEQUENCE:"#EXT-X-MEDIA-SEQUENCE:",
/**
* Specifies a method by which media segments can be decrypted, if encryption is present.
* Syntax: #EXT-X-KEY:<attribute-list>
* Note: This is likely irrelevant in the context of the Flash Player.
*/
KEY:"#EXT-X-KEY:",
/**
* Associates the first sample of a media segment with an absolute date and/or time. Applies only to the next media URI.
* Syntax: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
* Example: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
*/
PROGRAM_DATE_TIME:"#EXT-X-PROGRAM-DATE-TIME:",
/**
* Indicates whether the client MAY or MUST NOT cache downloaded media segments for later replay.
* Syntax: #EXT-X-ALLOW-CACHE:<YES|NO>
* Note: This is likely irrelevant in the context of the Flash Player.
*/
ALLOW_CACHE:"#EXT-X-ALLOW_CACHE:",
/**
* Provides mutability information about the manifest.
* Syntax: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
*/
PLAYLIST_TYPE:"#EXT-X-PLAYLIST-TYPE:",
/**
* Indicates that no more media segments will be added to the manifest. May occur ONCE, anywhere in the mainfest file.
*/
ENDLIST:"#EXT-X-ENDLIST",
/**
* Used to relate Playlists that contain alternative renditions of the same content.
* Syntax: #EXT-X-MEDIA:<attribute-list>
*/
MEDIA:"#EXT-X-MEDIA:",
/**
* Identifies a media URI as a Playlist file containing a multimedia presentation and provides information about that presentation.
* Syntax: #EXT-X-STREAM-INF:<attribute-list>
* <URI>
*/
STREAM_INF:"#EXT-X-STREAM-INF:",
/**
* Indicates an encoding discontinuity between the media segment that follows it and the one that preceded it.
*/
DISCONTINUITY:"#EXT-X-DISCONTINUITY",
/**
* Indicates that each media segment in the manifest describes a single I-frame.
*/
I_FRAMES_ONLY:"#EXT-X-I-FRAMES-ONLY",
/**
* Identifies a manifest file containing the I-frames of a multimedia presentation. It stands alone, in that it does not apply to a particular URI in the manifest.
* Syntax: #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
*/
I_FRAME_STREAM_INF:"#EXT-X-I-FRAME-STREAM-INF:",
/**
* Indicates the compatibility version of the Playlist file.
* Syntax: #EXT-X-VERSION:<n>
*/
VERSION:"#EXT-X-VERSION:",
/**
* Indicates the total duration as reported by Zencoder.
* Syntax: #ZEN-TOTAL-DURATION:<n>
*/
ZEN_TOTAL_DURATION: "#ZEN-TOTAL-DURATION:"
};
\ No newline at end of file
(function(window) {
window.videojs.hls.m3u8TagType = {
/*
* Derived from the HTTP Live Streaming Spec V8
* http://tools.ietf.org/html/draft-pantos-http-live-streaming-08
*/
/**
* Identifies manifest as Extended M3U - must be present on first line!
*/
EXTM3U:"#EXTM3U",
/**
* Specifies duration.
* Syntax: #EXTINF:<duration>,<title>
* Example: #EXTINF:10,
*/
EXTINF:"#EXTINF:",
/**
* Indicates that a media segment is a sub-range of the resource identified by its media URI.
* Syntax: #EXT-X-BYTERANGE:<n>[@o]
*/
BYTERANGE:"#EXT-X-BYTERANGE:",
/**
* Specifies the maximum media segment duration - applies to entire manifest.
* Syntax: #EXT-X-TARGETDURATION:<s>
* Example: #EXT-X-TARGETDURATION:10
*/
TARGETDURATION:"#EXT-X-TARGETDURATION:",
/**
* Specifies the sequence number of the first URI in a manifest.
* Syntax: #EXT-X-MEDIA-SEQUENCE:<i>
* Example: #EXT-X-MEDIA-SEQUENCE:50
*/
MEDIA_SEQUENCE:"#EXT-X-MEDIA-SEQUENCE:",
/**
* Specifies a method by which media segments can be decrypted, if encryption is present.
* Syntax: #EXT-X-KEY:<attribute-list>
* Note: This is likely irrelevant in the context of the Flash Player.
*/
KEY:"#EXT-X-KEY:",
/**
* Associates the first sample of a media segment with an absolute date and/or time. Applies only to the next media URI.
* Syntax: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
* Example: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
*/
PROGRAM_DATE_TIME:"#EXT-X-PROGRAM-DATE-TIME:",
/**
* Indicates whether the client MAY or MUST NOT cache downloaded media segments for later replay.
* Syntax: #EXT-X-ALLOW-CACHE:<YES|NO>
* Note: This is likely irrelevant in the context of the Flash Player.
*/
ALLOW_CACHE:"#EXT-X-ALLOW_CACHE:",
/**
* Provides mutability information about the manifest.
* Syntax: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
*/
PLAYLIST_TYPE:"#EXT-X-PLAYLIST-TYPE:",
/**
* Indicates that no more media segments will be added to the manifest. May occur ONCE, anywhere in the mainfest file.
*/
ENDLIST:"#EXT-X-ENDLIST",
/**
* Used to relate Playlists that contain alternative renditions of the same content.
* Syntax: #EXT-X-MEDIA:<attribute-list>
*/
MEDIA:"#EXT-X-MEDIA:",
/**
* Identifies a media URI as a Playlist file containing a multimedia presentation and provides information about that presentation.
* Syntax: #EXT-X-STREAM-INF:<attribute-list>
* <URI>
*/
STREAM_INF:"#EXT-X-STREAM-INF:",
/**
* Indicates an encoding discontinuity between the media segment that follows it and the one that preceded it.
*/
DISCONTINUITY:"#EXT-X-DISCONTINUITY",
/**
* Indicates that each media segment in the manifest describes a single I-frame.
*/
I_FRAMES_ONLY:"#EXT-X-I-FRAMES-ONLY",
/**
* Identifies a manifest file containing the I-frames of a multimedia presentation. It stands alone, in that it does not apply to a particular URI in the manifest.
* Syntax: #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
*/
I_FRAME_STREAM_INF:"#EXT-X-I-FRAME-STREAM-INF:",
/**
* Indicates the compatibility version of the Playlist file.
* Syntax: #EXT-X-VERSION:<n>
*/
VERSION:"#EXT-X-VERSION:",
/**
* Indicates the total duration as reported by Zencoder.
* Syntax: #ZEN-TOTAL-DURATION:<n>
*/
ZEN_TOTAL_DURATION: "#ZEN-TOTAL-DURATION:"
};
})(this);
......
(function(window) {
window.videojs.hls.M3U8 = function() {
this.allowCache = "NO";
this.playlistItems = [];
this.mediaItems = [];
this.iFrameItems = [];
this.invalidReasons = [];
this.hasValidM3UTag = false;
this.hasEndTag = false;
this.targetDuration = -1;
this.totalDuration = -1;
this.isPlaylist = false;
this.playlistType = "";
this.mediaSequence = -1;
this.version = -1;
}
})(this);
\ No newline at end of file
(function (window) {
window.videojs.hls.M3U8 = function () {
this.directory = "";
this.allowCache = "NO";
this.playlistItems = [];
this.mediaItems = [];
this.iFrameItems = [];
this.invalidReasons = [];
this.hasValidM3UTag = false;
this.hasEndTag = false;
this.targetDuration = -1;
this.totalDuration = -1;
this.isPlaylist = false;
this.playlistType = "";
this.mediaSequence = -1;
this.version = -1;
};
})(this);
......
(function(window) {
var M3U8 = window.videojs.hls.M3U8;
var M3U8Parser = window.videojs.hls.M3U8Parser;
window.videojs.hls.ManifestController = function(){
var self = this;
var parser;
var data;
var onDataCallback;
var onErrorCallback;
var onUpdateCallback;
self.loadManifest = function ( manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback ) {
self.onDataCallback = onDataCallback;
self.onErrorCallback = onErrorCallback;
self.onUpdateCallback = onUpdateCallback;
vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError);
};
self.parseManifest = function ( dataAsString ) {
self.parser = new M3U8Parser();
self.data = self.parser.parse( dataAsString );
return self.data;
};
self.onManifestLoadComplete = function(response) {
var output = self.parseManifest(response);
if(self.onDataCallback != undefined)
{
self.onDataCallback(output);
}
};
self.onManifestLoadError = function(err) {
if(err)
{
console.log(err.message);
}
if(self.onErrorCallback != undefined)
{
onErrorCallback((err != undefined) ? err : null);
}
};
}
(function (window) {
var
M3U8Parser = window.videojs.hls.M3U8Parser;
window.videojs.hls.ManifestController = function() {
var self = this;
self.loadManifest = function(manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback) {
self.url = manifestUrl;
if (onDataCallback) {
self.onDataCallback = onDataCallback;
}
if (onErrorCallback) {
self.onErrorCallback = onErrorCallback;
}
if (onUpdateCallback) {
self.onUpdateCallback = onUpdateCallback;
}
window.vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError);
};
self.parseManifest = function(dataAsString) {
self.parser = new M3U8Parser();
self.parser.directory = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(self.url).slice(1)[1];
self.data = self.parser.parse(dataAsString);
return self.data;
};
self.onManifestLoadComplete = function(response) {
var output = self.parseManifest(response);
if (self.onDataCallback !== undefined) {
self.onDataCallback(output);
}
};
self.onManifestLoadError = function(err) {
if (self.onErrorCallback !== undefined) {
self.onErrorCallback((err !== undefined) ? err : null);
}
};
};
})(this);
......
(function(window) {
var SegmentParser = window.videojs.hls.SegmentParser;
window.videojs.hls.SegmentController = function() {
var self = this;
window.videojs.hls.SegmentController = function(){
self.loadSegment = function(segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback) {
var request = new XMLHttpRequest();
var self = this;
var url;
var parser;
var requestTimestamp;
var responseTimestamp;
var data;
self.url = segmentUrl;
self.onDataCallback = onDataCallback;
self.onErrorCallback = onErrorCallback;
self.onUpdateCallback = onUpdateCallback;
self.requestTimestamp = +new Date();
var onDataCallback;
var onErrorCallback;
var onUpdateCallback;
request.open('GET', segmentUrl, true);
request.responseType = 'arraybuffer';
request.onload = function() {
self.onSegmentLoadComplete(new Uint8Array(request.response));
};
self.loadSegment = function ( segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback ) {
self.url = segmentUrl;
self.onDataCallback = onDataCallback;
self.onErrorCallback = onErrorCallback;
self.onUpdateCallback = onUpdateCallback;
self.requestTimestamp = new Date().getTime();
request.send(null);
};
var req = new XMLHttpRequest();
req.open('GET', segmentUrl, true);
req.responseType = 'arraybuffer';
req.onload = function(response) {
self.onSegmentLoadComplete(new Uint8Array(req.response));
};
req.send(null);
};
self.parseSegment = function(incomingData) {
self.data = {};
self.data.binaryData = incomingData;
self.data.url = self.url;
self.data.isCached = false;
self.data.requestTimestamp = self.requestTimestamp;
self.data.responseTimestamp = self.responseTimestamp;
self.data.byteLength = incomingData.byteLength;
self.data.isCached = parseInt(self.responseTimestamp - self.requestTimestamp,10) < 75;
self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp);
self.parseSegment = function ( incomingData ) {
// Add David's code later //
return self.data;
};
self.data = {
whatever: incomingData
};
self.data.url = self.url;
self.data.isCached = false;
self.data.requestTimestamp = self.requestTimestamp;
self.data.responseTimestamp = self.responseTimestamp;
self.data.byteLength = incomingData.byteLength;
self.data.isCached = ( parseInt(self.responseTimestamp - self.requestTimestamp) < 75 );
self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp)
self.calculateThroughput = function(dataAmount, startTime, endTime) {
return Math.round(dataAmount / (endTime - startTime) * 1000) * 8;
};
return self.data;
};
self.onSegmentLoadComplete = function(response) {
var output;
self.calculateThroughput = function(dataAmount, startTime, endTime) {
return Math.round(dataAmount/(endTime-startTime)*1000)*8;
}
self.responseTimestamp = +new Date();
self.onSegmentLoadComplete = function(response) {
self.responseTimestamp = new Date().getTime();
output = self.parseSegment(response);
var output = self.parseSegment(response);
if (self.onDataCallback !== undefined) {
self.onDataCallback(output);
}
};
if(self.onDataCallback != undefined)
{
self.onDataCallback(output);
}
};
self.onSegmentLoadError = function(error) {
if (error) {
throw error;
}
self.onSegmentLoadError = function(err) {
if(err)
{
console.log(err.message);
}
if(self.onErrorCallback != undefined)
{
onErrorCallback((err != undefined) ? err : null);
}
};
}
if (self.onErrorCallback !== undefined) {
self.onErrorCallback(error);
}
};
};
})(this);
......
(function(window) {
var
FlvTag = window.videojs.hls.FlvTag,
H264Stream = window.videojs.hls.H264Stream,
AacStream = window.videojs.hls.AacStream,
videojs = window.videojs,
FlvTag = videojs.hls.FlvTag,
H264Stream = videojs.hls.H264Stream,
AacStream = videojs.hls.AacStream,
m2tsPacketSize = 188;
console.assert(H264Stream);
......@@ -150,7 +151,7 @@
// until we receive more
// ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting.
console.log('data.length + streamBuffer.length < m2tsPacketSize ??');
videojs.log('data.length + streamBuffer.length < m2tsPacketSize ??');
streamBuffer.readBytes(data, data.length, streamBuffer.length);
return;
} else {
......@@ -194,7 +195,7 @@
// If there was an error parsing a TS packet. it could be
// because we are not TS packet aligned. Step one forward by
// one byte and allow the code above to find the next
console.log('error parsing m2ts packet, attempting to re-align');
videojs.log('error parsing m2ts packet, attempting to re-align');
dataPosition++;
}
}
......@@ -382,7 +383,7 @@
} else if (0x1FFF === pid) {
// NULL packet
} else {
console.log("Unknown PID " + pid);
videojs.log("Unknown PID parsing TS packet: " + pid);
}
return true;
......
......@@ -21,8 +21,7 @@
*/
var
buffer,
expGolomb,
view;
expGolomb;
module('Exponential Golomb coding');
......
......@@ -6,4 +6,4 @@ window.brightcove_playlist_data = '#EXTM3U\n'+
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224\n'+
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001\n'+
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540\n'+
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001'
\ No newline at end of file
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001';
......
window.playlistData = '#EXTM3U\n'+
'#EXT-X-TARGETDURATION:10\n' +
'#EXT-X-VERSION:4\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXT-X-PLAYLIST-TYPE:VOD\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:522828@0\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:587500@522828\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:713084@1110328\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:476580@1823412\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:535612@2299992\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:207176@2835604\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:455900@3042780\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:657248@3498680\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:571708@4155928\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:485040@4727636\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:709136@5212676\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:730004@5921812\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:456276@6651816\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:468684@7108092\n' +
'hls_450k_video.ts' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:444996@7576776\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:331444@8021772\n' +
'hls_450k_video.ts\n' +
'#EXTINF:1.4167,\n' +
'#EXT-X-BYTERANGE:44556@8353216\n' +
'hls_450k_video.ts\n' +
'#EXT-X-ENDLIST';
\ No newline at end of file
'#EXT-X-TARGETDURATION:10\n' +
'#EXT-X-VERSION:4\n' +
'#EXT-X-MEDIA-SEQUENCE:0\n' +
'#EXT-X-PLAYLIST-TYPE:VOD\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:522828@0\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:587500@522828\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:713084@1110328\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:476580@1823412\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:535612@2299992\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:207176@2835604\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:455900@3042780\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:657248@3498680\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:571708@4155928\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:485040@4727636\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:709136@5212676\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:730004@5921812\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:456276@6651816\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:468684@7108092\n' +
'hls_450k_video.ts' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:444996@7576776\n' +
'hls_450k_video.ts\n' +
'#EXTINF:10,\n' +
'#EXT-X-BYTERANGE:331444@8021772\n' +
'hls_450k_video.ts\n' +
'#EXTINF:1.4167,\n' +
'#EXT-X-BYTERANGE:44556@8353216\n' +
'hls_450k_video.ts\n' +
'#EXT-X-ENDLIST';
......
......@@ -4,37 +4,38 @@
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
manifestController,
segmentController,
m3u8parser,
manifestController,
segmentController,
m3u8parser,
parser,
expectedHeader = [
0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00
],
testAudioTag,
testVideoTag,
testScriptTag,
asciiFromBytes,
testScriptString,
testScriptEcmaArray;
expectedHeader = [
0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00
],
testAudioTag,
testVideoTag,
testScriptTag,
asciiFromBytes,
testScriptString,
testScriptEcmaArray,
testNalUnit;
module('environment');
......@@ -61,13 +62,9 @@
});
test('parses the first bipbop segment', function() {
var tag, bytes, i;
parser.parseSegmentBinaryData(window.bcSegment);
ok(parser.tagsAvailable(), 'tags are available');
console.log('h264 tags:', parser.stats.h264Tags(),
'aac tags:', parser.stats.aacTags());
ok(parser.tagsAvailable(), 'tags are available');
});
testAudioTag = function(tag) {
......@@ -87,18 +84,16 @@
ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type');
};
testVideoTag = function(tag) {
testVideoTag = function (tag) {
var
byte = tag.bytes[11],
frameType = (byte & 0xF0) >>> 4,
codecId = byte & 0x0F,
packetType = tag.bytes[12],
compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8,
nalHeader;
compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8;
// payload starts at tag.bytes[16]
// XXX: I'm not sure that frame types 3-5 are invalid
ok(frameType === 1 || frameType === 2,
'the frame type should be valid');
......@@ -110,7 +105,7 @@
compositionTime,
'the composition time is zero for non-NALU packets');
}
// TODO: the rest of the bytes are an NLU unit
if (packetType === 0) {
// AVC decoder configuration record
......@@ -122,15 +117,15 @@
testNalUnit = function(bytes) {
var
nalHeader = bytes[0],
unitType = nalHeader & 0x1F;
nalHeader = bytes[0];
// unitType = nalHeader & 0x1F;
equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0');
// equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
// ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
// ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
};
asciiFromBytes = function(bytes) {
var
......@@ -144,10 +139,10 @@
};
testScriptString = function(tag, offset, expected) {
var type = tag.bytes[offset],
stringLength = tag.view.getUint16(offset + 1),
string,
i = expected.length;
var
type = tag.bytes[offset],
stringLength = tag.view.getUint16(offset + 1),
string;
equal(2, type, 'the script element is of string type');
equal(stringLength, expected.length, 'the script string length is correct');
......@@ -214,7 +209,7 @@
// generic flv headers
ok(type === 8 || type === 9 || type === 18,
'the type field specifies audio, video or script');
byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8;
equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct');
......@@ -236,151 +231,117 @@
}
});
/*
M3U8 Test Suite
*/
/*
M3U8 Test Suite
*/
module('m3u8 parser', {
setup: function() {
m3u8parser = new window.videojs.hls.M3U8Parser();
m3u8parser = new window.videojs.hls.M3U8Parser();
}
});
test('should create my parser', function() {
ok(m3u8parser !== undefined);
});
test('should successfully parse manifest data', function() {
var parsedData = m3u8parser.parse(window.playlistData);
ok(parsedData);
});
test('test for expected results', function() {
var data = m3u8parser.parse(window.playlistData);
notEqual(data, null, 'data is not NULL');
equal(data.invalidReasons.length, 0, 'data has 0 invalid reasons');
equal(data.hasValidM3UTag, true, 'data has valid EXTM3U');
equal(data.targetDuration, 10, 'data has correct TARGET DURATION');
equal(data.allowCache, "NO", 'acceptable ALLOW CACHE');
equal(data.isPlaylist, true, 'data is parsed as a PLAYLIST as expected');
equal(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE');
equal(data.mediaItems.length, 16, 'acceptable mediaItem count');
equal(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct');
equal(data.totalDuration, -1, "ZEN TOTAL DURATION is unknown as expected");
equal(data.hasEndTag, true, 'should have ENDLIST tag');
});
module('brightcove playlist', {
setup: function() {
m3u8parser = new window.videojs.hls.M3U8Parser();
}
});
test('should create my parser', function() {
ok(m3u8parser != undefined);
}
);
test('should successfully parse manifest data', function() {
var parsedData = m3u8parser.parse(window.playlistData);
ok(parsedData);
}
);
test('test for expected results', function() {
var data = m3u8parser.parse(window.playlistData);
notEqual(data, null, 'data is not NULL');
equal(data.invalidReasons.length, 0,'data has 0 invalid reasons');
equal(data.hasValidM3UTag, true, 'data has valid EXTM3U');
equal(data.targetDuration, 10, 'data has correct TARGET DURATION');
equal(data.allowCache, "NO", 'acceptable ALLOW CACHE');
equal(data.isPlaylist, true, 'data is parsed as a PLAYLIST as expected');
equal(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE');
equal(data.mediaItems.length, 16, 'acceptable mediaItem count');
equal(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct');
equal(data.totalDuration, -1, "ZEN TOTAL DURATION is unknown as expected");
equal(data.hasEndTag, true, 'should have ENDLIST tag');
}
);
module('brightcove playlist', {
setup: function() {
m3u8parser = new window.videojs.hls.M3U8Parser();
}
});
test('should parse a brightcove manifest data', function() {
var data = m3u8parser.parse(window.brightcove_playlist_data);
ok(data);
equal(data.playlistItems.length, 4, 'Has correct rendition count');
equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct' );
equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct' );
equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct' );
equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct' );
}
);
module('manifest controller', {
setup: function() {
manifestController = new window.videojs.hls.ManifestController();
this.vjsget = vjs.get;
vjs.get = function(url, success, error){
console.log(url);
success(window.brightcove_playlist_data);
};
},
teardown: function() {
vjs.get = this.vjsget;
}
});
test('should create', function() {
ok(manifestController);
});
test('should return a parsed object', function() {
var data = manifestController.parseManifest(window.brightcove_playlist_data);
ok(data);
equal(data.playlistItems.length, 4, 'Has correct rendition count');
equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct' );
equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct' );
equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct' );
equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct' );
})
test('should get a manifest from hermes', function() {
var hermesUrl = "http://localhost:7070/test/basic-playback/brightcove/16x9-master.m3u8";
manifestController.loadManifest(
hermesUrl,
function(responseData){
ok(true);
},
function(errorData){
console.log('got error data');
},
function(updateData){
console.log('got update data');
}
)
});
module('segment controller', {
setup: function() {
segmentController = new window.videojs.hls.SegmentController();
this.vjsget = vjs.get;
vjs.get = function(url, success, error){
console.log('load segment url', url);
success(window.bcSegment);
};
},
teardown: function() {
vjs.get = this.vjsget;
}
});
test('should get a segment data', function() {
ok(true);
var hermesUrl = "http://localhost:7070/test/ts-files/brightcove/s-1.ts";
segmentController.loadSegment(
hermesUrl,
function(responseData){
console.log('got response from segment controller');
ok(true);
},
function(errorData){
console.log('got error data');
},
function(updateData){
console.log('got update data');
}
)
}
)
test('bandwidth calulation test', function() {
var multiSecondData = segmentController.calculateThroughput(10000,1000,2000);
var subSecondData = segmentController.calculateThroughput(10000,1000,1500);
equal(multiSecondData, 80000, 'MULTI-Second bits per second calculation');
equal(subSecondData, 160000, 'SUB-Second bits per second calculation');
})
})(this);
\ No newline at end of file
test('should parse a brightcove manifest data', function() {
var data = m3u8parser.parse(window.brightcove_playlist_data);
ok(data);
equal(data.playlistItems.length, 4, 'Has correct rendition count');
equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct');
equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct');
equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct');
equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct');
}
);
module('manifest controller', {
setup: function() {
manifestController = new window.videojs.hls.ManifestController();
this.vjsget = window.videojs.get;
window.videojs.get = function(url, success) {
success(window.brightcove_playlist_data);
};
},
teardown: function() {
window.videojs.get = this.vjsget;
}
});
test('should create', function() {
ok(manifestController);
});
test('should return a parsed object', function() {
var data = manifestController.parseManifest(window.brightcove_playlist_data);
ok(data);
equal(data.playlistItems.length, 4, 'Has correct rendition count');
equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct');
equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct');
equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct');
equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct');
});
test('should get a manifest from hermes', function() {
manifestController.loadManifest('http://example.com/16x9-master.m3u8',
function(responseData) {
ok(responseData);
},
function() {
ok(false, 'does not error');
},
function() {});
});
module('segment controller', {
setup: function() {
segmentController = new window.videojs.hls.SegmentController();
this.vjsget = window.videojs.get;
window.videojs.get = function(url, success) {
success(window.bcSegment);
};
},
teardown: function() {
window.videojs.get = this.vjsget;
}
});
test('bandwidth calulation test', function() {
var
multiSecondData = segmentController.calculateThroughput(10000, 1000, 2000),
subSecondData = segmentController.calculateThroughput(10000, 1000, 1500);
equal(multiSecondData, 80000, 'MULTI-Second bits per second calculation');
equal(subSecondData, 160000, 'SUB-Second bits per second calculation');
});
})(this);
......
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>