a810eb63 by David LaPalomento

Merge pull request #4 from dlapalomento/feature/m3u8s

HLS playback support cleanup
2 parents 3b271282 884e8d3c
1 /node_modules/ 1 /node_modules/
2 *~ 2 *~
3 *.iml
......
...@@ -10,5 +10,11 @@ ...@@ -10,5 +10,11 @@
10 "unused": true, 10 "unused": true,
11 "boss": true, 11 "boss": true,
12 "eqnull": true, 12 "eqnull": true,
13 "node": true 13 "node": true,
14
15 "camelcase": true,
16 "nonew": true,
17 "quotmark": "single",
18 "trailing": true,
19 "maxlen": 80
14 } 20 }
......
...@@ -20,7 +20,19 @@ module.exports = function(grunt) { ...@@ -20,7 +20,19 @@ module.exports = function(grunt) {
20 stripBanners: true 20 stripBanners: true
21 }, 21 },
22 dist: { 22 dist: {
23 src: ['src/*.js'], 23 src: ['src/video-js-hls.js',
24 'src/flv-tag.js',
25 'src/exp-golomb.js',
26 'src/h264-stream.js',
27 'src/aac-stream.js',
28 'src/segment-parser.js',
29 'src/segment-controller.js',
30 'src/m3u8/m3u8.js',
31 'src/m3u8/m3u8-tag-types.js',
32 'src/m3u8/m3u8-parser.js',
33 'src/manifest-controller.js',
34 'src/segment-controller.js',
35 'src/hls-playback-controller.js'],
24 dest: 'dist/videojs.hls.js' 36 dest: 'dist/videojs.hls.js'
25 }, 37 },
26 }, 38 },
...@@ -53,7 +65,7 @@ module.exports = function(grunt) { ...@@ -53,7 +65,7 @@ module.exports = function(grunt) {
53 options: { 65 options: {
54 jshintrc: 'test/.jshintrc' 66 jshintrc: 'test/.jshintrc'
55 }, 67 },
56 src: ['test/**/*.js', '!test/tsSegment.js'] 68 src: ['test/**/*.js', '!test/tsSegment.js', '!test/fixtures/*.js']
57 }, 69 },
58 }, 70 },
59 watch: { 71 watch: {
...@@ -81,6 +93,7 @@ module.exports = function(grunt) { ...@@ -81,6 +93,7 @@ module.exports = function(grunt) {
81 grunt.loadNpmTasks('grunt-contrib-watch'); 93 grunt.loadNpmTasks('grunt-contrib-watch');
82 94
83 // Default task. 95 // Default task.
84 grunt.registerTask('default', ['jshint', 'qunit', 'clean', 'concat', 'uglify']); 96 grunt.registerTask('default',
97 ['jshint', 'qunit', 'clean', 'concat', 'uglify']);
85 98
86 }; 99 };
......
...@@ -25,9 +25,11 @@ ...@@ -25,9 +25,11 @@
25 25
26 <!-- m3u8 handling --> 26 <!-- m3u8 handling -->
27 <script src="src/m3u8/m3u8.js"></script> 27 <script src="src/m3u8/m3u8.js"></script>
28 <script src="src/m3u8/m3u8-parser.js"></script>
29 <script src="src/m3u8/m3u8-tag-types.js"></script> 28 <script src="src/m3u8/m3u8-tag-types.js"></script>
29 <script src="src/m3u8/m3u8-parser.js"></script>
30 <script src="src/manifest-controller.js"></script> 30 <script src="src/manifest-controller.js"></script>
31 <script src="src/segment-controller.js"></script>
32 <script src="src/hls-playback-controller.js"></script>
31 33
32 <!-- example MPEG2-TS segments --> 34 <!-- example MPEG2-TS segments -->
33 <!-- bipbop --> 35 <!-- bipbop -->
...@@ -48,51 +50,14 @@ ...@@ -48,51 +50,14 @@
48 50
49 // initialize the player 51 // initialize the player
50 videojs.options.flash.swf = 'node_modules/videojs-media-sources/video-js-with-mse.swf'; 52 videojs.options.flash.swf = 'node_modules/videojs-media-sources/video-js-with-mse.swf';
51 video = videojs('video'); 53 video = videojs('video',{},function(){
52 54 this.playbackController = new window.videojs.hls.HLSPlaybackController(this);
53 // create a media source 55 this.playbackController.loadManifest('test/fixtures/bipbop.m3u8', function(data) {
54 mediaSource = new videojs.MediaSource(); 56 console.log(data);
55 mediaSource.addEventListener('sourceopen', function(event){ 57 });
56 var
57 parser = new videojs.hls.SegmentParser(),
58 sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
59 everything = [];
60
61 // feed parsed bytes into the player
62 everything.push(parser.getFlvHeader());
63 sourceBuffer.appendBuffer(everything[everything.length - 1], video);
64
65 parser.parseSegmentBinaryData(window.bcSegment);
66
67 while (parser.tagsAvailable()) {
68 everything.push(parser.getNextTag().bytes);
69 sourceBuffer.appendBuffer(everything[everything.length - 1], video);
70 }
71 parser.flushTags();
72 while (parser.tagsAvailable()) {
73 everything.push(parser.getNextTag().bytes);
74 sourceBuffer.appendBuffer(everything[everything.length - 1], video);
75 }
76
77 var iframe = document.createElement('iframe');
78 iframe.src = 'data:video/x-flv;base64,' + window.btoa((Array.prototype.map.call(everything.reduce(function(result, next) {
79 var array = new Uint8Array(result.byteLength + next.byteLength);
80 array.set(result);
81 array.set(next, result.length);
82 return array;
83 }), function(byte) {
84 return String.fromCharCode(byte);
85 })).join(''));
86 //console.log(iframe);
87 // document.body.appendChild(iframe);
88 }, false);
89
90 url = videojs.URL.createObjectURL(mediaSource);
91
92 video.src({
93 src: url,
94 type: "video/flv"
95 }); 58 });
96 </script> 59 </script>
60
61
97 </body> 62 </body>
98 </html> 63 </html>
......
...@@ -25,10 +25,6 @@ window.videojs.hls.ExpGolomb = function(workingData) { ...@@ -25,10 +25,6 @@ window.videojs.hls.ExpGolomb = function(workingData) {
25 return (8 * workingBytesAvailable) + workingBitsAvailable; 25 return (8 * workingBytesAvailable) + workingBitsAvailable;
26 }; 26 };
27 27
28 this.logStuff = function() {
29 console.log('bits', workingBitsAvailable, 'word', (workingWord >>> 0));
30 };
31
32 // ():void 28 // ():void
33 this.loadWord = function() { 29 this.loadWord = function() {
34 var 30 var
......
...@@ -47,7 +47,6 @@ hls.FlvTag = function(type, extraData) { ...@@ -47,7 +47,6 @@ hls.FlvTag = function(type, extraData) {
47 try { 47 try {
48 this.bytes.set(bytes.subarray(offset, offset + length), this.position); 48 this.bytes.set(bytes.subarray(offset, offset + length), this.position);
49 } catch(e) { 49 } catch(e) {
50 console.log(e);
51 throw e; 50 throw e;
52 } 51 }
53 this.position += length; 52 this.position += length;
......
...@@ -90,8 +90,6 @@ ...@@ -90,8 +90,6 @@
90 } else if (0 !== sps0[offset + 0]) { 90 } else if (0 !== sps0[offset + 0]) {
91 offset += 1; 91 offset += 1;
92 } else { 92 } else {
93 console.log('found emulation bytes');
94
95 rbsp.set([0x00, 0x00], rbspCount); 93 rbsp.set([0x00, 0x00], rbspCount);
96 spsCount += 2; 94 spsCount += 2;
97 rbspCount += 2; 95 rbspCount += 2;
...@@ -311,7 +309,6 @@ ...@@ -311,7 +309,6 @@
311 }; 309 };
312 310
313 this.finishFrame = function() { 311 this.finishFrame = function() {
314 console.log('finish frame');
315 if (h264Frame) { 312 if (h264Frame) {
316 // Push SPS before EVERY IDR frame fo seeking 313 // Push SPS before EVERY IDR frame fo seeking
317 if (newExtraData.extraDataExists()) { 314 if (newExtraData.extraDataExists()) {
......
1 (function(window) {
2 var
3 ManifestController = window.videojs.hls.ManifestController,
4 SegmentController = window.videojs.hls.SegmentController,
5 MediaSource = window.videojs.MediaSource,
6 SegmentParser = window.videojs.hls.SegmentParser;
7
8
9 window.videojs.hls.HLSPlaybackController = function(player) {
10
11 var self = this;
12
13 self.player = player;
14 self.mediaSource = new MediaSource();
15 self.parser = new SegmentParser();
16
17 self.manifestLoaded = false;
18 self.currentSegment = 0;
19
20 // register external callbacks
21 self.rendition = function(rendition) {
22 self.currentRendition = rendition;
23 self.loadManifest(self.currentRendition.url, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update);
24 };
25
26 self.loadManifest = function(manifestUrl, onDataCallback) {
27 self.mediaSource.addEventListener('sourceopen', function() {
28 // feed parsed bytes into the player
29 self.sourceBuffer = self.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
30
31 self.parser = new SegmentParser();
32
33 self.sourceBuffer.appendBuffer(self.parser.getFlvHeader(), self.player);
34
35 if (onDataCallback) {
36 self.manifestLoadCompleteCallback = onDataCallback;
37 }
38
39 self.manifestController = new ManifestController();
40 self.manifestController.loadManifest(manifestUrl, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update);
41
42 }, false);
43
44 self.player.src({
45 src: window.videojs.URL.createObjectURL(self.mediaSource),
46 type: "video/flv"
47 });
48 };
49
50 self.onM3U8LoadComplete = function(m3u8) {
51 if (m3u8.invalidReasons.length === 0) {
52 if (m3u8.isPlaylist) {
53 self.currentPlaylist = m3u8;
54 self.rendition(self.currentPlaylist.playlistItems[0]);
55 } else {
56 self.currentManifest = m3u8;
57 self.manifestLoaded = true;
58
59 self.loadSegment(self.currentManifest.mediaItems[0]);
60
61 if (self.manifestLoadCompleteCallback) {
62 self.manifestLoadCompleteCallback(m3u8);
63 }
64 }
65 }
66 };
67
68 self.onM3U8LoadError = function() {};
69 self.onM3U8Update = function() {};
70
71 self.loadSegment = function(segment) {
72 self.segmentController = new SegmentController();
73 self.segmentController.loadSegment(segment.url, self.onSegmentLoadComplete, self.onSegmentLoadError);
74 };
75
76 self.onSegmentLoadComplete = function(segment) {
77 self.parser.parseSegmentBinaryData(segment.binaryData);
78
79 while (self.parser.tagsAvailable()) {
80 self.sourceBuffer.appendBuffer(self.parser.getNextTag().bytes, self.player);
81 }
82
83 if (self.currentSegment < self.currentManifest.mediaItems.length-1) {
84 self.loadNextSegment();
85 }
86 };
87
88 self.loadNextSegment = function() {
89 self.currentSegment++;
90 self.loadSegment(self.currentManifest.mediaItems[self.currentSegment]);
91 };
92
93 self.onSegmentLoadError = function() {};
94
95 };
96 })(this);
1 (function(window) { 1 (function(window) {
2 var M3U8 = window.videojs.hls.M3U8; 2 var M3U8 = window.videojs.hls.M3U8;
3 3
4 window.videojs.hls.M3U8Parser = function() { 4 window.videojs.hls.M3U8Parser = function() {
5 5 var
6 var self = this; 6 self = this,
7 var tagTypes = window.videojs.hls.m3u8TagType; 7 tagTypes = window.videojs.hls.m3u8TagType,
8 var lines = []; 8 lines = [],
9 var data; 9 data;
10 10
11 self.getTagType = function( lineData ) { 11 self.getTagType = function(lineData) {
12 for ( var s in tagTypes ) 12 for (var s in tagTypes) {
13 { 13 if (lineData.indexOf(tagTypes[s]) === 0) {
14 if (lineData.indexOf(tagTypes[s]) == 0) 14 return tagTypes[s];
15 { 15 }
16 return tagTypes[s]; 16 }
17 }
18 }
19 }
20
21 self.getTagValue = function ( lineData ) {
22 for ( var s in tagTypes )
23 {
24 if (lineData.indexOf(tagTypes[s]) == 0)
25 {
26 return lineData.substr(tagTypes[s].length);
27 }
28 }
29 }
30
31 self.parse = function( rawDataString ) {
32 data = new M3U8();
33
34 if( rawDataString != undefined && rawDataString.toString().length > 0 )
35 {
36 lines = rawDataString.split('\n');
37
38 lines.forEach(
39 function(value,index) {
40 switch( self.getTagType(value) )
41 {
42 case tagTypes.EXTM3U:
43 data.hasValidM3UTag = (index == 0);
44 if(!data.hasValidM3UTag)
45 {
46 data.invalidReasons.push("Invalid EXTM3U Tag");
47 }
48 break;
49
50 case tagTypes.DISCONTINUITY:
51 break;
52
53 case tagTypes.PLAYLIST_TYPE:
54 if(self.getTagValue(value) == "VOD" || self.getTagValue(value) == "EVENT")
55 {
56 data.playlistType = self.getTagValue(value);
57 data.isPlaylist = true;
58 } else {
59 data.invalidReasons.push("Invalid Playlist Type Value");
60 }
61 break;
62
63 case tagTypes.EXTINF:
64 var segment = {url: "unknown", byterange: -1, targetDuration: data.targetDuration };
65
66 if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE )
67 {
68 segment.byterange = self.getTagValue(lines[index+1]).split('@');
69 segment.url = lines[index+2];
70 } else
71 {
72 segment.url = lines[index+1];
73 }
74
75 data.mediaItems.push(segment);
76
77 break;
78
79 case tagTypes.STREAM_INF:
80 var rendition = {};
81 var attributes = value.substr(tagTypes.STREAM_INF.length).split(',');
82
83 attributes.forEach(function(attr_value,attr_index) {
84 if(isNaN(attr_value.split('=')[1])){
85 rendition[attr_value.split('=')[0].toLowerCase()] = attr_value.split('=')[1];
86
87 if(rendition[attr_value.split('=')[0].toLowerCase()].split('x').length = 2)
88 {
89 rendition.resolution = {
90 width: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[0]),
91 height: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[1])
92 }
93 }
94
95 } else {
96 rendition[attr_value.split('=')[0].toLowerCase()] = Number(attr_value.split('=')[1]);
97 }
98 });
99
100
101 if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE )
102 {
103 rendition.byterange = self.getTagValue(lines[index+1]).split('@');
104 rendition.url = lines[index+2];
105 } else
106 {
107 rendition.url = lines[index+1];
108 }
109
110 data.isPlaylist = true;
111 data.playlistItems.push(rendition);
112 break;
113
114 case tagTypes.TARGETDURATION:
115 data.targetDuration = Number(self.getTagValue(value).split(',')[0]);
116 break;
117
118 case tagTypes.ZEN_TOTAL_DURATION:
119 data.totalDuration = self.getTagValue(value);
120 break;
121
122 case tagTypes.VERSION:
123 data.version = Number(self.getTagValue(value));
124 break;
125
126 case tagTypes.MEDIA_SEQUENCE:
127 data.mediaSequence = parseInt(self.getTagValue(value));
128 break;
129
130 case tagTypes.ALLOW_CACHE:
131 if(self.getTagValue(value) == "YES" || self.getTagValue(value) == "NO")
132 {
133 data.allowCache = self.getTagValue(value);
134 } else {
135 data.invalidReasons.push("Invalid ALLOW_CACHE Value");
136 }
137 break;
138
139 case tagTypes.ENDLIST:
140 data.hasEndTag = true;
141 break;
142 }
143 }
144 )
145 } else {
146 data.invalidReasons.push("Empty Manifest");
147 }
148
149 return data;
150
151 };
152 }; 17 };
153 18
154 })(this);
...\ No newline at end of file ...\ No newline at end of file
19 self.getTagValue = function(lineData) {
20 for (var s in tagTypes) {
21 if (lineData.indexOf(tagTypes[s]) === 0) {
22 return lineData.substr(tagTypes[s].length);
23 }
24 }
25 };
26
27 self.parse = function(rawDataString) {
28 data = new M3U8();
29
30 if (self.directory) {
31 data.directory = self.directory;
32 }
33
34 if (rawDataString === undefined || rawDataString.length <= 0) {
35 data.invalidReasons.push("Empty Manifest");
36 return;
37 }
38 lines = rawDataString.split('\n');
39
40 lines.forEach(function(value,index) {
41 var segment, rendition, attributes;
42
43 switch (self.getTagType(value)) {
44 case tagTypes.EXTM3U:
45 data.hasValidM3UTag = (index === 0);
46 if (!data.hasValidM3UTag) {
47 data.invalidReasons.push("Invalid EXTM3U Tag");
48 }
49 break;
50
51 case tagTypes.DISCONTINUITY:
52 break;
53
54 case tagTypes.PLAYLIST_TYPE:
55 if (self.getTagValue(value) === "VOD" ||
56 self.getTagValue(value) === "EVENT") {
57 data.playlistType = self.getTagValue(value);
58 data.isPlaylist = true;
59 } else {
60 data.invalidReasons.push("Invalid Playlist Type Value");
61 }
62 break;
63
64 case tagTypes.EXTINF:
65 segment = {
66 url: "unknown",
67 byterange: -1,
68 targetDuration: data.targetDuration
69 };
70
71 if (self.getTagType(lines[index + 1]) === tagTypes.BYTERANGE) {
72 segment.byterange = self.getTagValue(lines[index + 1]).split('@');
73 segment.url = lines[index + 2];
74 } else {
75 segment.url = lines[index + 1];
76 }
77
78 if (segment.url.indexOf("http") === -1 && self.directory) {
79 if (data.directory[data.directory.length-1] === segment.url[0] &&
80 segment.url[0] === "/") {
81 segment.url = segment.url.substr(1);
82 }
83 segment.url = self.directory + segment.url;
84 }
85 data.mediaItems.push(segment);
86 break;
87
88 case tagTypes.STREAM_INF:
89 rendition = {};
90 attributes = value.substr(tagTypes.STREAM_INF.length).split(',');
91
92 attributes.forEach(function(attrValue) {
93 if (isNaN(attrValue.split('=')[1])) {
94 rendition[attrValue.split('=')[0].toLowerCase()] = attrValue.split('=')[1];
95
96 if (rendition[attrValue.split('=')[0].toLowerCase()].split('x').length === 2) {
97 rendition.resolution = {
98 width: parseInt(rendition[attrValue.split('=')[0].toLowerCase()].split('x')[0],10),
99 height: parseInt(rendition[attrValue.split('=')[0].toLowerCase()].split('x')[1],10)
100 };
101 }
102 } else {
103 rendition[attrValue.split('=')[0].toLowerCase()] = parseInt(attrValue.split('=')[1],10);
104 }
105 });
106
107 if (self.getTagType(lines[index + 1]) === tagTypes.BYTERANGE) {
108 rendition.byterange = self.getTagValue(lines[index + 1]).split('@');
109 rendition.url = lines[index + 2];
110 } else {
111 rendition.url = lines[index + 1];
112 }
113
114 data.isPlaylist = true;
115 data.playlistItems.push(rendition);
116 break;
117
118 case tagTypes.TARGETDURATION:
119 data.targetDuration = parseFloat(self.getTagValue(value).split(',')[0]);
120 break;
121
122 case tagTypes.ZEN_TOTAL_DURATION:
123 data.totalDuration = parseFloat(self.getTagValue(value));
124 break;
125
126 case tagTypes.VERSION:
127 data.version = parseFloat(self.getTagValue(value));
128 break;
129
130 case tagTypes.MEDIA_SEQUENCE:
131 data.mediaSequence = parseInt(self.getTagValue(value),10);
132 break;
133
134 case tagTypes.ALLOW_CACHE:
135 if (self.getTagValue(value) === "YES" || self.getTagValue(value) === "NO") {
136 data.allowCache = self.getTagValue(value);
137 } else {
138 data.invalidReasons.push("Invalid ALLOW_CACHE Value");
139 }
140 break;
141
142 case tagTypes.ENDLIST:
143 data.hasEndTag = true;
144 break;
145 }
146 });
147
148 return data;
149 };
150 };
151 })(this);
......
1 window.videojs.hls.m3u8TagType = {
2 /*
3 * Derived from V8: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08
4 */
5
6 /**
7 * Identifies manifest as Extended M3U - must be present on first line!
8 */
9 EXTM3U:"#EXTM3U",
10
11 /**
12 * Specifies duration.
13 * Syntax: #EXTINF:<duration>,<title>
14 * Example: #EXTINF:10,
15 */
16 EXTINF:"#EXTINF:",
17
18 /**
19 * Indicates that a media segment is a sub-range of the resource identified by its media URI.
20 * Syntax: #EXT-X-BYTERANGE:<n>[@o]
21 */
22 BYTERANGE:"#EXT-X-BYTERANGE:",
23
24 /**
25 * Specifies the maximum media segment duration - applies to entire manifest.
26 * Syntax: #EXT-X-TARGETDURATION:<s>
27 * Example: #EXT-X-TARGETDURATION:10
28 */
29 TARGETDURATION:"#EXT-X-TARGETDURATION:",
30
31 /**
32 * Specifies the sequence number of the first URI in a manifest.
33 * Syntax: #EXT-X-MEDIA-SEQUENCE:<i>
34 * Example: #EXT-X-MEDIA-SEQUENCE:50
35 */
36 MEDIA_SEQUENCE:"#EXT-X-MEDIA-SEQUENCE:",
37
38 /**
39 * Specifies a method by which media segments can be decrypted, if encryption is present.
40 * Syntax: #EXT-X-KEY:<attribute-list>
41 * Note: This is likely irrelevant in the context of the Flash Player.
42 */
43 KEY:"#EXT-X-KEY:",
44
45 /**
46 * Associates the first sample of a media segment with an absolute date and/or time. Applies only to the next media URI.
47 * Syntax: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
48 * Example: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
49 */
50 PROGRAM_DATE_TIME:"#EXT-X-PROGRAM-DATE-TIME:",
51
52 /**
53 * Indicates whether the client MAY or MUST NOT cache downloaded media segments for later replay.
54 * Syntax: #EXT-X-ALLOW-CACHE:<YES|NO>
55 * Note: This is likely irrelevant in the context of the Flash Player.
56 */
57 ALLOW_CACHE:"#EXT-X-ALLOW_CACHE:",
58
59 /**
60 * Provides mutability information about the manifest.
61 * Syntax: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
62 */
63 PLAYLIST_TYPE:"#EXT-X-PLAYLIST-TYPE:",
64
65 /**
66 * Indicates that no more media segments will be added to the manifest. May occur ONCE, anywhere in the mainfest file.
67 */
68 ENDLIST:"#EXT-X-ENDLIST",
69
70 /**
71 * Used to relate Playlists that contain alternative renditions of the same content.
72 * Syntax: #EXT-X-MEDIA:<attribute-list>
73 */
74 MEDIA:"#EXT-X-MEDIA:",
75
76 /**
77 * Identifies a media URI as a Playlist file containing a multimedia presentation and provides information about that presentation.
78 * Syntax: #EXT-X-STREAM-INF:<attribute-list>
79 * <URI>
80 */
81 STREAM_INF:"#EXT-X-STREAM-INF:",
82
83 /**
84 * Indicates an encoding discontinuity between the media segment that follows it and the one that preceded it.
85 */
86 DISCONTINUITY:"#EXT-X-DISCONTINUITY",
87
88 /**
89 * Indicates that each media segment in the manifest describes a single I-frame.
90 */
91 I_FRAMES_ONLY:"#EXT-X-I-FRAMES-ONLY",
92
93 /**
94 * 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.
95 * Syntax: #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
96 */
97 I_FRAME_STREAM_INF:"#EXT-X-I-FRAME-STREAM-INF:",
98
99 /**
100 * Indicates the compatibility version of the Playlist file.
101 * Syntax: #EXT-X-VERSION:<n>
102 */
103 VERSION:"#EXT-X-VERSION:",
104
105 /**
106 * Indicates the total duration as reported by Zencoder.
107 * Syntax: #ZEN-TOTAL-DURATION:<n>
108 */
109 ZEN_TOTAL_DURATION: "#ZEN-TOTAL-DURATION:"
110
111 };
...\ No newline at end of file ...\ No newline at end of file
1 (function(window) {
2 window.videojs.hls.m3u8TagType = {
3 /*
4 * Derived from the HTTP Live Streaming Spec V8
5 * http://tools.ietf.org/html/draft-pantos-http-live-streaming-08
6 */
7
8 /**
9 * Identifies manifest as Extended M3U - must be present on first line!
10 */
11 EXTM3U:"#EXTM3U",
12
13 /**
14 * Specifies duration.
15 * Syntax: #EXTINF:<duration>,<title>
16 * Example: #EXTINF:10,
17 */
18 EXTINF:"#EXTINF:",
19
20 /**
21 * Indicates that a media segment is a sub-range of the resource identified by its media URI.
22 * Syntax: #EXT-X-BYTERANGE:<n>[@o]
23 */
24 BYTERANGE:"#EXT-X-BYTERANGE:",
25
26 /**
27 * Specifies the maximum media segment duration - applies to entire manifest.
28 * Syntax: #EXT-X-TARGETDURATION:<s>
29 * Example: #EXT-X-TARGETDURATION:10
30 */
31 TARGETDURATION:"#EXT-X-TARGETDURATION:",
32
33 /**
34 * Specifies the sequence number of the first URI in a manifest.
35 * Syntax: #EXT-X-MEDIA-SEQUENCE:<i>
36 * Example: #EXT-X-MEDIA-SEQUENCE:50
37 */
38 MEDIA_SEQUENCE:"#EXT-X-MEDIA-SEQUENCE:",
39
40 /**
41 * Specifies a method by which media segments can be decrypted, if encryption is present.
42 * Syntax: #EXT-X-KEY:<attribute-list>
43 * Note: This is likely irrelevant in the context of the Flash Player.
44 */
45 KEY:"#EXT-X-KEY:",
46
47 /**
48 * Associates the first sample of a media segment with an absolute date and/or time. Applies only to the next media URI.
49 * Syntax: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
50 * Example: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00
51 */
52 PROGRAM_DATE_TIME:"#EXT-X-PROGRAM-DATE-TIME:",
53
54 /**
55 * Indicates whether the client MAY or MUST NOT cache downloaded media segments for later replay.
56 * Syntax: #EXT-X-ALLOW-CACHE:<YES|NO>
57 * Note: This is likely irrelevant in the context of the Flash Player.
58 */
59 ALLOW_CACHE:"#EXT-X-ALLOW_CACHE:",
60
61 /**
62 * Provides mutability information about the manifest.
63 * Syntax: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD>
64 */
65 PLAYLIST_TYPE:"#EXT-X-PLAYLIST-TYPE:",
66
67 /**
68 * Indicates that no more media segments will be added to the manifest. May occur ONCE, anywhere in the mainfest file.
69 */
70 ENDLIST:"#EXT-X-ENDLIST",
71
72 /**
73 * Used to relate Playlists that contain alternative renditions of the same content.
74 * Syntax: #EXT-X-MEDIA:<attribute-list>
75 */
76 MEDIA:"#EXT-X-MEDIA:",
77
78 /**
79 * Identifies a media URI as a Playlist file containing a multimedia presentation and provides information about that presentation.
80 * Syntax: #EXT-X-STREAM-INF:<attribute-list>
81 * <URI>
82 */
83 STREAM_INF:"#EXT-X-STREAM-INF:",
84
85 /**
86 * Indicates an encoding discontinuity between the media segment that follows it and the one that preceded it.
87 */
88 DISCONTINUITY:"#EXT-X-DISCONTINUITY",
89
90 /**
91 * Indicates that each media segment in the manifest describes a single I-frame.
92 */
93 I_FRAMES_ONLY:"#EXT-X-I-FRAMES-ONLY",
94
95 /**
96 * 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.
97 * Syntax: #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
98 */
99 I_FRAME_STREAM_INF:"#EXT-X-I-FRAME-STREAM-INF:",
100
101 /**
102 * Indicates the compatibility version of the Playlist file.
103 * Syntax: #EXT-X-VERSION:<n>
104 */
105 VERSION:"#EXT-X-VERSION:",
106
107 /**
108 * Indicates the total duration as reported by Zencoder.
109 * Syntax: #ZEN-TOTAL-DURATION:<n>
110 */
111 ZEN_TOTAL_DURATION: "#ZEN-TOTAL-DURATION:"
112
113 };
114 })(this);
......
1 (function(window) {
2 window.videojs.hls.M3U8 = function() {
3 this.allowCache = "NO";
4 this.playlistItems = [];
5 this.mediaItems = [];
6 this.iFrameItems = [];
7 this.invalidReasons = [];
8 this.hasValidM3UTag = false;
9 this.hasEndTag = false;
10 this.targetDuration = -1;
11 this.totalDuration = -1;
12 this.isPlaylist = false;
13 this.playlistType = "";
14 this.mediaSequence = -1;
15 this.version = -1;
16 }
17 })(this);
...\ No newline at end of file ...\ No newline at end of file
1 (function (window) {
2 window.videojs.hls.M3U8 = function () {
3 this.directory = "";
4 this.allowCache = "NO";
5 this.playlistItems = [];
6 this.mediaItems = [];
7 this.iFrameItems = [];
8 this.invalidReasons = [];
9 this.hasValidM3UTag = false;
10 this.hasEndTag = false;
11 this.targetDuration = -1;
12 this.totalDuration = -1;
13 this.isPlaylist = false;
14 this.playlistType = "";
15 this.mediaSequence = -1;
16 this.version = -1;
17 };
18 })(this);
......
1 (function(window) { 1 (function (window) {
2 var M3U8 = window.videojs.hls.M3U8; 2 var
3 var M3U8Parser = window.videojs.hls.M3U8Parser; 3 M3U8Parser = window.videojs.hls.M3U8Parser;
4 4
5 window.videojs.hls.ManifestController = function(){ 5 window.videojs.hls.ManifestController = function() {
6 6 var self = this;
7 var self = this; 7
8 var parser; 8 self.loadManifest = function(manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback) {
9 var data; 9 self.url = manifestUrl;
10 10
11 var onDataCallback; 11 if (onDataCallback) {
12 var onErrorCallback; 12 self.onDataCallback = onDataCallback;
13 var onUpdateCallback; 13 }
14 14 if (onErrorCallback) {
15 self.loadManifest = function ( manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback ) { 15 self.onErrorCallback = onErrorCallback;
16 self.onDataCallback = onDataCallback; 16 }
17 self.onErrorCallback = onErrorCallback; 17
18 self.onUpdateCallback = onUpdateCallback; 18 if (onUpdateCallback) {
19 19 self.onUpdateCallback = onUpdateCallback;
20 vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError); 20 }
21 }; 21
22 22 window.vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError);
23 self.parseManifest = function ( dataAsString ) { 23 };
24 self.parser = new M3U8Parser(); 24
25 self.data = self.parser.parse( dataAsString ); 25 self.parseManifest = function(dataAsString) {
26 26 self.parser = new M3U8Parser();
27 return self.data; 27 self.parser.directory = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(self.url).slice(1)[1];
28 }; 28 self.data = self.parser.parse(dataAsString);
29 29
30 self.onManifestLoadComplete = function(response) { 30 return self.data;
31 var output = self.parseManifest(response); 31 };
32 32
33 if(self.onDataCallback != undefined) 33 self.onManifestLoadComplete = function(response) {
34 { 34 var output = self.parseManifest(response);
35 self.onDataCallback(output); 35
36 } 36 if (self.onDataCallback !== undefined) {
37 }; 37 self.onDataCallback(output);
38 38 }
39 self.onManifestLoadError = function(err) { 39 };
40 if(err) 40
41 { 41 self.onManifestLoadError = function(err) {
42 console.log(err.message); 42 if (self.onErrorCallback !== undefined) {
43 } 43 self.onErrorCallback((err !== undefined) ? err : null);
44 44 }
45 if(self.onErrorCallback != undefined) 45 };
46 { 46 };
47 onErrorCallback((err != undefined) ? err : null);
48 }
49 };
50 }
51 })(this); 47 })(this);
......
1 (function(window) { 1 (function(window) {
2 2
3 var SegmentParser = window.videojs.hls.SegmentParser; 3 window.videojs.hls.SegmentController = function() {
4 var self = this;
4 5
5 window.videojs.hls.SegmentController = function(){ 6 self.loadSegment = function(segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback) {
7 var request = new XMLHttpRequest();
6 8
7 var self = this; 9 self.url = segmentUrl;
8 var url; 10 self.onDataCallback = onDataCallback;
9 var parser; 11 self.onErrorCallback = onErrorCallback;
10 var requestTimestamp; 12 self.onUpdateCallback = onUpdateCallback;
11 var responseTimestamp; 13 self.requestTimestamp = +new Date();
12 var data;
13 14
14 var onDataCallback; 15 request.open('GET', segmentUrl, true);
15 var onErrorCallback; 16 request.responseType = 'arraybuffer';
16 var onUpdateCallback; 17 request.onload = function() {
18 self.onSegmentLoadComplete(new Uint8Array(request.response));
19 };
17 20
18 self.loadSegment = function ( segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback ) { 21 request.send(null);
19 self.url = segmentUrl; 22 };
20 self.onDataCallback = onDataCallback;
21 self.onErrorCallback = onErrorCallback;
22 self.onUpdateCallback = onUpdateCallback;
23 self.requestTimestamp = new Date().getTime();
24 23
25 var req = new XMLHttpRequest(); 24 self.parseSegment = function(incomingData) {
26 req.open('GET', segmentUrl, true); 25 self.data = {};
27 req.responseType = 'arraybuffer'; 26 self.data.binaryData = incomingData;
28 req.onload = function(response) { 27 self.data.url = self.url;
29 self.onSegmentLoadComplete(new Uint8Array(req.response)); 28 self.data.isCached = false;
30 }; 29 self.data.requestTimestamp = self.requestTimestamp;
31 30 self.data.responseTimestamp = self.responseTimestamp;
32 req.send(null); 31 self.data.byteLength = incomingData.byteLength;
33 }; 32 self.data.isCached = parseInt(self.responseTimestamp - self.requestTimestamp,10) < 75;
33 self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp);
34 34
35 self.parseSegment = function ( incomingData ) { 35 return self.data;
36 // Add David's code later // 36 };
37 37
38 self.data = { 38 self.calculateThroughput = function(dataAmount, startTime, endTime) {
39 whatever: incomingData 39 return Math.round(dataAmount / (endTime - startTime) * 1000) * 8;
40 }; 40 };
41 self.data.url = self.url;
42 self.data.isCached = false;
43 self.data.requestTimestamp = self.requestTimestamp;
44 self.data.responseTimestamp = self.responseTimestamp;
45 self.data.byteLength = incomingData.byteLength;
46 self.data.isCached = ( parseInt(self.responseTimestamp - self.requestTimestamp) < 75 );
47 self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp)
48 41
49 return self.data; 42 self.onSegmentLoadComplete = function(response) {
50 }; 43 var output;
51 44
52 self.calculateThroughput = function(dataAmount, startTime, endTime) { 45 self.responseTimestamp = +new Date();
53 return Math.round(dataAmount/(endTime-startTime)*1000)*8;
54 }
55 46
56 self.onSegmentLoadComplete = function(response) { 47 output = self.parseSegment(response);
57 self.responseTimestamp = new Date().getTime();
58 48
59 var output = self.parseSegment(response); 49 if (self.onDataCallback !== undefined) {
50 self.onDataCallback(output);
51 }
52 };
60 53
61 if(self.onDataCallback != undefined) 54 self.onSegmentLoadError = function(error) {
62 { 55 if (error) {
63 self.onDataCallback(output); 56 throw error;
64 } 57 }
65 };
66 58
67 self.onSegmentLoadError = function(err) { 59 if (self.onErrorCallback !== undefined) {
68 if(err) 60 self.onErrorCallback(error);
69 { 61 }
70 console.log(err.message); 62 };
71 } 63 };
72
73 if(self.onErrorCallback != undefined)
74 {
75 onErrorCallback((err != undefined) ? err : null);
76 }
77 };
78 }
79 })(this); 64 })(this);
......
1 (function(window) { 1 (function(window) {
2 var 2 var
3 FlvTag = window.videojs.hls.FlvTag, 3 videojs = window.videojs,
4 H264Stream = window.videojs.hls.H264Stream, 4 FlvTag = videojs.hls.FlvTag,
5 AacStream = window.videojs.hls.AacStream, 5 H264Stream = videojs.hls.H264Stream,
6 AacStream = videojs.hls.AacStream,
6 m2tsPacketSize = 188; 7 m2tsPacketSize = 188;
7 8
8 console.assert(H264Stream); 9 console.assert(H264Stream);
...@@ -150,7 +151,7 @@ ...@@ -150,7 +151,7 @@
150 // until we receive more 151 // until we receive more
151 152
152 // ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting. 153 // ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting.
153 console.log('data.length + streamBuffer.length < m2tsPacketSize ??'); 154 videojs.log('data.length + streamBuffer.length < m2tsPacketSize ??');
154 streamBuffer.readBytes(data, data.length, streamBuffer.length); 155 streamBuffer.readBytes(data, data.length, streamBuffer.length);
155 return; 156 return;
156 } else { 157 } else {
...@@ -194,7 +195,7 @@ ...@@ -194,7 +195,7 @@
194 // If there was an error parsing a TS packet. it could be 195 // If there was an error parsing a TS packet. it could be
195 // because we are not TS packet aligned. Step one forward by 196 // because we are not TS packet aligned. Step one forward by
196 // one byte and allow the code above to find the next 197 // one byte and allow the code above to find the next
197 console.log('error parsing m2ts packet, attempting to re-align'); 198 videojs.log('error parsing m2ts packet, attempting to re-align');
198 dataPosition++; 199 dataPosition++;
199 } 200 }
200 } 201 }
...@@ -382,7 +383,7 @@ ...@@ -382,7 +383,7 @@
382 } else if (0x1FFF === pid) { 383 } else if (0x1FFF === pid) {
383 // NULL packet 384 // NULL packet
384 } else { 385 } else {
385 console.log("Unknown PID " + pid); 386 videojs.log("Unknown PID parsing TS packet: " + pid);
386 } 387 }
387 388
388 return true; 389 return true;
......
...@@ -21,8 +21,7 @@ ...@@ -21,8 +21,7 @@
21 */ 21 */
22 var 22 var
23 buffer, 23 buffer,
24 expGolomb, 24 expGolomb;
25 view;
26 25
27 module('Exponential Golomb coding'); 26 module('Exponential Golomb coding');
28 27
......
...@@ -6,4 +6,4 @@ window.brightcove_playlist_data = '#EXTM3U\n'+ ...@@ -6,4 +6,4 @@ window.brightcove_playlist_data = '#EXTM3U\n'+
6 '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224\n'+ 6 '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=440000,RESOLUTION=396x224\n'+
7 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001\n'+ 7 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824686593001&videoId=1824650741001\n'+
8 '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540\n'+ 8 '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1928000,RESOLUTION=960x540\n'+
9 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001'
...\ No newline at end of file ...\ No newline at end of file
9 'http://c.brightcove.com/services/mobile/streaming/index/rendition.m3u8?assetId=1824687660001&videoId=1824650741001';
......
1 window.playlistData = '#EXTM3U\n'+ 1 window.playlistData = '#EXTM3U\n'+
2 '#EXT-X-TARGETDURATION:10\n' +
3 '#EXT-X-VERSION:4\n' +
4 '#EXT-X-MEDIA-SEQUENCE:0\n' +
5 '#EXT-X-PLAYLIST-TYPE:VOD\n' +
6 '#EXTINF:10,\n' +
7 '#EXT-X-BYTERANGE:522828@0\n' +
8 'hls_450k_video.ts\n' +
9 '#EXTINF:10,\n' +
10 '#EXT-X-BYTERANGE:587500@522828\n' +
11 'hls_450k_video.ts\n' +
12 '#EXTINF:10,\n' +
13 '#EXT-X-BYTERANGE:713084@1110328\n' +
14 'hls_450k_video.ts\n' +
15 '#EXTINF:10,\n' +
16 '#EXT-X-BYTERANGE:476580@1823412\n' +
17 'hls_450k_video.ts\n' +
18 '#EXTINF:10,\n' +
19 '#EXT-X-BYTERANGE:535612@2299992\n' +
20 'hls_450k_video.ts\n' +
21 '#EXTINF:10,\n' +
22 '#EXT-X-BYTERANGE:207176@2835604\n' +
23 'hls_450k_video.ts\n' +
24 '#EXTINF:10,\n' +
25 '#EXT-X-BYTERANGE:455900@3042780\n' +
26 'hls_450k_video.ts\n' +
27 '#EXTINF:10,\n' +
28 '#EXT-X-BYTERANGE:657248@3498680\n' +
29 'hls_450k_video.ts\n' +
30 '#EXTINF:10,\n' +
31 '#EXT-X-BYTERANGE:571708@4155928\n' +
32 'hls_450k_video.ts\n' +
33 '#EXTINF:10,\n' +
34 '#EXT-X-BYTERANGE:485040@4727636\n' +
35 'hls_450k_video.ts\n' +
36 '#EXTINF:10,\n' +
37 '#EXT-X-BYTERANGE:709136@5212676\n' +
38 'hls_450k_video.ts\n' +
39 '#EXTINF:10,\n' +
40 '#EXT-X-BYTERANGE:730004@5921812\n' +
41 'hls_450k_video.ts\n' +
42 '#EXTINF:10,\n' +
43 '#EXT-X-BYTERANGE:456276@6651816\n' +
44 'hls_450k_video.ts\n' +
45 '#EXTINF:10,\n' +
46 '#EXT-X-BYTERANGE:468684@7108092\n' +
47 'hls_450k_video.ts' +
48 '#EXTINF:10,\n' +
49 '#EXT-X-BYTERANGE:444996@7576776\n' +
50 'hls_450k_video.ts\n' +
51 '#EXTINF:10,\n' +
52 '#EXT-X-BYTERANGE:331444@8021772\n' +
53 'hls_450k_video.ts\n' +
54 '#EXTINF:1.4167,\n' +
55 '#EXT-X-BYTERANGE:44556@8353216\n' +
56 'hls_450k_video.ts\n' +
57 '#EXT-X-ENDLIST';
...\ No newline at end of file ...\ No newline at end of file
2 '#EXT-X-TARGETDURATION:10\n' +
3 '#EXT-X-VERSION:4\n' +
4 '#EXT-X-MEDIA-SEQUENCE:0\n' +
5 '#EXT-X-PLAYLIST-TYPE:VOD\n' +
6 '#EXTINF:10,\n' +
7 '#EXT-X-BYTERANGE:522828@0\n' +
8 'hls_450k_video.ts\n' +
9 '#EXTINF:10,\n' +
10 '#EXT-X-BYTERANGE:587500@522828\n' +
11 'hls_450k_video.ts\n' +
12 '#EXTINF:10,\n' +
13 '#EXT-X-BYTERANGE:713084@1110328\n' +
14 'hls_450k_video.ts\n' +
15 '#EXTINF:10,\n' +
16 '#EXT-X-BYTERANGE:476580@1823412\n' +
17 'hls_450k_video.ts\n' +
18 '#EXTINF:10,\n' +
19 '#EXT-X-BYTERANGE:535612@2299992\n' +
20 'hls_450k_video.ts\n' +
21 '#EXTINF:10,\n' +
22 '#EXT-X-BYTERANGE:207176@2835604\n' +
23 'hls_450k_video.ts\n' +
24 '#EXTINF:10,\n' +
25 '#EXT-X-BYTERANGE:455900@3042780\n' +
26 'hls_450k_video.ts\n' +
27 '#EXTINF:10,\n' +
28 '#EXT-X-BYTERANGE:657248@3498680\n' +
29 'hls_450k_video.ts\n' +
30 '#EXTINF:10,\n' +
31 '#EXT-X-BYTERANGE:571708@4155928\n' +
32 'hls_450k_video.ts\n' +
33 '#EXTINF:10,\n' +
34 '#EXT-X-BYTERANGE:485040@4727636\n' +
35 'hls_450k_video.ts\n' +
36 '#EXTINF:10,\n' +
37 '#EXT-X-BYTERANGE:709136@5212676\n' +
38 'hls_450k_video.ts\n' +
39 '#EXTINF:10,\n' +
40 '#EXT-X-BYTERANGE:730004@5921812\n' +
41 'hls_450k_video.ts\n' +
42 '#EXTINF:10,\n' +
43 '#EXT-X-BYTERANGE:456276@6651816\n' +
44 'hls_450k_video.ts\n' +
45 '#EXTINF:10,\n' +
46 '#EXT-X-BYTERANGE:468684@7108092\n' +
47 'hls_450k_video.ts' +
48 '#EXTINF:10,\n' +
49 '#EXT-X-BYTERANGE:444996@7576776\n' +
50 'hls_450k_video.ts\n' +
51 '#EXTINF:10,\n' +
52 '#EXT-X-BYTERANGE:331444@8021772\n' +
53 'hls_450k_video.ts\n' +
54 '#EXTINF:1.4167,\n' +
55 '#EXT-X-BYTERANGE:44556@8353216\n' +
56 'hls_450k_video.ts\n' +
57 '#EXT-X-ENDLIST';
......
...@@ -4,37 +4,38 @@ ...@@ -4,37 +4,38 @@
4 http://api.qunitjs.com/ 4 http://api.qunitjs.com/
5 5
6 Test methods: 6 Test methods:
7 module(name, {[setup][ ,teardown]}) 7 module(name, {[setup][ ,teardown]})
8 test(name, callback) 8 test(name, callback)
9 expect(numberOfAssertions) 9 expect(numberOfAssertions)
10 stop(increment) 10 stop(increment)
11 start(decrement) 11 start(decrement)
12 Test assertions: 12 Test assertions:
13 ok(value, [message]) 13 ok(value, [message])
14 equal(actual, expected, [message]) 14 equal(actual, expected, [message])
15 notEqual(actual, expected, [message]) 15 notEqual(actual, expected, [message])
16 deepEqual(actual, expected, [message]) 16 deepEqual(actual, expected, [message])
17 notDeepEqual(actual, expected, [message]) 17 notDeepEqual(actual, expected, [message])
18 strictEqual(actual, expected, [message]) 18 strictEqual(actual, expected, [message])
19 notStrictEqual(actual, expected, [message]) 19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message]) 20 throws(block, [expected], [message])
21 */ 21 */
22 var 22 var
23 manifestController, 23 manifestController,
24 segmentController, 24 segmentController,
25 m3u8parser, 25 m3u8parser,
26 parser, 26 parser,
27 27
28 expectedHeader = [ 28 expectedHeader = [
29 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 29 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
30 0x09, 0x00, 0x00, 0x00, 0x00 30 0x09, 0x00, 0x00, 0x00, 0x00
31 ], 31 ],
32 testAudioTag, 32 testAudioTag,
33 testVideoTag, 33 testVideoTag,
34 testScriptTag, 34 testScriptTag,
35 asciiFromBytes, 35 asciiFromBytes,
36 testScriptString, 36 testScriptString,
37 testScriptEcmaArray; 37 testScriptEcmaArray,
38 testNalUnit;
38 39
39 module('environment'); 40 module('environment');
40 41
...@@ -61,13 +62,9 @@ ...@@ -61,13 +62,9 @@
61 }); 62 });
62 63
63 test('parses the first bipbop segment', function() { 64 test('parses the first bipbop segment', function() {
64 var tag, bytes, i;
65 parser.parseSegmentBinaryData(window.bcSegment); 65 parser.parseSegmentBinaryData(window.bcSegment);
66
67 ok(parser.tagsAvailable(), 'tags are available');
68 66
69 console.log('h264 tags:', parser.stats.h264Tags(), 67 ok(parser.tagsAvailable(), 'tags are available');
70 'aac tags:', parser.stats.aacTags());
71 }); 68 });
72 69
73 testAudioTag = function(tag) { 70 testAudioTag = function(tag) {
...@@ -87,18 +84,16 @@ ...@@ -87,18 +84,16 @@
87 ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type'); 84 ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type');
88 }; 85 };
89 86
90 testVideoTag = function(tag) { 87 testVideoTag = function (tag) {
91 var 88 var
92 byte = tag.bytes[11], 89 byte = tag.bytes[11],
93 frameType = (byte & 0xF0) >>> 4, 90 frameType = (byte & 0xF0) >>> 4,
94 codecId = byte & 0x0F, 91 codecId = byte & 0x0F,
95 packetType = tag.bytes[12], 92 packetType = tag.bytes[12],
96 compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8, 93 compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8;
97 nalHeader;
98 94
99 // payload starts at tag.bytes[16] 95 // payload starts at tag.bytes[16]
100 96
101
102 // XXX: I'm not sure that frame types 3-5 are invalid 97 // XXX: I'm not sure that frame types 3-5 are invalid
103 ok(frameType === 1 || frameType === 2, 98 ok(frameType === 1 || frameType === 2,
104 'the frame type should be valid'); 99 'the frame type should be valid');
...@@ -110,7 +105,7 @@ ...@@ -110,7 +105,7 @@
110 compositionTime, 105 compositionTime,
111 'the composition time is zero for non-NALU packets'); 106 'the composition time is zero for non-NALU packets');
112 } 107 }
113 108
114 // TODO: the rest of the bytes are an NLU unit 109 // TODO: the rest of the bytes are an NLU unit
115 if (packetType === 0) { 110 if (packetType === 0) {
116 // AVC decoder configuration record 111 // AVC decoder configuration record
...@@ -122,15 +117,15 @@ ...@@ -122,15 +117,15 @@
122 117
123 testNalUnit = function(bytes) { 118 testNalUnit = function(bytes) {
124 var 119 var
125 nalHeader = bytes[0], 120 nalHeader = bytes[0];
126 unitType = nalHeader & 0x1F; 121 // unitType = nalHeader & 0x1F;
127 122
128 equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0'); 123 equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0');
129 // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something'); 124 // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
130 // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0'); 125 // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
131 // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22'); 126 // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
132 }; 127 };
133 128
134 129
135 asciiFromBytes = function(bytes) { 130 asciiFromBytes = function(bytes) {
136 var 131 var
...@@ -144,10 +139,10 @@ ...@@ -144,10 +139,10 @@
144 }; 139 };
145 140
146 testScriptString = function(tag, offset, expected) { 141 testScriptString = function(tag, offset, expected) {
147 var type = tag.bytes[offset], 142 var
148 stringLength = tag.view.getUint16(offset + 1), 143 type = tag.bytes[offset],
149 string, 144 stringLength = tag.view.getUint16(offset + 1),
150 i = expected.length; 145 string;
151 146
152 equal(2, type, 'the script element is of string type'); 147 equal(2, type, 'the script element is of string type');
153 equal(stringLength, expected.length, 'the script string length is correct'); 148 equal(stringLength, expected.length, 'the script string length is correct');
...@@ -214,7 +209,7 @@ ...@@ -214,7 +209,7 @@
214 // generic flv headers 209 // generic flv headers
215 ok(type === 8 || type === 9 || type === 18, 210 ok(type === 8 || type === 9 || type === 18,
216 'the type field specifies audio, video or script'); 211 'the type field specifies audio, video or script');
217 212
218 byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8; 213 byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8;
219 equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct'); 214 equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct');
220 215
...@@ -236,151 +231,117 @@ ...@@ -236,151 +231,117 @@
236 } 231 }
237 }); 232 });
238 233
239 /* 234 /*
240 M3U8 Test Suite 235 M3U8 Test Suite
241 */ 236 */
242 237
243 module('m3u8 parser', { 238 module('m3u8 parser', {
244 setup: function() { 239 setup: function() {
245 m3u8parser = new window.videojs.hls.M3U8Parser(); 240 m3u8parser = new window.videojs.hls.M3U8Parser();
241 }
242 });
243
244 test('should create my parser', function() {
245 ok(m3u8parser !== undefined);
246 });
247
248 test('should successfully parse manifest data', function() {
249 var parsedData = m3u8parser.parse(window.playlistData);
250 ok(parsedData);
251 });
252
253 test('test for expected results', function() {
254 var data = m3u8parser.parse(window.playlistData);
255
256 notEqual(data, null, 'data is not NULL');
257 equal(data.invalidReasons.length, 0, 'data has 0 invalid reasons');
258 equal(data.hasValidM3UTag, true, 'data has valid EXTM3U');
259 equal(data.targetDuration, 10, 'data has correct TARGET DURATION');
260 equal(data.allowCache, "NO", 'acceptable ALLOW CACHE');
261 equal(data.isPlaylist, true, 'data is parsed as a PLAYLIST as expected');
262 equal(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE');
263 equal(data.mediaItems.length, 16, 'acceptable mediaItem count');
264 equal(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct');
265 equal(data.totalDuration, -1, "ZEN TOTAL DURATION is unknown as expected");
266 equal(data.hasEndTag, true, 'should have ENDLIST tag');
267 });
268
269 module('brightcove playlist', {
270 setup: function() {
271 m3u8parser = new window.videojs.hls.M3U8Parser();
246 } 272 }
247 }); 273 });
248 274
249 test('should create my parser', function() {
250 ok(m3u8parser != undefined);
251 }
252 );
253
254 test('should successfully parse manifest data', function() {
255 var parsedData = m3u8parser.parse(window.playlistData);
256 ok(parsedData);
257 }
258 );
259
260 test('test for expected results', function() {
261 var data = m3u8parser.parse(window.playlistData);
262
263 notEqual(data, null, 'data is not NULL');
264 equal(data.invalidReasons.length, 0,'data has 0 invalid reasons');
265 equal(data.hasValidM3UTag, true, 'data has valid EXTM3U');
266 equal(data.targetDuration, 10, 'data has correct TARGET DURATION');
267 equal(data.allowCache, "NO", 'acceptable ALLOW CACHE');
268 equal(data.isPlaylist, true, 'data is parsed as a PLAYLIST as expected');
269 equal(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE');
270 equal(data.mediaItems.length, 16, 'acceptable mediaItem count');
271 equal(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct');
272 equal(data.totalDuration, -1, "ZEN TOTAL DURATION is unknown as expected");
273 equal(data.hasEndTag, true, 'should have ENDLIST tag');
274 }
275 );
276
277 module('brightcove playlist', {
278 setup: function() {
279 m3u8parser = new window.videojs.hls.M3U8Parser();
280 }
281 });
282
283 test('should parse a brightcove manifest data', function() {
284 var data = m3u8parser.parse(window.brightcove_playlist_data);
285
286 ok(data);
287 equal(data.playlistItems.length, 4, 'Has correct rendition count');
288 equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct' );
289 equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct' );
290 equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct' );
291 equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct' );
292
293 }
294 );
295
296 module('manifest controller', {
297 setup: function() {
298 manifestController = new window.videojs.hls.ManifestController();
299 this.vjsget = vjs.get;
300 vjs.get = function(url, success, error){
301 console.log(url);
302 success(window.brightcove_playlist_data);
303 };
304 },
305 teardown: function() {
306 vjs.get = this.vjsget;
307 }
308 });
309
310 test('should create', function() {
311 ok(manifestController);
312 });
313
314 test('should return a parsed object', function() {
315 var data = manifestController.parseManifest(window.brightcove_playlist_data);
316
317 ok(data);
318
319 equal(data.playlistItems.length, 4, 'Has correct rendition count');
320 equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct' );
321 equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct' );
322 equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct' );
323 equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct' );
324 })
325
326 test('should get a manifest from hermes', function() {
327 var hermesUrl = "http://localhost:7070/test/basic-playback/brightcove/16x9-master.m3u8";
328
329 manifestController.loadManifest(
330 hermesUrl,
331 function(responseData){
332 ok(true);
333 },
334 function(errorData){
335 console.log('got error data');
336 },
337 function(updateData){
338 console.log('got update data');
339 }
340 )
341 });
342
343 module('segment controller', {
344 setup: function() {
345 segmentController = new window.videojs.hls.SegmentController();
346 this.vjsget = vjs.get;
347 vjs.get = function(url, success, error){
348 console.log('load segment url', url);
349 success(window.bcSegment);
350 };
351 },
352 teardown: function() {
353 vjs.get = this.vjsget;
354 }
355 });
356
357 test('should get a segment data', function() {
358 ok(true);
359 var hermesUrl = "http://localhost:7070/test/ts-files/brightcove/s-1.ts";
360
361 segmentController.loadSegment(
362 hermesUrl,
363 function(responseData){
364 console.log('got response from segment controller');
365 ok(true);
366
367 },
368 function(errorData){
369 console.log('got error data');
370 },
371 function(updateData){
372 console.log('got update data');
373 }
374 )
375 }
376 )
377
378 test('bandwidth calulation test', function() {
379 var multiSecondData = segmentController.calculateThroughput(10000,1000,2000);
380 var subSecondData = segmentController.calculateThroughput(10000,1000,1500);
381 equal(multiSecondData, 80000, 'MULTI-Second bits per second calculation');
382 equal(subSecondData, 160000, 'SUB-Second bits per second calculation');
383
384 })
385
386 })(this);
...\ No newline at end of file ...\ No newline at end of file
275 test('should parse a brightcove manifest data', function() {
276 var data = m3u8parser.parse(window.brightcove_playlist_data);
277
278 ok(data);
279 equal(data.playlistItems.length, 4, 'Has correct rendition count');
280 equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct');
281 equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct');
282 equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct');
283 equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct');
284
285 }
286 );
287
288 module('manifest controller', {
289 setup: function() {
290 manifestController = new window.videojs.hls.ManifestController();
291 this.vjsget = window.videojs.get;
292 window.videojs.get = function(url, success) {
293 success(window.brightcove_playlist_data);
294 };
295 },
296 teardown: function() {
297 window.videojs.get = this.vjsget;
298 }
299 });
300
301 test('should create', function() {
302 ok(manifestController);
303 });
304
305 test('should return a parsed object', function() {
306 var data = manifestController.parseManifest(window.brightcove_playlist_data);
307
308 ok(data);
309 equal(data.playlistItems.length, 4, 'Has correct rendition count');
310 equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct');
311 equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct');
312 equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct');
313 equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct');
314 });
315
316 test('should get a manifest from hermes', function() {
317 manifestController.loadManifest('http://example.com/16x9-master.m3u8',
318 function(responseData) {
319 ok(responseData);
320 },
321 function() {
322 ok(false, 'does not error');
323 },
324 function() {});
325 });
326
327 module('segment controller', {
328 setup: function() {
329 segmentController = new window.videojs.hls.SegmentController();
330 this.vjsget = window.videojs.get;
331 window.videojs.get = function(url, success) {
332 success(window.bcSegment);
333 };
334 },
335 teardown: function() {
336 window.videojs.get = this.vjsget;
337 }
338 });
339
340 test('bandwidth calulation test', function() {
341 var
342 multiSecondData = segmentController.calculateThroughput(10000, 1000, 2000),
343 subSecondData = segmentController.calculateThroughput(10000, 1000, 1500);
344 equal(multiSecondData, 80000, 'MULTI-Second bits per second calculation');
345 equal(subSecondData, 160000, 'SUB-Second bits per second calculation');
346 });
347 })(this);
......
1 <?xml version="1.0" encoding="UTF-8"?>
2 <module type="WEB_MODULE" version="4">
3 <component name="NewModuleRootManager" inherit-compiler-output="true">
4 <exclude-output />
5 <content url="file://$MODULE_DIR$" />
6 <orderEntry type="sourceFolder" forTests="false" />
7 </component>
8 </module>
9