a1799650 by David LaPalomento

Merge pull request #1 from tojohnson/master

Tom's Hack week progress
2 parents 1541a1dc 866d581d
(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;
};
};
})(this);
\ No newline at end of file
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.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) {
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);
}
};
}
})(this);
\ No newline at end of file
(function(window) {
var SegmentParser = window.videojs.hls.SegmentParser;
window.videojs.hls.SegmentController = function(){
var self = this;
var url;
var parser;
var requestTimestamp;
var responseTimestamp;
var data;
var onDataCallback;
var onErrorCallback;
var onUpdateCallback;
self.loadSegment = function ( segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback ) {
self.url = segmentUrl;
self.onDataCallback = onDataCallback;
self.onErrorCallback = onErrorCallback;
self.onUpdateCallback = onUpdateCallback;
self.requestTimestamp = new Date().getTime();
vjs.get(segmentUrl, self.onSegmentLoadComplete, self.onSegmentLoadError);
};
self.parseSegment = function ( incomingData ) {
// Add David's code later //
self.data = {};
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)
return self.data;
};
self.calculateThroughput = function(dataAmount, startTime, endTime) {
return Math.round(dataAmount/(endTime-startTime)*1000)*8;
}
self.onSegmentLoadComplete = function(response) {
self.responseTimestamp = new Date().getTime();
var output = self.parseSegment(response);
if(self.onDataCallback != undefined)
{
self.onDataCallback(output);
}
};
self.onSegmentLoadError = function(err) {
if(err)
{
console.log(err.message);
}
if(self.onErrorCallback != undefined)
{
onErrorCallback((err != undefined) ? err : null);
}
};
}
})(this);
window.brightcove_playlist_data = '#EXTM3U\n'+
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=240000,RESOLUTION=396x224\n'+
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686811001&videoId=1824650741001\n'+
'#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=40000\n'+
'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824683759001&videoId=1824650741001\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
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10,
#EXT-X-BYTERANGE:522828@0
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:587500@522828
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:713084@1110328
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:476580@1823412
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:535612@2299992
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:207176@2835604
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:455900@3042780
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:657248@3498680
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:571708@4155928
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:485040@4727636
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:709136@5212676
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:730004@5921812
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:456276@6651816
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:468684@7108092
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:444996@7576776
hls_450k_video.ts
#EXTINF:10,
#EXT-X-BYTERANGE:331444@8021772
hls_450k_video.ts
#EXTINF:1.4167,
#EXT-X-BYTERANGE:44556@8353216
hls_450k_video.ts
#EXT-X-ENDLIST
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
......@@ -44,12 +44,27 @@
<script src="../src/h264-stream.js"></script>
<script src="../src/aac-stream.js"></script>
<script src="../src/segment-parser.js"></script>
<!-- an example MPEG2-TS segment -->
<!-- <script src="tsSegment.js"></script> -->
<!-- M3U8 -->
<script src="../src/m3u8/m3u8.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>
<!-- M3U8 TEST DATA -->
<script src="manifest/playlistM3U8data.js"></script>
<script src="manifest/brightcove_playlist_m3u8.js"></script>
<!-- M3U8 -->
<!-- SEGMENT -->
<script src="tsSegment-bc.js"></script>
<script src="../src/segment-controller.js"></script>
<script src="../src/bin-utils.js"></script>
<script src="exp-golomb_test.js"></script>
<script src="video-js-hls_test.js"></script>
<script src="exp-golomb_test.js"></script>
<script src="flv-tag_test.js"></script>
......
......@@ -20,6 +20,9 @@
throws(block, [expected], [message])
*/
var
manifestController,
segmentController,
m3u8parser,
parser,
expectedHeader = [
......@@ -95,7 +98,6 @@
// payload starts at tag.bytes[16]
console.log(frameType);
// XXX: I'm not sure that frame types 3-5 are invalid
ok(frameType === 1 || frameType === 2,
......@@ -233,4 +235,152 @@
'the size of the previous tag is correct');
}
});
/*
M3U8 Test Suite
*/
module('m3u8 parser', {
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
......
<?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>