e95cfe5a by David LaPalomento

Fix whitespace

All files should use 2-space indenting.
1 parent eda08662
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);
......