Merge pull request #4 from dlapalomento/feature/m3u8s
HLS playback support cleanup
Showing
19 changed files
with
716 additions
and
709 deletions
... | @@ -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()) { | ... | ... |
src/hls-playback-controller.js
0 → 100644
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; | ... | ... |
... | @@ -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); | ... | ... |
video-js-hls.iml
deleted
100644 → 0
-
Please register or sign in to post a comment