Fix whitespace
All files should use 2-space indenting.
Showing
9 changed files
with
959 additions
and
956 deletions
1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
2 | <html> | 2 | <html> |
3 | <head> | 3 | <head> |
4 | <meta charset="utf-8"> | 4 | <meta charset="utf-8"> |
5 | <title>video.js HLS Plugin Example</title> | 5 | <title>video.js HLS Plugin Example</title> |
6 | 6 | ||
7 | <link href="node_modules/video.js/video-js.css" rel="stylesheet"> | 7 | <link href="node_modules/video.js/video-js.css" rel="stylesheet"> |
8 | 8 | ||
... | @@ -52,7 +52,7 @@ | ... | @@ -52,7 +52,7 @@ |
52 | 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'; |
53 | video = videojs('video',{},function(){ | 53 | video = videojs('video',{},function(){ |
54 | this.playbackController = new window.videojs.hls.HLSPlaybackController(this); | 54 | this.playbackController = new window.videojs.hls.HLSPlaybackController(this); |
55 | this.playbackController.loadManifest("http://localhost:7070/test/basic-playback/zencoder/gogo/manifest.m3u8",function(data){console.log(data)}); | 55 | this.playbackController.loadManifest('http://localhost:7070/test/basic-playback/zencoder/gogo/manifest.m3u8',function(data){console.log(data)}); |
56 | }); | 56 | }); |
57 | </script> | 57 | </script> |
58 | 58 | ... | ... |
1 | (function (window) { | 1 | (function (window) { |
2 | window.videojs.hls.HLSPlaybackController = function (vjsPlayerReference) { | 2 | window.videojs.hls.HLSPlaybackController = function (vjsPlayerReference) { |
3 | var ManifestController = window.videojs.hls.ManifestController; | 3 | var ManifestController = window.videojs.hls.ManifestController; |
4 | var SegmentController = window.videojs.hls.SegmentController; | 4 | var SegmentController = window.videojs.hls.SegmentController; |
5 | var MediaSource = window.videojs.MediaSource; | 5 | var MediaSource = window.videojs.MediaSource; |
6 | var SegmentParser = window.videojs.hls.SegmentParser; | 6 | var SegmentParser = window.videojs.hls.SegmentParser; |
7 | var M3U8 = window.videojs.hls.M3U8; | 7 | var M3U8 = window.videojs.hls.M3U8; |
8 | 8 | ||
9 | var self = this; | 9 | var self = this; |
10 | 10 | ||
11 | self.player = vjsPlayerReference; | 11 | self.player = vjsPlayerReference; |
12 | self.mediaSource = new MediaSource(); | 12 | self.mediaSource = new MediaSource(); |
13 | self.parser = new SegmentParser(); | 13 | self.parser = new SegmentParser(); |
14 | 14 | ||
15 | self.manifestController = null; | 15 | self.manifestController = null; |
16 | self.segmentController = null; | 16 | self.segmentController = null; |
17 | self.manifestLoaded = false; | 17 | self.manifestLoaded = false; |
18 | self.currentSegment = 0; | 18 | self.currentSegment = 0; |
19 | self.currentManifest = null; | 19 | self.currentManifest = null; |
20 | self.currentPlaylist = null; | 20 | self.currentPlaylist = null; |
21 | self.currentRendition = null; | 21 | self.currentRendition = null; |
22 | 22 | ||
23 | // Register Externall Callbacks | 23 | // Register Externall Callbacks |
24 | self.manifestLoadCompleteCallback; | 24 | self.manifestLoadCompleteCallback; |
25 | 25 | ||
26 | self.player.on('timeupdate', function () { | 26 | self.player.on('timeupdate', function () { |
27 | console.log(self.player.currentTime()); | 27 | console.log(self.player.currentTime()); |
28 | }); | 28 | }); |
29 | 29 | ||
30 | self.player.on('onsrcchange', function () { | 30 | self.player.on('onsrcchange', function () { |
31 | console.log('src change', self.player.currentSrc()); | 31 | console.log('src change', self.player.currentSrc()); |
32 | //if src.url.m3u8 -- loadManifest.url | 32 | //if src.url.m3u8 -- loadManifest.url |
33 | }); | 33 | }); |
34 | 34 | ||
35 | self.rendition = function (rendition) { | 35 | self.rendition = function (rendition) { |
36 | self.currentRendition = rendition; | 36 | self.currentRendition = rendition; |
37 | self.loadManifest(self.currentRendition.url, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update); | 37 | self.loadManifest(self.currentRendition.url, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update); |
38 | }; | 38 | }; |
39 | 39 | ||
40 | self.loadManifest = function (manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback) { | 40 | self.loadManifest = function (manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback) { |
41 | self.mediaSource.addEventListener('sourceopen', function (event) { | 41 | self.mediaSource.addEventListener('sourceopen', function (event) { |
42 | console.log('source open here'); | 42 | console.log('source open here'); |
43 | // feed parsed bytes into the player | 43 | // feed parsed bytes into the player |
44 | self.sourceBuffer = self.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); | 44 | self.sourceBuffer = self.mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); |
45 | 45 | ||
46 | self.parser = new SegmentParser(); | 46 | self.parser = new SegmentParser(); |
47 | 47 | ||
48 | self.sourceBuffer.appendBuffer(self.parser.getFlvHeader(), video); | 48 | self.sourceBuffer.appendBuffer(self.parser.getFlvHeader(), video); |
49 | 49 | ||
50 | if( onDataCallback ) | 50 | if( onDataCallback ) |
51 | { | 51 | { |
52 | self.manifestLoadCompleteCallback = onDataCallback; | 52 | self.manifestLoadCompleteCallback = onDataCallback; |
53 | } | 53 | } |
54 | 54 | ||
55 | self.manifestController = new ManifestController(); | 55 | self.manifestController = new ManifestController(); |
56 | self.manifestController.loadManifest(manifestUrl, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update); | 56 | self.manifestController.loadManifest(manifestUrl, self.onM3U8LoadComplete, self.onM3U8LoadError, self.onM3U8Update); |
57 | 57 | ||
58 | }, false); | 58 | }, false); |
59 | 59 | ||
60 | self.player.src({ | 60 | self.player.src({ |
61 | src: videojs.URL.createObjectURL(self.mediaSource), | 61 | src: videojs.URL.createObjectURL(self.mediaSource), |
62 | type: "video/flv" | 62 | type: "video/flv" |
63 | }); | 63 | }); |
64 | }; | 64 | }; |
65 | 65 | ||
66 | self.onM3U8LoadComplete = function (m3u8) { | 66 | self.onM3U8LoadComplete = function (m3u8) { |
67 | if (m3u8.invalidReasons.length == 0) { | 67 | if (m3u8.invalidReasons.length == 0) { |
68 | if(m3u8.isPlaylist) | 68 | if(m3u8.isPlaylist) |
69 | { | 69 | { |
70 | self.currentPlaylist = m3u8; | 70 | self.currentPlaylist = m3u8; |
71 | self.rendition(self.currentPlaylist.playlistItems[0]); | 71 | self.rendition(self.currentPlaylist.playlistItems[0]); |
72 | } else { | 72 | } else { |
73 | self.currentManifest = m3u8; | 73 | self.currentManifest = m3u8; |
74 | self.manifestLoaded = true; | 74 | self.manifestLoaded = true; |
75 | 75 | ||
76 | self.loadSegment(self.currentManifest.mediaItems[0]); | 76 | self.loadSegment(self.currentManifest.mediaItems[0]); |
77 | 77 | ||
78 | if(self.manifestLoadCompleteCallback) | 78 | if(self.manifestLoadCompleteCallback) |
79 | { | 79 | { |
80 | self.manifestLoadCompleteCallback(m3u8); | 80 | self.manifestLoadCompleteCallback(m3u8); |
81 | } | 81 | } |
82 | } | 82 | } |
83 | } | 83 | } |
84 | }; | 84 | }; |
85 | 85 | ||
86 | self.onM3U8LoadError = function (error) { | 86 | self.onM3U8LoadError = function (error) { |
87 | 87 | ||
88 | }; | 88 | }; |
89 | 89 | ||
90 | self.onM3U8Update = function (m3u8) { | 90 | self.onM3U8Update = function (m3u8) { |
91 | 91 | ||
92 | }; | 92 | }; |
93 | 93 | ||
94 | self.loadSegment = function(segment) { | 94 | self.loadSegment = function(segment) { |
95 | self.segmentController = new SegmentController(); | 95 | self.segmentController = new SegmentController(); |
96 | self.segmentController.loadSegment(segment.url, self.onSegmentLoadComplete, self.onSegmentLoadError); | 96 | self.segmentController.loadSegment(segment.url, self.onSegmentLoadComplete, self.onSegmentLoadError); |
97 | 97 | ||
98 | }; | 98 | }; |
99 | 99 | ||
100 | self.onSegmentLoadComplete = function (segment) { | 100 | self.onSegmentLoadComplete = function (segment) { |
101 | self.parser.parseSegmentBinaryData(segment.binaryData); | 101 | self.parser.parseSegmentBinaryData(segment.binaryData); |
102 | 102 | ||
103 | while (self.parser.tagsAvailable()) { | 103 | while (self.parser.tagsAvailable()) { |
104 | self.sourceBuffer.appendBuffer(self.parser.getNextTag().bytes, self.player); | 104 | self.sourceBuffer.appendBuffer(self.parser.getNextTag().bytes, self.player); |
105 | }; | 105 | }; |
106 | 106 | ||
107 | console.log('load another',self.currentSegment,self.currentManifest.mediaItems.length); | 107 | console.log('load another',self.currentSegment,self.currentManifest.mediaItems.length); |
108 | 108 | ||
109 | if(self.currentSegment < self.currentManifest.mediaItems.length-1) | 109 | if(self.currentSegment < self.currentManifest.mediaItems.length-1) |
110 | { | 110 | { |
111 | console.log('load another'); | 111 | console.log('load another'); |
112 | self.loadNextSegment(); | 112 | self.loadNextSegment(); |
113 | } | 113 | } |
114 | }; | 114 | }; |
115 | 115 | ||
116 | self.loadNextSegment = function () { | 116 | self.loadNextSegment = function () { |
117 | self.currentSegment++; | 117 | self.currentSegment++; |
118 | self.loadSegment(self.currentManifest.mediaItems[self.currentSegment]); | 118 | self.loadSegment(self.currentManifest.mediaItems[self.currentSegment]); |
119 | } | 119 | } |
120 | 120 | ||
121 | self.onSegmentLoadError = function (error) { | 121 | self.onSegmentLoadError = function (error) { |
122 | 122 | ||
123 | }; | 123 | }; |
124 | 124 | ||
125 | }; | ||
126 | })(this); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
125 | }; | ||
126 | })(this); | ... | ... |
1 | (function(window) { | 1 | (function(window) { |
2 | var M3U8 = window.videojs.hls.M3U8; | 2 | var M3U8 = window.videojs.hls.M3U8; |
3 | |||
4 | window.videojs.hls.M3U8Parser = function() { | ||
5 | |||
6 | var self = this; | ||
7 | self.directory; | ||
8 | |||
9 | var tagTypes = window.videojs.hls.m3u8TagType; | ||
10 | var lines = []; | ||
11 | var data; | ||
12 | |||
13 | self.getTagType = function( lineData ) { | ||
14 | for ( var s in tagTypes ) | ||
15 | { | ||
16 | if (lineData.indexOf(tagTypes[s]) == 0) | ||
17 | { | ||
18 | return tagTypes[s]; | ||
19 | } | ||
20 | } | ||
21 | } | ||
22 | |||
23 | self.getTagValue = function ( lineData ) { | ||
24 | for ( var s in tagTypes ) | ||
25 | { | ||
26 | if (lineData.indexOf(tagTypes[s]) == 0) | ||
27 | { | ||
28 | return lineData.substr(tagTypes[s].length); | ||
29 | } | ||
30 | } | ||
31 | } | ||
32 | |||
33 | self.parse = function( rawDataString ) { | ||
34 | data = new M3U8(); | ||
35 | |||
36 | if(self.directory) | ||
37 | { | ||
38 | data.directory = self.directory; | ||
39 | } | ||
40 | |||
41 | if( rawDataString != undefined && rawDataString.toString().length > 0 ) | ||
42 | { | ||
43 | lines = rawDataString.split('\n'); | ||
44 | |||
45 | lines.forEach( | ||
46 | function(value,index) { | ||
47 | switch( self.getTagType(value) ) | ||
48 | { | ||
49 | case tagTypes.EXTM3U: | ||
50 | data.hasValidM3UTag = (index == 0); | ||
51 | if(!data.hasValidM3UTag) | ||
52 | { | ||
53 | data.invalidReasons.push("Invalid EXTM3U Tag"); | ||
54 | } | ||
55 | break; | ||
56 | |||
57 | case tagTypes.DISCONTINUITY: | ||
58 | break; | ||
59 | |||
60 | case tagTypes.PLAYLIST_TYPE: | ||
61 | if(self.getTagValue(value) == "VOD" || self.getTagValue(value) == "EVENT") | ||
62 | { | ||
63 | data.playlistType = self.getTagValue(value); | ||
64 | data.isPlaylist = true; | ||
65 | } else { | ||
66 | data.invalidReasons.push("Invalid Playlist Type Value"); | ||
67 | } | ||
68 | break; | ||
69 | |||
70 | case tagTypes.EXTINF: | ||
71 | var segment = {url: "unknown", byterange: -1, targetDuration: data.targetDuration }; | ||
72 | |||
73 | if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE ) | ||
74 | { | ||
75 | segment.byterange = self.getTagValue(lines[index+1]).split('@'); | ||
76 | segment.url = lines[index+2]; | ||
77 | } else | ||
78 | { | ||
79 | segment.url = lines[index+1]; | ||
80 | } | ||
81 | |||
82 | if(segment.url.indexOf("http")===-1 && self.directory) | ||
83 | { | ||
84 | if(data.directory[data.directory.length-1] === segment.url[0] && segment.url[0] === "/") | ||
85 | { | ||
86 | segment.url = segment.url.substr(1); | ||
87 | } | ||
88 | segment.url = self.directory + segment.url; | ||
89 | } | ||
90 | |||
91 | data.mediaItems.push(segment); | ||
92 | |||
93 | break; | ||
94 | |||
95 | case tagTypes.STREAM_INF: | ||
96 | var rendition = {}; | ||
97 | var attributes = value.substr(tagTypes.STREAM_INF.length).split(','); | ||
3 | 98 | ||
4 | window.videojs.hls.M3U8Parser = function() { | 99 | attributes.forEach(function(attr_value,attr_index) { |
100 | if(isNaN(attr_value.split('=')[1])){ | ||
101 | rendition[attr_value.split('=')[0].toLowerCase()] = attr_value.split('=')[1]; | ||
5 | 102 | ||
6 | var self = this; | 103 | if(rendition[attr_value.split('=')[0].toLowerCase()].split('x').length = 2) |
7 | self.directory; | 104 | { |
105 | rendition.resolution = { | ||
106 | width: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[0]), | ||
107 | height: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[1]) | ||
108 | } | ||
109 | } | ||
8 | 110 | ||
9 | var tagTypes = window.videojs.hls.m3u8TagType; | 111 | } else { |
10 | var lines = []; | 112 | rendition[attr_value.split('=')[0].toLowerCase()] = Number(attr_value.split('=')[1]); |
11 | var data; | 113 | } |
114 | }); | ||
12 | 115 | ||
13 | self.getTagType = function( lineData ) { | 116 | |
14 | for ( var s in tagTypes ) | 117 | if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE ) |
15 | { | 118 | { |
16 | if (lineData.indexOf(tagTypes[s]) == 0) | 119 | rendition.byterange = self.getTagValue(lines[index+1]).split('@'); |
17 | { | 120 | rendition.url = lines[index+2]; |
18 | return tagTypes[s]; | 121 | } else |
19 | } | 122 | { |
20 | } | 123 | rendition.url = lines[index+1]; |
21 | } | 124 | } |
22 | 125 | ||
23 | self.getTagValue = function ( lineData ) { | 126 | data.isPlaylist = true; |
24 | for ( var s in tagTypes ) | 127 | data.playlistItems.push(rendition); |
25 | { | 128 | break; |
26 | if (lineData.indexOf(tagTypes[s]) == 0) | 129 | |
27 | { | 130 | case tagTypes.TARGETDURATION: |
28 | return lineData.substr(tagTypes[s].length); | 131 | data.targetDuration = Number(self.getTagValue(value).split(',')[0]); |
29 | } | 132 | break; |
30 | } | 133 | |
134 | case tagTypes.ZEN_TOTAL_DURATION: | ||
135 | data.totalDuration = Number(self.getTagValue(value)); | ||
136 | break; | ||
137 | |||
138 | case tagTypes.VERSION: | ||
139 | data.version = Number(self.getTagValue(value)); | ||
140 | break; | ||
141 | |||
142 | case tagTypes.MEDIA_SEQUENCE: | ||
143 | data.mediaSequence = parseInt(self.getTagValue(value)); | ||
144 | break; | ||
145 | |||
146 | case tagTypes.ALLOW_CACHE: | ||
147 | if(self.getTagValue(value) == "YES" || self.getTagValue(value) == "NO") | ||
148 | { | ||
149 | data.allowCache = self.getTagValue(value); | ||
150 | } else { | ||
151 | data.invalidReasons.push("Invalid ALLOW_CACHE Value"); | ||
31 | } | 152 | } |
153 | break; | ||
154 | |||
155 | case tagTypes.ENDLIST: | ||
156 | data.hasEndTag = true; | ||
157 | break; | ||
158 | } | ||
159 | } | ||
160 | ) | ||
161 | } else { | ||
162 | data.invalidReasons.push("Empty Manifest"); | ||
163 | } | ||
164 | |||
165 | return data; | ||
32 | 166 | ||
33 | self.parse = function( rawDataString ) { | ||
34 | data = new M3U8(); | ||
35 | |||
36 | if(self.directory) | ||
37 | { | ||
38 | data.directory = self.directory; | ||
39 | } | ||
40 | |||
41 | if( rawDataString != undefined && rawDataString.toString().length > 0 ) | ||
42 | { | ||
43 | lines = rawDataString.split('\n'); | ||
44 | |||
45 | lines.forEach( | ||
46 | function(value,index) { | ||
47 | switch( self.getTagType(value) ) | ||
48 | { | ||
49 | case tagTypes.EXTM3U: | ||
50 | data.hasValidM3UTag = (index == 0); | ||
51 | if(!data.hasValidM3UTag) | ||
52 | { | ||
53 | data.invalidReasons.push("Invalid EXTM3U Tag"); | ||
54 | } | ||
55 | break; | ||
56 | |||
57 | case tagTypes.DISCONTINUITY: | ||
58 | break; | ||
59 | |||
60 | case tagTypes.PLAYLIST_TYPE: | ||
61 | if(self.getTagValue(value) == "VOD" || self.getTagValue(value) == "EVENT") | ||
62 | { | ||
63 | data.playlistType = self.getTagValue(value); | ||
64 | data.isPlaylist = true; | ||
65 | } else { | ||
66 | data.invalidReasons.push("Invalid Playlist Type Value"); | ||
67 | } | ||
68 | break; | ||
69 | |||
70 | case tagTypes.EXTINF: | ||
71 | var segment = {url: "unknown", byterange: -1, targetDuration: data.targetDuration }; | ||
72 | |||
73 | if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE ) | ||
74 | { | ||
75 | segment.byterange = self.getTagValue(lines[index+1]).split('@'); | ||
76 | segment.url = lines[index+2]; | ||
77 | } else | ||
78 | { | ||
79 | segment.url = lines[index+1]; | ||
80 | } | ||
81 | |||
82 | if(segment.url.indexOf("http")===-1 && self.directory) | ||
83 | { | ||
84 | if(data.directory[data.directory.length-1] === segment.url[0] && segment.url[0] === "/") | ||
85 | { | ||
86 | segment.url = segment.url.substr(1); | ||
87 | } | ||
88 | segment.url = self.directory + segment.url; | ||
89 | } | ||
90 | |||
91 | data.mediaItems.push(segment); | ||
92 | |||
93 | break; | ||
94 | |||
95 | case tagTypes.STREAM_INF: | ||
96 | var rendition = {}; | ||
97 | var attributes = value.substr(tagTypes.STREAM_INF.length).split(','); | ||
98 | |||
99 | attributes.forEach(function(attr_value,attr_index) { | ||
100 | if(isNaN(attr_value.split('=')[1])){ | ||
101 | rendition[attr_value.split('=')[0].toLowerCase()] = attr_value.split('=')[1]; | ||
102 | |||
103 | if(rendition[attr_value.split('=')[0].toLowerCase()].split('x').length = 2) | ||
104 | { | ||
105 | rendition.resolution = { | ||
106 | width: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[0]), | ||
107 | height: Number(rendition[attr_value.split('=')[0].toLowerCase()].split('x')[1]) | ||
108 | } | ||
109 | } | ||
110 | |||
111 | } else { | ||
112 | rendition[attr_value.split('=')[0].toLowerCase()] = Number(attr_value.split('=')[1]); | ||
113 | } | ||
114 | }); | ||
115 | |||
116 | |||
117 | if( self.getTagType(lines[index+1]) == tagTypes.BYTERANGE ) | ||
118 | { | ||
119 | rendition.byterange = self.getTagValue(lines[index+1]).split('@'); | ||
120 | rendition.url = lines[index+2]; | ||
121 | } else | ||
122 | { | ||
123 | rendition.url = lines[index+1]; | ||
124 | } | ||
125 | |||
126 | data.isPlaylist = true; | ||
127 | data.playlistItems.push(rendition); | ||
128 | break; | ||
129 | |||
130 | case tagTypes.TARGETDURATION: | ||
131 | data.targetDuration = Number(self.getTagValue(value).split(',')[0]); | ||
132 | break; | ||
133 | |||
134 | case tagTypes.ZEN_TOTAL_DURATION: | ||
135 | data.totalDuration = Number(self.getTagValue(value)); | ||
136 | break; | ||
137 | |||
138 | case tagTypes.VERSION: | ||
139 | data.version = Number(self.getTagValue(value)); | ||
140 | break; | ||
141 | |||
142 | case tagTypes.MEDIA_SEQUENCE: | ||
143 | data.mediaSequence = parseInt(self.getTagValue(value)); | ||
144 | break; | ||
145 | |||
146 | case tagTypes.ALLOW_CACHE: | ||
147 | if(self.getTagValue(value) == "YES" || self.getTagValue(value) == "NO") | ||
148 | { | ||
149 | data.allowCache = self.getTagValue(value); | ||
150 | } else { | ||
151 | data.invalidReasons.push("Invalid ALLOW_CACHE Value"); | ||
152 | } | ||
153 | break; | ||
154 | |||
155 | case tagTypes.ENDLIST: | ||
156 | data.hasEndTag = true; | ||
157 | break; | ||
158 | } | ||
159 | } | ||
160 | ) | ||
161 | } else { | ||
162 | data.invalidReasons.push("Empty Manifest"); | ||
163 | } | ||
164 | |||
165 | return data; | ||
166 | |||
167 | }; | ||
168 | }; | 167 | }; |
168 | }; | ||
169 | 169 | ||
170 | })(this); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
170 | })(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 V8: http://tools.ietf.org/html/draft-pantos-http-live-streaming-08 | ||
5 | */ | ||
6 | |||
7 | /** | ||
8 | * Identifies manifest as Extended M3U - must be present on first line! | ||
9 | */ | ||
10 | EXTM3U:"#EXTM3U", | ||
11 | |||
12 | /** | ||
13 | * Specifies duration. | ||
14 | * Syntax: #EXTINF:<duration>,<title> | ||
15 | * Example: #EXTINF:10, | ||
16 | */ | ||
17 | EXTINF:"#EXTINF:", | ||
18 | |||
19 | /** | ||
20 | * Indicates that a media segment is a sub-range of the resource identified by its media URI. | ||
21 | * Syntax: #EXT-X-BYTERANGE:<n>[@o] | ||
22 | */ | ||
23 | BYTERANGE:"#EXT-X-BYTERANGE:", | ||
24 | |||
25 | /** | ||
26 | * Specifies the maximum media segment duration - applies to entire manifest. | ||
27 | * Syntax: #EXT-X-TARGETDURATION:<s> | ||
28 | * Example: #EXT-X-TARGETDURATION:10 | ||
29 | */ | ||
30 | TARGETDURATION:"#EXT-X-TARGETDURATION:", | ||
31 | |||
32 | /** | ||
33 | * Specifies the sequence number of the first URI in a manifest. | ||
34 | * Syntax: #EXT-X-MEDIA-SEQUENCE:<i> | ||
35 | * Example: #EXT-X-MEDIA-SEQUENCE:50 | ||
36 | */ | ||
37 | MEDIA_SEQUENCE:"#EXT-X-MEDIA-SEQUENCE:", | ||
38 | |||
39 | /** | ||
40 | * Specifies a method by which media segments can be decrypted, if encryption is present. | ||
41 | * Syntax: #EXT-X-KEY:<attribute-list> | ||
42 | * Note: This is likely irrelevant in the context of the Flash Player. | ||
43 | */ | ||
44 | KEY:"#EXT-X-KEY:", | ||
45 | |||
46 | /** | ||
47 | * Associates the first sample of a media segment with an absolute date and/or time. Applies only to the next media URI. | ||
48 | * Syntax: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ> | ||
49 | * Example: #EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00 | ||
50 | */ | ||
51 | PROGRAM_DATE_TIME:"#EXT-X-PROGRAM-DATE-TIME:", | ||
52 | |||
53 | /** | ||
54 | * Indicates whether the client MAY or MUST NOT cache downloaded media segments for later replay. | ||
55 | * Syntax: #EXT-X-ALLOW-CACHE:<YES|NO> | ||
56 | * Note: This is likely irrelevant in the context of the Flash Player. | ||
57 | */ | ||
58 | ALLOW_CACHE:"#EXT-X-ALLOW_CACHE:", | ||
59 | |||
60 | /** | ||
61 | * Provides mutability information about the manifest. | ||
62 | * Syntax: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD> | ||
63 | */ | ||
64 | PLAYLIST_TYPE:"#EXT-X-PLAYLIST-TYPE:", | ||
65 | |||
66 | /** | ||
67 | * Indicates that no more media segments will be added to the manifest. May occur ONCE, anywhere in the mainfest file. | ||
68 | */ | ||
69 | ENDLIST:"#EXT-X-ENDLIST", | ||
70 | |||
71 | /** | ||
72 | * Used to relate Playlists that contain alternative renditions of the same content. | ||
73 | * Syntax: #EXT-X-MEDIA:<attribute-list> | ||
74 | */ | ||
75 | MEDIA:"#EXT-X-MEDIA:", | ||
76 | |||
77 | /** | ||
78 | * Identifies a media URI as a Playlist file containing a multimedia presentation and provides information about that presentation. | ||
79 | * Syntax: #EXT-X-STREAM-INF:<attribute-list> | ||
80 | * <URI> | ||
81 | */ | ||
82 | STREAM_INF:"#EXT-X-STREAM-INF:", | ||
83 | |||
84 | /** | ||
85 | * Indicates an encoding discontinuity between the media segment that follows it and the one that preceded it. | ||
86 | */ | ||
87 | DISCONTINUITY:"#EXT-X-DISCONTINUITY", | ||
88 | |||
89 | /** | ||
90 | * Indicates that each media segment in the manifest describes a single I-frame. | ||
91 | */ | ||
92 | I_FRAMES_ONLY:"#EXT-X-I-FRAMES-ONLY", | ||
93 | |||
94 | /** | ||
95 | * 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. | ||
96 | * Syntax: #EXT-X-I-FRAME-STREAM-INF:<attribute-list> | ||
97 | */ | ||
98 | I_FRAME_STREAM_INF:"#EXT-X-I-FRAME-STREAM-INF:", | ||
99 | |||
100 | /** | ||
101 | * Indicates the compatibility version of the Playlist file. | ||
102 | * Syntax: #EXT-X-VERSION:<n> | ||
103 | */ | ||
104 | VERSION:"#EXT-X-VERSION:", | ||
105 | |||
106 | /** | ||
107 | * Indicates the total duration as reported by Zencoder. | ||
108 | * Syntax: #ZEN-TOTAL-DURATION:<n> | ||
109 | */ | ||
110 | ZEN_TOTAL_DURATION: "#ZEN-TOTAL-DURATION:" | ||
111 | |||
112 | }; | ||
113 | })(this); | ... | ... |
1 | (function(window) { | 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); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
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 M3U8 = window.videojs.hls.M3U8; |
3 | var M3U8Parser = window.videojs.hls.M3U8Parser; | 3 | var M3U8Parser = window.videojs.hls.M3U8Parser; |
4 | 4 | ||
5 | window.videojs.hls.ManifestController = function () { | 5 | window.videojs.hls.ManifestController = function () { |
6 | var self = this; | 6 | var self = this; |
7 | 7 | ||
8 | self.parser; | 8 | self.parser; |
9 | self.data; | 9 | self.data; |
10 | self.url; | 10 | self.url; |
11 | 11 | ||
12 | self.onDataCallback; | 12 | self.onDataCallback; |
13 | self.onErrorCallback; | 13 | self.onErrorCallback; |
14 | self.onUpdateCallback; | 14 | self.onUpdateCallback; |
15 | 15 | ||
16 | self.loadManifest = function (manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback) { | 16 | self.loadManifest = function (manifestUrl, onDataCallback, onErrorCallback, onUpdateCallback) { |
17 | self.url = manifestUrl; | 17 | self.url = manifestUrl; |
18 | 18 | ||
19 | if (onDataCallback) { | 19 | if (onDataCallback) { |
20 | self.onDataCallback = onDataCallback; | 20 | self.onDataCallback = onDataCallback; |
21 | } | 21 | } |
22 | if (onErrorCallback) { | 22 | if (onErrorCallback) { |
23 | self.onErrorCallback = onErrorCallback; | 23 | self.onErrorCallback = onErrorCallback; |
24 | } | 24 | } |
25 | 25 | ||
26 | if (onUpdateCallback) { | 26 | if (onUpdateCallback) { |
27 | self.onUpdateCallback = onUpdateCallback; | 27 | self.onUpdateCallback = onUpdateCallback; |
28 | } | 28 | } |
29 | 29 | ||
30 | vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError); | 30 | vjs.get(manifestUrl, self.onManifestLoadComplete, self.onManifestLoadError); |
31 | }; | 31 | }; |
32 | 32 | ||
33 | self.parseManifest = function (dataAsString) { | 33 | self.parseManifest = function (dataAsString) { |
34 | self.parser = new M3U8Parser(); | 34 | self.parser = new M3U8Parser(); |
35 | self.parser.directory = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(self.url).slice(1)[1]; | 35 | self.parser.directory = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(self.url).slice(1)[1]; |
36 | self.data = self.parser.parse(dataAsString); | 36 | self.data = self.parser.parse(dataAsString); |
37 | 37 | ||
38 | return self.data; | 38 | return self.data; |
39 | }; | 39 | }; |
40 | 40 | ||
41 | self.onManifestLoadComplete = function (response) { | 41 | self.onManifestLoadComplete = function (response) { |
42 | var output = self.parseManifest(response); | 42 | var output = self.parseManifest(response); |
43 | 43 | ||
44 | if (self.onDataCallback != undefined) { | 44 | if (self.onDataCallback != undefined) { |
45 | self.onDataCallback(output); | 45 | self.onDataCallback(output); |
46 | } | 46 | } |
47 | }; | 47 | }; |
48 | 48 | ||
49 | self.onManifestLoadError = function (err) { | 49 | self.onManifestLoadError = function (err) { |
50 | if (self.onErrorCallback != undefined) { | 50 | if (self.onErrorCallback != undefined) { |
51 | self.onErrorCallback((err != undefined) ? err : null); | 51 | self.onErrorCallback((err != undefined) ? err : null); |
52 | } | 52 | } |
53 | }; | 53 | }; |
54 | } | 54 | } |
55 | })(this); | 55 | })(this); | ... | ... |
1 | (function(window) { | 1 | (function(window) { |
2 | 2 | ||
3 | window.videojs.hls.SegmentController = function(){ | 3 | window.videojs.hls.SegmentController = function(){ |
4 | 4 | ||
5 | var self = this; | 5 | var self = this; |
6 | 6 | ||
7 | self.url; | 7 | self.url; |
8 | 8 | ||
9 | self.requestTimestamp; | 9 | self.requestTimestamp; |
10 | self.responseTimestamp; | 10 | self.responseTimestamp; |
11 | self.data; | 11 | self.data; |
12 | 12 | ||
13 | self.onDataCallback; | 13 | self.onDataCallback; |
14 | self.onErrorCallback; | 14 | self.onErrorCallback; |
15 | self.onUpdateCallback; | 15 | self.onUpdateCallback; |
16 | 16 | ||
17 | self.loadSegment = function ( segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback ) { | 17 | self.loadSegment = function ( segmentUrl, onDataCallback, onErrorCallback, onUpdateCallback ) { |
18 | self.url = segmentUrl; | 18 | self.url = segmentUrl; |
19 | self.onDataCallback = onDataCallback; | 19 | self.onDataCallback = onDataCallback; |
20 | self.onErrorCallback = onErrorCallback; | 20 | self.onErrorCallback = onErrorCallback; |
21 | self.onUpdateCallback = onUpdateCallback; | 21 | self.onUpdateCallback = onUpdateCallback; |
22 | self.requestTimestamp = new Date().getTime(); | 22 | self.requestTimestamp = new Date().getTime(); |
23 | 23 | ||
24 | var req = new XMLHttpRequest(); | 24 | var req = new XMLHttpRequest(); |
25 | req.open('GET', segmentUrl, true); | 25 | req.open('GET', segmentUrl, true); |
26 | req.responseType = 'arraybuffer'; | 26 | req.responseType = 'arraybuffer'; |
27 | req.onload = function(response) { | 27 | req.onload = function(response) { |
28 | self.onSegmentLoadComplete(new Uint8Array(req.response)); | 28 | self.onSegmentLoadComplete(new Uint8Array(req.response)); |
29 | }; | 29 | }; |
30 | 30 | ||
31 | req.send(null); | 31 | req.send(null); |
32 | 32 | ||
33 | //vjs.get(segmentUrl, self.onSegmentLoadComplete, self.onSegmentLoadError); | 33 | //vjs.get(segmentUrl, self.onSegmentLoadComplete, self.onSegmentLoadError); |
34 | }; | 34 | }; |
35 | 35 | ||
36 | self.parseSegment = function ( incomingData ) { | 36 | self.parseSegment = function ( incomingData ) { |
37 | // Add David's code later // | 37 | // Add David's code later // |
38 | console.log('got segment data', incomingData.byteLength); | 38 | console.log('got segment data', incomingData.byteLength); |
39 | 39 | ||
40 | self.data = {}; | 40 | self.data = {}; |
41 | self.data.binaryData = incomingData; | 41 | self.data.binaryData = incomingData; |
42 | self.data.url = self.url; | 42 | self.data.url = self.url; |
43 | self.data.isCached = false; | 43 | self.data.isCached = false; |
44 | self.data.requestTimestamp = self.requestTimestamp; | 44 | self.data.requestTimestamp = self.requestTimestamp; |
45 | self.data.responseTimestamp = self.responseTimestamp; | 45 | self.data.responseTimestamp = self.responseTimestamp; |
46 | self.data.byteLength = incomingData.byteLength; | 46 | self.data.byteLength = incomingData.byteLength; |
47 | self.data.isCached = ( parseInt(self.responseTimestamp - self.requestTimestamp) < 75 ); | 47 | self.data.isCached = ( parseInt(self.responseTimestamp - self.requestTimestamp) < 75 ); |
48 | self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp); | 48 | self.data.throughput = self.calculateThroughput(self.data.byteLength, self.requestTimestamp ,self.responseTimestamp); |
49 | 49 | ||
50 | return self.data; | 50 | return self.data; |
51 | }; | 51 | }; |
52 | 52 | ||
53 | self.calculateThroughput = function(dataAmount, startTime, endTime) { | 53 | self.calculateThroughput = function(dataAmount, startTime, endTime) { |
54 | return Math.round(dataAmount/(endTime-startTime)*1000)*8; | 54 | return Math.round(dataAmount/(endTime-startTime)*1000)*8; |
55 | } | 55 | } |
56 | 56 | ||
57 | self.onSegmentLoadComplete = function(response) { | 57 | self.onSegmentLoadComplete = function(response) { |
58 | self.responseTimestamp = new Date().getTime(); | 58 | self.responseTimestamp = new Date().getTime(); |
59 | 59 | ||
60 | var output = self.parseSegment(response); | 60 | var output = self.parseSegment(response); |
61 | 61 | ||
62 | if(self.onDataCallback != undefined) | 62 | if(self.onDataCallback != undefined) |
63 | { | 63 | { |
64 | self.onDataCallback(output); | 64 | self.onDataCallback(output); |
65 | } | 65 | } |
66 | }; | 66 | }; |
67 | 67 | ||
68 | self.onSegmentLoadError = function(err) { | 68 | self.onSegmentLoadError = function(err) { |
69 | if(err) | 69 | if(err) |
70 | { | 70 | { |
71 | console.log(err.message); | 71 | console.log(err.message); |
72 | } | 72 | } |
73 | 73 | ||
74 | if(self.onErrorCallback != undefined) | 74 | if(self.onErrorCallback != undefined) |
75 | { | 75 | { |
76 | onErrorCallback((err != undefined) ? err : null); | 76 | onErrorCallback((err != undefined) ? err : null); |
77 | } | 77 | } |
78 | }; | 78 | }; |
79 | } | 79 | } |
80 | })(this); | 80 | })(this); | ... | ... |
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'; | ... | ... |
1 | (function (window) { | 1 | (function (window) { |
2 | /* | ||
3 | ======== A Handy Little QUnit Reference ======== | ||
4 | http://api.qunitjs.com/ | ||
5 | |||
6 | Test methods: | ||
7 | module(name, {[setup][ ,teardown]}) | ||
8 | test(name, callback) | ||
9 | expect(numberOfAssertions) | ||
10 | stop(increment) | ||
11 | start(decrement) | ||
12 | Test assertions: | ||
13 | ok(value, [message]) | ||
14 | equal(actual, expected, [message]) | ||
15 | notEqual(actual, expected, [message]) | ||
16 | deepEqual(actual, expected, [message]) | ||
17 | notDeepEqual(actual, expected, [message]) | ||
18 | strictEqual(actual, expected, [message]) | ||
19 | notStrictEqual(actual, expected, [message]) | ||
20 | throws(block, [expected], [message]) | ||
21 | */ | ||
22 | var | ||
23 | manifestController, | ||
24 | segmentController, | ||
25 | m3u8parser, | ||
26 | parser, | ||
27 | |||
28 | expectedHeader = [ | ||
29 | 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, | ||
30 | 0x09, 0x00, 0x00, 0x00, 0x00 | ||
31 | ], | ||
32 | testAudioTag, | ||
33 | testVideoTag, | ||
34 | testScriptTag, | ||
35 | asciiFromBytes, | ||
36 | testScriptString, | ||
37 | testScriptEcmaArray; | ||
38 | |||
39 | module('environment'); | ||
40 | |||
41 | test('is sane', function () { | ||
42 | expect(1); | ||
43 | ok(true); | ||
44 | }); | ||
45 | |||
46 | module('segment parser', { | ||
47 | setup: function () { | ||
48 | parser = new window.videojs.hls.SegmentParser(); | ||
49 | } | ||
50 | }); | ||
51 | |||
52 | test('creates an flv header', function () { | ||
53 | var header = Array.prototype.slice.call(parser.getFlvHeader()); | ||
54 | ok(header, 'the header is truthy'); | ||
55 | equal(9 + 4, header.length, 'the header length is correct'); | ||
56 | equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"'); | ||
57 | equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"'); | ||
58 | equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"'); | ||
59 | |||
60 | deepEqual(expectedHeader, header, 'the rest of the header is correct'); | ||
61 | }); | ||
62 | |||
63 | test('parses the first bipbop segment', function () { | ||
64 | var tag, bytes, i; | ||
65 | parser.parseSegmentBinaryData(window.bcSegment); | ||
66 | |||
67 | ok(parser.tagsAvailable(), 'tags are available'); | ||
68 | |||
69 | console.log('h264 tags:', parser.stats.h264Tags(), | ||
70 | 'aac tags:', parser.stats.aacTags()); | ||
71 | }); | ||
72 | |||
73 | testAudioTag = function (tag) { | ||
74 | var | ||
75 | byte = tag.bytes[11], | ||
76 | format = (byte & 0xF0) >>> 4, | ||
77 | soundRate = byte & 0x03, | ||
78 | soundSize = (byte & 0x2) >>> 1, | ||
79 | soundType = byte & 0x1, | ||
80 | aacPacketType = tag.bytes[12]; | ||
81 | |||
82 | equal(10, format, 'the audio format is aac'); | ||
83 | equal(3, soundRate, 'the sound rate is 44kHhz'); | ||
84 | equal(1, soundSize, 'the sound size is 16-bit samples'); | ||
85 | equal(1, soundType, 'the sound type is stereo'); | ||
86 | |||
87 | ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type'); | ||
88 | }; | ||
89 | |||
90 | testVideoTag = function (tag) { | ||
91 | var | ||
92 | byte = tag.bytes[11], | ||
93 | frameType = (byte & 0xF0) >>> 4, | ||
94 | codecId = byte & 0x0F, | ||
95 | packetType = tag.bytes[12], | ||
96 | compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8, | ||
97 | nalHeader; | ||
98 | |||
99 | // payload starts at tag.bytes[16] | ||
100 | |||
101 | |||
102 | // XXX: I'm not sure that frame types 3-5 are invalid | ||
103 | ok(frameType === 1 || frameType === 2, | ||
104 | 'the frame type should be valid'); | ||
105 | |||
106 | equal(7, codecId, 'the codec ID is AVC for h264'); | ||
107 | ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]'); | ||
108 | if (packetType !== 1) { | ||
109 | equal(0, | ||
110 | compositionTime, | ||
111 | 'the composition time is zero for non-NALU packets'); | ||
112 | } | ||
113 | |||
114 | // TODO: the rest of the bytes are an NLU unit | ||
115 | if (packetType === 0) { | ||
116 | // AVC decoder configuration record | ||
117 | } else { | ||
118 | // NAL units | ||
119 | testNalUnit(tag.bytes.subarray(16)); | ||
120 | } | ||
121 | }; | ||
122 | |||
123 | testNalUnit = function (bytes) { | ||
124 | var | ||
125 | nalHeader = bytes[0], | ||
126 | unitType = nalHeader & 0x1F; | ||
127 | |||
128 | equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0'); | ||
129 | // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something'); | ||
130 | // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0'); | ||
131 | // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22'); | ||
132 | }; | ||
133 | |||
134 | |||
135 | asciiFromBytes = function (bytes) { | ||
136 | var | ||
137 | string = [], | ||
138 | i = bytes.byteLength; | ||
139 | |||
140 | while (i--) { | ||
141 | string[i] = String.fromCharCode(bytes[i]); | ||
142 | } | ||
143 | return string.join(''); | ||
144 | }; | ||
145 | |||
146 | testScriptString = function (tag, offset, expected) { | ||
147 | var type = tag.bytes[offset], | ||
148 | stringLength = tag.view.getUint16(offset + 1), | ||
149 | string, | ||
150 | i = expected.length; | ||
151 | |||
152 | equal(2, type, 'the script element is of string type'); | ||
153 | equal(stringLength, expected.length, 'the script string length is correct'); | ||
154 | string = asciiFromBytes(tag.bytes.subarray(offset + 3, | ||
155 | offset + 3 + stringLength)); | ||
156 | equal(expected, string, 'the string value is "' + expected + '"'); | ||
157 | }; | ||
158 | |||
159 | testScriptEcmaArray = function (tag, start) { | ||
160 | var | ||
161 | numItems = tag.view.getUint32(start), | ||
162 | i = numItems, | ||
163 | offset = start + 4, | ||
164 | length, | ||
165 | type; | ||
166 | |||
167 | while (i--) { | ||
168 | length = tag.view.getUint16(offset); | ||
169 | |||
170 | // advance offset to the property value | ||
171 | offset += 2 + length; | ||
172 | |||
173 | type = tag.bytes[offset]; | ||
174 | ok(type === 1 || type === 0, | ||
175 | 'the ecma array property value type is number or boolean'); | ||
176 | offset++; | ||
177 | if (type) { | ||
178 | // boolean | ||
179 | ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1, | ||
180 | 'the script boolean value is 0 or 1'); | ||
181 | offset++; | ||
182 | } else { | ||
183 | // number | ||
184 | ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN'); | ||
185 | offset += 8; | ||
186 | } | ||
187 | } | ||
188 | equal(tag.bytes[offset], 0, 'the property array terminator is valid'); | ||
189 | equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid'); | ||
190 | equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid'); | ||
191 | }; | ||
192 | |||
193 | testScriptTag = function (tag) { | ||
194 | testScriptString(tag, 11, 'onMetaData'); | ||
195 | |||
196 | // the onMetaData object is stored as an 'ecma array', an array with non- | ||
197 | // integer indices (i.e. a dictionary or hash-map). | ||
198 | equal(8, tag.bytes[24], 'onMetaData is of ecma array type'); | ||
199 | testScriptEcmaArray(tag, 25); | ||
200 | }; | ||
201 | |||
202 | test('the flv tags are well-formed', function () { | ||
203 | var | ||
204 | tag, | ||
205 | byte, | ||
206 | type, | ||
207 | lastTime = 0; | ||
208 | parser.parseSegmentBinaryData(window.bcSegment); | ||
209 | |||
210 | while (parser.tagsAvailable()) { | ||
211 | tag = parser.getNextTag(); | ||
212 | type = tag.bytes[0]; | ||
213 | |||
214 | // generic flv headers | ||
215 | ok(type === 8 || type === 9 || type === 18, | ||
216 | 'the type field specifies audio, video or script'); | ||
217 | |||
218 | byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8; | ||
219 | equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct'); | ||
220 | |||
221 | byte = tag.view.getUint32(5) & 0xFFFFFF00; | ||
222 | ok(byte >= lastTime, 'the timestamp for the tag is greater than zero'); | ||
223 | lastTime = byte; | ||
224 | |||
225 | // tag type-specific headers | ||
226 | ({ | ||
227 | 8: testAudioTag, | ||
228 | 9: testVideoTag, | ||
229 | 18: testScriptTag | ||
230 | })[type](tag); | ||
231 | |||
232 | // previous tag size | ||
233 | equal(tag.bytes.byteLength - 4, | ||
234 | tag.view.getUint32(tag.bytes.byteLength - 4), | ||
235 | 'the size of the previous tag is correct'); | ||
236 | } | ||
237 | }); | ||
238 | |||
239 | /* | ||
240 | M3U8 Test Suite | ||
241 | */ | ||
242 | |||
243 | module('m3u8 parser', { | ||
244 | setup: function () { | ||
245 | m3u8parser = new window.videojs.hls.M3U8Parser(); | ||
246 | } | ||
247 | }); | ||
248 | |||
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 |
2 | /* | ||
3 | ======== A Handy Little QUnit Reference ======== | ||
4 | http://api.qunitjs.com/ | ||
5 | |||
6 | Test methods: | ||
7 | module(name, {[setup][ ,teardown]}) | ||
8 | test(name, callback) | ||
9 | expect(numberOfAssertions) | ||
10 | stop(increment) | ||
11 | start(decrement) | ||
12 | Test assertions: | ||
13 | ok(value, [message]) | ||
14 | equal(actual, expected, [message]) | ||
15 | notEqual(actual, expected, [message]) | ||
16 | deepEqual(actual, expected, [message]) | ||
17 | notDeepEqual(actual, expected, [message]) | ||
18 | strictEqual(actual, expected, [message]) | ||
19 | notStrictEqual(actual, expected, [message]) | ||
20 | throws(block, [expected], [message]) | ||
21 | */ | ||
22 | var | ||
23 | manifestController, | ||
24 | segmentController, | ||
25 | m3u8parser, | ||
26 | parser, | ||
27 | |||
28 | expectedHeader = [ | ||
29 | 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, | ||
30 | 0x09, 0x00, 0x00, 0x00, 0x00 | ||
31 | ], | ||
32 | testAudioTag, | ||
33 | testVideoTag, | ||
34 | testScriptTag, | ||
35 | asciiFromBytes, | ||
36 | testScriptString, | ||
37 | testScriptEcmaArray; | ||
38 | |||
39 | module('environment'); | ||
40 | |||
41 | test('is sane', function () { | ||
42 | expect(1); | ||
43 | ok(true); | ||
44 | }); | ||
45 | |||
46 | module('segment parser', { | ||
47 | setup: function () { | ||
48 | parser = new window.videojs.hls.SegmentParser(); | ||
49 | } | ||
50 | }); | ||
51 | |||
52 | test('creates an flv header', function () { | ||
53 | var header = Array.prototype.slice.call(parser.getFlvHeader()); | ||
54 | ok(header, 'the header is truthy'); | ||
55 | equal(9 + 4, header.length, 'the header length is correct'); | ||
56 | equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"'); | ||
57 | equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"'); | ||
58 | equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"'); | ||
59 | |||
60 | deepEqual(expectedHeader, header, 'the rest of the header is correct'); | ||
61 | }); | ||
62 | |||
63 | test('parses the first bipbop segment', function () { | ||
64 | var tag, bytes, i; | ||
65 | parser.parseSegmentBinaryData(window.bcSegment); | ||
66 | |||
67 | ok(parser.tagsAvailable(), 'tags are available'); | ||
68 | |||
69 | console.log('h264 tags:', parser.stats.h264Tags(), | ||
70 | 'aac tags:', parser.stats.aacTags()); | ||
71 | }); | ||
72 | |||
73 | testAudioTag = function (tag) { | ||
74 | var | ||
75 | byte = tag.bytes[11], | ||
76 | format = (byte & 0xF0) >>> 4, | ||
77 | soundRate = byte & 0x03, | ||
78 | soundSize = (byte & 0x2) >>> 1, | ||
79 | soundType = byte & 0x1, | ||
80 | aacPacketType = tag.bytes[12]; | ||
81 | |||
82 | equal(10, format, 'the audio format is aac'); | ||
83 | equal(3, soundRate, 'the sound rate is 44kHhz'); | ||
84 | equal(1, soundSize, 'the sound size is 16-bit samples'); | ||
85 | equal(1, soundType, 'the sound type is stereo'); | ||
86 | |||
87 | ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type'); | ||
88 | }; | ||
89 | |||
90 | testVideoTag = function (tag) { | ||
91 | var | ||
92 | byte = tag.bytes[11], | ||
93 | frameType = (byte & 0xF0) >>> 4, | ||
94 | codecId = byte & 0x0F, | ||
95 | packetType = tag.bytes[12], | ||
96 | compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8, | ||
97 | nalHeader; | ||
98 | |||
99 | // payload starts at tag.bytes[16] | ||
100 | |||
101 | |||
102 | // XXX: I'm not sure that frame types 3-5 are invalid | ||
103 | ok(frameType === 1 || frameType === 2, | ||
104 | 'the frame type should be valid'); | ||
105 | |||
106 | equal(7, codecId, 'the codec ID is AVC for h264'); | ||
107 | ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]'); | ||
108 | if (packetType !== 1) { | ||
109 | equal(0, | ||
110 | compositionTime, | ||
111 | 'the composition time is zero for non-NALU packets'); | ||
112 | } | ||
113 | |||
114 | // TODO: the rest of the bytes are an NLU unit | ||
115 | if (packetType === 0) { | ||
116 | // AVC decoder configuration record | ||
117 | } else { | ||
118 | // NAL units | ||
119 | testNalUnit(tag.bytes.subarray(16)); | ||
120 | } | ||
121 | }; | ||
122 | |||
123 | testNalUnit = function (bytes) { | ||
124 | var | ||
125 | nalHeader = bytes[0], | ||
126 | unitType = nalHeader & 0x1F; | ||
127 | |||
128 | equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0'); | ||
129 | // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something'); | ||
130 | // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0'); | ||
131 | // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22'); | ||
132 | }; | ||
133 | |||
134 | |||
135 | asciiFromBytes = function (bytes) { | ||
136 | var | ||
137 | string = [], | ||
138 | i = bytes.byteLength; | ||
139 | |||
140 | while (i--) { | ||
141 | string[i] = String.fromCharCode(bytes[i]); | ||
142 | } | ||
143 | return string.join(''); | ||
144 | }; | ||
145 | |||
146 | testScriptString = function (tag, offset, expected) { | ||
147 | var | ||
148 | type = tag.bytes[offset], | ||
149 | stringLength = tag.view.getUint16(offset + 1), | ||
150 | string, | ||
151 | i = expected.length; | ||
152 | |||
153 | equal(2, type, 'the script element is of string type'); | ||
154 | equal(stringLength, expected.length, 'the script string length is correct'); | ||
155 | string = asciiFromBytes(tag.bytes.subarray(offset + 3, | ||
156 | offset + 3 + stringLength)); | ||
157 | equal(expected, string, 'the string value is "' + expected + '"'); | ||
158 | }; | ||
159 | |||
160 | testScriptEcmaArray = function (tag, start) { | ||
161 | var | ||
162 | numItems = tag.view.getUint32(start), | ||
163 | i = numItems, | ||
164 | offset = start + 4, | ||
165 | length, | ||
166 | type; | ||
167 | |||
168 | while (i--) { | ||
169 | length = tag.view.getUint16(offset); | ||
170 | |||
171 | // advance offset to the property value | ||
172 | offset += 2 + length; | ||
173 | |||
174 | type = tag.bytes[offset]; | ||
175 | ok(type === 1 || type === 0, | ||
176 | 'the ecma array property value type is number or boolean'); | ||
177 | offset++; | ||
178 | if (type) { | ||
179 | // boolean | ||
180 | ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1, | ||
181 | 'the script boolean value is 0 or 1'); | ||
182 | offset++; | ||
183 | } else { | ||
184 | // number | ||
185 | ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN'); | ||
186 | offset += 8; | ||
187 | } | ||
188 | } | ||
189 | equal(tag.bytes[offset], 0, 'the property array terminator is valid'); | ||
190 | equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid'); | ||
191 | equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid'); | ||
192 | }; | ||
193 | |||
194 | testScriptTag = function (tag) { | ||
195 | testScriptString(tag, 11, 'onMetaData'); | ||
196 | |||
197 | // the onMetaData object is stored as an 'ecma array', an array with non- | ||
198 | // integer indices (i.e. a dictionary or hash-map). | ||
199 | equal(8, tag.bytes[24], 'onMetaData is of ecma array type'); | ||
200 | testScriptEcmaArray(tag, 25); | ||
201 | }; | ||
202 | |||
203 | test('the flv tags are well-formed', function () { | ||
204 | var | ||
205 | tag, | ||
206 | byte, | ||
207 | type, | ||
208 | lastTime = 0; | ||
209 | parser.parseSegmentBinaryData(window.bcSegment); | ||
210 | |||
211 | while (parser.tagsAvailable()) { | ||
212 | tag = parser.getNextTag(); | ||
213 | type = tag.bytes[0]; | ||
214 | |||
215 | // generic flv headers | ||
216 | ok(type === 8 || type === 9 || type === 18, | ||
217 | 'the type field specifies audio, video or script'); | ||
218 | |||
219 | byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8; | ||
220 | equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct'); | ||
221 | |||
222 | byte = tag.view.getUint32(5) & 0xFFFFFF00; | ||
223 | ok(byte >= lastTime, 'the timestamp for the tag is greater than zero'); | ||
224 | lastTime = byte; | ||
225 | |||
226 | // tag type-specific headers | ||
227 | ({ | ||
228 | 8: testAudioTag, | ||
229 | 9: testVideoTag, | ||
230 | 18: testScriptTag | ||
231 | })[type](tag); | ||
232 | |||
233 | // previous tag size | ||
234 | equal(tag.bytes.byteLength - 4, | ||
235 | tag.view.getUint32(tag.bytes.byteLength - 4), | ||
236 | 'the size of the previous tag is correct'); | ||
237 | } | ||
238 | }); | ||
239 | |||
240 | /* | ||
241 | M3U8 Test Suite | ||
242 | */ | ||
243 | |||
244 | module('m3u8 parser', { | ||
245 | setup: function () { | ||
246 | m3u8parser = new window.videojs.hls.M3U8Parser(); | ||
247 | } | ||
248 | }); | ||
249 | |||
250 | test('should create my parser', function () { | ||
251 | ok(m3u8parser != undefined); | ||
252 | } | ||
253 | ); | ||
254 | |||
255 | test('should successfully parse manifest data', function () { | ||
256 | var parsedData = m3u8parser.parse(window.playlistData); | ||
257 | ok(parsedData); | ||
258 | } | ||
259 | ); | ||
260 | |||
261 | test('test for expected results', function () { | ||
262 | var data = m3u8parser.parse(window.playlistData); | ||
263 | |||
264 | notEqual(data, null, 'data is not NULL'); | ||
265 | equal(data.invalidReasons.length, 0, 'data has 0 invalid reasons'); | ||
266 | equal(data.hasValidM3UTag, true, 'data has valid EXTM3U'); | ||
267 | equal(data.targetDuration, 10, 'data has correct TARGET DURATION'); | ||
268 | equal(data.allowCache, "NO", 'acceptable ALLOW CACHE'); | ||
269 | equal(data.isPlaylist, true, 'data is parsed as a PLAYLIST as expected'); | ||
270 | equal(data.playlistType, "VOD", 'acceptable PLAYLIST TYPE'); | ||
271 | equal(data.mediaItems.length, 16, 'acceptable mediaItem count'); | ||
272 | equal(data.mediaSequence, 0, 'MEDIA SEQUENCE is correct'); | ||
273 | equal(data.totalDuration, -1, "ZEN TOTAL DURATION is unknown as expected"); | ||
274 | equal(data.hasEndTag, true, 'should have ENDLIST tag'); | ||
275 | } | ||
276 | ); | ||
277 | |||
278 | module('brightcove playlist', { | ||
279 | setup: function () { | ||
280 | m3u8parser = new window.videojs.hls.M3U8Parser(); | ||
281 | } | ||
282 | }); | ||
283 | |||
284 | test('should parse a brightcove manifest data', function () { | ||
285 | var data = m3u8parser.parse(window.brightcove_playlist_data); | ||
286 | |||
287 | ok(data); | ||
288 | equal(data.playlistItems.length, 4, 'Has correct rendition count'); | ||
289 | equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct'); | ||
290 | equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct'); | ||
291 | equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct'); | ||
292 | equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct'); | ||
293 | |||
294 | } | ||
295 | ); | ||
296 | |||
297 | module('manifest controller', { | ||
298 | setup: function () { | ||
299 | manifestController = new window.videojs.hls.ManifestController(); | ||
300 | this.vjsget = vjs.get; | ||
301 | vjs.get = function (url, success, error) { | ||
302 | console.log(url); | ||
303 | success(window.brightcove_playlist_data); | ||
304 | }; | ||
305 | }, | ||
306 | teardown: function () { | ||
307 | vjs.get = this.vjsget; | ||
308 | } | ||
309 | }); | ||
310 | |||
311 | test('should create', function () { | ||
312 | ok(manifestController); | ||
313 | }); | ||
314 | |||
315 | test('should return a parsed object', function () { | ||
316 | var data = manifestController.parseManifest(window.brightcove_playlist_data); | ||
317 | |||
318 | ok(data); | ||
319 | |||
320 | equal(data.playlistItems.length, 4, 'Has correct rendition count'); | ||
321 | equal(data.playlistItems[0].bandwidth, 240000, 'First rendition index bandwidth is correct'); | ||
322 | equal(data.playlistItems[0]["program-id"], 1, 'First rendition index program-id is correct'); | ||
323 | equal(data.playlistItems[0].resolution.width, 396, 'First rendition index resolution width is correct'); | ||
324 | equal(data.playlistItems[0].resolution.height, 224, 'First rendition index resolution height is correct'); | ||
325 | }) | ||
326 | |||
327 | test('should get a manifest from hermes', function () { | ||
328 | var hermesUrl = "http://localhost:7070/test/basic-playback/brightcove/16x9-master.m3u8"; | ||
329 | |||
330 | manifestController.loadManifest( | ||
331 | hermesUrl, | ||
332 | function (responseData) { | ||
333 | ok(true); | ||
334 | }, | ||
335 | function (errorData) { | ||
336 | console.log('got error data'); | ||
337 | }, | ||
338 | function (updateData) { | ||
339 | console.log('got update data'); | ||
340 | } | ||
341 | ) | ||
342 | }); | ||
343 | |||
344 | module('segment controller', { | ||
345 | setup: function () { | ||
346 | segmentController = new window.videojs.hls.SegmentController(); | ||
347 | this.vjsget = vjs.get; | ||
348 | vjs.get = function (url, success, error) { | ||
349 | console.log('load segment url', url); | ||
350 | success(window.bcSegment); | ||
351 | }; | ||
352 | }, | ||
353 | teardown: function () { | ||
354 | vjs.get = this.vjsget; | ||
355 | } | ||
356 | }); | ||
357 | |||
358 | test('should get a segment data', function () { | ||
359 | ok(true); | ||
360 | var hermesUrl = "http://localhost:7070/test/ts-files/brightcove/s-1.ts"; | ||
361 | |||
362 | segmentController.loadSegment( | ||
363 | hermesUrl, | ||
364 | function (responseData) { | ||
365 | console.log('got response from segment controller'); | ||
366 | ok(true); | ||
367 | |||
368 | }, | ||
369 | function (errorData) { | ||
370 | console.log('got error data'); | ||
371 | }, | ||
372 | function (updateData) { | ||
373 | console.log('got update data'); | ||
374 | } | ||
375 | ) | ||
376 | } | ||
377 | ) | ||
378 | |||
379 | test('bandwidth calulation test', function () { | ||
380 | var multiSecondData = segmentController.calculateThroughput(10000, 1000, 2000); | ||
381 | var subSecondData = segmentController.calculateThroughput(10000, 1000, 1500); | ||
382 | equal(multiSecondData, 80000, 'MULTI-Second bits per second calculation'); | ||
383 | equal(subSecondData, 160000, 'SUB-Second bits per second calculation'); | ||
384 | |||
385 | }) | ||
386 | |||
387 | })(this); | ... | ... |
-
Please register or sign in to post a comment