Segment parser finishes with the example TS segment
The parser can process the example TS but it doesn't appear to be correctly formed. The player does seem to correctly interpret the video duration, but the display area is black and there is no audio.
Showing
11 changed files
with
778 additions
and
363 deletions
example.html
0 → 100644
1 | /<!DOCTYPE html> | ||
2 | <html> | ||
3 | <head> | ||
4 | <meta charset="utf-8"> | ||
5 | <title>video.js HLS Plugin Example</title> | ||
6 | |||
7 | <link href="http://vjs.zencdn.net/4.0/video-js.css" rel="stylesheet"> | ||
8 | |||
9 | <!-- video.js --> | ||
10 | <script src="libs/video-js/src/js/core.js"></script> | ||
11 | <script src="libs/video-js/src/js/core-object.js"></script> | ||
12 | <script src="libs/video-js/src/js/events.js"></script> | ||
13 | <script src="libs/video-js/src/js/lib.js"></script> | ||
14 | <script src="libs/video-js/src/js/component.js"></script> | ||
15 | <script src="libs/video-js/src/js/button.js"></script> | ||
16 | <script src="libs/video-js/src/js/slider.js"></script> | ||
17 | <script src="libs/video-js/src/js/menu.js"></script> | ||
18 | <script src="libs/video-js/src/js/player.js"></script> | ||
19 | <script src="libs/video-js/src/js/control-bar/control-bar.js"></script> | ||
20 | <script src="libs/video-js/src/js/control-bar/play-toggle.js"></script> | ||
21 | <script src="libs/video-js/src/js/control-bar/time-display.js"></script> | ||
22 | <script src="libs/video-js/src/js/control-bar/fullscreen-toggle.js"></script> | ||
23 | <script src="libs/video-js/src/js/control-bar/progress-control.js"></script> | ||
24 | <script src="libs/video-js/src/js/control-bar/volume-control.js"></script> | ||
25 | <script src="libs/video-js/src/js/control-bar/mute-toggle.js"></script> | ||
26 | <script src="libs/video-js/src/js/control-bar/volume-menu-button.js"></script> | ||
27 | <script src="libs/video-js/src/js/poster.js"></script> | ||
28 | <script src="libs/video-js/src/js/loading-spinner.js"></script> | ||
29 | <script src="libs/video-js/src/js/big-play-button.js"></script> | ||
30 | <script src="libs/video-js/src/js/media/media.js"></script> | ||
31 | <script src="libs/video-js/src/js/media/html5.js"></script> | ||
32 | <script src="libs/video-js/src/js/media/flash.js"></script> | ||
33 | <script src="libs/video-js/src/js/media/loader.js"></script> | ||
34 | <script src="libs/video-js/src/js/tracks.js"></script> | ||
35 | <script src="libs/video-js/src/js/json.js"></script> | ||
36 | <script src="libs/video-js/src/js/setup.js"></script> | ||
37 | <script src="libs/video-js/src/js/plugins.js"></script> | ||
38 | |||
39 | <!-- Media Sources plugin --> | ||
40 | <script src="libs/videojs-media-sources.js"></script> | ||
41 | <!-- HLS plugin --> | ||
42 | <script src="src/video-js-hls.js"></script> | ||
43 | <script src="src/flv-tag.js"></script> | ||
44 | <script src="src/exp-golomb.js"></script> | ||
45 | <script src="src/h264-stream.js"></script> | ||
46 | <script src="src/aac-stream.js"></script> | ||
47 | <script src="src/segment-parser.js"></script> | ||
48 | <!-- an example MPEG2-TS segment --> | ||
49 | <script src="test/tsSegment.js"></script> | ||
50 | |||
51 | </head> | ||
52 | <body> | ||
53 | <video id='video' | ||
54 | class="video-js vjs-default-skin" | ||
55 | height="300" | ||
56 | width="600" | ||
57 | controls> | ||
58 | </video> | ||
59 | <script> | ||
60 | var video, mediaSource; | ||
61 | |||
62 | // initialize the player | ||
63 | videojs.options.flash.swf = "libs/video-js.swf"; | ||
64 | video = videojs('video'); | ||
65 | |||
66 | // create a media source | ||
67 | mediaSource = new videojs.MediaSource(); | ||
68 | |||
69 | mediaSource.addEventListener('sourceopen', function(event){ | ||
70 | var tag, bytes, parser; | ||
71 | |||
72 | // feed parsed bytes into the player | ||
73 | var sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"'); | ||
74 | parser = new videojs.hls.SegmentParser(); | ||
75 | var header = parser.getFlvHeader(); | ||
76 | sourceBuffer.appendBuffer(header); | ||
77 | |||
78 | parser.parseSegmentBinaryData(window.testSegment); | ||
79 | while (parser.tagsAvailable()) { | ||
80 | tag = parser.getNextTag(); | ||
81 | console.log('sending bytes', tag.length); | ||
82 | sourceBuffer.appendBuffer(tag.bytes.subarray(0, tag.length), video); | ||
83 | } | ||
84 | }, false); | ||
85 | |||
86 | url = videojs.URL.createObjectURL(mediaSource); | ||
87 | |||
88 | video.src({ | ||
89 | src: url, | ||
90 | type: "video/flv" | ||
91 | }); | ||
92 | </script> | ||
93 | </body> | ||
94 | </html> |
... | @@ -7,9 +7,216 @@ | ... | @@ -7,9 +7,216 @@ |
7 | */ | 7 | */ |
8 | 8 | ||
9 | (function(window) { | 9 | (function(window) { |
10 | var | ||
11 | FlvTag = window.videojs.hls.FlvTag, | ||
12 | adtsSampleingRates = [ | ||
13 | 96000, 88200, | ||
14 | 64000, 48000, | ||
15 | 44100, 32000, | ||
16 | 24000, 22050, | ||
17 | 16000, 12000 | ||
18 | ]; | ||
19 | |||
20 | window.videojs.hls.AacStream = function() { | ||
21 | var | ||
22 | next_pts, // :uint | ||
23 | pts_delta = -1, // :int | ||
24 | state, // :uint | ||
25 | pes_length, // :int | ||
26 | |||
27 | adtsProtectionAbsent, // :Boolean | ||
28 | adtsObjectType, // :int | ||
29 | adtsSampleingIndex, // :int | ||
30 | adtsChanelConfig, // :int | ||
31 | adtsFrameSize, // :int | ||
32 | adtsSampleCount, // :int | ||
33 | adtsDuration, // :int | ||
34 | |||
35 | aacFrame, // :FlvTag = null; | ||
36 | extraData; // :uint; | ||
10 | 37 | ||
11 | window.videojs.hls.AacStream = function(){ | ||
12 | this.tags = []; | 38 | this.tags = []; |
39 | |||
40 | // (pts:uint, pes_size:int, dataAligned:Boolean):void | ||
41 | this.setNextTimeStamp = function(pts, pes_size, dataAligned) { | ||
42 | if (0 > pts_delta) { | ||
43 | // We assume the very firts pts is less than 0x80000000 | ||
44 | pts_delta = pts; | ||
45 | } | ||
46 | |||
47 | next_pts = pts - pts_delta; | ||
48 | pes_length = pes_size; | ||
49 | |||
50 | // If data is aligned, flush all internal buffers | ||
51 | if (dataAligned) { | ||
52 | state = 0; | ||
53 | } | ||
54 | }; | ||
55 | |||
56 | // (pData:ByteArray, o:int = 0, l:int = 0):void | ||
57 | this.writeBytes = function(pData, o, l) { | ||
58 | var | ||
59 | e, // :int | ||
60 | newExtraData, // :uint | ||
61 | bytesToCopy; // :int | ||
62 | |||
63 | // default arguments | ||
64 | o = o || 0; | ||
65 | l = l || 0; | ||
66 | |||
67 | // Do not allow more that 'pes_length' bytes to be written | ||
68 | l = (pes_length < l ? pes_length : l); | ||
69 | pes_length -= l; | ||
70 | e = o + l; | ||
71 | while(o < e) { | ||
72 | switch (state) { | ||
73 | default: | ||
74 | state = 0; | ||
75 | break; | ||
76 | case 0: | ||
77 | if (o >= e) { | ||
78 | return; | ||
79 | } | ||
80 | if (0xFF !== pData[o]) { | ||
81 | console.log("Error no ATDS header found"); | ||
82 | o += 1; | ||
83 | state = 0; | ||
84 | return; | ||
85 | } | ||
86 | |||
87 | o += 1; | ||
88 | state = 1; | ||
89 | break; | ||
90 | case 1: | ||
91 | if (o >= e) { | ||
92 | return; | ||
93 | } | ||
94 | if (0xF0 !== (pData[o] & 0xF0)) { | ||
95 | console.log("Error no ATDS header found"); | ||
96 | o +=1; | ||
97 | state = 0; | ||
98 | return; | ||
99 | } | ||
100 | |||
101 | adtsProtectionAbsent = !!(pData[o] & 0x01); | ||
102 | |||
103 | o += 1; | ||
104 | state = 2; | ||
105 | break; | ||
106 | case 2: | ||
107 | if (o >= e) { | ||
108 | return; | ||
109 | } | ||
110 | adtsObjectType = ((pData[o] & 0xC0) >>> 6) + 1; | ||
111 | adtsSampleingIndex = ((pData[o] & 0x3C) >>> 2); | ||
112 | adtsChanelConfig = ((pData[o] & 0x01) << 2); | ||
113 | |||
114 | o += 1; | ||
115 | state = 3; | ||
116 | break; | ||
117 | case 3: | ||
118 | if (o >= e) { | ||
119 | return; | ||
120 | } | ||
121 | adtsChanelConfig |= ((pData[o] & 0xC0) >>> 6); | ||
122 | adtsFrameSize = ((pData[o] & 0x03) << 11); | ||
123 | |||
124 | o += 1; | ||
125 | state = 4; | ||
126 | break; | ||
127 | case 4: | ||
128 | if (o >= e) { | ||
129 | return; | ||
130 | } | ||
131 | adtsFrameSize |= (pData[o] << 3); | ||
132 | |||
133 | o += 1; | ||
134 | state = 5; | ||
135 | break; | ||
136 | case 5: | ||
137 | if(o >= e) { | ||
138 | return; | ||
139 | } | ||
140 | adtsFrameSize |= ((pData[o] & 0xE0) >>> 5); | ||
141 | adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9); | ||
142 | |||
143 | o += 1; | ||
144 | state = 6; | ||
145 | break; | ||
146 | case 6: | ||
147 | if (o >= e) { | ||
148 | return; | ||
149 | } | ||
150 | adtsSampleCount = ((pData[o] & 0x03) + 1) * 1024; | ||
151 | adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex]; | ||
152 | |||
153 | newExtraData = (adtsObjectType << 11) | | ||
154 | (adtsSampleingIndex << 7) | | ||
155 | (adtsChanelConfig << 3); | ||
156 | if (newExtraData !== extraData) { | ||
157 | aacFrame = new FlvTag(FlvTag.METADATA_TAG); | ||
158 | aacFrame.pts = next_pts; | ||
159 | aacFrame.dts = next_pts; | ||
160 | |||
161 | // AAC is always 10 | ||
162 | aacFrame.writeMetaDataDouble("audiocodecid", 10); | ||
163 | aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig); | ||
164 | aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]); | ||
165 | // Is AAC always 16 bit? | ||
166 | aacFrame.writeMetaDataDouble ("audiosamplesize", 16); | ||
167 | |||
168 | this.tags.push(aacFrame); | ||
169 | |||
170 | extraData = newExtraData; | ||
171 | aacFrame = new FlvTag(FlvTag.AUDIO_TAG, true); | ||
172 | aacFrame.pts = aacFrame.dts; | ||
173 | // For audio, DTS is always the same as PTS. We want to set the DTS | ||
174 | // however so we can compare with video DTS to determine approximate | ||
175 | // packet order | ||
176 | aacFrame.pts = next_pts; | ||
177 | aacFrame.view.setUint16(aacFrame.position, newExtraData); | ||
178 | aacFrame.position += 2; | ||
179 | |||
180 | this.tags.push(aacFrame); | ||
181 | } | ||
182 | |||
183 | // Skip the checksum if there is one | ||
184 | o += 1; | ||
185 | state = 7; | ||
186 | break; | ||
187 | case 7: | ||
188 | if (!adtsProtectionAbsent) { | ||
189 | if (2 > (e - o)) { | ||
190 | return; | ||
191 | } else { | ||
192 | o += 2; | ||
193 | } | ||
194 | } | ||
195 | |||
196 | aacFrame = new FlvTag(FlvTag.AUDIO_TAG); | ||
197 | aacFrame.pts = next_pts; | ||
198 | aacFrame.dts = next_pts; | ||
199 | state = 8; | ||
200 | break; | ||
201 | case 8: | ||
202 | while (adtsFrameSize) { | ||
203 | if (o >= e) { | ||
204 | return; | ||
205 | } | ||
206 | bytesToCopy = (e - o) < adtsFrameSize ? (e - o) : adtsFrameSize; | ||
207 | aacFrame.writeBytes( pData, o, bytesToCopy ); | ||
208 | o += bytesToCopy; | ||
209 | adtsFrameSize -= bytesToCopy; | ||
210 | } | ||
211 | |||
212 | this.tags.push(aacFrame); | ||
213 | |||
214 | // finished with this frame | ||
215 | state = 0; | ||
216 | next_pts += adtsDuration; | ||
217 | } | ||
218 | } | ||
219 | }; | ||
13 | }; | 220 | }; |
14 | 221 | ||
15 | })(this); | 222 | })(this); | ... | ... |
1 | (function() { | 1 | (function(window) { |
2 | |||
3 | window.videojs.hls.ExpGolomb = function() {}; | ||
4 | |||
5 | /* | ||
6 | public class ExpGolomb | ||
7 | { | ||
8 | private var workingData:ByteArray; | ||
9 | private var workingWord:uint; | ||
10 | private var workingbBitsAvailable:uint; | ||
11 | |||
12 | public function ExpGolomb(pData:ByteArray) | ||
13 | { | ||
14 | workingData = pData; | ||
15 | workingData.position = 0; | ||
16 | loadWord(); | ||
17 | } | ||
18 | 2 | ||
19 | public function length():uint | 3 | window.videojs.hls.ExpGolomb = function(workingData) { |
20 | { | 4 | var |
21 | return ( 8 * workingData.length ); | 5 | // the number of bytes left to examine in workingData |
22 | } | 6 | workingBytesAvailable = workingData.byteLength, |
23 | 7 | ||
24 | public function bitsAvailable():uint | 8 | // the current word being examined |
25 | { | 9 | workingWord, // :uint |
26 | return ( 8 * workingData.bytesAvailable ) + workingbBitsAvailable; | ||
27 | } | ||
28 | 10 | ||
29 | private function loadWord():void | 11 | // the number of bits left to examine in the current word |
30 | { | 12 | workingBitsAvailable; // :uint; |
31 | workingWord = 0; workingbBitsAvailable = 0; | ||
32 | switch( workingData.bytesAvailable ) | ||
33 | { | ||
34 | case 0: workingbBitsAvailable = 0; break; | ||
35 | default: // not 0, but greater than 4 | ||
36 | case 4: workingWord = workingData.readUnsignedByte(); workingbBitsAvailable = 8; | ||
37 | case 3: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8; | ||
38 | case 2: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8; | ||
39 | case 1: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8; | ||
40 | } | ||
41 | 13 | ||
42 | workingWord <<= (32 - workingbBitsAvailable); | 14 | // ():uint |
43 | } | 15 | this.length = function() { |
16 | return (8 * workingBytesAvailable); | ||
17 | }; | ||
18 | |||
19 | // ():uint | ||
20 | this.bitsAvailable = function() { | ||
21 | return (8 * workingBytesAvailable) + workingBitsAvailable; | ||
22 | }; | ||
44 | 23 | ||
45 | public function skipBits(size:int):void | 24 | // ():void |
46 | { | 25 | this.loadWord = function() { |
47 | if ( workingbBitsAvailable > size ) | 26 | var |
48 | { | 27 | workingBytes = new Uint8Array(4), |
28 | availableBytes = Math.min(4, workingBytesAvailable); | ||
29 | |||
30 | // console.assert(availableBytes > 0); | ||
31 | |||
32 | workingBytes.set(workingData.subarray(0, availableBytes)); | ||
33 | workingWord = new DataView(workingBytes.buffer).getUint32(0); | ||
34 | |||
35 | // track the amount of workingData that has been processed | ||
36 | workingBitsAvailable = availableBytes * 8; | ||
37 | workingBytesAvailable -= availableBytes; | ||
38 | }; | ||
39 | |||
40 | // (size:int):void | ||
41 | this.skipBits = function(size) { | ||
42 | var skipBytes; // :int | ||
43 | if (workingBitsAvailable > size) { | ||
49 | workingWord <<= size; | 44 | workingWord <<= size; |
50 | workingbBitsAvailable -= size; | 45 | workingBitsAvailable -= size; |
51 | } | 46 | } else { |
52 | else | 47 | size -= workingBitsAvailable; |
53 | { | 48 | skipBytes = size / 8; |
54 | size -= workingbBitsAvailable; | ||
55 | var skipBytes:int = size / 8; | ||
56 | 49 | ||
57 | size -= ( skipBytes * 8 ); | 50 | size -= (skipBytes * 8); |
58 | workingData.position += skipBytes; | 51 | workingData.position += skipBytes; |
59 | 52 | ||
60 | loadWord(); | 53 | this.loadWord(); |
61 | 54 | ||
62 | workingWord <<= size; | 55 | workingWord <<= size; |
63 | workingbBitsAvailable -= size; | 56 | workingBitsAvailable -= size; |
64 | } | ||
65 | } | 57 | } |
58 | }; | ||
66 | 59 | ||
67 | public function readBits(size:int):uint | 60 | // (size:int):uint |
68 | { | 61 | this.readBits = function(size) { |
69 | // if ( 32 < size ) | 62 | var |
70 | // throw new Error("Can not read more than 32 bits at a time"); | 63 | bits = Math.min(workingBitsAvailable, size), // :uint |
64 | valu = workingWord >>> (32 - bits); // :uint | ||
71 | 65 | ||
72 | var bits:uint = ( workingbBitsAvailable < size ? workingbBitsAvailable : size); | 66 | console.assert(32 > size, 'Cannot read more than 32 bits at a time'); |
73 | var valu:uint = workingWord >>> (32 - bits); | ||
74 | 67 | ||
75 | workingbBitsAvailable -= bits; | 68 | workingBitsAvailable -= bits; |
76 | if ( 0 < workingbBitsAvailable ) | 69 | if (0 < workingBitsAvailable) { |
77 | workingWord <<= bits; | 70 | workingWord <<= bits; |
78 | else | 71 | } else { |
79 | loadWord(); | 72 | this.loadWord(); |
73 | } | ||
80 | 74 | ||
81 | bits = size - bits; | 75 | bits = size - bits; |
82 | if ( 0 < bits ) | 76 | if (0 < bits) { |
83 | return valu << bits | readBits( bits ); | 77 | return valu << bits | this.readBits(bits); |
84 | else | 78 | } else { |
85 | return valu; | 79 | return valu; |
86 | } | 80 | } |
81 | }; | ||
87 | 82 | ||
88 | private function skipLeadingZeros():uint | 83 | // ():uint |
89 | { | 84 | this.skipLeadingZeros = function() { |
90 | for( var clz:uint = 0 ; clz < workingbBitsAvailable ; ++clz ) | 85 | var clz; // :uint |
91 | { | 86 | for (clz = 0 ; clz < workingBitsAvailable ; ++clz) { |
92 | if( 0 != ( workingWord & ( 0x80000000 >>> clz ) ) ) | 87 | if (0 !== (workingWord & (0x80000000 >>> clz))) { |
93 | { | ||
94 | workingWord <<= clz; | 88 | workingWord <<= clz; |
95 | workingbBitsAvailable -= clz; | 89 | workingBitsAvailable -= clz; |
96 | return clz; | 90 | return clz; |
97 | } | 91 | } |
98 | } | 92 | } |
99 | 93 | ||
100 | loadWord(); // we exhausted workingWord and still have not found a 1 | 94 | // we exhausted workingWord and still have not found a 1 |
101 | return clz + skipLeadingZeros(); | 95 | this.loadWord(); |
102 | } | 96 | return clz + this.skipLeadingZeros(); |
97 | }; | ||
103 | 98 | ||
104 | public function skipUnsignedExpGolomb():void | 99 | // ():void |
105 | { | 100 | this.skipUnsignedExpGolomb = function() { |
106 | skipBits(1 + skipLeadingZeros() ); | 101 | this.skipBits(1 + this.skipLeadingZeros()); |
107 | } | 102 | }; |
108 | 103 | ||
109 | public function skipExpGolomb():void | 104 | // ():void |
110 | { | 105 | this.skipExpGolomb = function() { |
111 | skipBits(1 + skipLeadingZeros() ); | 106 | this.skipBits(1 + this.skipLeadingZeros()); |
112 | } | 107 | }; |
113 | 108 | ||
114 | public function readUnsignedExpGolomb():uint | 109 | // ():uint |
115 | { | 110 | this.readUnsignedExpGolomb = function() { |
116 | var clz:uint = skipLeadingZeros(); | 111 | var clz = this.skipLeadingZeros(); // :uint |
117 | return readBits(clz+1) - 1; | 112 | return this.readBits(clz + 1) - 1; |
118 | } | 113 | }; |
119 | 114 | ||
120 | public function readExpGolomb():int | 115 | // ():int |
121 | { | 116 | this.readExpGolomb = function() { |
122 | var valu:int = readUnsignedExpGolomb(); | 117 | var valu = this.readUnsignedExpGolomb(); // :int |
123 | if ( 0x01 & valu ) // the number is odd if the low order bit is set | 118 | if (0x01 & valu) { |
124 | return (1 + valu) >>> 1; // add 1 to make it even, and devide by 2 | 119 | // the number is odd if the low order bit is set |
125 | else | 120 | return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2 |
126 | return -1 * (valu >>> 1); // devide by two then make it negative | 121 | } else { |
122 | return -1 * (valu >>> 1); // divide by two then make it negative | ||
127 | } | 123 | } |
124 | }; | ||
128 | 125 | ||
129 | // Some convenience functions | 126 | // Some convenience functions |
130 | public function readBoolean():Boolean | 127 | // :Boolean |
131 | { | 128 | this.readBoolean = function() { |
132 | return 1 == readBits(1); | 129 | return 1 === this.readBits(1); |
133 | } | 130 | }; |
134 | 131 | ||
135 | public function readUnsignedByte():int | 132 | // ():int |
136 | { | 133 | this.readUnsignedByte = function() { |
137 | return readBits(8); | 134 | return this.readBits(8); |
138 | } | 135 | }; |
139 | } | 136 | |
140 | */ | 137 | this.loadWord(); |
141 | })(); | 138 | |
139 | }; | ||
140 | })(this); | ... | ... |
1 | (function(window) { | 1 | (function(window) { |
2 | 2 | ||
3 | window.videojs.hls.FlvTag = function() {}; | 3 | var hls = window.videojs.hls; |
4 | 4 | ||
5 | /* | 5 | // (type:uint, extraData:Boolean = false) extends ByteArray |
6 | package com.videojs.providers.hls.utils{ | 6 | hls.FlvTag = function(type, extraData) { |
7 | var | ||
8 | // Counter if this is a metadata tag, nal start marker if this is a video | ||
9 | // tag. unused if this is an audio tag | ||
10 | adHoc = 0; // :uint | ||
7 | 11 | ||
8 | import flash.utils.ByteArray; | 12 | this.keyFrame = false; // :Boolean |
9 | import flash.utils.Endian; | ||
10 | 13 | ||
11 | public class FlvTag extends ByteArray | 14 | switch(type) { |
12 | { | 15 | case hls.FlvTag.VIDEO_TAG: |
13 | public static const AUDIO_TAG:uint = 0x08; | 16 | this.length = 16; |
14 | public static const VIDEO_TAG:uint = 0x09; | 17 | break; |
15 | public static const METADATA_TAG:uint = 0x12; | 18 | case hls.FlvTag.AUDIO_TAG: |
16 | 19 | this.length = 13; | |
17 | public var keyFrame:Boolean = false; | 20 | this.keyFrame = true; |
18 | private var extraData:Boolean = false; | 21 | break; |
19 | private var adHoc:uint = 0; // Counter if this is a metadata tag, nal start marker if this is a video tag. unused if this is an audio tag | 22 | case hls.FlvTag.METADATA_TAG: |
20 | 23 | this.length = 29; | |
21 | public var pts:uint; | 24 | this.keyFrame = true; |
22 | public var dts:uint; | 25 | break; |
23 | 26 | default: | |
24 | public static function isAudioFrame(tag:ByteArray):Boolean | 27 | throw("Error Unknown TagType"); |
25 | { | ||
26 | return AUDIO_TAG == tag[0]; | ||
27 | } | ||
28 | |||
29 | public static function isVideoFrame(tag:ByteArray):Boolean | ||
30 | { | ||
31 | return VIDEO_TAG == tag[0]; | ||
32 | } | ||
33 | |||
34 | public static function isMetaData(tag:ByteArray):Boolean | ||
35 | { | ||
36 | return METADATA_TAG == tag[0]; | ||
37 | } | ||
38 | |||
39 | public static function isKeyFrame(tag:ByteArray):Boolean | ||
40 | { | ||
41 | if ( isVideoFrame(tag) ) | ||
42 | return tag[11] == 0x17; | ||
43 | |||
44 | if( isAudioFrame(tag) ) | ||
45 | return true; | ||
46 | |||
47 | if( isMetaData(tag) ) | ||
48 | return true; | ||
49 | |||
50 | return false; | ||
51 | } | ||
52 | |||
53 | public static function frameTime(tag:ByteArray):uint | ||
54 | { | ||
55 | var pts:uint = tag[ 4] << 16; | ||
56 | pts |= tag[ 5] << 8; | ||
57 | pts |= tag[ 6] << 0; | ||
58 | pts |= tag[ 7] << 24; | ||
59 | return pts; | ||
60 | } | ||
61 | |||
62 | |||
63 | public function FlvTag(type:uint, ed:Boolean = false) | ||
64 | { | ||
65 | super(); | ||
66 | extraData = ed; | ||
67 | this.endian = Endian.BIG_ENDIAN; | ||
68 | switch(type) | ||
69 | { | ||
70 | case VIDEO_TAG: this.length = 16; break; | ||
71 | case AUDIO_TAG: this.length = 13; keyFrame = true; break; | ||
72 | case METADATA_TAG: this.length = 29; keyFrame = true; break; | ||
73 | default: throw("Error Unknown TagType"); | ||
74 | } | 28 | } |
75 | 29 | ||
76 | this[0] = type | 30 | // XXX: I have no idea if 16k is enough to buffer arbitrary FLV tags |
31 | this.bytes = new Uint8Array(16384); | ||
32 | this.view = new DataView(this.bytes.buffer); | ||
33 | this.bytes[0] = type; | ||
77 | this.position = this.length; | 34 | this.position = this.length; |
78 | keyFrame = extraData; // Defaults to false | 35 | this.keyFrame = extraData; // Defaults to false |
79 | pts = dts = 0; | 36 | |
37 | // presentation timestamp | ||
38 | this.pts = 0; | ||
39 | // decoder timestamp | ||
40 | this.dts = 0; | ||
41 | |||
42 | // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) | ||
43 | this.writeBytes = function(bytes, offset, length) { | ||
44 | offset = offset || 0; | ||
45 | length = length || 0; | ||
46 | |||
47 | try { | ||
48 | this.bytes.set(bytes.subarray(offset, offset + length), this.position); | ||
49 | } catch(e) { | ||
50 | console.log(e); | ||
51 | throw e; | ||
80 | } | 52 | } |
53 | this.position += length; | ||
54 | this.length = Math.max(this.length, this.position); | ||
55 | }; | ||
56 | |||
57 | // ByteArray#writeByte(value:int):void | ||
58 | this.writeByte = function(byte) { | ||
59 | this.bytes[this.position] = byte; | ||
60 | this.position++; | ||
61 | this.length = Math.max(this.length, this.position); | ||
62 | }; | ||
63 | |||
64 | // ByteArray#writeShort(value:int):void | ||
65 | this.writeShort = function(short) { | ||
66 | this.view.setUint16(short, this.position); | ||
67 | this.position += 2; | ||
68 | this.length = Math.max(this.length, this.position); | ||
69 | }; | ||
81 | 70 | ||
82 | // Negative index into array | 71 | // Negative index into array |
83 | public function negIndex(pos:uint):int | 72 | // (pos:uint):int |
84 | { | 73 | this.negIndex = function(pos) { |
85 | return this[this.length - pos]; | 74 | return this.bytes[this.length - pos]; |
86 | } | 75 | }; |
87 | 76 | ||
88 | // The functions below ONLY work when this[0] == VIDEO_TAG. | 77 | // The functions below ONLY work when this[0] == VIDEO_TAG. |
89 | // We are not going to check for that because we dont want the overhead | 78 | // We are not going to check for that because we dont want the overhead |
90 | public function nalUnitSize(nal:ByteArray = null):int | 79 | // (nal:ByteArray = null):int |
91 | { | 80 | this.nalUnitSize = function() { |
92 | if( 0 == adHoc ) | 81 | if (adHoc === 0) { |
93 | return 0; | 82 | return 0; |
94 | |||
95 | return this.length - ( adHoc + 4 ); | ||
96 | } | 83 | } |
97 | 84 | ||
85 | return this.length - (adHoc + 4); | ||
86 | }; | ||
98 | 87 | ||
99 | public function startNalUnit():void | 88 | this.startNalUnit = function() { |
100 | { // remember position and add 4 bytes | 89 | // remember position and add 4 bytes |
101 | if ( 0 < adHoc ) | 90 | if (adHoc > 0) { |
102 | { | ||
103 | throw new Error("Attempted to create new NAL wihout closing the old one"); | 91 | throw new Error("Attempted to create new NAL wihout closing the old one"); |
104 | } | 92 | } |
105 | 93 | ||
... | @@ -107,97 +95,176 @@ package com.videojs.providers.hls.utils{ | ... | @@ -107,97 +95,176 @@ package com.videojs.providers.hls.utils{ |
107 | adHoc = this.length; | 95 | adHoc = this.length; |
108 | this.length += 4; | 96 | this.length += 4; |
109 | this.position = this.length; | 97 | this.position = this.length; |
110 | } | 98 | }; |
111 | 99 | ||
112 | public function endNalUnit(nal:ByteArray = null):void | 100 | // (nal:ByteArray = null):void |
113 | { // Rewind to the marker and write the size | 101 | this.endNalUnit = function(nalContainer) { |
114 | if ( this.length == adHoc + 4 ) | 102 | var |
115 | { | 103 | nalStart, // :uint |
104 | nalLength; // :uint | ||
105 | |||
106 | // Rewind to the marker and write the size | ||
107 | if (this.length === adHoc + 4) { | ||
116 | this.length -= 4; // we started a nal unit, but didnt write one, so roll back the 4 byte size value | 108 | this.length -= 4; // we started a nal unit, but didnt write one, so roll back the 4 byte size value |
117 | } | 109 | } else if (adHoc > 0) { |
118 | else | 110 | nalStart = adHoc + 4; |
119 | if ( 0 < adHoc ) | 111 | nalLength = this.length - nalStart; |
120 | { | ||
121 | var nalStart:uint = adHoc + 4; | ||
122 | var nalLength:uint = this.length - nalStart; | ||
123 | 112 | ||
124 | this.position = adHoc; | 113 | this.position = adHoc; |
125 | this.writeUnsignedInt( nalLength ); | 114 | this.view.setUint32(this.position, nalLength); |
126 | this.position = this.length; | 115 | this.position = this.length; |
127 | 116 | ||
128 | if ( null != nal ) // If the user pass in a ByteArray, copy the NAL to it. | 117 | if (nalContainer) { |
129 | nal.writeBytes( this, nalStart, nalLength ); | 118 | // Add the tag to the NAL unit |
119 | nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); | ||
120 | } | ||
130 | } | 121 | } |
131 | 122 | ||
132 | adHoc = 0; | 123 | adHoc = 0; |
124 | }; | ||
125 | |||
126 | // (key:String, val:Number):void | ||
127 | this.writeMetaDataDouble = function(key, val) { | ||
128 | var i; | ||
129 | this.view.setUint16(this.position, key.length); | ||
130 | this.position += 2; | ||
131 | for (i in key) { | ||
132 | console.assert(key.charCodeAt(i) < 255); | ||
133 | this.bytes[this.position] = key.charCodeAt(i); | ||
134 | this.position++; | ||
133 | } | 135 | } |
134 | 136 | this.view.setUint8(this.position, 0x00); | |
135 | public function writeMetaDataDouble(key:String, val:Number):void | 137 | this.position++; |
136 | { | 138 | this.view.setFloat64(this.position, val); |
137 | writeShort ( key.length ); | 139 | this.position += 2; |
138 | writeUTFBytes ( key ); | ||
139 | writeByte ( 0x00 ); | ||
140 | writeDouble ( val ); | ||
141 | ++adHoc; | 140 | ++adHoc; |
141 | }; | ||
142 | |||
143 | // (key:String, val:Boolean):void | ||
144 | this.writeMetaDataBoolean = function(key, val) { | ||
145 | var i; | ||
146 | this.view.setUint16(this.position, key.length); | ||
147 | this.position += 2; | ||
148 | for (i in key) { | ||
149 | console.assert(key.charCodeAt(i) < 255); | ||
150 | this.bytes[this.position] = key.charCodeAt(i); | ||
151 | this.position++; | ||
142 | } | 152 | } |
143 | 153 | this.view.setUint8(this.position, 0x01); | |
144 | public function writeMetaDataBoolean(key:String, val:Boolean):void | 154 | this.position++; |
145 | { | 155 | this.view.setUint8(this.position, val ? 0x01 : 0x00); |
146 | writeShort ( key.length ); | 156 | this.position++; |
147 | writeUTFBytes ( key ); | ||
148 | writeByte ( 0x01 ); | ||
149 | writeByte ( true == val ? 0x01 : 0x00 ); | ||
150 | ++adHoc; | 157 | ++adHoc; |
151 | } | 158 | }; |
152 | 159 | ||
153 | public function finalize():ByteArray | 160 | // ():ByteArray |
154 | { | 161 | this.finalize = function() { |
155 | switch(this[0]) | 162 | var |
156 | { // Video Data | 163 | dtsDelta, // :int |
157 | case VIDEO_TAG: | 164 | len; // :int |
158 | this[11] = ( ( keyFrame || extraData ) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame) | 165 | |
159 | this[12] = extraData ? 0x00 : 0x01; | 166 | switch(this.bytes[0]) { |
160 | 167 | // Video Data | |
161 | var dtsDelta:int = pts - dts; | 168 | case hls.FlvTag.VIDEO_TAG: |
162 | this[13] = ( dtsDelta & 0x00FF0000 ) >>> 16; | 169 | this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame) |
163 | this[14] = ( dtsDelta & 0x0000FF00 ) >>> 8; | 170 | this.bytes[12] = extraData ? 0x00 : 0x01; |
164 | this[15] = ( dtsDelta & 0x000000FF ) >>> 0; | 171 | |
172 | dtsDelta = this.pts - this.dts; | ||
173 | this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16; | ||
174 | this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8; | ||
175 | this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0; | ||
165 | break; | 176 | break; |
166 | 177 | ||
167 | case AUDIO_TAG: | 178 | case hls.FlvTag.AUDIO_TAG: |
168 | this[11] = 0xAF; | 179 | this.bytes[11] = 0xAF; |
169 | this[12] = extraData ? 0x00 : 0x01; | 180 | this.bytes[12] = extraData ? 0x00 : 0x01; |
170 | break; | 181 | break; |
171 | 182 | ||
172 | case METADATA_TAG: | 183 | case hls.FlvTag.METADATA_TAG: |
173 | this.position = 11; | 184 | this.position = 11; |
174 | writeByte(0x02); // String type | 185 | this.view.setUint8(this.position, 0x02); // String type |
175 | writeShort(0x0A); // 10 Bytes | 186 | this.position++; |
176 | writeUTFBytes("onMetaData"); | 187 | this.view.setUint16(this.position, 0x0A); // 10 Bytes |
177 | writeByte(0x08); // Array type | 188 | this.position += 2; |
178 | writeUnsignedInt( adHoc ); | 189 | // set "onMetaData" |
190 | this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, | ||
191 | 0x74, 0x61, 0x44, 0x61, | ||
192 | 0x74, 0x61], this.position); | ||
193 | this.position += 10; | ||
194 | this.view.setUint8(this.position, | ||
195 | this.bytes[this.position] | 0x08); // Array type | ||
196 | this.position++; | ||
197 | this.view.setUint32(this.position, adHoc); | ||
179 | this.position = this.length; | 198 | this.position = this.length; |
180 | writeUnsignedInt( 0x09 ); // End Data Tag | 199 | this.view.setUint32(this.position, 0x09); // End Data Tag |
200 | this.position += 4; | ||
201 | this.length = this.position; | ||
181 | break; | 202 | break; |
182 | } | 203 | } |
183 | 204 | ||
184 | var len:int = this.length - 11; | 205 | len = this.length - 11; |
185 | 206 | ||
186 | this[ 1] = ( len & 0x00FF0000 ) >>> 16; | 207 | this.bytes[ 1] = ( len & 0x00FF0000 ) >>> 16; |
187 | this[ 2] = ( len & 0x0000FF00 ) >>> 8; | 208 | this.bytes[ 2] = ( len & 0x0000FF00 ) >>> 8; |
188 | this[ 3] = ( len & 0x000000FF ) >>> 0; | 209 | this.bytes[ 3] = ( len & 0x000000FF ) >>> 0; |
189 | this[ 4] = ( pts & 0x00FF0000 ) >>> 16; | 210 | this.bytes[ 4] = ( this.pts & 0x00FF0000 ) >>> 16; |
190 | this[ 5] = ( pts & 0x0000FF00 ) >>> 8; | 211 | this.bytes[ 5] = ( this.pts & 0x0000FF00 ) >>> 8; |
191 | this[ 6] = ( pts & 0x000000FF ) >>> 0; | 212 | this.bytes[ 6] = ( this.pts & 0x000000FF ) >>> 0; |
192 | this[ 7] = ( pts & 0xFF000000 ) >>> 24; | 213 | this.bytes[ 7] = ( this.pts & 0xFF000000 ) >>> 24; |
193 | this[ 8] = 0; | 214 | this.bytes[ 8] = 0; |
194 | this[ 9] = 0; | 215 | this.bytes[ 9] = 0; |
195 | this[10] = 0; | 216 | this.bytes[10] = 0; |
196 | 217 | ||
197 | this.writeUnsignedInt( this.length ); | 218 | this.view.setUint32(this.length, this.length); |
219 | this.length += 4; | ||
220 | this.position += 4; | ||
198 | return this; | 221 | return this; |
222 | }; | ||
223 | }; | ||
224 | |||
225 | hls.FlvTag.AUDIO_TAG = 0x08; // :uint | ||
226 | hls.FlvTag.VIDEO_TAG = 0x09; // :uint | ||
227 | hls.FlvTag.METADATA_TAG = 0x12; // :uint | ||
228 | |||
229 | // (tag:ByteArray):Boolean { | ||
230 | hls.FlvTag.isAudioFrame = function(tag) { | ||
231 | return hls.FlvTag.AUDIO_TAG === tag[0]; | ||
232 | }; | ||
233 | |||
234 | // (tag:ByteArray):Boolean { | ||
235 | hls.FlvTag.isVideoFrame = function(tag) { | ||
236 | return hls.FlvTag.VIDEO_TAG === tag[0]; | ||
237 | }; | ||
238 | |||
239 | // (tag:ByteArray):Boolean { | ||
240 | hls.FlvTag.isMetaData = function(tag) { | ||
241 | return hls.FlvTag.METADATA_TAG === tag[0]; | ||
242 | }; | ||
243 | |||
244 | // (tag:ByteArray):Boolean { | ||
245 | hls.FlvTag.isKeyFrame = function(tag) { | ||
246 | if (hls.FlvTag.isVideoFrame(tag)) { | ||
247 | return tag[11] === 0x17; | ||
199 | } | 248 | } |
249 | |||
250 | if (hls.FlvTag.isAudioFrame(tag)) { | ||
251 | return true; | ||
252 | } | ||
253 | |||
254 | if (hls.FlvTag.isMetaData(tag)) { | ||
255 | return true; | ||
200 | } | 256 | } |
201 | } | 257 | |
202 | */ | 258 | return false; |
259 | }; | ||
260 | |||
261 | // (tag:ByteArray):uint { | ||
262 | hls.FlvTag.frameTime = function(tag) { | ||
263 | var pts = tag[ 4] << 16; // :uint | ||
264 | pts |= tag[ 5] << 8; | ||
265 | pts |= tag[ 6] << 0; | ||
266 | pts |= tag[ 7] << 24; | ||
267 | return pts; | ||
268 | }; | ||
269 | |||
203 | })(this); | 270 | })(this); | ... | ... |
... | @@ -8,27 +8,31 @@ | ... | @@ -8,27 +8,31 @@ |
8 | 8 | ||
9 | (function(window) { | 9 | (function(window) { |
10 | var | 10 | var |
11 | |||
11 | ExpGolomb = window.videojs.hls.ExpGolomb, | 12 | ExpGolomb = window.videojs.hls.ExpGolomb, |
12 | FlvTag = window.videojs.hls.FlvTag, | 13 | FlvTag = window.videojs.hls.FlvTag, |
13 | H264ExtraData = function() { | ||
14 | var | ||
15 | sps = [], // :Array | ||
16 | pps = []; // :Array | ||
17 | 14 | ||
18 | this.addSPS = function() { // :ByteArray | 15 | H264ExtraData = function() { |
19 | var tmp = new Uint8Array(); // :ByteArray | 16 | this.sps = []; // :Array |
20 | sps.push(tmp); | 17 | this.pps = []; // :Array |
18 | |||
19 | this.addSPS = function(size) { // :ByteArray | ||
20 | console.log('come on, you fucker'); | ||
21 | console.assert(size > 0); | ||
22 | var tmp = new Uint8Array(size); // :ByteArray | ||
23 | this.sps.push(tmp); | ||
21 | return tmp; | 24 | return tmp; |
22 | }; | 25 | }; |
23 | 26 | ||
24 | this.addPPS = function() { // :ByteArray | 27 | this.addPPS = function(size) { // :ByteArray |
25 | var tmp = new Uint8Array(); // :ByteArray | 28 | console.assert(size); |
26 | pps.push(tmp); | 29 | var tmp = new Uint8Array(size); // :ByteArray |
30 | this.pps.push(tmp); | ||
27 | return tmp; | 31 | return tmp; |
28 | }; | 32 | }; |
29 | 33 | ||
30 | this.extraDataExists = function() { // :Boolean | 34 | this.extraDataExists = function() { // :Boolean |
31 | return 0 < sps.length; | 35 | return 0 < this.sps.length; |
32 | }; | 36 | }; |
33 | 37 | ||
34 | // (sizeOfScalingList:int, expGolomb:ExpGolomb):void | 38 | // (sizeOfScalingList:int, expGolomb:ExpGolomb):void |
... | @@ -53,18 +57,20 @@ | ... | @@ -53,18 +57,20 @@ |
53 | }; | 57 | }; |
54 | 58 | ||
55 | this.getSps0Rbsp = function() { // :ByteArray | 59 | this.getSps0Rbsp = function() { // :ByteArray |
56 | // remove emulation bytes. Is this nesessary? is there ever emulation bytes in the SPS? | 60 | // remove emulation bytes. Is this nesessary? is there ever emulation |
61 | // bytes in the SPS? | ||
57 | var | 62 | var |
58 | sps0 = sps[0],// :ByteArray = sps[0]; | 63 | spsCount = 0, |
64 | sps0 = this.sps[0], // :ByteArray | ||
65 | rbspCount = 0, | ||
59 | s, // :uint | 66 | s, // :uint |
60 | e, // :uint | 67 | e, // :uint |
61 | rbsp, // :ByteArray | 68 | rbsp, // :ByteArray |
62 | o; // :uint | 69 | o; // :uint |
63 | 70 | ||
64 | sps0.position = 1; | 71 | s = 1; |
65 | s = sps0.position; | 72 | e = sps0.byteLength - 2; |
66 | e = sps0.bytesAvailable - 2; | 73 | rbsp = new Uint8Array(sps0.byteLength); // new ByteArray(); |
67 | rbsp = new Uint8Array(); // new ByteArray(); | ||
68 | 74 | ||
69 | for (o = s ; o < e ;) { | 75 | for (o = s ; o < e ;) { |
70 | if (3 !== sps0[o + 2]) { | 76 | if (3 !== sps0[o + 2]) { |
... | @@ -73,27 +79,32 @@ | ... | @@ -73,27 +79,32 @@ |
73 | o += 2; | 79 | o += 2; |
74 | } else if (0 !== sps0[o + 0]) { | 80 | } else if (0 !== sps0[o + 0]) { |
75 | o += 1; | 81 | o += 1; |
76 | } else { // found emulation bytess | 82 | } else { |
77 | rbsp.writeShort(0x0000); | 83 | console.log('found emulation bytes'); |
78 | 84 | ||
79 | if ( o > s ) { | 85 | rbsp.set([0x00, 0x00], rbspCount); |
86 | spsCount += 2; | ||
87 | rbspCount += 2; | ||
88 | |||
89 | if (o > s) { | ||
80 | // If there are bytes to write, write them | 90 | // If there are bytes to write, write them |
81 | sps0.readBytes( rbsp, rbsp.length, o-s ); | 91 | rbsp.set(sps0.subarray(0, o - s), rbspCount); |
92 | spsCount += o - s; | ||
93 | rbspCount += o - s; | ||
82 | } | 94 | } |
83 | 95 | ||
84 | // skip the emulation bytes | 96 | // skip the emulation bytes |
85 | sps0.position += 3; | 97 | o += 3; |
86 | o = s = sps0.position; | 98 | s = o; |
87 | } | 99 | } |
88 | } | 100 | } |
89 | 101 | ||
90 | // copy any remaining bytes | 102 | // copy any remaining bytes |
91 | sps0.readBytes(rbsp, rbsp.length); | 103 | rbsp.set(sps0.subarray(spsCount), rbspCount); // sps0.readBytes(rbsp, rbsp.length); |
92 | sps0.position = 0; | ||
93 | return rbsp; | 104 | return rbsp; |
94 | }; | 105 | }; |
95 | 106 | ||
96 | //(pts:uint):FlvTag | 107 | // (pts:uint):FlvTag |
97 | this.metaDataTag = function(pts) { | 108 | this.metaDataTag = function(pts) { |
98 | var | 109 | var |
99 | tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag | 110 | tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag |
... | @@ -144,7 +155,7 @@ | ... | @@ -144,7 +155,7 @@ |
144 | expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 | 155 | expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 |
145 | expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 | 156 | expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 |
146 | expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag | 157 | expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag |
147 | if ( expGolomb.readBoolean() ) { // seq_scaling_matrix_present_flag | 158 | if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag |
148 | imax = (chroma_format_idc !== 3) ? 8 : 12; | 159 | imax = (chroma_format_idc !== 3) ? 8 : 12; |
149 | for (i = 0 ; i < imax ; ++i) { | 160 | for (i = 0 ; i < imax ; ++i) { |
150 | if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ] | 161 | if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ] |
... | @@ -214,22 +225,38 @@ | ... | @@ -214,22 +225,38 @@ |
214 | tag.pts = pts; | 225 | tag.pts = pts; |
215 | 226 | ||
216 | tag.writeByte(0x01);// version | 227 | tag.writeByte(0x01);// version |
217 | tag.writeByte(sps[0][1]);// profile | 228 | tag.writeByte(this.sps[0][1]);// profile |
218 | tag.writeByte(sps[0][2]);// compatibility | 229 | tag.writeByte(this.sps[0][2]);// compatibility |
219 | tag.writeByte(sps[0][3]);// level | 230 | tag.writeByte(this.sps[0][3]);// level |
220 | tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) | 231 | tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) |
221 | tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits) | 232 | tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits) |
222 | tag.writeShort( sps[0].length ); // data of SPS | 233 | tag.writeShort( this.sps[0].length ); // data of SPS |
223 | tag.writeBytes( sps[0] ); // SPS | 234 | tag.writeBytes( this.sps[0] ); // SPS |
224 | 235 | ||
225 | tag.writeByte( pps.length ); // num of PPS (will there ever be more that 1 PPS?) | 236 | tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?) |
226 | for (i = 0 ; i < pps.length ; ++i) { | 237 | for (i = 0 ; i < this.pps.length ; ++i) { |
227 | tag.writeShort(pps[i].length); // 2 bytes for length of PPS | 238 | tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS |
228 | tag.writeBytes(pps[i]); // data of PPS | 239 | tag.writeBytes(this.pps[i]); // data of PPS |
229 | } | 240 | } |
230 | 241 | ||
231 | return tag; | 242 | return tag; |
232 | }; | 243 | }; |
244 | }, | ||
245 | |||
246 | // incomplete, see Table 7.1 of ITU-T H.264 for 12-32 | ||
247 | NALUnitType = { | ||
248 | unspecified: 0, | ||
249 | slice_layer_without_partitioning_rbsp_non_idr: 1, | ||
250 | slice_data_partition_a_layer_rbsp: 2, | ||
251 | slice_data_partition_b_layer_rbsp: 3, | ||
252 | slice_data_partition_c_layer_rbsp: 4, | ||
253 | slice_layer_without_partitioning_rbsp_idr: 5, | ||
254 | sei_rbsp: 6, | ||
255 | seq_parameter_set_rbsp: 7, | ||
256 | pic_parameter_set_rbsp: 8, | ||
257 | access_unit_delimiter_rbsp: 9, | ||
258 | end_of_seq_rbsp: 10, | ||
259 | end_of_stream_rbsp: 11 | ||
233 | }; | 260 | }; |
234 | 261 | ||
235 | window.videojs.hls.H264Stream = function() { | 262 | window.videojs.hls.H264Stream = function() { |
... | @@ -270,17 +297,18 @@ | ... | @@ -270,17 +297,18 @@ |
270 | }; | 297 | }; |
271 | 298 | ||
272 | this.finishFrame = function() { | 299 | this.finishFrame = function() { |
273 | if (null !== h264Frame) { | 300 | console.log('finish frame'); |
301 | if (h264Frame) { | ||
274 | // Push SPS before EVERY IDR frame fo seeking | 302 | // Push SPS before EVERY IDR frame fo seeking |
275 | if (newExtraData.extraDataExists()) { | 303 | if (newExtraData.extraDataExists()) { |
276 | oldExtraData = newExtraData; | 304 | oldExtraData = newExtraData; |
277 | newExtraData = new H264ExtraData(); | 305 | newExtraData = new H264ExtraData(); |
278 | } | 306 | } |
279 | 307 | ||
280 | if(true === h264Frame.keyFrame) { | 308 | if (h264Frame.keyFrame) { |
281 | // Push extra data on every IDR frame in case we did a stream change + seek | 309 | // Push extra data on every IDR frame in case we did a stream change + seek |
282 | tags.push( oldExtraData.metaDataTag (h264Frame.pts) ); | 310 | tags.push(oldExtraData.metaDataTag(h264Frame.pts)); |
283 | tags.push( oldExtraData.extraDataTag(h264Frame.pts) ); | 311 | tags.push(oldExtraData.extraDataTag(h264Frame.pts)); |
284 | } | 312 | } |
285 | 313 | ||
286 | h264Frame.endNalUnit(); | 314 | h264Frame.endNalUnit(); |
... | @@ -292,31 +320,35 @@ | ... | @@ -292,31 +320,35 @@ |
292 | state = 0; | 320 | state = 0; |
293 | }; | 321 | }; |
294 | 322 | ||
295 | // (pData:ByteArray, o:int, l:int):void | 323 | // (data:ByteArray, o:int, l:int):void |
296 | this.writeBytes = function(pData, o, l) { | 324 | this.writeBytes = function(data, o, l) { |
297 | var | 325 | var |
298 | nalUnitSize, // :uint | 326 | nalUnitSize, // :uint |
299 | s, // :uint | 327 | s, // :uint |
300 | e, // :uint | 328 | e, // :uint |
301 | t; // :int | 329 | t; // :int |
302 | 330 | ||
303 | if (0 >= l) { | 331 | if (l <= 0) { |
332 | // data is empty so there's nothing to write | ||
304 | return; | 333 | return; |
305 | } | 334 | } |
335 | |||
336 | // scan through the bytes until we find the start code (0x000001) for a | ||
337 | // NAL unit and then begin writing it out | ||
306 | switch (state) { | 338 | switch (state) { |
307 | default: | 339 | default: |
308 | state = 1; | 340 | /* falls through */ |
309 | break; | ||
310 | case 0: | 341 | case 0: |
311 | state = 1; | 342 | state = 1; |
312 | break; | 343 | /* falls through */ |
313 | /*--------------------------------------------------------------------------------------------------------------------*/ | 344 | case 1: |
314 | case 1: // We are looking for overlaping start codes | 345 | // A NAL unit may be split across two TS packets. Look back a bit to |
315 | if (1 >= pData[o]) { | 346 | // make sure the prefix of the start code wasn't already written out. |
316 | nalUnitSize = (null === h264Frame) ? 0 : h264Frame.nalUnitSize(); | 347 | if (data[o] <= 1) { |
317 | if (1 <= nalUnitSize && 0 === h264Frame.negIndex(1)) { | 348 | nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0; |
349 | if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) { | ||
318 | // ?? ?? 00 | O[01] ?? ?? | 350 | // ?? ?? 00 | O[01] ?? ?? |
319 | if (1 === pData[o] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2) ) { | 351 | if (1 === data[o] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { |
320 | // ?? 00 00 : 01 | 352 | // ?? 00 00 : 01 |
321 | if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) { | 353 | if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) { |
322 | h264Frame.length -= 3; // 00 00 00 : 01 | 354 | h264Frame.length -= 3; // 00 00 00 : 01 |
... | @@ -325,10 +357,10 @@ | ... | @@ -325,10 +357,10 @@ |
325 | } | 357 | } |
326 | 358 | ||
327 | state = 3; | 359 | state = 3; |
328 | return this.writeBytes(pData, o + 1, l - 1); | 360 | return this.writeBytes(data, o + 1, l - 1); |
329 | } | 361 | } |
330 | 362 | ||
331 | if (1 < l && 0 === pData[o] && 1 === pData[o + 1]) { | 363 | if (1 < l && 0 === data[o] && 1 === data[o + 1]) { |
332 | // ?? 00 | 00 01 | 364 | // ?? 00 | 00 01 |
333 | if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { | 365 | if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) { |
334 | h264Frame.length -= 2; // 00 00 : 00 01 | 366 | h264Frame.length -= 2; // 00 00 : 00 01 |
... | @@ -337,71 +369,77 @@ | ... | @@ -337,71 +369,77 @@ |
337 | } | 369 | } |
338 | 370 | ||
339 | state = 3; | 371 | state = 3; |
340 | return this.writeBytes(pData, o + 2, l - 2); | 372 | return this.writeBytes(data, o + 2, l - 2); |
341 | } | 373 | } |
342 | 374 | ||
343 | if (2 < l && 0 === pData[o] && 0 === pData[o + 1] && 1 === pData[o + 2]) { | 375 | if (2 < l && 0 === data[o] && 0 === data[o + 1] && 1 === data[o + 2]) { |
344 | // 00 | 00 00 01 | 376 | // 00 | 00 00 01 |
345 | h264Frame.length -= 1; | 377 | h264Frame.length -= 1; |
346 | state = 3; | 378 | state = 3; |
347 | return this.writeBytes(pData, o + 3, l - 3); | 379 | return this.writeBytes(data, o + 3, l - 3); |
348 | } | 380 | } |
349 | } | 381 | } |
350 | } | 382 | } |
351 | // allow fall through if the above fails, we may end up checking a few | 383 | // allow fall through if the above fails, we may end up checking a few |
352 | // bytes a second time. But that case will be VERY rare | 384 | // bytes a second time. But that case will be VERY rare |
353 | state = 2; | 385 | state = 2; |
354 | break; | 386 | /* falls through */ |
355 | case 2: // Look for start codes in pData | 387 | case 2: // Look for start codes in data |
356 | s = o; // s = Start | 388 | s = o; // s = Start |
357 | e = s + l; // e = End | 389 | e = s + l; // e = End |
358 | for (t = e - 3 ; o < t ;) { | 390 | for (t = e - 3 ; o < t ;) { |
359 | if (1 < pData[o + 2]) { | 391 | if (1 < data[o + 2]) { |
360 | o += 3; // if pData[o+2] is greater than 1, there is no way a start code can begin before o+3 | 392 | o += 3; // if data[o + 2] is greater than 1, there is no way a start code can begin before o+3 |
361 | } else if (0 !== pData[o + 1]) { | 393 | } else if (0 !== data[o + 1]) { |
362 | o += 2; | 394 | o += 2; |
363 | } else if (0 !== pData[o]) { | 395 | } else if (0 !== data[o]) { |
364 | o += 1; | 396 | o += 1; |
365 | } else { | 397 | } else { |
366 | // If we get here we have 00 00 00 or 00 00 01 | 398 | // If we get here we have 00 00 00 or 00 00 01 |
367 | if (1 === pData[o + 2]) { | 399 | if (1 === data[o + 2]) { |
368 | if (o > s) { | 400 | if (o > s) { |
369 | h264Frame.writeBytes(pData, s, o - s); | 401 | h264Frame.writeBytes(data, s, o - s); |
370 | } | 402 | } |
371 | state = 3; o += 3; | 403 | state = 3; |
372 | return this.writeBytes(pData, o, e - o); | 404 | o += 3; |
405 | return this.writeBytes(data, o, e - o); | ||
373 | } | 406 | } |
374 | 407 | ||
375 | if (4 <= e-o && 0 === pData[o + 2] && 1 === pData[o + 3]) { | 408 | if (e - o >= 4 && 0 === data[o + 2] && 1 === data[o + 3]) { |
376 | if (o > s) { | 409 | if (o > s) { |
377 | h264Frame.writeBytes(pData, s, o - s); | 410 | h264Frame.writeBytes(data, s, o - s); |
378 | } | 411 | } |
379 | state = 3; | 412 | state = 3; |
380 | o += 4; | 413 | o += 4; |
381 | return this.writeBytes(pData, o, e - o); | 414 | return this.writeBytes(data, o, e - o); |
382 | } | 415 | } |
383 | 416 | ||
384 | // We are at the end of the buffer, or we have 3 NULLS followed by something that is not a 1, eaither way we can step forward by at least 3 | 417 | // We are at the end of the buffer, or we have 3 NULLS followed by |
418 | // something that is not a 1, either way we can step forward by at | ||
419 | // least 3 | ||
385 | o += 3; | 420 | o += 3; |
386 | } | 421 | } |
387 | } | 422 | } |
388 | 423 | ||
389 | // We did not find any start codes. Try again next packet | 424 | // We did not find any start codes. Try again next packet |
390 | state = 1; | 425 | state = 1; |
391 | h264Frame.writeBytes( pData, s, l ); | 426 | h264Frame.writeBytes(data, s, l); |
392 | return; | 427 | return; |
393 | /*--------------------------------------------------------------------------------------------------------------------*/ | 428 | case 3: |
394 | case 3: // The next byte is the first byte of a NAL Unit | 429 | // The next byte is the first byte of a NAL Unit |
395 | if (null !== h264Frame) { | 430 | |
431 | if (h264Frame) { | ||
432 | // we've come to a new NAL unit so finish up the one we've been | ||
433 | // working on | ||
434 | |||
396 | switch (nalUnitType) { | 435 | switch (nalUnitType) { |
397 | // We are still operating on the previous NAL Unit | 436 | case NALUnitType.seq_parameter_set_rbsp: |
398 | case 7: | 437 | h264Frame.endNalUnit(newExtraData.sps); |
399 | h264Frame.endNalUnit(newExtraData.addSPS()); | ||
400 | break; | 438 | break; |
401 | case 8: | 439 | case NALUnitType.pic_parameter_set_rbsp: |
402 | h264Frame.endNalUnit(newExtraData.addPPS()); | 440 | h264Frame.endNalUnit(newExtraData.pps); |
403 | break; | 441 | break; |
404 | case 5: | 442 | case NALUnitType.slice_layer_without_partitioning_rbsp_idr: |
405 | h264Frame.keyFrame = true; | 443 | h264Frame.keyFrame = true; |
406 | h264Frame.endNalUnit(); | 444 | h264Frame.endNalUnit(); |
407 | break; | 445 | break; |
... | @@ -411,14 +449,14 @@ | ... | @@ -411,14 +449,14 @@ |
411 | } | 449 | } |
412 | } | 450 | } |
413 | 451 | ||
414 | nalUnitType = pData[o] & 0x1F; | 452 | // setup to begin processing the new NAL unit |
415 | if ( null != h264Frame && 9 === nalUnitType ) { | 453 | nalUnitType = data[o] & 0x1F; |
454 | if (h264Frame && 9 === nalUnitType) { | ||
416 | this.finishFrame(); // We are starting a new access unit. Flush the previous one | 455 | this.finishFrame(); // We are starting a new access unit. Flush the previous one |
417 | } | 456 | } |
418 | 457 | ||
419 | // finishFrame may render h264Frame null, so we must test again | 458 | // finishFrame may render h264Frame null, so we must test again |
420 | if ( null === h264Frame ) | 459 | if (!h264Frame) { |
421 | { | ||
422 | h264Frame = new FlvTag(FlvTag.VIDEO_TAG); | 460 | h264Frame = new FlvTag(FlvTag.VIDEO_TAG); |
423 | h264Frame.pts = next_pts; | 461 | h264Frame.pts = next_pts; |
424 | h264Frame.dts = next_dts; | 462 | h264Frame.dts = next_dts; |
... | @@ -426,7 +464,7 @@ | ... | @@ -426,7 +464,7 @@ |
426 | 464 | ||
427 | h264Frame.startNalUnit(); | 465 | h264Frame.startNalUnit(); |
428 | state = 2; // We know there will not be an overlapping start code, so we can skip that test | 466 | state = 2; // We know there will not be an overlapping start code, so we can skip that test |
429 | return this.writeBytes(pData, o, l); | 467 | return this.writeBytes(data, o, l); |
430 | /*--------------------------------------------------------------------------------------------------------------------*/ | 468 | /*--------------------------------------------------------------------------------------------------------------------*/ |
431 | } // switch | 469 | } // switch |
432 | }; | 470 | }; | ... | ... |
... | @@ -187,7 +187,7 @@ | ... | @@ -187,7 +187,7 @@ |
187 | } | 187 | } |
188 | 188 | ||
189 | // attempt to parse a m2ts packet | 189 | // attempt to parse a m2ts packet |
190 | if (parseTSPacket(data.subarray(dataPosition, m2tsPacketSize))) { | 190 | if (parseTSPacket(data.subarray(dataPosition, dataPosition + m2tsPacketSize))) { |
191 | dataPosition += m2tsPacketSize; | 191 | dataPosition += m2tsPacketSize; |
192 | } else { | 192 | } else { |
193 | // If there was an error parsing a TS packet. it could be | 193 | // If there was an error parsing a TS packet. it could be |
... | @@ -203,7 +203,7 @@ | ... | @@ -203,7 +203,7 @@ |
203 | // packet! | 203 | // packet! |
204 | parseTSPacket = function(data) { // :ByteArray):Boolean { | 204 | parseTSPacket = function(data) { // :ByteArray):Boolean { |
205 | var | 205 | var |
206 | s = data.position, //:uint | 206 | s = 0, //:uint |
207 | o = s, // :uint | 207 | o = s, // :uint |
208 | e = o + m2tsPacketSize, // :uint | 208 | e = o + m2tsPacketSize, // :uint |
209 | 209 | ||
... | @@ -211,10 +211,10 @@ | ... | @@ -211,10 +211,10 @@ |
211 | // parseSegmentBinaryData() | 211 | // parseSegmentBinaryData() |
212 | 212 | ||
213 | // Payload Unit Start Indicator | 213 | // Payload Unit Start Indicator |
214 | pusi = !!(data[o+1] & 0x40), | 214 | pusi = !!(data[o + 1] & 0x40), // mask: 0100 0000 |
215 | 215 | ||
216 | // PacketId | 216 | // PacketId |
217 | pid = (data[o + 1] & 0x1F) << 8 | data[o+2], | 217 | pid = (data[o + 1] & 0x1F) << 8 | data[o + 2], // mask: 0001 1111 |
218 | afflag = (data[o + 3] & 0x30 ) >>> 4, | 218 | afflag = (data[o + 3] & 0x30 ) >>> 4, |
219 | 219 | ||
220 | aflen, // :uint | 220 | aflen, // :uint |
... | @@ -259,7 +259,7 @@ | ... | @@ -259,7 +259,7 @@ |
259 | 259 | ||
260 | console.assert(0x00 === patTableId, 'patTableId should be 0x00'); | 260 | console.assert(0x00 === patTableId, 'patTableId should be 0x00'); |
261 | 261 | ||
262 | patCurrentNextIndicator = !!(data[o+5] & 0x01); | 262 | patCurrentNextIndicator = !!(data[o + 5] & 0x01); |
263 | if (patCurrentNextIndicator) { | 263 | if (patCurrentNextIndicator) { |
264 | patSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2]; | 264 | patSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2]; |
265 | o += 8; // skip past PSI header | 265 | o += 8; // skip past PSI header |
... | @@ -280,10 +280,10 @@ | ... | @@ -280,10 +280,10 @@ |
280 | } else if (videoPid === pid || audioPid === pid) { | 280 | } else if (videoPid === pid || audioPid === pid) { |
281 | if (pusi) { | 281 | if (pusi) { |
282 | // comment out for speed | 282 | // comment out for speed |
283 | // if( 0x00 != data[o+0] || 0x00 != data[o+1] || 0x01 != data[o+2] ) | 283 | if (0x00 !== data[o + 0] || 0x00 !== data[o + 1] || 0x01 !== data[o + 2]) { |
284 | // {// look for PES start code | 284 | // look for PES start code |
285 | // throw new Error("PES did not begin with start code"); | 285 | throw new Error("PES did not begin with start code"); |
286 | // } | 286 | } |
287 | 287 | ||
288 | // var sid:int = data[o+3]; // StreamID | 288 | // var sid:int = data[o+3]; // StreamID |
289 | pesPacketSize = (data[o + 4] << 8) | data[o + 5]; | 289 | pesPacketSize = (data[o + 4] << 8) | data[o + 5]; |
... | @@ -334,14 +334,14 @@ | ... | @@ -334,14 +334,14 @@ |
334 | } | 334 | } |
335 | 335 | ||
336 | if (audioPid === pid) { | 336 | if (audioPid === pid) { |
337 | aacStream.writeBytes(data,o,e-o); | 337 | aacStream.writeBytes(data, o, e - o); |
338 | } else if (videoPid === pid) { | 338 | } else if (videoPid === pid) { |
339 | h264Stream.writeBytes(data,o,e-o); | 339 | h264Stream.writeBytes(data, o, e - o); |
340 | } | 340 | } |
341 | } else if (pmtPid === pid) { | 341 | } else if (pmtPid === pid) { |
342 | // TODO sanity check data[o] | 342 | // TODO sanity check data[o] |
343 | // if pusi is set we must skip X bytes (PSI pointer field) | 343 | // if pusi is set we must skip X bytes (PSI pointer field) |
344 | o += ( pusi ? 1 + data[o] : 0 ); | 344 | o += (pusi ? 1 + data[o] : 0); |
345 | pmtTableId = data[o]; | 345 | pmtTableId = data[o]; |
346 | 346 | ||
347 | console.assert(0x02 === pmtTableId); | 347 | console.assert(0x02 === pmtTableId); |
... | @@ -349,7 +349,7 @@ | ... | @@ -349,7 +349,7 @@ |
349 | pmtCurrentNextIndicator = !!(data[o + 5] & 0x01); | 349 | pmtCurrentNextIndicator = !!(data[o + 5] & 0x01); |
350 | if (pmtCurrentNextIndicator) { | 350 | if (pmtCurrentNextIndicator) { |
351 | audioPid = videoPid = 0; | 351 | audioPid = videoPid = 0; |
352 | pmtSectionLength = (data[o + 1] & 0x0F ) << 8 | data[o + 2]; | 352 | pmtSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2]; |
353 | // skip CRC and PSI data we dont care about | 353 | // skip CRC and PSI data we dont care about |
354 | pmtSectionLength -= 13; | 354 | pmtSectionLength -= 13; |
355 | 355 | ||
... | @@ -357,7 +357,7 @@ | ... | @@ -357,7 +357,7 @@ |
357 | while (0 < pmtSectionLength) { | 357 | while (0 < pmtSectionLength) { |
358 | streamType = data[o + 0]; | 358 | streamType = data[o + 0]; |
359 | elementaryPID = (data[o + 1] & 0x1F) << 8 | data[o + 2]; | 359 | elementaryPID = (data[o + 1] & 0x1F) << 8 | data[o + 2]; |
360 | ESInfolength = (data[o + 3] & 0x0F ) << 8 | data[o + 4]; | 360 | ESInfolength = (data[o + 3] & 0x0F) << 8 | data[o + 4]; |
361 | o += 5 + ESInfolength; | 361 | o += 5 + ESInfolength; |
362 | pmtSectionLength -= 5 + ESInfolength; | 362 | pmtSectionLength -= 5 + ESInfolength; |
363 | 363 | ... | ... |
test/bipbop0.flv
0 → 100644
No preview for this file type
test/bipbop0.ts
0 → 100644
No preview for this file type
test/bipbop0.ts.b64
0 → 100644
This diff could not be displayed because it is too large.
... | @@ -39,9 +39,10 @@ | ... | @@ -39,9 +39,10 @@ |
39 | 39 | ||
40 | <!-- HLS plugin --> | 40 | <!-- HLS plugin --> |
41 | <script src="../src/video-js-hls.js"></script> | 41 | <script src="../src/video-js-hls.js"></script> |
42 | <script src="../src/flv-tag.js"></script> | ||
43 | <script src="../src/exp-golomb.js"></script> | ||
42 | <script src="../src/h264-stream.js"></script> | 44 | <script src="../src/h264-stream.js"></script> |
43 | <script src="../src/aac-stream.js"></script> | 45 | <script src="../src/aac-stream.js"></script> |
44 | <script src="../src/flv-tag.js"></script> | ||
45 | <script src="../src/segment-parser.js"></script> | 46 | <script src="../src/segment-parser.js"></script> |
46 | <!-- an example MPEG2-TS segment --> | 47 | <!-- an example MPEG2-TS segment --> |
47 | <script src="tsSegment.js"></script> | 48 | <script src="tsSegment.js"></script> | ... | ... |
... | @@ -19,7 +19,13 @@ | ... | @@ -19,7 +19,13 @@ |
19 | notStrictEqual(actual, expected, [message]) | 19 | notStrictEqual(actual, expected, [message]) |
20 | throws(block, [expected], [message]) | 20 | throws(block, [expected], [message]) |
21 | */ | 21 | */ |
22 | var parser; | 22 | var |
23 | parser, | ||
24 | |||
25 | expectedHeader = [ | ||
26 | 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, | ||
27 | 0x09, 0x00, 0x00, 0x00, 0x00 | ||
28 | ]; | ||
23 | 29 | ||
24 | module('environment'); | 30 | module('environment'); |
25 | 31 | ||
... | @@ -35,17 +41,20 @@ | ... | @@ -35,17 +41,20 @@ |
35 | }); | 41 | }); |
36 | 42 | ||
37 | test('creates an flv header', function() { | 43 | test('creates an flv header', function() { |
38 | var header = parser.getFlvHeader(); | 44 | var header = Array.prototype.slice.call(parser.getFlvHeader()); |
39 | ok(header, 'the header is truthy'); | 45 | ok(header, 'the header is truthy'); |
40 | equal(9 + 4, header.byteLength, 'the header length is correct'); | 46 | equal(9 + 4, header.length, 'the header length is correct'); |
41 | equal(header[0], 'F'.charCodeAt(0), 'the signature is correct'); | 47 | equal(header[0], 'F'.charCodeAt(0), 'the signature is correct'); |
42 | equal(header[1], 'L'.charCodeAt(0), 'the signature is correct'); | 48 | equal(header[1], 'L'.charCodeAt(0), 'the signature is correct'); |
43 | equal(header[2], 'V'.charCodeAt(0), 'the signature is correct'); | 49 | equal(header[2], 'V'.charCodeAt(0), 'the signature is correct'); |
50 | |||
51 | deepEqual(expectedHeader, header, 'the rest of the header is correct'); | ||
44 | }); | 52 | }); |
45 | 53 | ||
46 | test('parses the first bipbop segment', function() { | 54 | test('parses the first bipbop segment', function() { |
55 | var tag, bytes; | ||
47 | parser.parseSegmentBinaryData(window.testSegment); | 56 | parser.parseSegmentBinaryData(window.testSegment); |
48 | 57 | ||
49 | ok(parser.tagsAvailable(), 'tags should be available'); | 58 | ok(parser.tagsAvailable(), 'tags are available'); |
50 | }); | 59 | }); |
51 | })(this); | 60 | })(this); | ... | ... |
-
Please register or sign in to post a comment