Merge pull request #405 from dmlap/remove-transmuxing
Remove old transmuxing infrastructure
Showing
35 changed files
with
0 additions
and
11269 deletions
src/aac-stream.js
deleted
100644 → 0
1 | (function(window) { | ||
2 | var | ||
3 | FlvTag = window.videojs.Hls.FlvTag, | ||
4 | adtsSampleingRates = [ | ||
5 | 96000, 88200, | ||
6 | 64000, 48000, | ||
7 | 44100, 32000, | ||
8 | 24000, 22050, | ||
9 | 16000, 12000 | ||
10 | ]; | ||
11 | |||
12 | window.videojs.Hls.AacStream = function() { | ||
13 | var | ||
14 | next_pts, // :uint | ||
15 | state, // :uint | ||
16 | pes_length, // :int | ||
17 | lastMetaPts, | ||
18 | |||
19 | adtsProtectionAbsent, // :Boolean | ||
20 | adtsObjectType, // :int | ||
21 | adtsSampleingIndex, // :int | ||
22 | adtsChanelConfig, // :int | ||
23 | adtsFrameSize, // :int | ||
24 | adtsSampleCount, // :int | ||
25 | adtsDuration, // :int | ||
26 | |||
27 | aacFrame, // :FlvTag = null; | ||
28 | extraData; // :uint; | ||
29 | |||
30 | this.tags = []; | ||
31 | |||
32 | // (pts:uint):void | ||
33 | this.setTimeStampOffset = function(pts) { | ||
34 | |||
35 | // keep track of the last time a metadata tag was written out | ||
36 | // set the initial value so metadata will be generated before any | ||
37 | // payload data | ||
38 | lastMetaPts = pts - 1000; | ||
39 | }; | ||
40 | |||
41 | // (pts:uint, pes_size:int, dataAligned:Boolean):void | ||
42 | this.setNextTimeStamp = function(pts, pes_size, dataAligned) { | ||
43 | next_pts = pts; | ||
44 | pes_length = pes_size; | ||
45 | |||
46 | // If data is aligned, flush all internal buffers | ||
47 | if (dataAligned) { | ||
48 | state = 0; | ||
49 | } | ||
50 | }; | ||
51 | |||
52 | // (data:ByteArray, o:int = 0, l:int = 0):void | ||
53 | this.writeBytes = function(data, offset, length) { | ||
54 | var | ||
55 | end, // :int | ||
56 | newExtraData, // :uint | ||
57 | bytesToCopy; // :int | ||
58 | |||
59 | // default arguments | ||
60 | offset = offset || 0; | ||
61 | length = length || 0; | ||
62 | |||
63 | // Do not allow more than 'pes_length' bytes to be written | ||
64 | length = (pes_length < length ? pes_length : length); | ||
65 | pes_length -= length; | ||
66 | end = offset + length; | ||
67 | while (offset < end) { | ||
68 | switch (state) { | ||
69 | default: | ||
70 | state = 0; | ||
71 | break; | ||
72 | case 0: | ||
73 | if (offset >= end) { | ||
74 | return; | ||
75 | } | ||
76 | if (0xFF !== data[offset]) { | ||
77 | console.assert(false, 'Error no ATDS header found'); | ||
78 | offset += 1; | ||
79 | state = 0; | ||
80 | return; | ||
81 | } | ||
82 | |||
83 | offset += 1; | ||
84 | state = 1; | ||
85 | break; | ||
86 | case 1: | ||
87 | if (offset >= end) { | ||
88 | return; | ||
89 | } | ||
90 | if (0xF0 !== (data[offset] & 0xF0)) { | ||
91 | console.assert(false, 'Error no ATDS header found'); | ||
92 | offset +=1; | ||
93 | state = 0; | ||
94 | return; | ||
95 | } | ||
96 | |||
97 | adtsProtectionAbsent = !!(data[offset] & 0x01); | ||
98 | |||
99 | offset += 1; | ||
100 | state = 2; | ||
101 | break; | ||
102 | case 2: | ||
103 | if (offset >= end) { | ||
104 | return; | ||
105 | } | ||
106 | adtsObjectType = ((data[offset] & 0xC0) >>> 6) + 1; | ||
107 | adtsSampleingIndex = ((data[offset] & 0x3C) >>> 2); | ||
108 | adtsChanelConfig = ((data[offset] & 0x01) << 2); | ||
109 | |||
110 | offset += 1; | ||
111 | state = 3; | ||
112 | break; | ||
113 | case 3: | ||
114 | if (offset >= end) { | ||
115 | return; | ||
116 | } | ||
117 | adtsChanelConfig |= ((data[offset] & 0xC0) >>> 6); | ||
118 | adtsFrameSize = ((data[offset] & 0x03) << 11); | ||
119 | |||
120 | offset += 1; | ||
121 | state = 4; | ||
122 | break; | ||
123 | case 4: | ||
124 | if (offset >= end) { | ||
125 | return; | ||
126 | } | ||
127 | adtsFrameSize |= (data[offset] << 3); | ||
128 | |||
129 | offset += 1; | ||
130 | state = 5; | ||
131 | break; | ||
132 | case 5: | ||
133 | if(offset >= end) { | ||
134 | return; | ||
135 | } | ||
136 | adtsFrameSize |= ((data[offset] & 0xE0) >>> 5); | ||
137 | adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9); | ||
138 | |||
139 | offset += 1; | ||
140 | state = 6; | ||
141 | break; | ||
142 | case 6: | ||
143 | if (offset >= end) { | ||
144 | return; | ||
145 | } | ||
146 | adtsSampleCount = ((data[offset] & 0x03) + 1) * 1024; | ||
147 | adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex]; | ||
148 | |||
149 | newExtraData = (adtsObjectType << 11) | | ||
150 | (adtsSampleingIndex << 7) | | ||
151 | (adtsChanelConfig << 3); | ||
152 | |||
153 | // write out metadata tags every 1 second so that the decoder | ||
154 | // is re-initialized quickly after seeking into a different | ||
155 | // audio configuration | ||
156 | if (newExtraData !== extraData || next_pts - lastMetaPts >= 1000) { | ||
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 | // For audio, DTS is always the same as PTS. We want to set the DTS | ||
173 | // however so we can compare with video DTS to determine approximate | ||
174 | // packet order | ||
175 | aacFrame.pts = next_pts; | ||
176 | aacFrame.dts = aacFrame.pts; | ||
177 | |||
178 | aacFrame.view.setUint16(aacFrame.position, newExtraData); | ||
179 | aacFrame.position += 2; | ||
180 | aacFrame.length = Math.max(aacFrame.length, aacFrame.position); | ||
181 | |||
182 | this.tags.push(aacFrame); | ||
183 | |||
184 | lastMetaPts = next_pts; | ||
185 | } | ||
186 | |||
187 | // Skip the checksum if there is one | ||
188 | offset += 1; | ||
189 | state = 7; | ||
190 | break; | ||
191 | case 7: | ||
192 | if (!adtsProtectionAbsent) { | ||
193 | if (2 > (end - offset)) { | ||
194 | return; | ||
195 | } else { | ||
196 | offset += 2; | ||
197 | } | ||
198 | } | ||
199 | |||
200 | aacFrame = new FlvTag(FlvTag.AUDIO_TAG); | ||
201 | aacFrame.pts = next_pts; | ||
202 | aacFrame.dts = next_pts; | ||
203 | state = 8; | ||
204 | break; | ||
205 | case 8: | ||
206 | while (adtsFrameSize) { | ||
207 | if (offset >= end) { | ||
208 | return; | ||
209 | } | ||
210 | bytesToCopy = (end - offset) < adtsFrameSize ? (end - offset) : adtsFrameSize; | ||
211 | aacFrame.writeBytes(data, offset, bytesToCopy); | ||
212 | offset += bytesToCopy; | ||
213 | adtsFrameSize -= bytesToCopy; | ||
214 | } | ||
215 | |||
216 | this.tags.push(aacFrame); | ||
217 | |||
218 | // finished with this frame | ||
219 | state = 0; | ||
220 | next_pts += adtsDuration; | ||
221 | } | ||
222 | } | ||
223 | }; | ||
224 | }; | ||
225 | |||
226 | })(this); |
src/exp-golomb.js
deleted
100644 → 0
1 | (function(window) { | ||
2 | |||
3 | /** | ||
4 | * Parser for exponential Golomb codes, a variable-bitwidth number encoding | ||
5 | * scheme used by h264. | ||
6 | */ | ||
7 | window.videojs.Hls.ExpGolomb = function(workingData) { | ||
8 | var | ||
9 | // the number of bytes left to examine in workingData | ||
10 | workingBytesAvailable = workingData.byteLength, | ||
11 | |||
12 | // the current word being examined | ||
13 | workingWord = 0, // :uint | ||
14 | |||
15 | // the number of bits left to examine in the current word | ||
16 | workingBitsAvailable = 0; // :uint; | ||
17 | |||
18 | // ():uint | ||
19 | this.length = function() { | ||
20 | return (8 * workingBytesAvailable); | ||
21 | }; | ||
22 | |||
23 | // ():uint | ||
24 | this.bitsAvailable = function() { | ||
25 | return (8 * workingBytesAvailable) + workingBitsAvailable; | ||
26 | }; | ||
27 | |||
28 | // ():void | ||
29 | this.loadWord = function() { | ||
30 | var | ||
31 | position = workingData.byteLength - workingBytesAvailable, | ||
32 | workingBytes = new Uint8Array(4), | ||
33 | availableBytes = Math.min(4, workingBytesAvailable); | ||
34 | |||
35 | if (availableBytes === 0) { | ||
36 | throw new Error('no bytes available'); | ||
37 | } | ||
38 | |||
39 | workingBytes.set(workingData.subarray(position, | ||
40 | position + availableBytes)); | ||
41 | workingWord = new DataView(workingBytes.buffer).getUint32(0); | ||
42 | |||
43 | // track the amount of workingData that has been processed | ||
44 | workingBitsAvailable = availableBytes * 8; | ||
45 | workingBytesAvailable -= availableBytes; | ||
46 | }; | ||
47 | |||
48 | // (count:int):void | ||
49 | this.skipBits = function(count) { | ||
50 | var skipBytes; // :int | ||
51 | if (workingBitsAvailable > count) { | ||
52 | workingWord <<= count; | ||
53 | workingBitsAvailable -= count; | ||
54 | } else { | ||
55 | count -= workingBitsAvailable; | ||
56 | skipBytes = count / 8; | ||
57 | |||
58 | count -= (skipBytes * 8); | ||
59 | workingBytesAvailable -= skipBytes; | ||
60 | |||
61 | this.loadWord(); | ||
62 | |||
63 | workingWord <<= count; | ||
64 | workingBitsAvailable -= count; | ||
65 | } | ||
66 | }; | ||
67 | |||
68 | // (size:int):uint | ||
69 | this.readBits = function(size) { | ||
70 | var | ||
71 | bits = Math.min(workingBitsAvailable, size), // :uint | ||
72 | valu = workingWord >>> (32 - bits); // :uint | ||
73 | |||
74 | console.assert(size < 32, 'Cannot read more than 32 bits at a time'); | ||
75 | |||
76 | workingBitsAvailable -= bits; | ||
77 | if (workingBitsAvailable > 0) { | ||
78 | workingWord <<= bits; | ||
79 | } else if (workingBytesAvailable > 0) { | ||
80 | this.loadWord(); | ||
81 | } | ||
82 | |||
83 | bits = size - bits; | ||
84 | if (bits > 0) { | ||
85 | return valu << bits | this.readBits(bits); | ||
86 | } else { | ||
87 | return valu; | ||
88 | } | ||
89 | }; | ||
90 | |||
91 | // ():uint | ||
92 | this.skipLeadingZeros = function() { | ||
93 | var leadingZeroCount; // :uint | ||
94 | for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) { | ||
95 | if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) { | ||
96 | // the first bit of working word is 1 | ||
97 | workingWord <<= leadingZeroCount; | ||
98 | workingBitsAvailable -= leadingZeroCount; | ||
99 | return leadingZeroCount; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | // we exhausted workingWord and still have not found a 1 | ||
104 | this.loadWord(); | ||
105 | return leadingZeroCount + this.skipLeadingZeros(); | ||
106 | }; | ||
107 | |||
108 | // ():void | ||
109 | this.skipUnsignedExpGolomb = function() { | ||
110 | this.skipBits(1 + this.skipLeadingZeros()); | ||
111 | }; | ||
112 | |||
113 | // ():void | ||
114 | this.skipExpGolomb = function() { | ||
115 | this.skipBits(1 + this.skipLeadingZeros()); | ||
116 | }; | ||
117 | |||
118 | // ():uint | ||
119 | this.readUnsignedExpGolomb = function() { | ||
120 | var clz = this.skipLeadingZeros(); // :uint | ||
121 | return this.readBits(clz + 1) - 1; | ||
122 | }; | ||
123 | |||
124 | // ():int | ||
125 | this.readExpGolomb = function() { | ||
126 | var valu = this.readUnsignedExpGolomb(); // :int | ||
127 | if (0x01 & valu) { | ||
128 | // the number is odd if the low order bit is set | ||
129 | return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2 | ||
130 | } else { | ||
131 | return -1 * (valu >>> 1); // divide by two then make it negative | ||
132 | } | ||
133 | }; | ||
134 | |||
135 | // Some convenience functions | ||
136 | // :Boolean | ||
137 | this.readBoolean = function() { | ||
138 | return 1 === this.readBits(1); | ||
139 | }; | ||
140 | |||
141 | // ():int | ||
142 | this.readUnsignedByte = function() { | ||
143 | return this.readBits(8); | ||
144 | }; | ||
145 | |||
146 | this.loadWord(); | ||
147 | |||
148 | }; | ||
149 | })(this); |
src/flv-tag.js
deleted
100644 → 0
1 | /** | ||
2 | * An object that stores the bytes of an FLV tag and methods for | ||
3 | * querying and manipulating that data. | ||
4 | * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf | ||
5 | */ | ||
6 | (function(window) { | ||
7 | |||
8 | window.videojs = window.videojs || {}; | ||
9 | window.videojs.Hls = window.videojs.Hls || {}; | ||
10 | |||
11 | var hls = window.videojs.Hls; | ||
12 | |||
13 | // (type:uint, extraData:Boolean = false) extends ByteArray | ||
14 | hls.FlvTag = function(type, extraData) { | ||
15 | var | ||
16 | // Counter if this is a metadata tag, nal start marker if this is a video | ||
17 | // tag. unused if this is an audio tag | ||
18 | adHoc = 0, // :uint | ||
19 | |||
20 | // checks whether the FLV tag has enough capacity to accept the proposed | ||
21 | // write and re-allocates the internal buffers if necessary | ||
22 | prepareWrite = function(flv, count) { | ||
23 | var | ||
24 | bytes, | ||
25 | minLength = flv.position + count; | ||
26 | if (minLength < flv.bytes.byteLength) { | ||
27 | // there's enough capacity so do nothing | ||
28 | return; | ||
29 | } | ||
30 | |||
31 | // allocate a new buffer and copy over the data that will not be modified | ||
32 | bytes = new Uint8Array(minLength * 2); | ||
33 | bytes.set(flv.bytes.subarray(0, flv.position), 0); | ||
34 | flv.bytes = bytes; | ||
35 | flv.view = new DataView(flv.bytes.buffer); | ||
36 | }, | ||
37 | |||
38 | // commonly used metadata properties | ||
39 | widthBytes = hls.FlvTag.widthBytes || new Uint8Array('width'.length), | ||
40 | heightBytes = hls.FlvTag.heightBytes || new Uint8Array('height'.length), | ||
41 | videocodecidBytes = hls.FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length), | ||
42 | i; | ||
43 | |||
44 | if (!hls.FlvTag.widthBytes) { | ||
45 | // calculating the bytes of common metadata names ahead of time makes the | ||
46 | // corresponding writes faster because we don't have to loop over the | ||
47 | // characters | ||
48 | // re-test with test/perf.html if you're planning on changing this | ||
49 | for (i = 0; i < 'width'.length; i++) { | ||
50 | widthBytes[i] = 'width'.charCodeAt(i); | ||
51 | } | ||
52 | for (i = 0; i < 'height'.length; i++) { | ||
53 | heightBytes[i] = 'height'.charCodeAt(i); | ||
54 | } | ||
55 | for (i = 0; i < 'videocodecid'.length; i++) { | ||
56 | videocodecidBytes[i] = 'videocodecid'.charCodeAt(i); | ||
57 | } | ||
58 | |||
59 | hls.FlvTag.widthBytes = widthBytes; | ||
60 | hls.FlvTag.heightBytes = heightBytes; | ||
61 | hls.FlvTag.videocodecidBytes = videocodecidBytes; | ||
62 | } | ||
63 | |||
64 | this.keyFrame = false; // :Boolean | ||
65 | |||
66 | switch(type) { | ||
67 | case hls.FlvTag.VIDEO_TAG: | ||
68 | this.length = 16; | ||
69 | break; | ||
70 | case hls.FlvTag.AUDIO_TAG: | ||
71 | this.length = 13; | ||
72 | this.keyFrame = true; | ||
73 | break; | ||
74 | case hls.FlvTag.METADATA_TAG: | ||
75 | this.length = 29; | ||
76 | this.keyFrame = true; | ||
77 | break; | ||
78 | default: | ||
79 | throw("Error Unknown TagType"); | ||
80 | } | ||
81 | |||
82 | this.bytes = new Uint8Array(16384); | ||
83 | this.view = new DataView(this.bytes.buffer); | ||
84 | this.bytes[0] = type; | ||
85 | this.position = this.length; | ||
86 | this.keyFrame = extraData; // Defaults to false | ||
87 | |||
88 | // presentation timestamp | ||
89 | this.pts = 0; | ||
90 | // decoder timestamp | ||
91 | this.dts = 0; | ||
92 | |||
93 | // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) | ||
94 | this.writeBytes = function(bytes, offset, length) { | ||
95 | var | ||
96 | start = offset || 0, | ||
97 | end; | ||
98 | length = length || bytes.byteLength; | ||
99 | end = start + length; | ||
100 | |||
101 | prepareWrite(this, length); | ||
102 | this.bytes.set(bytes.subarray(start, end), this.position); | ||
103 | |||
104 | this.position += length; | ||
105 | this.length = Math.max(this.length, this.position); | ||
106 | }; | ||
107 | |||
108 | // ByteArray#writeByte(value:int):void | ||
109 | this.writeByte = function(byte) { | ||
110 | prepareWrite(this, 1); | ||
111 | this.bytes[this.position] = byte; | ||
112 | this.position++; | ||
113 | this.length = Math.max(this.length, this.position); | ||
114 | }; | ||
115 | |||
116 | // ByteArray#writeShort(value:int):void | ||
117 | this.writeShort = function(short) { | ||
118 | prepareWrite(this, 2); | ||
119 | this.view.setUint16(this.position, short); | ||
120 | this.position += 2; | ||
121 | this.length = Math.max(this.length, this.position); | ||
122 | }; | ||
123 | |||
124 | // Negative index into array | ||
125 | // (pos:uint):int | ||
126 | this.negIndex = function(pos) { | ||
127 | return this.bytes[this.length - pos]; | ||
128 | }; | ||
129 | |||
130 | // The functions below ONLY work when this[0] == VIDEO_TAG. | ||
131 | // We are not going to check for that because we dont want the overhead | ||
132 | // (nal:ByteArray = null):int | ||
133 | this.nalUnitSize = function() { | ||
134 | if (adHoc === 0) { | ||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | return this.length - (adHoc + 4); | ||
139 | }; | ||
140 | |||
141 | this.startNalUnit = function() { | ||
142 | // remember position and add 4 bytes | ||
143 | if (adHoc > 0) { | ||
144 | throw new Error("Attempted to create new NAL wihout closing the old one"); | ||
145 | } | ||
146 | |||
147 | // reserve 4 bytes for nal unit size | ||
148 | adHoc = this.length; | ||
149 | this.length += 4; | ||
150 | this.position = this.length; | ||
151 | }; | ||
152 | |||
153 | // (nal:ByteArray = null):void | ||
154 | this.endNalUnit = function(nalContainer) { | ||
155 | var | ||
156 | nalStart, // :uint | ||
157 | nalLength; // :uint | ||
158 | |||
159 | // Rewind to the marker and write the size | ||
160 | if (this.length === adHoc + 4) { | ||
161 | // we started a nal unit, but didnt write one, so roll back the 4 byte size value | ||
162 | this.length -= 4; | ||
163 | } else if (adHoc > 0) { | ||
164 | nalStart = adHoc + 4; | ||
165 | nalLength = this.length - nalStart; | ||
166 | |||
167 | this.position = adHoc; | ||
168 | this.view.setUint32(this.position, nalLength); | ||
169 | this.position = this.length; | ||
170 | |||
171 | if (nalContainer) { | ||
172 | // Add the tag to the NAL unit | ||
173 | nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | adHoc = 0; | ||
178 | }; | ||
179 | |||
180 | /** | ||
181 | * Write out a 64-bit floating point valued metadata property. This method is | ||
182 | * called frequently during a typical parse and needs to be fast. | ||
183 | */ | ||
184 | // (key:String, val:Number):void | ||
185 | this.writeMetaDataDouble = function(key, val) { | ||
186 | var i; | ||
187 | prepareWrite(this, 2 + key.length + 9); | ||
188 | |||
189 | // write size of property name | ||
190 | this.view.setUint16(this.position, key.length); | ||
191 | this.position += 2; | ||
192 | |||
193 | // this next part looks terrible but it improves parser throughput by | ||
194 | // 10kB/s in my testing | ||
195 | |||
196 | // write property name | ||
197 | if (key === 'width') { | ||
198 | this.bytes.set(widthBytes, this.position); | ||
199 | this.position += 5; | ||
200 | } else if (key === 'height') { | ||
201 | this.bytes.set(heightBytes, this.position); | ||
202 | this.position += 6; | ||
203 | } else if (key === 'videocodecid') { | ||
204 | this.bytes.set(videocodecidBytes, this.position); | ||
205 | this.position += 12; | ||
206 | } else { | ||
207 | for (i = 0; i < key.length; i++) { | ||
208 | this.bytes[this.position] = key.charCodeAt(i); | ||
209 | this.position++; | ||
210 | } | ||
211 | } | ||
212 | |||
213 | // skip null byte | ||
214 | this.position++; | ||
215 | |||
216 | // write property value | ||
217 | this.view.setFloat64(this.position, val); | ||
218 | this.position += 8; | ||
219 | |||
220 | // update flv tag length | ||
221 | this.length = Math.max(this.length, this.position); | ||
222 | ++adHoc; | ||
223 | }; | ||
224 | |||
225 | // (key:String, val:Boolean):void | ||
226 | this.writeMetaDataBoolean = function(key, val) { | ||
227 | var i; | ||
228 | prepareWrite(this, 2); | ||
229 | this.view.setUint16(this.position, key.length); | ||
230 | this.position += 2; | ||
231 | for (i = 0; i < key.length; i++) { | ||
232 | console.assert(key.charCodeAt(i) < 255); | ||
233 | prepareWrite(this, 1); | ||
234 | this.bytes[this.position] = key.charCodeAt(i); | ||
235 | this.position++; | ||
236 | } | ||
237 | prepareWrite(this, 2); | ||
238 | this.view.setUint8(this.position, 0x01); | ||
239 | this.position++; | ||
240 | this.view.setUint8(this.position, val ? 0x01 : 0x00); | ||
241 | this.position++; | ||
242 | this.length = Math.max(this.length, this.position); | ||
243 | ++adHoc; | ||
244 | }; | ||
245 | |||
246 | // ():ByteArray | ||
247 | this.finalize = function() { | ||
248 | var | ||
249 | dtsDelta, // :int | ||
250 | len; // :int | ||
251 | |||
252 | switch(this.bytes[0]) { | ||
253 | // Video Data | ||
254 | case hls.FlvTag.VIDEO_TAG: | ||
255 | 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) | ||
256 | this.bytes[12] = extraData ? 0x00 : 0x01; | ||
257 | |||
258 | dtsDelta = this.pts - this.dts; | ||
259 | this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16; | ||
260 | this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8; | ||
261 | this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0; | ||
262 | break; | ||
263 | |||
264 | case hls.FlvTag.AUDIO_TAG: | ||
265 | this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo | ||
266 | this.bytes[12] = extraData ? 0x00 : 0x01; | ||
267 | break; | ||
268 | |||
269 | case hls.FlvTag.METADATA_TAG: | ||
270 | this.position = 11; | ||
271 | this.view.setUint8(this.position, 0x02); // String type | ||
272 | this.position++; | ||
273 | this.view.setUint16(this.position, 0x0A); // 10 Bytes | ||
274 | this.position += 2; | ||
275 | // set "onMetaData" | ||
276 | this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, | ||
277 | 0x74, 0x61, 0x44, 0x61, | ||
278 | 0x74, 0x61], this.position); | ||
279 | this.position += 10; | ||
280 | this.bytes[this.position] = 0x08; // Array type | ||
281 | this.position++; | ||
282 | this.view.setUint32(this.position, adHoc); | ||
283 | this.position = this.length; | ||
284 | this.bytes.set([0, 0, 9], this.position); | ||
285 | this.position += 3; // End Data Tag | ||
286 | this.length = this.position; | ||
287 | break; | ||
288 | } | ||
289 | |||
290 | len = this.length - 11; | ||
291 | |||
292 | // write the DataSize field | ||
293 | this.bytes[ 1] = (len & 0x00FF0000) >>> 16; | ||
294 | this.bytes[ 2] = (len & 0x0000FF00) >>> 8; | ||
295 | this.bytes[ 3] = (len & 0x000000FF) >>> 0; | ||
296 | // write the Timestamp | ||
297 | this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16; | ||
298 | this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8; | ||
299 | this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0; | ||
300 | this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24; | ||
301 | // write the StreamID | ||
302 | this.bytes[ 8] = 0; | ||
303 | this.bytes[ 9] = 0; | ||
304 | this.bytes[10] = 0; | ||
305 | |||
306 | // Sometimes we're at the end of the view and have one slot to write a | ||
307 | // uint32, so, prepareWrite of count 4, since, view is uint8 | ||
308 | prepareWrite(this, 4); | ||
309 | this.view.setUint32(this.length, this.length); | ||
310 | this.length += 4; | ||
311 | this.position += 4; | ||
312 | |||
313 | // trim down the byte buffer to what is actually being used | ||
314 | this.bytes = this.bytes.subarray(0, this.length); | ||
315 | this.frameTime = hls.FlvTag.frameTime(this.bytes); | ||
316 | console.assert(this.bytes.byteLength === this.length); | ||
317 | return this; | ||
318 | }; | ||
319 | }; | ||
320 | |||
321 | hls.FlvTag.AUDIO_TAG = 0x08; // == 8, :uint | ||
322 | hls.FlvTag.VIDEO_TAG = 0x09; // == 9, :uint | ||
323 | hls.FlvTag.METADATA_TAG = 0x12; // == 18, :uint | ||
324 | |||
325 | // (tag:ByteArray):Boolean { | ||
326 | hls.FlvTag.isAudioFrame = function(tag) { | ||
327 | return hls.FlvTag.AUDIO_TAG === tag[0]; | ||
328 | }; | ||
329 | |||
330 | // (tag:ByteArray):Boolean { | ||
331 | hls.FlvTag.isVideoFrame = function(tag) { | ||
332 | return hls.FlvTag.VIDEO_TAG === tag[0]; | ||
333 | }; | ||
334 | |||
335 | // (tag:ByteArray):Boolean { | ||
336 | hls.FlvTag.isMetaData = function(tag) { | ||
337 | return hls.FlvTag.METADATA_TAG === tag[0]; | ||
338 | }; | ||
339 | |||
340 | // (tag:ByteArray):Boolean { | ||
341 | hls.FlvTag.isKeyFrame = function(tag) { | ||
342 | if (hls.FlvTag.isVideoFrame(tag)) { | ||
343 | return tag[11] === 0x17; | ||
344 | } | ||
345 | |||
346 | if (hls.FlvTag.isAudioFrame(tag)) { | ||
347 | return true; | ||
348 | } | ||
349 | |||
350 | if (hls.FlvTag.isMetaData(tag)) { | ||
351 | return true; | ||
352 | } | ||
353 | |||
354 | return false; | ||
355 | }; | ||
356 | |||
357 | // (tag:ByteArray):uint { | ||
358 | hls.FlvTag.frameTime = function(tag) { | ||
359 | var pts = tag[ 4] << 16; // :uint | ||
360 | pts |= tag[ 5] << 8; | ||
361 | pts |= tag[ 6] << 0; | ||
362 | pts |= tag[ 7] << 24; | ||
363 | return pts; | ||
364 | }; | ||
365 | |||
366 | })(this); |
src/h264-extradata.js
deleted
100644 → 0
1 | (function() { | ||
2 | var | ||
3 | H264ExtraData, | ||
4 | ExpGolomb = window.videojs.Hls.ExpGolomb, | ||
5 | FlvTag = window.videojs.Hls.FlvTag; | ||
6 | |||
7 | window.videojs.Hls.H264ExtraData = H264ExtraData = function() { | ||
8 | this.sps = []; // :Array | ||
9 | this.pps = []; // :Array | ||
10 | }; | ||
11 | |||
12 | H264ExtraData.prototype.extraDataExists = function() { // :Boolean | ||
13 | return this.sps.length > 0; | ||
14 | }; | ||
15 | |||
16 | // (sizeOfScalingList:int, expGolomb:ExpGolomb):void | ||
17 | H264ExtraData.prototype.scaling_list = function(sizeOfScalingList, expGolomb) { | ||
18 | var | ||
19 | lastScale = 8, // :int | ||
20 | nextScale = 8, // :int | ||
21 | j, | ||
22 | delta_scale; // :int | ||
23 | |||
24 | for (j = 0; j < sizeOfScalingList; ++j) { | ||
25 | if (0 !== nextScale) { | ||
26 | delta_scale = expGolomb.readExpGolomb(); | ||
27 | nextScale = (lastScale + delta_scale + 256) % 256; | ||
28 | //useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 ) | ||
29 | } | ||
30 | |||
31 | lastScale = (nextScale === 0) ? lastScale : nextScale; | ||
32 | // scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale; | ||
33 | // lastScale = scalingList[ j ] | ||
34 | } | ||
35 | }; | ||
36 | |||
37 | /** | ||
38 | * RBSP: raw bit-stream payload. The actual encoded video data. | ||
39 | * | ||
40 | * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied | ||
41 | * to a complete video sequence, like width and height. | ||
42 | */ | ||
43 | H264ExtraData.prototype.getSps0Rbsp = function() { // :ByteArray | ||
44 | var | ||
45 | sps = this.sps[0], | ||
46 | offset = 1, | ||
47 | start = 1, | ||
48 | written = 0, | ||
49 | end = sps.byteLength - 2, | ||
50 | result = new Uint8Array(sps.byteLength); | ||
51 | |||
52 | // In order to prevent 0x0000 01 from being interpreted as a | ||
53 | // NAL start code, occurences of that byte sequence in the | ||
54 | // RBSP are escaped with an "emulation byte". That turns | ||
55 | // sequences of 0x0000 01 into 0x0000 0301. When interpreting | ||
56 | // a NAL payload, they must be filtered back out. | ||
57 | while (offset < end) { | ||
58 | if (sps[offset] === 0x00 && | ||
59 | sps[offset + 1] === 0x00 && | ||
60 | sps[offset + 2] === 0x03) { | ||
61 | result.set(sps.subarray(start, offset + 1), written); | ||
62 | written += offset + 1 - start; | ||
63 | start = offset + 3; | ||
64 | } | ||
65 | offset++; | ||
66 | } | ||
67 | result.set(sps.subarray(start), written); | ||
68 | return result.subarray(0, written + (sps.byteLength - start)); | ||
69 | }; | ||
70 | |||
71 | // (pts:uint):FlvTag | ||
72 | H264ExtraData.prototype.metaDataTag = function(pts) { | ||
73 | var | ||
74 | tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag | ||
75 | expGolomb, // :ExpGolomb | ||
76 | profile_idc, // :int | ||
77 | chroma_format_idc, // :int | ||
78 | imax, // :int | ||
79 | i, // :int | ||
80 | |||
81 | pic_order_cnt_type, // :int | ||
82 | num_ref_frames_in_pic_order_cnt_cycle, // :uint | ||
83 | |||
84 | pic_width_in_mbs_minus1, // :int | ||
85 | pic_height_in_map_units_minus1, // :int | ||
86 | |||
87 | frame_mbs_only_flag, // :int | ||
88 | frame_cropping_flag, // :Boolean | ||
89 | |||
90 | frame_crop_left_offset = 0, // :int | ||
91 | frame_crop_right_offset = 0, // :int | ||
92 | frame_crop_top_offset = 0, // :int | ||
93 | frame_crop_bottom_offset = 0, // :int | ||
94 | |||
95 | width, | ||
96 | height; | ||
97 | |||
98 | tag.dts = pts; | ||
99 | tag.pts = pts; | ||
100 | expGolomb = new ExpGolomb(this.getSps0Rbsp()); | ||
101 | |||
102 | // :int = expGolomb.readUnsignedByte(); // profile_idc u(8) | ||
103 | profile_idc = expGolomb.readUnsignedByte(); | ||
104 | |||
105 | // constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8) | ||
106 | expGolomb.skipBits(16); | ||
107 | |||
108 | // seq_parameter_set_id | ||
109 | expGolomb.skipUnsignedExpGolomb(); | ||
110 | |||
111 | if (profile_idc === 100 || | ||
112 | profile_idc === 110 || | ||
113 | profile_idc === 122 || | ||
114 | profile_idc === 244 || | ||
115 | profile_idc === 44 || | ||
116 | profile_idc === 83 || | ||
117 | profile_idc === 86 || | ||
118 | profile_idc === 118 || | ||
119 | profile_idc === 128) { | ||
120 | chroma_format_idc = expGolomb.readUnsignedExpGolomb(); | ||
121 | if (3 === chroma_format_idc) { | ||
122 | expGolomb.skipBits(1); // separate_colour_plane_flag | ||
123 | } | ||
124 | expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 | ||
125 | expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 | ||
126 | expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag | ||
127 | if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag | ||
128 | imax = (chroma_format_idc !== 3) ? 8 : 12; | ||
129 | for (i = 0 ; i < imax ; ++i) { | ||
130 | if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ] | ||
131 | if (i < 6) { | ||
132 | this.scaling_list(16, expGolomb); | ||
133 | } else { | ||
134 | this.scaling_list(64, expGolomb); | ||
135 | } | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | } | ||
140 | |||
141 | expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 | ||
142 | pic_order_cnt_type = expGolomb.readUnsignedExpGolomb(); | ||
143 | |||
144 | if ( 0 === pic_order_cnt_type ) { | ||
145 | expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4 | ||
146 | } else if ( 1 === pic_order_cnt_type ) { | ||
147 | expGolomb.skipBits(1); // delta_pic_order_always_zero_flag | ||
148 | expGolomb.skipExpGolomb(); // offset_for_non_ref_pic | ||
149 | expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field | ||
150 | num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb(); | ||
151 | for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) { | ||
152 | expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ] | ||
153 | } | ||
154 | } | ||
155 | |||
156 | expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames | ||
157 | expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag | ||
158 | pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb(); | ||
159 | pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb(); | ||
160 | |||
161 | frame_mbs_only_flag = expGolomb.readBits(1); | ||
162 | if (0 === frame_mbs_only_flag) { | ||
163 | expGolomb.skipBits(1); // mb_adaptive_frame_field_flag | ||
164 | } | ||
165 | |||
166 | expGolomb.skipBits(1); // direct_8x8_inference_flag | ||
167 | frame_cropping_flag = expGolomb.readBoolean(); | ||
168 | if (frame_cropping_flag) { | ||
169 | frame_crop_left_offset = expGolomb.readUnsignedExpGolomb(); | ||
170 | frame_crop_right_offset = expGolomb.readUnsignedExpGolomb(); | ||
171 | frame_crop_top_offset = expGolomb.readUnsignedExpGolomb(); | ||
172 | frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb(); | ||
173 | } | ||
174 | |||
175 | width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2; | ||
176 | height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2); | ||
177 | |||
178 | tag.writeMetaDataDouble("videocodecid", 7); | ||
179 | tag.writeMetaDataDouble("width", width); | ||
180 | tag.writeMetaDataDouble("height", height); | ||
181 | // tag.writeMetaDataDouble("videodatarate", 0 ); | ||
182 | // tag.writeMetaDataDouble("framerate", 0); | ||
183 | |||
184 | return tag; | ||
185 | }; | ||
186 | |||
187 | // (pts:uint):FlvTag | ||
188 | H264ExtraData.prototype.extraDataTag = function(pts) { | ||
189 | var | ||
190 | i, | ||
191 | tag = new FlvTag(FlvTag.VIDEO_TAG, true); | ||
192 | |||
193 | tag.dts = pts; | ||
194 | tag.pts = pts; | ||
195 | |||
196 | tag.writeByte(0x01);// version | ||
197 | tag.writeByte(this.sps[0][1]);// profile | ||
198 | tag.writeByte(this.sps[0][2]);// compatibility | ||
199 | tag.writeByte(this.sps[0][3]);// level | ||
200 | tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) | ||
201 | tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits) | ||
202 | tag.writeShort( this.sps[0].length ); // data of SPS | ||
203 | tag.writeBytes( this.sps[0] ); // SPS | ||
204 | |||
205 | tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?) | ||
206 | for (i = 0 ; i < this.pps.length ; ++i) { | ||
207 | tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS | ||
208 | tag.writeBytes(this.pps[i]); // data of PPS | ||
209 | } | ||
210 | |||
211 | return tag; | ||
212 | }; | ||
213 | })(); |
src/h264-stream.js
deleted
100644 → 0
1 | (function(window) { | ||
2 | var | ||
3 | FlvTag = window.videojs.Hls.FlvTag, | ||
4 | H264ExtraData = window.videojs.Hls.H264ExtraData, | ||
5 | H264Stream, | ||
6 | NALUnitType; | ||
7 | |||
8 | /** | ||
9 | * Network Abstraction Layer (NAL) units are the packets of an H264 | ||
10 | * stream. NAL units are divided into types based on their payload | ||
11 | * data. Each type has a unique numeric identifier. | ||
12 | * | ||
13 | * NAL unit | ||
14 | * |- NAL header -|------ RBSP ------| | ||
15 | * | ||
16 | * NAL unit: Network abstraction layer unit. The combination of a NAL | ||
17 | * header and an RBSP. | ||
18 | * NAL header: the encapsulation unit for transport-specific metadata in | ||
19 | * an h264 stream. Exactly one byte. | ||
20 | */ | ||
21 | // incomplete, see Table 7.1 of ITU-T H.264 for 12-32 | ||
22 | window.videojs.Hls.NALUnitType = NALUnitType = { | ||
23 | unspecified: 0, | ||
24 | slice_layer_without_partitioning_rbsp_non_idr: 1, | ||
25 | slice_data_partition_a_layer_rbsp: 2, | ||
26 | slice_data_partition_b_layer_rbsp: 3, | ||
27 | slice_data_partition_c_layer_rbsp: 4, | ||
28 | slice_layer_without_partitioning_rbsp_idr: 5, | ||
29 | sei_rbsp: 6, | ||
30 | seq_parameter_set_rbsp: 7, | ||
31 | pic_parameter_set_rbsp: 8, | ||
32 | access_unit_delimiter_rbsp: 9, | ||
33 | end_of_seq_rbsp: 10, | ||
34 | end_of_stream_rbsp: 11 | ||
35 | }; | ||
36 | |||
37 | window.videojs.Hls.H264Stream = H264Stream = function() { | ||
38 | this._next_pts = 0; // :uint; | ||
39 | this._next_dts = 0; // :uint; | ||
40 | |||
41 | this._h264Frame = null; // :FlvTag | ||
42 | |||
43 | this._oldExtraData = new H264ExtraData(); // :H264ExtraData | ||
44 | this._newExtraData = new H264ExtraData(); // :H264ExtraData | ||
45 | |||
46 | this._nalUnitType = -1; // :int | ||
47 | |||
48 | this._state = 0; // :uint; | ||
49 | |||
50 | this.tags = []; | ||
51 | }; | ||
52 | |||
53 | //(pts:uint):void | ||
54 | H264Stream.prototype.setTimeStampOffset = function() {}; | ||
55 | |||
56 | //(pts:uint, dts:uint, dataAligned:Boolean):void | ||
57 | H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) { | ||
58 | // We could end up with a DTS less than 0 here. We need to deal with that! | ||
59 | this._next_pts = pts; | ||
60 | this._next_dts = dts; | ||
61 | |||
62 | // If data is aligned, flush all internal buffers | ||
63 | if (dataAligned) { | ||
64 | this.finishFrame(); | ||
65 | } | ||
66 | }; | ||
67 | |||
68 | H264Stream.prototype.finishFrame = function() { | ||
69 | if (this._h264Frame) { | ||
70 | // Push SPS before EVERY IDR frame for seeking | ||
71 | if (this._newExtraData.extraDataExists()) { | ||
72 | this._oldExtraData = this._newExtraData; | ||
73 | this._newExtraData = new H264ExtraData(); | ||
74 | } | ||
75 | |||
76 | // Check if keyframe and the length of tags. | ||
77 | // This makes sure we write metadata on the first frame of a segment. | ||
78 | if (this._oldExtraData.extraDataExists() && | ||
79 | (this._h264Frame.keyFrame || this.tags.length === 0)) { | ||
80 | // Push extra data on every IDR frame in case we did a stream change + seek | ||
81 | this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts)); | ||
82 | this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts)); | ||
83 | } | ||
84 | |||
85 | this._h264Frame.endNalUnit(); | ||
86 | this.tags.push(this._h264Frame); | ||
87 | |||
88 | } | ||
89 | |||
90 | this._h264Frame = null; | ||
91 | this._nalUnitType = -1; | ||
92 | this._state = 0; | ||
93 | }; | ||
94 | |||
95 | // (data:ByteArray, o:int, l:int):void | ||
96 | H264Stream.prototype.writeBytes = function(data, offset, length) { | ||
97 | var | ||
98 | nalUnitSize, // :uint | ||
99 | start, // :uint | ||
100 | end, // :uint | ||
101 | t; // :int | ||
102 | |||
103 | // default argument values | ||
104 | offset = offset || 0; | ||
105 | length = length || 0; | ||
106 | |||
107 | if (length <= 0) { | ||
108 | // data is empty so there's nothing to write | ||
109 | return; | ||
110 | } | ||
111 | |||
112 | // scan through the bytes until we find the start code (0x000001) for a | ||
113 | // NAL unit and then begin writing it out | ||
114 | // strip NAL start codes as we go | ||
115 | switch (this._state) { | ||
116 | default: | ||
117 | /* falls through */ | ||
118 | case 0: | ||
119 | this._state = 1; | ||
120 | /* falls through */ | ||
121 | case 1: | ||
122 | // A NAL unit may be split across two TS packets. Look back a bit to | ||
123 | // make sure the prefix of the start code wasn't already written out. | ||
124 | if (data[offset] <= 1) { | ||
125 | nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0; | ||
126 | if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) { | ||
127 | // ?? ?? 00 | O[01] ?? ?? | ||
128 | if (data[offset] === 1 && | ||
129 | nalUnitSize >= 2 && | ||
130 | this._h264Frame.negIndex(2) === 0) { | ||
131 | // ?? 00 00 : 01 | ||
132 | if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) { | ||
133 | this._h264Frame.length -= 3; // 00 00 00 : 01 | ||
134 | } else { | ||
135 | this._h264Frame.length -= 2; // 00 00 : 01 | ||
136 | } | ||
137 | |||
138 | this._state = 3; | ||
139 | return this.writeBytes(data, offset + 1, length - 1); | ||
140 | } | ||
141 | |||
142 | if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) { | ||
143 | // ?? 00 | 00 01 | ||
144 | if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) { | ||
145 | this._h264Frame.length -= 2; // 00 00 : 00 01 | ||
146 | } else { | ||
147 | this._h264Frame.length -= 1; // 00 : 00 01 | ||
148 | } | ||
149 | |||
150 | this._state = 3; | ||
151 | return this.writeBytes(data, offset + 2, length - 2); | ||
152 | } | ||
153 | |||
154 | if (length > 2 && | ||
155 | data[offset] === 0 && | ||
156 | data[offset + 1] === 0 && | ||
157 | data[offset + 2] === 1) { | ||
158 | // 00 : 00 00 01 | ||
159 | // this._h264Frame.length -= 1; | ||
160 | this._state = 3; | ||
161 | return this.writeBytes(data, offset + 3, length - 3); | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | // allow fall through if the above fails, we may end up checking a few | ||
166 | // bytes a second time. But that case will be VERY rare | ||
167 | this._state = 2; | ||
168 | /* falls through */ | ||
169 | case 2: | ||
170 | // Look for start codes in the data from the current offset forward | ||
171 | start = offset; | ||
172 | end = start + length; | ||
173 | for (t = end - 3; offset < t;) { | ||
174 | if (data[offset + 2] > 1) { | ||
175 | // if data[offset + 2] is greater than 1, there is no way a start | ||
176 | // code can begin before offset + 3 | ||
177 | offset += 3; | ||
178 | } else if (data[offset + 1] !== 0) { | ||
179 | offset += 2; | ||
180 | } else if (data[offset] !== 0) { | ||
181 | offset += 1; | ||
182 | } else { | ||
183 | // If we get here we have 00 00 00 or 00 00 01 | ||
184 | if (data[offset + 2] === 1) { | ||
185 | if (offset > start) { | ||
186 | this._h264Frame.writeBytes(data, start, offset - start); | ||
187 | } | ||
188 | this._state = 3; | ||
189 | offset += 3; | ||
190 | return this.writeBytes(data, offset, end - offset); | ||
191 | } | ||
192 | |||
193 | if (end - offset >= 4 && | ||
194 | data[offset + 2] === 0 && | ||
195 | data[offset + 3] === 1) { | ||
196 | if (offset > start) { | ||
197 | this._h264Frame.writeBytes(data, start, offset - start); | ||
198 | } | ||
199 | this._state = 3; | ||
200 | offset += 4; | ||
201 | return this.writeBytes(data, offset, end - offset); | ||
202 | } | ||
203 | |||
204 | // We are at the end of the buffer, or we have 3 NULLS followed by | ||
205 | // something that is not a 1, either way we can step forward by at | ||
206 | // least 3 | ||
207 | offset += 3; | ||
208 | } | ||
209 | } | ||
210 | |||
211 | // We did not find any start codes. Try again next packet | ||
212 | this._state = 1; | ||
213 | if (this._h264Frame) { | ||
214 | this._h264Frame.writeBytes(data, start, length); | ||
215 | } | ||
216 | return; | ||
217 | case 3: | ||
218 | // The next byte is the first byte of a NAL Unit | ||
219 | |||
220 | if (this._h264Frame) { | ||
221 | // we've come to a new NAL unit so finish up the one we've been | ||
222 | // working on | ||
223 | |||
224 | switch (this._nalUnitType) { | ||
225 | case NALUnitType.seq_parameter_set_rbsp: | ||
226 | this._h264Frame.endNalUnit(this._newExtraData.sps); | ||
227 | break; | ||
228 | case NALUnitType.pic_parameter_set_rbsp: | ||
229 | this._h264Frame.endNalUnit(this._newExtraData.pps); | ||
230 | break; | ||
231 | case NALUnitType.slice_layer_without_partitioning_rbsp_idr: | ||
232 | this._h264Frame.endNalUnit(); | ||
233 | break; | ||
234 | default: | ||
235 | this._h264Frame.endNalUnit(); | ||
236 | break; | ||
237 | } | ||
238 | } | ||
239 | |||
240 | // setup to begin processing the new NAL unit | ||
241 | this._nalUnitType = data[offset] & 0x1F; | ||
242 | if (this._h264Frame) { | ||
243 | if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) { | ||
244 | // starting a new access unit, flush the previous one | ||
245 | this.finishFrame(); | ||
246 | } else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) { | ||
247 | this._h264Frame.keyFrame = true; | ||
248 | } | ||
249 | } | ||
250 | |||
251 | // finishFrame may render this._h264Frame null, so we must test again | ||
252 | if (!this._h264Frame) { | ||
253 | this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG); | ||
254 | this._h264Frame.pts = this._next_pts; | ||
255 | this._h264Frame.dts = this._next_dts; | ||
256 | } | ||
257 | |||
258 | this._h264Frame.startNalUnit(); | ||
259 | // We know there will not be an overlapping start code, so we can skip | ||
260 | // that test | ||
261 | this._state = 2; | ||
262 | return this.writeBytes(data, offset, length); | ||
263 | } // switch | ||
264 | }; | ||
265 | })(this); |
src/metadata-stream.js
deleted
100644 → 0
1 | /** | ||
2 | * Accepts program elementary stream (PES) data events and parses out | ||
3 | * ID3 metadata from them, if present. | ||
4 | * @see http://id3.org/id3v2.3.0 | ||
5 | */ | ||
6 | (function(window, videojs, undefined) { | ||
7 | 'use strict'; | ||
8 | var | ||
9 | // return a percent-encoded representation of the specified byte range | ||
10 | // @see http://en.wikipedia.org/wiki/Percent-encoding | ||
11 | percentEncode = function(bytes, start, end) { | ||
12 | var i, result = ''; | ||
13 | for (i = start; i < end; i++) { | ||
14 | result += '%' + ('00' + bytes[i].toString(16)).slice(-2); | ||
15 | } | ||
16 | return result; | ||
17 | }, | ||
18 | // return the string representation of the specified byte range, | ||
19 | // interpreted as UTf-8. | ||
20 | parseUtf8 = function(bytes, start, end) { | ||
21 | return window.decodeURIComponent(percentEncode(bytes, start, end)); | ||
22 | }, | ||
23 | // return the string representation of the specified byte range, | ||
24 | // interpreted as ISO-8859-1. | ||
25 | parseIso88591 = function(bytes, start, end) { | ||
26 | return window.unescape(percentEncode(bytes, start, end)); | ||
27 | }, | ||
28 | tagParsers = { | ||
29 | 'TXXX': function(tag) { | ||
30 | var i; | ||
31 | if (tag.data[0] !== 3) { | ||
32 | // ignore frames with unrecognized character encodings | ||
33 | return; | ||
34 | } | ||
35 | |||
36 | for (i = 1; i < tag.data.length; i++) { | ||
37 | if (tag.data[i] === 0) { | ||
38 | // parse the text fields | ||
39 | tag.description = parseUtf8(tag.data, 1, i); | ||
40 | // do not include the null terminator in the tag value | ||
41 | tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1); | ||
42 | break; | ||
43 | } | ||
44 | } | ||
45 | }, | ||
46 | 'WXXX': function(tag) { | ||
47 | var i; | ||
48 | if (tag.data[0] !== 3) { | ||
49 | // ignore frames with unrecognized character encodings | ||
50 | return; | ||
51 | } | ||
52 | |||
53 | for (i = 1; i < tag.data.length; i++) { | ||
54 | if (tag.data[i] === 0) { | ||
55 | // parse the description and URL fields | ||
56 | tag.description = parseUtf8(tag.data, 1, i); | ||
57 | tag.url = parseUtf8(tag.data, i + 1, tag.data.length); | ||
58 | break; | ||
59 | } | ||
60 | } | ||
61 | }, | ||
62 | 'PRIV': function(tag) { | ||
63 | var i; | ||
64 | |||
65 | for (i = 0; i < tag.data.length; i++) { | ||
66 | if (tag.data[i] === 0) { | ||
67 | // parse the description and URL fields | ||
68 | tag.owner = parseIso88591(tag.data, 0, i); | ||
69 | break; | ||
70 | } | ||
71 | } | ||
72 | tag.privateData = tag.data.subarray(i + 1); | ||
73 | } | ||
74 | }, | ||
75 | MetadataStream; | ||
76 | |||
77 | MetadataStream = function(options) { | ||
78 | var | ||
79 | settings = { | ||
80 | debug: !!(options && options.debug), | ||
81 | |||
82 | // the bytes of the program-level descriptor field in MP2T | ||
83 | // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and | ||
84 | // program element descriptors" | ||
85 | descriptor: options && options.descriptor | ||
86 | }, | ||
87 | // the total size in bytes of the ID3 tag being parsed | ||
88 | tagSize = 0, | ||
89 | // tag data that is not complete enough to be parsed | ||
90 | buffer = [], | ||
91 | // the total number of bytes currently in the buffer | ||
92 | bufferSize = 0, | ||
93 | i; | ||
94 | |||
95 | MetadataStream.prototype.init.call(this); | ||
96 | |||
97 | // calculate the text track in-band metadata track dispatch type | ||
98 | // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track | ||
99 | this.dispatchType = videojs.Hls.SegmentParser.STREAM_TYPES.metadata.toString(16); | ||
100 | if (settings.descriptor) { | ||
101 | for (i = 0; i < settings.descriptor.length; i++) { | ||
102 | this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | this.push = function(chunk) { | ||
107 | var tag, frameStart, frameSize, frame, i; | ||
108 | |||
109 | // ignore events that don't look like ID3 data | ||
110 | if (buffer.length === 0 && | ||
111 | (chunk.data.length < 10 || | ||
112 | chunk.data[0] !== 'I'.charCodeAt(0) || | ||
113 | chunk.data[1] !== 'D'.charCodeAt(0) || | ||
114 | chunk.data[2] !== '3'.charCodeAt(0))) { | ||
115 | if (settings.debug) { | ||
116 | videojs.log('Skipping unrecognized metadata packet'); | ||
117 | } | ||
118 | return; | ||
119 | } | ||
120 | |||
121 | // add this chunk to the data we've collected so far | ||
122 | buffer.push(chunk); | ||
123 | bufferSize += chunk.data.byteLength; | ||
124 | |||
125 | // grab the size of the entire frame from the ID3 header | ||
126 | if (buffer.length === 1) { | ||
127 | // the frame size is transmitted as a 28-bit integer in the | ||
128 | // last four bytes of the ID3 header. | ||
129 | // The most significant bit of each byte is dropped and the | ||
130 | // results concatenated to recover the actual value. | ||
131 | tagSize = (chunk.data[6] << 21) | | ||
132 | (chunk.data[7] << 14) | | ||
133 | (chunk.data[8] << 7) | | ||
134 | (chunk.data[9]); | ||
135 | |||
136 | // ID3 reports the tag size excluding the header but it's more | ||
137 | // convenient for our comparisons to include it | ||
138 | tagSize += 10; | ||
139 | } | ||
140 | |||
141 | // if the entire frame has not arrived, wait for more data | ||
142 | if (bufferSize < tagSize) { | ||
143 | return; | ||
144 | } | ||
145 | |||
146 | // collect the entire frame so it can be parsed | ||
147 | tag = { | ||
148 | data: new Uint8Array(tagSize), | ||
149 | frames: [], | ||
150 | pts: buffer[0].pts, | ||
151 | dts: buffer[0].dts | ||
152 | }; | ||
153 | for (i = 0; i < tagSize;) { | ||
154 | tag.data.set(buffer[0].data, i); | ||
155 | i += buffer[0].data.byteLength; | ||
156 | bufferSize -= buffer[0].data.byteLength; | ||
157 | buffer.shift(); | ||
158 | } | ||
159 | |||
160 | // find the start of the first frame and the end of the tag | ||
161 | frameStart = 10; | ||
162 | if (tag.data[5] & 0x40) { | ||
163 | // advance the frame start past the extended header | ||
164 | frameStart += 4; // header size field | ||
165 | frameStart += (tag.data[10] << 24) | | ||
166 | (tag.data[11] << 16) | | ||
167 | (tag.data[12] << 8) | | ||
168 | (tag.data[13]); | ||
169 | |||
170 | // clip any padding off the end | ||
171 | tagSize -= (tag.data[16] << 24) | | ||
172 | (tag.data[17] << 16) | | ||
173 | (tag.data[18] << 8) | | ||
174 | (tag.data[19]); | ||
175 | } | ||
176 | |||
177 | // parse one or more ID3 frames | ||
178 | // http://id3.org/id3v2.3.0#ID3v2_frame_overview | ||
179 | do { | ||
180 | // determine the number of bytes in this frame | ||
181 | frameSize = (tag.data[frameStart + 4] << 24) | | ||
182 | (tag.data[frameStart + 5] << 16) | | ||
183 | (tag.data[frameStart + 6] << 8) | | ||
184 | (tag.data[frameStart + 7]); | ||
185 | if (frameSize < 1) { | ||
186 | return videojs.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); | ||
187 | } | ||
188 | |||
189 | frame = { | ||
190 | id: String.fromCharCode(tag.data[frameStart], | ||
191 | tag.data[frameStart + 1], | ||
192 | tag.data[frameStart + 2], | ||
193 | tag.data[frameStart + 3]), | ||
194 | data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) | ||
195 | }; | ||
196 | if (tagParsers[frame.id]) { | ||
197 | tagParsers[frame.id](frame); | ||
198 | } | ||
199 | tag.frames.push(frame); | ||
200 | |||
201 | frameStart += 10; // advance past the frame header | ||
202 | frameStart += frameSize; // advance past the frame body | ||
203 | } while (frameStart < tagSize); | ||
204 | this.trigger('data', tag); | ||
205 | }; | ||
206 | }; | ||
207 | MetadataStream.prototype = new videojs.Hls.Stream(); | ||
208 | |||
209 | videojs.Hls.MetadataStream = MetadataStream; | ||
210 | })(window, window.videojs); |
src/mp4-generator.js
deleted
100644 → 0
1 | (function(window, videojs, undefined) { | ||
2 | 'use strict'; | ||
3 | |||
4 | var box, dinf, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, | ||
5 | tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, traf, trex, trun, | ||
6 | types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, | ||
7 | AUDIO_HDLR, HDLR_TYPES, ESDS, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS, | ||
8 | Uint8Array, DataView; | ||
9 | |||
10 | Uint8Array = window.Uint8Array; | ||
11 | DataView = window.DataView; | ||
12 | |||
13 | // pre-calculate constants | ||
14 | (function() { | ||
15 | var i; | ||
16 | types = { | ||
17 | avc1: [], // codingname | ||
18 | avcC: [], | ||
19 | btrt: [], | ||
20 | dinf: [], | ||
21 | dref: [], | ||
22 | esds: [], | ||
23 | ftyp: [], | ||
24 | hdlr: [], | ||
25 | mdat: [], | ||
26 | mdhd: [], | ||
27 | mdia: [], | ||
28 | mfhd: [], | ||
29 | minf: [], | ||
30 | moof: [], | ||
31 | moov: [], | ||
32 | mp4a: [], // codingname | ||
33 | mvex: [], | ||
34 | mvhd: [], | ||
35 | sdtp: [], | ||
36 | smhd: [], | ||
37 | stbl: [], | ||
38 | stco: [], | ||
39 | stsc: [], | ||
40 | stsd: [], | ||
41 | stsz: [], | ||
42 | stts: [], | ||
43 | styp: [], | ||
44 | tfdt: [], | ||
45 | tfhd: [], | ||
46 | traf: [], | ||
47 | trak: [], | ||
48 | trun: [], | ||
49 | trex: [], | ||
50 | tkhd: [], | ||
51 | vmhd: [] | ||
52 | }; | ||
53 | |||
54 | for (i in types) { | ||
55 | if (types.hasOwnProperty(i)) { | ||
56 | types[i] = [ | ||
57 | i.charCodeAt(0), | ||
58 | i.charCodeAt(1), | ||
59 | i.charCodeAt(2), | ||
60 | i.charCodeAt(3) | ||
61 | ]; | ||
62 | } | ||
63 | } | ||
64 | |||
65 | MAJOR_BRAND = new Uint8Array([ | ||
66 | 'i'.charCodeAt(0), | ||
67 | 's'.charCodeAt(0), | ||
68 | 'o'.charCodeAt(0), | ||
69 | 'm'.charCodeAt(0) | ||
70 | ]); | ||
71 | AVC1_BRAND = new Uint8Array([ | ||
72 | 'a'.charCodeAt(0), | ||
73 | 'v'.charCodeAt(0), | ||
74 | 'c'.charCodeAt(0), | ||
75 | '1'.charCodeAt(0) | ||
76 | ]); | ||
77 | MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); | ||
78 | VIDEO_HDLR = new Uint8Array([ | ||
79 | 0x00, // version 0 | ||
80 | 0x00, 0x00, 0x00, // flags | ||
81 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
82 | 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' | ||
83 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
84 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
85 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
86 | 0x56, 0x69, 0x64, 0x65, | ||
87 | 0x6f, 0x48, 0x61, 0x6e, | ||
88 | 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' | ||
89 | ]); | ||
90 | AUDIO_HDLR = new Uint8Array([ | ||
91 | 0x00, // version 0 | ||
92 | 0x00, 0x00, 0x00, // flags | ||
93 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
94 | 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' | ||
95 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
96 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
97 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
98 | 0x53, 0x6f, 0x75, 0x6e, | ||
99 | 0x64, 0x48, 0x61, 0x6e, | ||
100 | 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' | ||
101 | ]); | ||
102 | HDLR_TYPES = { | ||
103 | "video":VIDEO_HDLR, | ||
104 | "audio": AUDIO_HDLR | ||
105 | }; | ||
106 | DREF = new Uint8Array([ | ||
107 | 0x00, // version 0 | ||
108 | 0x00, 0x00, 0x00, // flags | ||
109 | 0x00, 0x00, 0x00, 0x01, // entry_count | ||
110 | 0x00, 0x00, 0x00, 0x0c, // entry_size | ||
111 | 0x75, 0x72, 0x6c, 0x20, // 'url' type | ||
112 | 0x00, // version 0 | ||
113 | 0x00, 0x00, 0x01 // entry_flags | ||
114 | ]); | ||
115 | ESDS = new Uint8Array([ | ||
116 | 0x00, // version | ||
117 | 0x00, 0x00, 0x00, // flags | ||
118 | |||
119 | // ES_Descriptor | ||
120 | 0x03, // tag, ES_DescrTag | ||
121 | 0x19, // length | ||
122 | 0x00, 0x00, // ES_ID | ||
123 | 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority | ||
124 | |||
125 | // DecoderConfigDescriptor | ||
126 | 0x04, // tag, DecoderConfigDescrTag | ||
127 | 0x11, // length | ||
128 | 0x40, // object type | ||
129 | 0x15, // streamType | ||
130 | 0x00, 0x06, 0x00, // bufferSizeDB | ||
131 | 0x00, 0x00, 0xda, 0xc0, // maxBitrate | ||
132 | 0x00, 0x00, 0xda, 0xc0, // avgBitrate | ||
133 | |||
134 | // DecoderSpecificInfo | ||
135 | 0x05, // tag, DecoderSpecificInfoTag | ||
136 | 0x02, // length | ||
137 | // ISO/IEC 14496-3, AudioSpecificConfig | ||
138 | 0x11, // AudioObjectType, AAC LC. | ||
139 | 0x90, // samplingFrequencyIndex, 8 -> 16000. channelConfig, 2 -> stereo. | ||
140 | 0x06, 0x01, 0x02 // GASpecificConfig | ||
141 | ]); | ||
142 | SMHD = new Uint8Array([ | ||
143 | 0x00, // version | ||
144 | 0x00, 0x00, 0x00, // flags | ||
145 | 0x00, 0x00, // balance, 0 means centered | ||
146 | 0x00, 0x00 // reserved | ||
147 | ]); | ||
148 | STCO = new Uint8Array([ | ||
149 | 0x00, // version | ||
150 | 0x00, 0x00, 0x00, // flags | ||
151 | 0x00, 0x00, 0x00, 0x00 // entry_count | ||
152 | ]); | ||
153 | STSC = STCO; | ||
154 | STSZ = new Uint8Array([ | ||
155 | 0x00, // version | ||
156 | 0x00, 0x00, 0x00, // flags | ||
157 | 0x00, 0x00, 0x00, 0x00, // sample_size | ||
158 | 0x00, 0x00, 0x00, 0x00, // sample_count | ||
159 | ]); | ||
160 | STTS = STCO; | ||
161 | VMHD = new Uint8Array([ | ||
162 | 0x00, // version | ||
163 | 0x00, 0x00, 0x01, // flags | ||
164 | 0x00, 0x00, // graphicsmode | ||
165 | 0x00, 0x00, | ||
166 | 0x00, 0x00, | ||
167 | 0x00, 0x00 // opcolor | ||
168 | ]); | ||
169 | })(); | ||
170 | |||
171 | box = function(type) { | ||
172 | var | ||
173 | payload = Array.prototype.slice.call(arguments, 1), | ||
174 | size = 0, | ||
175 | i = payload.length, | ||
176 | result, | ||
177 | view; | ||
178 | |||
179 | // calculate the total size we need to allocate | ||
180 | while (i--) { | ||
181 | size += payload[i].byteLength; | ||
182 | } | ||
183 | result = new Uint8Array(size + 8); | ||
184 | view = new DataView(result.buffer, result.byteOffset, result.byteLength); | ||
185 | view.setUint32(0, result.byteLength); | ||
186 | result.set(type, 4); | ||
187 | |||
188 | // copy the payload into the result | ||
189 | for (i = 0, size = 8; i < payload.length; i++) { | ||
190 | result.set(payload[i], size); | ||
191 | size += payload[i].byteLength; | ||
192 | } | ||
193 | return result; | ||
194 | }; | ||
195 | |||
196 | dinf = function() { | ||
197 | return box(types.dinf, box(types.dref, DREF)); | ||
198 | }; | ||
199 | |||
200 | ftyp = function() { | ||
201 | return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); | ||
202 | }; | ||
203 | |||
204 | hdlr = function(type) { | ||
205 | return box(types.hdlr, HDLR_TYPES[type]); | ||
206 | }; | ||
207 | mdat = function(data) { | ||
208 | return box(types.mdat, data); | ||
209 | }; | ||
210 | mdhd = function(track) { | ||
211 | var result = new Uint8Array([ | ||
212 | 0x00, // version 0 | ||
213 | 0x00, 0x00, 0x00, // flags | ||
214 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
215 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
216 | 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second | ||
217 | |||
218 | (track.duration >>> 24), | ||
219 | (track.duration >>> 16) & 0xFF, | ||
220 | (track.duration >>> 8) & 0xFF, | ||
221 | track.duration & 0xFF, // duration | ||
222 | 0x55, 0xc4, // 'und' language (undetermined) | ||
223 | 0x00, 0x00 | ||
224 | ]); | ||
225 | |||
226 | // Use the sample rate from the track metadata, when it is | ||
227 | // defined. The sample rate can be parsed out of an ADTS header, for | ||
228 | // instance. | ||
229 | if (track.samplerate) { | ||
230 | result[12] = (track.samplerate >>> 24); | ||
231 | result[13] = (track.samplerate >>> 16) & 0xFF; | ||
232 | result[14] = (track.samplerate >>> 8) & 0xFF; | ||
233 | result[15] = (track.samplerate) & 0xFF; | ||
234 | } | ||
235 | return box(types.mdhd, result); | ||
236 | }; | ||
237 | mdia = function(track) { | ||
238 | return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); | ||
239 | }; | ||
240 | mfhd = function(sequenceNumber) { | ||
241 | return box(types.mfhd, new Uint8Array([ | ||
242 | 0x00, | ||
243 | 0x00, 0x00, 0x00, // flags | ||
244 | (sequenceNumber & 0xFF000000) >> 24, | ||
245 | (sequenceNumber & 0xFF0000) >> 16, | ||
246 | (sequenceNumber & 0xFF00) >> 8, | ||
247 | sequenceNumber & 0xFF, // sequence_number | ||
248 | ])); | ||
249 | }; | ||
250 | minf = function(track) { | ||
251 | return box(types.minf, | ||
252 | track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), | ||
253 | dinf(), | ||
254 | stbl(track)); | ||
255 | }; | ||
256 | moof = function(sequenceNumber, tracks) { | ||
257 | var | ||
258 | trackFragments = [], | ||
259 | i = tracks.length; | ||
260 | // build traf boxes for each track fragment | ||
261 | while (i--) { | ||
262 | trackFragments[i] = traf(tracks[i]); | ||
263 | } | ||
264 | return box.apply(null, [ | ||
265 | types.moof, | ||
266 | mfhd(sequenceNumber) | ||
267 | ].concat(trackFragments)); | ||
268 | }; | ||
269 | /** | ||
270 | * Returns a movie box. | ||
271 | * @param tracks {array} the tracks associated with this movie | ||
272 | * @see ISO/IEC 14496-12:2012(E), section 8.2.1 | ||
273 | */ | ||
274 | moov = function(tracks) { | ||
275 | var | ||
276 | i = tracks.length, | ||
277 | boxes = []; | ||
278 | |||
279 | while (i--) { | ||
280 | boxes[i] = trak(tracks[i]); | ||
281 | } | ||
282 | |||
283 | return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); | ||
284 | }; | ||
285 | mvex = function(tracks) { | ||
286 | var | ||
287 | i = tracks.length, | ||
288 | boxes = []; | ||
289 | |||
290 | while (i--) { | ||
291 | boxes[i] = trex(tracks[i]); | ||
292 | } | ||
293 | return box.apply(null, [types.mvex].concat(boxes)); | ||
294 | }; | ||
295 | mvhd = function(duration) { | ||
296 | var | ||
297 | bytes = new Uint8Array([ | ||
298 | 0x00, // version 0 | ||
299 | 0x00, 0x00, 0x00, // flags | ||
300 | 0x00, 0x00, 0x00, 0x01, // creation_time | ||
301 | 0x00, 0x00, 0x00, 0x02, // modification_time | ||
302 | 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second | ||
303 | (duration & 0xFF000000) >> 24, | ||
304 | (duration & 0xFF0000) >> 16, | ||
305 | (duration & 0xFF00) >> 8, | ||
306 | duration & 0xFF, // duration | ||
307 | 0x00, 0x01, 0x00, 0x00, // 1.0 rate | ||
308 | 0x01, 0x00, // 1.0 volume | ||
309 | 0x00, 0x00, // reserved | ||
310 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
311 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
312 | 0x00, 0x01, 0x00, 0x00, | ||
313 | 0x00, 0x00, 0x00, 0x00, | ||
314 | 0x00, 0x00, 0x00, 0x00, | ||
315 | 0x00, 0x00, 0x00, 0x00, | ||
316 | 0x00, 0x01, 0x00, 0x00, | ||
317 | 0x00, 0x00, 0x00, 0x00, | ||
318 | 0x00, 0x00, 0x00, 0x00, | ||
319 | 0x00, 0x00, 0x00, 0x00, | ||
320 | 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix | ||
321 | 0x00, 0x00, 0x00, 0x00, | ||
322 | 0x00, 0x00, 0x00, 0x00, | ||
323 | 0x00, 0x00, 0x00, 0x00, | ||
324 | 0x00, 0x00, 0x00, 0x00, | ||
325 | 0x00, 0x00, 0x00, 0x00, | ||
326 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
327 | 0xff, 0xff, 0xff, 0xff // next_track_ID | ||
328 | ]); | ||
329 | return box(types.mvhd, bytes); | ||
330 | }; | ||
331 | |||
332 | sdtp = function(track) { | ||
333 | var | ||
334 | samples = track.samples || [], | ||
335 | bytes = new Uint8Array(4 + samples.length), | ||
336 | flags, | ||
337 | i; | ||
338 | |||
339 | // leave the full box header (4 bytes) all zero | ||
340 | |||
341 | // write the sample table | ||
342 | for (i = 0; i < samples.length; i++) { | ||
343 | flags = samples[i].flags; | ||
344 | |||
345 | bytes[i + 4] = (flags.dependsOn << 4) | | ||
346 | (flags.isDependedOn << 2) | | ||
347 | (flags.hasRedundancy); | ||
348 | } | ||
349 | |||
350 | return box(types.sdtp, | ||
351 | bytes); | ||
352 | }; | ||
353 | |||
354 | stbl = function(track) { | ||
355 | return box(types.stbl, | ||
356 | stsd(track), | ||
357 | box(types.stts, STTS), | ||
358 | box(types.stsc, STSC), | ||
359 | box(types.stsz, STSZ), | ||
360 | box(types.stco, STCO)); | ||
361 | }; | ||
362 | |||
363 | (function() { | ||
364 | var videoSample, audioSample; | ||
365 | |||
366 | stsd = function(track) { | ||
367 | |||
368 | return box(types.stsd, new Uint8Array([ | ||
369 | 0x00, // version 0 | ||
370 | 0x00, 0x00, 0x00, // flags | ||
371 | 0x00, 0x00, 0x00, 0x01 | ||
372 | ]), track.type === 'video' ? videoSample(track) : audioSample(track)); | ||
373 | }; | ||
374 | |||
375 | videoSample = function(track) { | ||
376 | var sequenceParameterSets = [], pictureParameterSets = [], i; | ||
377 | |||
378 | // assemble the SPSs | ||
379 | for (i = 0; i < track.sps.length; i++) { | ||
380 | sequenceParameterSets.push((track.sps[i].byteLength & 0xFF00) >>> 8); | ||
381 | sequenceParameterSets.push((track.sps[i].byteLength & 0xFF)); // sequenceParameterSetLength | ||
382 | sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(track.sps[i])); // SPS | ||
383 | } | ||
384 | |||
385 | // assemble the PPSs | ||
386 | for (i = 0; i < track.pps.length; i++) { | ||
387 | pictureParameterSets.push((track.pps[i].byteLength & 0xFF00) >>> 8); | ||
388 | pictureParameterSets.push((track.pps[i].byteLength & 0xFF)); | ||
389 | pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(track.pps[i])); | ||
390 | } | ||
391 | |||
392 | return box(types.avc1, new Uint8Array([ | ||
393 | 0x00, 0x00, 0x00, | ||
394 | 0x00, 0x00, 0x00, // reserved | ||
395 | 0x00, 0x01, // data_reference_index | ||
396 | 0x00, 0x00, // pre_defined | ||
397 | 0x00, 0x00, // reserved | ||
398 | 0x00, 0x00, 0x00, 0x00, | ||
399 | 0x00, 0x00, 0x00, 0x00, | ||
400 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
401 | (track.width & 0xff00) >> 8, | ||
402 | track.width & 0xff, // width | ||
403 | (track.height & 0xff00) >> 8, | ||
404 | track.height & 0xff, // height | ||
405 | 0x00, 0x48, 0x00, 0x00, // horizresolution | ||
406 | 0x00, 0x48, 0x00, 0x00, // vertresolution | ||
407 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
408 | 0x00, 0x01, // frame_count | ||
409 | 0x13, | ||
410 | 0x76, 0x69, 0x64, 0x65, | ||
411 | 0x6f, 0x6a, 0x73, 0x2d, | ||
412 | 0x63, 0x6f, 0x6e, 0x74, | ||
413 | 0x72, 0x69, 0x62, 0x2d, | ||
414 | 0x68, 0x6c, 0x73, 0x00, | ||
415 | 0x00, 0x00, 0x00, 0x00, | ||
416 | 0x00, 0x00, 0x00, 0x00, | ||
417 | 0x00, 0x00, 0x00, // compressorname | ||
418 | 0x00, 0x18, // depth = 24 | ||
419 | 0x11, 0x11 // pre_defined = -1 | ||
420 | ]), box(types.avcC, new Uint8Array([ | ||
421 | 0x01, // configurationVersion | ||
422 | track.profileIdc, // AVCProfileIndication | ||
423 | track.profileCompatibility, // profile_compatibility | ||
424 | track.levelIdc, // AVCLevelIndication | ||
425 | 0xff // lengthSizeMinusOne, hard-coded to 4 bytes | ||
426 | ].concat([ | ||
427 | track.sps.length // numOfSequenceParameterSets | ||
428 | ]).concat(sequenceParameterSets).concat([ | ||
429 | track.pps.length // numOfPictureParameterSets | ||
430 | ]).concat(pictureParameterSets))), // "PPS" | ||
431 | box(types.btrt, new Uint8Array([ | ||
432 | 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB | ||
433 | 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate | ||
434 | 0x00, 0x2d, 0xc6, 0xc0 | ||
435 | ])) // avgBitrate | ||
436 | ); | ||
437 | }; | ||
438 | |||
439 | audioSample = function(track) { | ||
440 | return box(types.mp4a, new Uint8Array([ | ||
441 | |||
442 | // SampleEntry, ISO/IEC 14496-12 | ||
443 | 0x00, 0x00, 0x00, | ||
444 | 0x00, 0x00, 0x00, // reserved | ||
445 | 0x00, 0x01, // data_reference_index | ||
446 | |||
447 | // AudioSampleEntry, ISO/IEC 14496-12 | ||
448 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
449 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
450 | (track.channelcount & 0xff00) >> 8, | ||
451 | (track.channelcount & 0xff), // channelcount | ||
452 | |||
453 | (track.samplesize & 0xff00) >> 8, | ||
454 | (track.samplesize & 0xff), // samplesize | ||
455 | 0x00, 0x00, // pre_defined | ||
456 | 0x00, 0x00, // reserved | ||
457 | |||
458 | (track.samplerate & 0xff00) >> 8, | ||
459 | (track.samplerate & 0xff), | ||
460 | 0x00, 0x00 // samplerate, 16.16 | ||
461 | |||
462 | // MP4AudioSampleEntry, ISO/IEC 14496-14 | ||
463 | ]), box(types.esds, ESDS)); | ||
464 | }; | ||
465 | })(); | ||
466 | |||
467 | styp = function() { | ||
468 | return box(types.styp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND); | ||
469 | }; | ||
470 | |||
471 | tkhd = function(track) { | ||
472 | var result = new Uint8Array([ | ||
473 | 0x00, // version 0 | ||
474 | 0x00, 0x00, 0x07, // flags | ||
475 | 0x00, 0x00, 0x00, 0x00, // creation_time | ||
476 | 0x00, 0x00, 0x00, 0x00, // modification_time | ||
477 | (track.id & 0xFF000000) >> 24, | ||
478 | (track.id & 0xFF0000) >> 16, | ||
479 | (track.id & 0xFF00) >> 8, | ||
480 | track.id & 0xFF, // track_ID | ||
481 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
482 | (track.duration & 0xFF000000) >> 24, | ||
483 | (track.duration & 0xFF0000) >> 16, | ||
484 | (track.duration & 0xFF00) >> 8, | ||
485 | track.duration & 0xFF, // duration | ||
486 | 0x00, 0x00, 0x00, 0x00, | ||
487 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
488 | 0x00, 0x00, // layer | ||
489 | 0x00, 0x00, // alternate_group | ||
490 | 0x01, 0x00, // non-audio track volume | ||
491 | 0x00, 0x00, // reserved | ||
492 | 0x00, 0x01, 0x00, 0x00, | ||
493 | 0x00, 0x00, 0x00, 0x00, | ||
494 | 0x00, 0x00, 0x00, 0x00, | ||
495 | 0x00, 0x00, 0x00, 0x00, | ||
496 | 0x00, 0x01, 0x00, 0x00, | ||
497 | 0x00, 0x00, 0x00, 0x00, | ||
498 | 0x00, 0x00, 0x00, 0x00, | ||
499 | 0x00, 0x00, 0x00, 0x00, | ||
500 | 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix | ||
501 | (track.width & 0xFF00) >> 8, | ||
502 | track.width & 0xFF, | ||
503 | 0x00, 0x00, // width | ||
504 | (track.height & 0xFF00) >> 8, | ||
505 | track.height & 0xFF, | ||
506 | 0x00, 0x00 // height | ||
507 | ]); | ||
508 | |||
509 | return box(types.tkhd, result); | ||
510 | }; | ||
511 | |||
512 | /** | ||
513 | * Generate a track fragment (traf) box. A traf box collects metadata | ||
514 | * about tracks in a movie fragment (moof) box. | ||
515 | */ | ||
516 | traf = function(track) { | ||
517 | var trackFragmentHeader, trackFragmentDecodeTime, | ||
518 | trackFragmentRun, sampleDependencyTable, dataOffset; | ||
519 | |||
520 | trackFragmentHeader = box(types.tfhd, new Uint8Array([ | ||
521 | 0x00, // version 0 | ||
522 | 0x00, 0x00, 0x3a, // flags | ||
523 | (track.id & 0xFF000000) >> 24, | ||
524 | (track.id & 0xFF0000) >> 16, | ||
525 | (track.id & 0xFF00) >> 8, | ||
526 | (track.id & 0xFF), // track_ID | ||
527 | 0x00, 0x00, 0x00, 0x01, // sample_description_index | ||
528 | 0x00, 0x00, 0x00, 0x00, // default_sample_duration | ||
529 | 0x00, 0x00, 0x00, 0x00, // default_sample_size | ||
530 | 0x00, 0x00, 0x00, 0x00 // default_sample_flags | ||
531 | ])); | ||
532 | |||
533 | trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([ | ||
534 | 0x00, // version 0 | ||
535 | 0x00, 0x00, 0x00, // flags | ||
536 | 0x00, 0x00, 0x00, 0x00 // baseMediaDecodeTime | ||
537 | ])); | ||
538 | |||
539 | // the data offset specifies the number of bytes from the start of | ||
540 | // the containing moof to the first payload byte of the associated | ||
541 | // mdat | ||
542 | dataOffset = (32 + // tfhd | ||
543 | 16 + // tfdt | ||
544 | 8 + // traf header | ||
545 | 16 + // mfhd | ||
546 | 8 + // moof header | ||
547 | 8); // mdat header | ||
548 | |||
549 | // audio tracks require less metadata | ||
550 | if (track.type === 'audio') { | ||
551 | trackFragmentRun = trun(track, dataOffset); | ||
552 | return box(types.traf, | ||
553 | trackFragmentHeader, | ||
554 | trackFragmentDecodeTime, | ||
555 | trackFragmentRun); | ||
556 | } | ||
557 | |||
558 | // video tracks should contain an independent and disposable samples | ||
559 | // box (sdtp) | ||
560 | // generate one and adjust offsets to match | ||
561 | sampleDependencyTable = sdtp(track); | ||
562 | trackFragmentRun = trun(track, | ||
563 | sampleDependencyTable.length + dataOffset); | ||
564 | return box(types.traf, | ||
565 | trackFragmentHeader, | ||
566 | trackFragmentDecodeTime, | ||
567 | trackFragmentRun, | ||
568 | sampleDependencyTable); | ||
569 | }; | ||
570 | |||
571 | /** | ||
572 | * Generate a track box. | ||
573 | * @param track {object} a track definition | ||
574 | * @return {Uint8Array} the track box | ||
575 | */ | ||
576 | trak = function(track) { | ||
577 | track.duration = track.duration || 0xffffffff; | ||
578 | return box(types.trak, | ||
579 | tkhd(track), | ||
580 | mdia(track)); | ||
581 | }; | ||
582 | |||
583 | trex = function(track) { | ||
584 | var result = new Uint8Array([ | ||
585 | 0x00, // version 0 | ||
586 | 0x00, 0x00, 0x00, // flags | ||
587 | (track.id & 0xFF000000) >> 24, | ||
588 | (track.id & 0xFF0000) >> 16, | ||
589 | (track.id & 0xFF00) >> 8, | ||
590 | (track.id & 0xFF), // track_ID | ||
591 | 0x00, 0x00, 0x00, 0x01, // default_sample_description_index | ||
592 | 0x00, 0x00, 0x00, 0x00, // default_sample_duration | ||
593 | 0x00, 0x00, 0x00, 0x00, // default_sample_size | ||
594 | 0x00, 0x01, 0x00, 0x01 // default_sample_flags | ||
595 | ]); | ||
596 | // the last two bytes of default_sample_flags is the sample | ||
597 | // degradation priority, a hint about the importance of this sample | ||
598 | // relative to others. Lower the degradation priority for all sample | ||
599 | // types other than video. | ||
600 | if (track.type !== 'video') { | ||
601 | result[result.length - 1] = 0x00; | ||
602 | } | ||
603 | |||
604 | return box(types.trex, result); | ||
605 | }; | ||
606 | |||
607 | (function() { | ||
608 | var audioTrun, videoTrun, trunHeader; | ||
609 | |||
610 | // This method assumes all samples are uniform. That is, if a | ||
611 | // duration is present for the first sample, it will be present for | ||
612 | // all subsequent samples. | ||
613 | // see ISO/IEC 14496-12:2012, Section 8.8.8.1 | ||
614 | trunHeader = function(samples, offset) { | ||
615 | var durationPresent = 0, sizePresent = 0, | ||
616 | flagsPresent = 0, compositionTimeOffset = 0; | ||
617 | |||
618 | // trun flag constants | ||
619 | if (samples.length) { | ||
620 | if (samples[0].duration !== undefined) { | ||
621 | durationPresent = 0x1; | ||
622 | } | ||
623 | if (samples[0].size !== undefined) { | ||
624 | sizePresent = 0x2; | ||
625 | } | ||
626 | if (samples[0].flags !== undefined) { | ||
627 | flagsPresent = 0x4; | ||
628 | } | ||
629 | if (samples[0].compositionTimeOffset !== undefined) { | ||
630 | compositionTimeOffset = 0x8; | ||
631 | } | ||
632 | } | ||
633 | |||
634 | return [ | ||
635 | 0x00, // version 0 | ||
636 | 0x00, | ||
637 | durationPresent | sizePresent | flagsPresent | compositionTimeOffset, | ||
638 | 0x01, // flags | ||
639 | (samples.length & 0xFF000000) >>> 24, | ||
640 | (samples.length & 0xFF0000) >>> 16, | ||
641 | (samples.length & 0xFF00) >>> 8, | ||
642 | samples.length & 0xFF, // sample_count | ||
643 | (offset & 0xFF000000) >>> 24, | ||
644 | (offset & 0xFF0000) >>> 16, | ||
645 | (offset & 0xFF00) >>> 8, | ||
646 | offset & 0xFF // data_offset | ||
647 | ]; | ||
648 | }; | ||
649 | |||
650 | videoTrun = function(track, offset) { | ||
651 | var bytes, samples, sample, i; | ||
652 | |||
653 | samples = track.samples || []; | ||
654 | offset += 8 + 12 + (16 * samples.length); | ||
655 | |||
656 | bytes = trunHeader(samples, offset); | ||
657 | |||
658 | for (i = 0; i < samples.length; i++) { | ||
659 | sample = samples[i]; | ||
660 | bytes = bytes.concat([ | ||
661 | (sample.duration & 0xFF000000) >>> 24, | ||
662 | (sample.duration & 0xFF0000) >>> 16, | ||
663 | (sample.duration & 0xFF00) >>> 8, | ||
664 | sample.duration & 0xFF, // sample_duration | ||
665 | (sample.size & 0xFF000000) >>> 24, | ||
666 | (sample.size & 0xFF0000) >>> 16, | ||
667 | (sample.size & 0xFF00) >>> 8, | ||
668 | sample.size & 0xFF, // sample_size | ||
669 | (sample.flags.isLeading << 2) | sample.flags.dependsOn, | ||
670 | (sample.flags.isDependedOn << 6) | | ||
671 | (sample.flags.hasRedundancy << 4) | | ||
672 | (sample.flags.paddingValue << 1) | | ||
673 | sample.flags.isNonSyncSample, | ||
674 | sample.flags.degradationPriority & 0xF0 << 8, | ||
675 | sample.flags.degradationPriority & 0x0F, // sample_flags | ||
676 | (sample.compositionTimeOffset & 0xFF000000) >>> 24, | ||
677 | (sample.compositionTimeOffset & 0xFF0000) >>> 16, | ||
678 | (sample.compositionTimeOffset & 0xFF00) >>> 8, | ||
679 | sample.compositionTimeOffset & 0xFF // sample_composition_time_offset | ||
680 | ]); | ||
681 | } | ||
682 | return box(types.trun, new Uint8Array(bytes)); | ||
683 | }; | ||
684 | |||
685 | audioTrun = function(track, offset) { | ||
686 | var bytes, samples, sample, i; | ||
687 | |||
688 | samples = track.samples || []; | ||
689 | offset += 8 + 12 + (8 * samples.length); | ||
690 | |||
691 | bytes = trunHeader(samples, offset); | ||
692 | |||
693 | for (i = 0; i < samples.length; i++) { | ||
694 | sample = samples[i]; | ||
695 | bytes = bytes.concat([ | ||
696 | (sample.duration & 0xFF000000) >>> 24, | ||
697 | (sample.duration & 0xFF0000) >>> 16, | ||
698 | (sample.duration & 0xFF00) >>> 8, | ||
699 | sample.duration & 0xFF, // sample_duration | ||
700 | (sample.size & 0xFF000000) >>> 24, | ||
701 | (sample.size & 0xFF0000) >>> 16, | ||
702 | (sample.size & 0xFF00) >>> 8, | ||
703 | sample.size & 0xFF]); // sample_size | ||
704 | } | ||
705 | |||
706 | return box(types.trun, new Uint8Array(bytes)); | ||
707 | }; | ||
708 | |||
709 | trun = function(track, offset) { | ||
710 | if (track.type === 'audio') { | ||
711 | return audioTrun(track, offset); | ||
712 | } else { | ||
713 | return videoTrun(track, offset); | ||
714 | } | ||
715 | }; | ||
716 | })(); | ||
717 | |||
718 | window.videojs.mp4 = { | ||
719 | ftyp: ftyp, | ||
720 | mdat: mdat, | ||
721 | moof: moof, | ||
722 | moov: moov, | ||
723 | initSegment: function(tracks) { | ||
724 | var | ||
725 | fileType = ftyp(), | ||
726 | movie = moov(tracks), | ||
727 | result; | ||
728 | |||
729 | result = new Uint8Array(fileType.byteLength + movie.byteLength); | ||
730 | result.set(fileType); | ||
731 | result.set(movie, fileType.byteLength); | ||
732 | return result; | ||
733 | } | ||
734 | }; | ||
735 | |||
736 | })(window, window.videojs); |
src/segment-parser.js
deleted
100644 → 0
1 | (function(window) { | ||
2 | var | ||
3 | videojs = window.videojs, | ||
4 | FlvTag = videojs.Hls.FlvTag, | ||
5 | H264Stream = videojs.Hls.H264Stream, | ||
6 | AacStream = videojs.Hls.AacStream, | ||
7 | MetadataStream = videojs.Hls.MetadataStream, | ||
8 | MP2T_PACKET_LENGTH, | ||
9 | STREAM_TYPES; | ||
10 | |||
11 | /** | ||
12 | * An object that incrementally transmuxes MPEG2 Trasport Stream | ||
13 | * chunks into an FLV. | ||
14 | */ | ||
15 | videojs.Hls.SegmentParser = function() { | ||
16 | var | ||
17 | self = this, | ||
18 | parseTSPacket, | ||
19 | streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH), | ||
20 | streamBufferByteCount = 0, | ||
21 | h264Stream = new H264Stream(), | ||
22 | aacStream = new AacStream(); | ||
23 | |||
24 | // expose the stream metadata | ||
25 | self.stream = { | ||
26 | // the mapping between transport stream programs and the PIDs | ||
27 | // that form their elementary streams | ||
28 | programMapTable: {} | ||
29 | }; | ||
30 | |||
31 | // allow in-band metadata to be observed | ||
32 | self.metadataStream = new MetadataStream(); | ||
33 | |||
34 | // For information on the FLV format, see | ||
35 | // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf. | ||
36 | // Technically, this function returns the header and a metadata FLV tag | ||
37 | // if duration is greater than zero | ||
38 | // duration in seconds | ||
39 | // @return {object} the bytes of the FLV header as a Uint8Array | ||
40 | self.getFlvHeader = function(duration, audio, video) { // :ByteArray { | ||
41 | var | ||
42 | headBytes = new Uint8Array(3 + 1 + 1 + 4), | ||
43 | head = new DataView(headBytes.buffer), | ||
44 | metadata, | ||
45 | result, | ||
46 | metadataLength; | ||
47 | |||
48 | // default arguments | ||
49 | duration = duration || 0; | ||
50 | audio = audio === undefined? true : audio; | ||
51 | video = video === undefined? true : video; | ||
52 | |||
53 | // signature | ||
54 | head.setUint8(0, 0x46); // 'F' | ||
55 | head.setUint8(1, 0x4c); // 'L' | ||
56 | head.setUint8(2, 0x56); // 'V' | ||
57 | |||
58 | // version | ||
59 | head.setUint8(3, 0x01); | ||
60 | |||
61 | // flags | ||
62 | head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); | ||
63 | |||
64 | // data offset, should be 9 for FLV v1 | ||
65 | head.setUint32(5, headBytes.byteLength); | ||
66 | |||
67 | // init the first FLV tag | ||
68 | if (duration <= 0) { | ||
69 | // no duration available so just write the first field of the first | ||
70 | // FLV tag | ||
71 | result = new Uint8Array(headBytes.byteLength + 4); | ||
72 | result.set(headBytes); | ||
73 | result.set([0, 0, 0, 0], headBytes.byteLength); | ||
74 | return result; | ||
75 | } | ||
76 | |||
77 | // write out the duration metadata tag | ||
78 | metadata = new FlvTag(FlvTag.METADATA_TAG); | ||
79 | metadata.pts = metadata.dts = 0; | ||
80 | metadata.writeMetaDataDouble("duration", duration); | ||
81 | metadataLength = metadata.finalize().length; | ||
82 | result = new Uint8Array(headBytes.byteLength + metadataLength); | ||
83 | result.set(headBytes); | ||
84 | result.set(head.byteLength, metadataLength); | ||
85 | |||
86 | return result; | ||
87 | }; | ||
88 | |||
89 | self.flushTags = function() { | ||
90 | h264Stream.finishFrame(); | ||
91 | }; | ||
92 | |||
93 | /** | ||
94 | * Returns whether a call to `getNextTag()` will be successful. | ||
95 | * @return {boolean} whether there is at least one transmuxed FLV | ||
96 | * tag ready | ||
97 | */ | ||
98 | self.tagsAvailable = function() { // :int { | ||
99 | return h264Stream.tags.length + aacStream.tags.length; | ||
100 | }; | ||
101 | |||
102 | /** | ||
103 | * Returns the next tag in decoder-timestamp (DTS) order. | ||
104 | * @returns {object} the next tag to decoded. | ||
105 | */ | ||
106 | self.getNextTag = function() { | ||
107 | var tag; | ||
108 | |||
109 | if (!h264Stream.tags.length) { | ||
110 | // only audio tags remain | ||
111 | tag = aacStream.tags.shift(); | ||
112 | } else if (!aacStream.tags.length) { | ||
113 | // only video tags remain | ||
114 | tag = h264Stream.tags.shift(); | ||
115 | } else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) { | ||
116 | // audio should be decoded next | ||
117 | tag = aacStream.tags.shift(); | ||
118 | } else { | ||
119 | // video should be decoded next | ||
120 | tag = h264Stream.tags.shift(); | ||
121 | } | ||
122 | |||
123 | return tag.finalize(); | ||
124 | }; | ||
125 | |||
126 | self.parseSegmentBinaryData = function(data) { // :ByteArray) { | ||
127 | var | ||
128 | dataPosition = 0, | ||
129 | dataSlice; | ||
130 | |||
131 | // To avoid an extra copy, we will stash overflow data, and only | ||
132 | // reconstruct the first packet. The rest of the packets will be | ||
133 | // parsed directly from data | ||
134 | if (streamBufferByteCount > 0) { | ||
135 | if (data.byteLength + streamBufferByteCount < MP2T_PACKET_LENGTH) { | ||
136 | // the current data is less than a single m2ts packet, so stash it | ||
137 | // until we receive more | ||
138 | |||
139 | // ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting. | ||
140 | videojs.log('data.length + streamBuffer.length < MP2T_PACKET_LENGTH ??'); | ||
141 | streamBuffer.readBytes(data, data.length, streamBuffer.length); | ||
142 | return; | ||
143 | } else { | ||
144 | // we have enough data for an m2ts packet | ||
145 | // process it immediately | ||
146 | dataSlice = data.subarray(0, MP2T_PACKET_LENGTH - streamBufferByteCount); | ||
147 | streamBuffer.set(dataSlice, streamBufferByteCount); | ||
148 | |||
149 | parseTSPacket(streamBuffer); | ||
150 | |||
151 | // reset the buffer | ||
152 | streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH); | ||
153 | streamBufferByteCount = 0; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | while (true) { | ||
158 | // Make sure we are TS aligned | ||
159 | while(dataPosition < data.byteLength && data[dataPosition] !== 0x47) { | ||
160 | // If there is no sync byte skip forward until we find one | ||
161 | // TODO if we find a sync byte, look 188 bytes in the future (if | ||
162 | // possible). If there is not a sync byte there, keep looking | ||
163 | dataPosition++; | ||
164 | } | ||
165 | |||
166 | // base case: not enough data to parse a m2ts packet | ||
167 | if (data.byteLength - dataPosition < MP2T_PACKET_LENGTH) { | ||
168 | if (data.byteLength - dataPosition > 0) { | ||
169 | // there are bytes remaining, save them for next time | ||
170 | streamBuffer.set(data.subarray(dataPosition), | ||
171 | streamBufferByteCount); | ||
172 | streamBufferByteCount += data.byteLength - dataPosition; | ||
173 | } | ||
174 | return; | ||
175 | } | ||
176 | |||
177 | // attempt to parse a m2ts packet | ||
178 | if (parseTSPacket(data.subarray(dataPosition, dataPosition + MP2T_PACKET_LENGTH))) { | ||
179 | dataPosition += MP2T_PACKET_LENGTH; | ||
180 | } else { | ||
181 | // If there was an error parsing a TS packet. it could be | ||
182 | // because we are not TS packet aligned. Step one forward by | ||
183 | // one byte and allow the code above to find the next | ||
184 | videojs.log('error parsing m2ts packet, attempting to re-align'); | ||
185 | dataPosition++; | ||
186 | } | ||
187 | } | ||
188 | }; | ||
189 | |||
190 | /** | ||
191 | * Parses a video/mp2t packet and appends the underlying video and | ||
192 | * audio data onto h264stream and aacStream, respectively. | ||
193 | * @param data {Uint8Array} the bytes of an MPEG2-TS packet, | ||
194 | * including the sync byte. | ||
195 | * @return {boolean} whether a valid packet was encountered | ||
196 | */ | ||
197 | // TODO add more testing to make sure we dont walk past the end of a TS | ||
198 | // packet! | ||
199 | parseTSPacket = function(data) { // :ByteArray):Boolean { | ||
200 | var | ||
201 | offset = 0, // :uint | ||
202 | end = offset + MP2T_PACKET_LENGTH, // :uint | ||
203 | |||
204 | // Payload Unit Start Indicator | ||
205 | pusi = !!(data[offset + 1] & 0x40), // mask: 0100 0000 | ||
206 | |||
207 | // packet identifier (PID), a unique identifier for the elementary | ||
208 | // stream this packet describes | ||
209 | pid = (data[offset + 1] & 0x1F) << 8 | data[offset + 2], // mask: 0001 1111 | ||
210 | |||
211 | // adaptation_field_control, whether this header is followed by an | ||
212 | // adaptation field, a payload, or both | ||
213 | afflag = (data[offset + 3] & 0x30 ) >>> 4, | ||
214 | |||
215 | patTableId, // :int | ||
216 | patCurrentNextIndicator, // Boolean | ||
217 | patSectionLength, // :uint | ||
218 | programNumber, // :uint | ||
219 | programPid, // :uint | ||
220 | patEntriesEnd, // :uint | ||
221 | |||
222 | pesPacketSize, // :int, | ||
223 | dataAlignmentIndicator, // :Boolean, | ||
224 | ptsDtsIndicator, // :int | ||
225 | pesHeaderLength, // :int | ||
226 | |||
227 | pts, // :uint | ||
228 | dts, // :uint | ||
229 | |||
230 | pmtCurrentNextIndicator, // :Boolean | ||
231 | pmtProgramDescriptorsLength, | ||
232 | pmtSectionLength, // :uint | ||
233 | |||
234 | streamType, // :int | ||
235 | elementaryPID, // :int | ||
236 | ESInfolength; // :int | ||
237 | |||
238 | // Continuity Counter we could use this for sanity check, and | ||
239 | // corrupt stream detection | ||
240 | // cc = (data[offset + 3] & 0x0F); | ||
241 | |||
242 | // move past the header | ||
243 | offset += 4; | ||
244 | |||
245 | // if an adaption field is present, its length is specified by | ||
246 | // the fifth byte of the PES header. The adaptation field is | ||
247 | // used to specify some forms of timing and control data that we | ||
248 | // do not currently use. | ||
249 | if (afflag > 0x01) { | ||
250 | offset += data[offset] + 1; | ||
251 | } | ||
252 | |||
253 | // Handle a Program Association Table (PAT). PATs map PIDs to | ||
254 | // individual programs. If this transport stream was being used | ||
255 | // for television broadcast, a program would probably be | ||
256 | // equivalent to a channel. In HLS, it would be very unusual to | ||
257 | // create an mp2t stream with multiple programs. | ||
258 | if (0x0000 === pid) { | ||
259 | // The PAT may be split into multiple sections and those | ||
260 | // sections may be split into multiple packets. If a PAT | ||
261 | // section starts in this packet, PUSI will be true and the | ||
262 | // first byte of the playload will indicate the offset from | ||
263 | // the current position to the start of the section. | ||
264 | if (pusi) { | ||
265 | offset += 1 + data[offset]; | ||
266 | } | ||
267 | patTableId = data[offset]; | ||
268 | |||
269 | if (patTableId !== 0x00) { | ||
270 | videojs.log('the table_id of the PAT should be 0x00 but was' + | ||
271 | patTableId.toString(16)); | ||
272 | } | ||
273 | |||
274 | // the current_next_indicator specifies whether this PAT is | ||
275 | // currently applicable or is part of the next table to become | ||
276 | // active | ||
277 | patCurrentNextIndicator = !!(data[offset + 5] & 0x01); | ||
278 | if (patCurrentNextIndicator) { | ||
279 | // section_length specifies the number of bytes following | ||
280 | // its position to the end of this section | ||
281 | // section_length = rest of header + (n * entry length) + CRC | ||
282 | // = 5 + (n * 4) + 4 | ||
283 | patSectionLength = (data[offset + 1] & 0x0F) << 8 | data[offset + 2]; | ||
284 | // move past the rest of the PSI header to the first program | ||
285 | // map table entry | ||
286 | offset += 8; | ||
287 | |||
288 | // we don't handle streams with more than one program, so | ||
289 | // raise an exception if we encounter one | ||
290 | patEntriesEnd = offset + (patSectionLength - 5 - 4); | ||
291 | for (; offset < patEntriesEnd; offset += 4) { | ||
292 | programNumber = (data[offset] << 8 | data[offset + 1]); | ||
293 | programPid = (data[offset + 2] & 0x1F) << 8 | data[offset + 3]; | ||
294 | // network PID program number equals 0 | ||
295 | // this is primarily an artifact of EBU DVB and can be ignored | ||
296 | if (programNumber === 0) { | ||
297 | self.stream.networkPid = programPid; | ||
298 | } else if (self.stream.pmtPid === undefined) { | ||
299 | // the Program Map Table (PMT) associates the underlying | ||
300 | // video and audio streams with a unique PID | ||
301 | self.stream.pmtPid = programPid; | ||
302 | } else if (self.stream.pmtPid !== programPid) { | ||
303 | throw new Error("TS has more that 1 program"); | ||
304 | } | ||
305 | } | ||
306 | } | ||
307 | } else if (pid === self.stream.programMapTable[STREAM_TYPES.h264] || | ||
308 | pid === self.stream.programMapTable[STREAM_TYPES.adts] || | ||
309 | pid === self.stream.programMapTable[STREAM_TYPES.metadata]) { | ||
310 | if (pusi) { | ||
311 | // comment out for speed | ||
312 | if (0x00 !== data[offset + 0] || 0x00 !== data[offset + 1] || 0x01 !== data[offset + 2]) { | ||
313 | // look for PES start code | ||
314 | throw new Error("PES did not begin with start code"); | ||
315 | } | ||
316 | |||
317 | // var sid:int = data[offset+3]; // StreamID | ||
318 | pesPacketSize = (data[offset + 4] << 8) | data[offset + 5]; | ||
319 | dataAlignmentIndicator = (data[offset + 6] & 0x04) !== 0; | ||
320 | ptsDtsIndicator = data[offset + 7]; | ||
321 | pesHeaderLength = data[offset + 8]; // TODO sanity check header length | ||
322 | offset += 9; // Skip past PES header | ||
323 | |||
324 | // PTS and DTS are normially stored as a 33 bit number. | ||
325 | // JavaScript does not have a integer type larger than 32 bit | ||
326 | // BUT, we need to convert from 90ns to 1ms time scale anyway. | ||
327 | // so what we are going to do instead, is drop the least | ||
328 | // significant bit (the same as dividing by two) then we can | ||
329 | // divide by 45 (45 * 2 = 90) to get ms. | ||
330 | if (ptsDtsIndicator & 0xC0) { | ||
331 | // the PTS and DTS are not written out directly. For information on | ||
332 | // how they are encoded, see | ||
333 | // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html | ||
334 | pts = (data[offset + 0] & 0x0E) << 28 | ||
335 | | (data[offset + 1] & 0xFF) << 21 | ||
336 | | (data[offset + 2] & 0xFE) << 13 | ||
337 | | (data[offset + 3] & 0xFF) << 6 | ||
338 | | (data[offset + 4] & 0xFE) >>> 2; | ||
339 | pts /= 45; | ||
340 | dts = pts; | ||
341 | if (ptsDtsIndicator & 0x40) {// DTS | ||
342 | dts = (data[offset + 5] & 0x0E ) << 28 | ||
343 | | (data[offset + 6] & 0xFF ) << 21 | ||
344 | | (data[offset + 7] & 0xFE ) << 13 | ||
345 | | (data[offset + 8] & 0xFF ) << 6 | ||
346 | | (data[offset + 9] & 0xFE ) >>> 2; | ||
347 | dts /= 45; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | // Skip past "optional" portion of PTS header | ||
352 | offset += pesHeaderLength; | ||
353 | |||
354 | if (pid === self.stream.programMapTable[STREAM_TYPES.h264]) { | ||
355 | h264Stream.setNextTimeStamp(pts, | ||
356 | dts, | ||
357 | dataAlignmentIndicator); | ||
358 | } else if (pid === self.stream.programMapTable[STREAM_TYPES.adts]) { | ||
359 | aacStream.setNextTimeStamp(pts, | ||
360 | pesPacketSize, | ||
361 | dataAlignmentIndicator); | ||
362 | } | ||
363 | } | ||
364 | |||
365 | if (pid === self.stream.programMapTable[STREAM_TYPES.adts]) { | ||
366 | aacStream.writeBytes(data, offset, end - offset); | ||
367 | } else if (pid === self.stream.programMapTable[STREAM_TYPES.h264]) { | ||
368 | h264Stream.writeBytes(data, offset, end - offset); | ||
369 | } else if (pid === self.stream.programMapTable[STREAM_TYPES.metadata]) { | ||
370 | self.metadataStream.push({ | ||
371 | pts: pts, | ||
372 | dts: dts, | ||
373 | data: data.subarray(offset) | ||
374 | }); | ||
375 | } | ||
376 | } else if (self.stream.pmtPid === pid) { | ||
377 | // similarly to the PAT, jump to the first byte of the section | ||
378 | if (pusi) { | ||
379 | offset += 1 + data[offset]; | ||
380 | } | ||
381 | if (data[offset] !== 0x02) { | ||
382 | videojs.log('The table_id of a PMT should be 0x02 but was ' + | ||
383 | data[offset].toString(16)); | ||
384 | } | ||
385 | |||
386 | // whether this PMT is currently applicable or is part of the | ||
387 | // next table to become active | ||
388 | pmtCurrentNextIndicator = !!(data[offset + 5] & 0x01); | ||
389 | if (pmtCurrentNextIndicator) { | ||
390 | // overwrite any existing program map table | ||
391 | self.stream.programMapTable = {}; | ||
392 | // section_length specifies the number of bytes following | ||
393 | // its position to the end of this section | ||
394 | pmtSectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2]; | ||
395 | // subtract the length of the program info descriptors | ||
396 | pmtProgramDescriptorsLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11]; | ||
397 | pmtSectionLength -= pmtProgramDescriptorsLength; | ||
398 | // skip CRC and PSI data we dont care about | ||
399 | // rest of header + CRC = 9 + 4 | ||
400 | pmtSectionLength -= 13; | ||
401 | |||
402 | // capture the PID of PCR packets so we can ignore them if we see any | ||
403 | self.stream.programMapTable.pcrPid = (data[offset + 8] & 0x1f) << 8 | data[offset + 9]; | ||
404 | |||
405 | // align offset to the first entry in the PMT | ||
406 | offset += 12 + pmtProgramDescriptorsLength; | ||
407 | |||
408 | // iterate through the entries | ||
409 | while (0 < pmtSectionLength) { | ||
410 | // the type of data carried in the PID this entry describes | ||
411 | streamType = data[offset + 0]; | ||
412 | // the PID for this entry | ||
413 | elementaryPID = (data[offset + 1] & 0x1F) << 8 | data[offset + 2]; | ||
414 | |||
415 | if (streamType === STREAM_TYPES.h264 && | ||
416 | self.stream.programMapTable[streamType] && | ||
417 | self.stream.programMapTable[streamType] !== elementaryPID) { | ||
418 | throw new Error("Program has more than 1 video stream"); | ||
419 | } else if (streamType === STREAM_TYPES.adts && | ||
420 | self.stream.programMapTable[streamType] && | ||
421 | self.stream.programMapTable[streamType] !== elementaryPID) { | ||
422 | throw new Error("Program has more than 1 audio Stream"); | ||
423 | } | ||
424 | // add the stream type entry to the map | ||
425 | self.stream.programMapTable[streamType] = elementaryPID; | ||
426 | |||
427 | // TODO add support for MP3 audio | ||
428 | |||
429 | // the length of the entry descriptor | ||
430 | ESInfolength = (data[offset + 3] & 0x0F) << 8 | data[offset + 4]; | ||
431 | // capture the stream descriptor for metadata streams | ||
432 | if (streamType === STREAM_TYPES.metadata) { | ||
433 | self.metadataStream.descriptor = new Uint8Array(data.subarray(offset + 5, offset + 5 + ESInfolength)); | ||
434 | } | ||
435 | // move to the first byte after the end of this entry | ||
436 | offset += 5 + ESInfolength; | ||
437 | pmtSectionLength -= 5 + ESInfolength; | ||
438 | } | ||
439 | } | ||
440 | // We could test the CRC here to detect corruption with extra CPU cost | ||
441 | } else if (self.stream.networkPid === pid) { | ||
442 | // network information specific data (NIT) packet | ||
443 | } else if (0x0011 === pid) { | ||
444 | // Service Description Table | ||
445 | } else if (0x1FFF === pid) { | ||
446 | // NULL packet | ||
447 | } else if (self.stream.programMapTable.pcrPid) { | ||
448 | // program clock reference (PCR) PID for the primary program | ||
449 | // PTS values are sufficient to synchronize playback for us so | ||
450 | // we can safely ignore these | ||
451 | } else { | ||
452 | videojs.log("Unknown PID parsing TS packet: " + pid); | ||
453 | } | ||
454 | |||
455 | return true; | ||
456 | }; | ||
457 | |||
458 | self.getTags = function() { | ||
459 | return h264Stream.tags; | ||
460 | }; | ||
461 | |||
462 | self.stats = { | ||
463 | h264Tags: function() { | ||
464 | return h264Stream.tags.length; | ||
465 | }, | ||
466 | minVideoPts: function() { | ||
467 | return h264Stream.tags[0].pts; | ||
468 | }, | ||
469 | maxVideoPts: function() { | ||
470 | return h264Stream.tags[h264Stream.tags.length - 1].pts; | ||
471 | }, | ||
472 | aacTags: function() { | ||
473 | return aacStream.tags.length; | ||
474 | }, | ||
475 | minAudioPts: function() { | ||
476 | return aacStream.tags[0].pts; | ||
477 | }, | ||
478 | maxAudioPts: function() { | ||
479 | return aacStream.tags[aacStream.tags.length - 1].pts; | ||
480 | } | ||
481 | }; | ||
482 | }; | ||
483 | |||
484 | // MPEG2-TS constants | ||
485 | videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188; | ||
486 | videojs.Hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = { | ||
487 | h264: 0x1b, | ||
488 | adts: 0x0f, | ||
489 | metadata: 0x15 | ||
490 | }; | ||
491 | |||
492 | })(window); |
src/transmuxer.js
deleted
100644 → 0
1 | /** | ||
2 | * video-js-hls | ||
3 | * | ||
4 | * Copyright (c) 2014 Brightcove | ||
5 | * All rights reserved. | ||
6 | */ | ||
7 | |||
8 | /** | ||
9 | * A stream-based mp2t to mp4 converter. This utility is used to | ||
10 | * deliver mp4s to a SourceBuffer on platforms that support native | ||
11 | * Media Source Extensions. The equivalent process for Flash-based | ||
12 | * platforms can be found in segment-parser.js | ||
13 | */ | ||
14 | (function(window, videojs, undefined) { | ||
15 | 'use strict'; | ||
16 | |||
17 | var | ||
18 | TransportPacketStream, TransportParseStream, ElementaryStream, VideoSegmentStream, | ||
19 | AudioSegmentStream, Transmuxer, AacStream, H264Stream, NalByteStream, | ||
20 | MP2T_PACKET_LENGTH, H264_STREAM_TYPE, ADTS_STREAM_TYPE, | ||
21 | ADTS_SAMPLING_FREQUENCIES, mp4; | ||
22 | |||
23 | MP2T_PACKET_LENGTH = 188; // bytes | ||
24 | H264_STREAM_TYPE = 0x1b; | ||
25 | ADTS_STREAM_TYPE = 0x0f; | ||
26 | ADTS_SAMPLING_FREQUENCIES = [ | ||
27 | 96000, | ||
28 | 88200, | ||
29 | 64000, | ||
30 | 48000, | ||
31 | 44100, | ||
32 | 32000, | ||
33 | 24000, | ||
34 | 22050, | ||
35 | 16000, | ||
36 | 12000, | ||
37 | 11025, | ||
38 | 8000, | ||
39 | 7350 | ||
40 | ]; | ||
41 | |||
42 | mp4 = videojs.mp4; | ||
43 | |||
44 | /** | ||
45 | * Splits an incoming stream of binary data into MPEG-2 Transport | ||
46 | * Stream packets. | ||
47 | */ | ||
48 | TransportPacketStream = function() { | ||
49 | var | ||
50 | buffer = new Uint8Array(MP2T_PACKET_LENGTH), | ||
51 | end = 0; | ||
52 | |||
53 | TransportPacketStream.prototype.init.call(this); | ||
54 | |||
55 | /** | ||
56 | * Deliver new bytes to the stream. | ||
57 | */ | ||
58 | this.push = function(bytes) { | ||
59 | var remaining, i; | ||
60 | |||
61 | // clear out any partial packets in the buffer | ||
62 | if (end > 0) { | ||
63 | remaining = MP2T_PACKET_LENGTH - end; | ||
64 | buffer.set(bytes.subarray(0, remaining), end); | ||
65 | |||
66 | // we still didn't write out a complete packet | ||
67 | if (bytes.byteLength < remaining) { | ||
68 | end += bytes.byteLength; | ||
69 | return; | ||
70 | } | ||
71 | |||
72 | bytes = bytes.subarray(remaining); | ||
73 | end = 0; | ||
74 | this.trigger('data', buffer); | ||
75 | } | ||
76 | |||
77 | // if less than a single packet is available, buffer it up for later | ||
78 | if (bytes.byteLength < MP2T_PACKET_LENGTH) { | ||
79 | buffer.set(bytes.subarray(i), end); | ||
80 | end += bytes.byteLength; | ||
81 | return; | ||
82 | } | ||
83 | // parse out all the completed packets | ||
84 | i = 0; | ||
85 | do { | ||
86 | this.trigger('data', bytes.subarray(i, i + MP2T_PACKET_LENGTH)); | ||
87 | i += MP2T_PACKET_LENGTH; | ||
88 | remaining = bytes.byteLength - i; | ||
89 | } while (i < bytes.byteLength && remaining >= MP2T_PACKET_LENGTH); | ||
90 | // buffer any partial packets left over | ||
91 | if (remaining > 0) { | ||
92 | buffer.set(bytes.subarray(i)); | ||
93 | end = remaining; | ||
94 | } | ||
95 | }; | ||
96 | }; | ||
97 | TransportPacketStream.prototype = new videojs.Hls.Stream(); | ||
98 | |||
99 | /** | ||
100 | * Accepts an MP2T TransportPacketStream and emits data events with parsed | ||
101 | * forms of the individual transport stream packets. | ||
102 | */ | ||
103 | TransportParseStream = function() { | ||
104 | var parsePsi, parsePat, parsePmt, parsePes, self; | ||
105 | TransportParseStream.prototype.init.call(this); | ||
106 | self = this; | ||
107 | |||
108 | this.programMapTable = {}; | ||
109 | |||
110 | parsePsi = function(payload, psi) { | ||
111 | var offset = 0; | ||
112 | |||
113 | // PSI packets may be split into multiple sections and those | ||
114 | // sections may be split into multiple packets. If a PSI | ||
115 | // section starts in this packet, the payload_unit_start_indicator | ||
116 | // will be true and the first byte of the payload will indicate | ||
117 | // the offset from the current position to the start of the | ||
118 | // section. | ||
119 | if (psi.payloadUnitStartIndicator) { | ||
120 | offset += payload[offset] + 1; | ||
121 | } | ||
122 | |||
123 | if (psi.type === 'pat') { | ||
124 | parsePat(payload.subarray(offset), psi); | ||
125 | } else { | ||
126 | parsePmt(payload.subarray(offset), psi); | ||
127 | } | ||
128 | }; | ||
129 | |||
130 | parsePat = function(payload, pat) { | ||
131 | pat.section_number = payload[7]; | ||
132 | pat.last_section_number = payload[8]; | ||
133 | |||
134 | // skip the PSI header and parse the first PMT entry | ||
135 | self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; | ||
136 | pat.pmtPid = self.pmtPid; | ||
137 | }; | ||
138 | |||
139 | /** | ||
140 | * Parse out the relevant fields of a Program Map Table (PMT). | ||
141 | * @param payload {Uint8Array} the PMT-specific portion of an MP2T | ||
142 | * packet. The first byte in this array should be the table_id | ||
143 | * field. | ||
144 | * @param pmt {object} the object that should be decorated with | ||
145 | * fields parsed from the PMT. | ||
146 | */ | ||
147 | parsePmt = function(payload, pmt) { | ||
148 | var sectionLength, tableEnd, programInfoLength, offset; | ||
149 | |||
150 | // PMTs can be sent ahead of the time when they should actually | ||
151 | // take effect. We don't believe this should ever be the case | ||
152 | // for HLS but we'll ignore "forward" PMT declarations if we see | ||
153 | // them. Future PMT declarations have the current_next_indicator | ||
154 | // set to zero. | ||
155 | if (!(payload[5] & 0x01)) { | ||
156 | return; | ||
157 | } | ||
158 | |||
159 | // overwrite any existing program map table | ||
160 | self.programMapTable = {}; | ||
161 | |||
162 | // the mapping table ends at the end of the current section | ||
163 | sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; | ||
164 | tableEnd = 3 + sectionLength - 4; | ||
165 | |||
166 | // to determine where the table is, we have to figure out how | ||
167 | // long the program info descriptors are | ||
168 | programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; | ||
169 | |||
170 | // advance the offset to the first entry in the mapping table | ||
171 | offset = 12 + programInfoLength; | ||
172 | while (offset < tableEnd) { | ||
173 | // add an entry that maps the elementary_pid to the stream_type | ||
174 | self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset]; | ||
175 | |||
176 | // move to the next table entry | ||
177 | // skip past the elementary stream descriptors, if present | ||
178 | offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; | ||
179 | } | ||
180 | |||
181 | // record the map on the packet as well | ||
182 | pmt.programMapTable = self.programMapTable; | ||
183 | }; | ||
184 | |||
185 | parsePes = function(payload, pes) { | ||
186 | var ptsDtsFlags; | ||
187 | |||
188 | if (!pes.payloadUnitStartIndicator) { | ||
189 | pes.data = payload; | ||
190 | return; | ||
191 | } | ||
192 | |||
193 | // find out if this packets starts a new keyframe | ||
194 | pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; | ||
195 | // PES packets may be annotated with a PTS value, or a PTS value | ||
196 | // and a DTS value. Determine what combination of values is | ||
197 | // available to work with. | ||
198 | ptsDtsFlags = payload[7]; | ||
199 | |||
200 | // PTS and DTS are normally stored as a 33-bit number. Javascript | ||
201 | // performs all bitwise operations on 32-bit integers but it's | ||
202 | // convenient to convert from 90ns to 1ms time scale anyway. So | ||
203 | // what we are going to do instead is drop the least significant | ||
204 | // bit (in effect, dividing by two) then we can divide by 45 (45 * | ||
205 | // 2 = 90) to get ms. | ||
206 | if (ptsDtsFlags & 0xC0) { | ||
207 | // the PTS and DTS are not written out directly. For information | ||
208 | // on how they are encoded, see | ||
209 | // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html | ||
210 | pes.pts = (payload[9] & 0x0E) << 28 | ||
211 | | (payload[10] & 0xFF) << 21 | ||
212 | | (payload[11] & 0xFE) << 13 | ||
213 | | (payload[12] & 0xFF) << 6 | ||
214 | | (payload[13] & 0xFE) >>> 2; | ||
215 | pes.pts /= 45; | ||
216 | pes.dts = pes.pts; | ||
217 | if (ptsDtsFlags & 0x40) { | ||
218 | pes.dts = (payload[14] & 0x0E ) << 28 | ||
219 | | (payload[15] & 0xFF ) << 21 | ||
220 | | (payload[16] & 0xFE ) << 13 | ||
221 | | (payload[17] & 0xFF ) << 6 | ||
222 | | (payload[18] & 0xFE ) >>> 2; | ||
223 | pes.dts /= 45; | ||
224 | } | ||
225 | } | ||
226 | |||
227 | // the data section starts immediately after the PES header. | ||
228 | // pes_header_data_length specifies the number of header bytes | ||
229 | // that follow the last byte of the field. | ||
230 | pes.data = payload.subarray(9 + payload[8]); | ||
231 | }; | ||
232 | |||
233 | /** | ||
234 | * Deliver a new MP2T packet to the stream. | ||
235 | */ | ||
236 | this.push = function(packet) { | ||
237 | var | ||
238 | result = {}, | ||
239 | offset = 4; | ||
240 | // make sure packet is aligned on a sync byte | ||
241 | if (packet[0] !== 0x47) { | ||
242 | return this.trigger('error', 'mis-aligned packet'); | ||
243 | } | ||
244 | result.payloadUnitStartIndicator = !!(packet[1] & 0x40); | ||
245 | |||
246 | // pid is a 13-bit field starting at the last bit of packet[1] | ||
247 | result.pid = packet[1] & 0x1f; | ||
248 | result.pid <<= 8; | ||
249 | result.pid |= packet[2]; | ||
250 | |||
251 | // if an adaption field is present, its length is specified by the | ||
252 | // fifth byte of the TS packet header. The adaptation field is | ||
253 | // used to add stuffing to PES packets that don't fill a complete | ||
254 | // TS packet, and to specify some forms of timing and control data | ||
255 | // that we do not currently use. | ||
256 | if (((packet[3] & 0x30) >>> 4) > 0x01) { | ||
257 | offset += packet[offset] + 1; | ||
258 | } | ||
259 | |||
260 | // parse the rest of the packet based on the type | ||
261 | if (result.pid === 0) { | ||
262 | result.type = 'pat'; | ||
263 | parsePsi(packet.subarray(offset), result); | ||
264 | } else if (result.pid === this.pmtPid) { | ||
265 | result.type = 'pmt'; | ||
266 | parsePsi(packet.subarray(offset), result); | ||
267 | } else { | ||
268 | result.streamType = this.programMapTable[result.pid]; | ||
269 | result.type = 'pes'; | ||
270 | parsePes(packet.subarray(offset), result); | ||
271 | } | ||
272 | |||
273 | this.trigger('data', result); | ||
274 | }; | ||
275 | }; | ||
276 | TransportParseStream.prototype = new videojs.Hls.Stream(); | ||
277 | TransportParseStream.STREAM_TYPES = { | ||
278 | h264: 0x1b, | ||
279 | adts: 0x0f | ||
280 | }; | ||
281 | |||
282 | /** | ||
283 | * Reconsistutes program elementary stream (PES) packets from parsed | ||
284 | * transport stream packets. That is, if you pipe an | ||
285 | * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output | ||
286 | * events will be events which capture the bytes for individual PES | ||
287 | * packets plus relevant metadata that has been extracted from the | ||
288 | * container. | ||
289 | */ | ||
290 | ElementaryStream = function() { | ||
291 | var | ||
292 | // PES packet fragments | ||
293 | video = { | ||
294 | data: [], | ||
295 | size: 0 | ||
296 | }, | ||
297 | audio = { | ||
298 | data: [], | ||
299 | size: 0 | ||
300 | }, | ||
301 | flushStream = function(stream, type) { | ||
302 | var | ||
303 | event = { | ||
304 | type: type, | ||
305 | data: new Uint8Array(stream.size), | ||
306 | }, | ||
307 | i = 0, | ||
308 | fragment; | ||
309 | |||
310 | // do nothing if there is no buffered data | ||
311 | if (!stream.data.length) { | ||
312 | return; | ||
313 | } | ||
314 | event.trackId = stream.data[0].pid; | ||
315 | event.pts = stream.data[0].pts; | ||
316 | event.dts = stream.data[0].dts; | ||
317 | |||
318 | // reassemble the packet | ||
319 | while (stream.data.length) { | ||
320 | fragment = stream.data.shift(); | ||
321 | |||
322 | event.data.set(fragment.data, i); | ||
323 | i += fragment.data.byteLength; | ||
324 | } | ||
325 | stream.size = 0; | ||
326 | |||
327 | self.trigger('data', event); | ||
328 | }, | ||
329 | self; | ||
330 | |||
331 | ElementaryStream.prototype.init.call(this); | ||
332 | self = this; | ||
333 | |||
334 | this.push = function(data) { | ||
335 | ({ | ||
336 | pat: function() { | ||
337 | // we have to wait for the PMT to arrive as well before we | ||
338 | // have any meaningful metadata | ||
339 | }, | ||
340 | pes: function() { | ||
341 | var stream, streamType; | ||
342 | |||
343 | switch (data.streamType) { | ||
344 | case H264_STREAM_TYPE: | ||
345 | stream = video; | ||
346 | streamType = 'video'; | ||
347 | break; | ||
348 | case ADTS_STREAM_TYPE: | ||
349 | stream = audio; | ||
350 | streamType = 'audio'; | ||
351 | break; | ||
352 | default: | ||
353 | // ignore unknown stream types | ||
354 | return; | ||
355 | } | ||
356 | |||
357 | // if a new packet is starting, we can flush the completed | ||
358 | // packet | ||
359 | if (data.payloadUnitStartIndicator) { | ||
360 | flushStream(stream, streamType); | ||
361 | } | ||
362 | |||
363 | // buffer this fragment until we are sure we've received the | ||
364 | // complete payload | ||
365 | stream.data.push(data); | ||
366 | stream.size += data.data.byteLength; | ||
367 | }, | ||
368 | pmt: function() { | ||
369 | var | ||
370 | event = { | ||
371 | type: 'metadata', | ||
372 | tracks: [] | ||
373 | }, | ||
374 | programMapTable = data.programMapTable, | ||
375 | k, | ||
376 | track; | ||
377 | |||
378 | // translate streams to tracks | ||
379 | for (k in programMapTable) { | ||
380 | if (programMapTable.hasOwnProperty(k)) { | ||
381 | track = {}; | ||
382 | track.id = +k; | ||
383 | if (programMapTable[k] === H264_STREAM_TYPE) { | ||
384 | track.codec = 'avc'; | ||
385 | track.type = 'video'; | ||
386 | } else if (programMapTable[k] === ADTS_STREAM_TYPE) { | ||
387 | track.codec = 'adts'; | ||
388 | track.type = 'audio'; | ||
389 | } | ||
390 | event.tracks.push(track); | ||
391 | } | ||
392 | } | ||
393 | self.trigger('data', event); | ||
394 | } | ||
395 | })[data.type](); | ||
396 | }; | ||
397 | |||
398 | /** | ||
399 | * Flush any remaining input. Video PES packets may be of variable | ||
400 | * length. Normally, the start of a new video packet can trigger the | ||
401 | * finalization of the previous packet. That is not possible if no | ||
402 | * more video is forthcoming, however. In that case, some other | ||
403 | * mechanism (like the end of the file) has to be employed. When it is | ||
404 | * clear that no additional data is forthcoming, calling this method | ||
405 | * will flush the buffered packets. | ||
406 | */ | ||
407 | this.end = function() { | ||
408 | flushStream(video, 'video'); | ||
409 | flushStream(audio, 'audio'); | ||
410 | }; | ||
411 | }; | ||
412 | ElementaryStream.prototype = new videojs.Hls.Stream(); | ||
413 | |||
414 | /* | ||
415 | * Accepts a ElementaryStream and emits data events with parsed | ||
416 | * AAC Audio Frames of the individual packets. Input audio in ADTS | ||
417 | * format is unpacked and re-emitted as AAC frames. | ||
418 | * | ||
419 | * @see http://wiki.multimedia.cx/index.php?title=ADTS | ||
420 | * @see http://wiki.multimedia.cx/?title=Understanding_AAC | ||
421 | */ | ||
422 | AacStream = function() { | ||
423 | var i = 1, self, buffer; | ||
424 | AacStream.prototype.init.call(this); | ||
425 | self = this; | ||
426 | |||
427 | this.push = function(packet) { | ||
428 | var frameLength; | ||
429 | |||
430 | if (packet.type !== 'audio') { | ||
431 | // ignore non-audio data | ||
432 | return; | ||
433 | } | ||
434 | |||
435 | buffer = packet.data; | ||
436 | |||
437 | // unpack any ADTS frames which have been fully received | ||
438 | while (i + 4 < buffer.length) { | ||
439 | // frame length is a 13 bit integer starting 16 bits from the | ||
440 | // end of the sync sequence | ||
441 | frameLength = ((buffer[i + 2] & 0x03) << 11) | | ||
442 | (buffer[i + 3] << 3) | | ||
443 | ((buffer[i + 4] & 0xe0) >> 5); | ||
444 | |||
445 | // deliver the AAC frame | ||
446 | this.trigger('data', { | ||
447 | channelcount: ((buffer[i + 1] & 1) << 3) | | ||
448 | ((buffer[i + 2] & 0xc0) >> 6), | ||
449 | samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 1] & 0x3c) >> 2], | ||
450 | // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 | ||
451 | samplesize: 16, | ||
452 | data: buffer.subarray(i + 6, i + frameLength - 1) | ||
453 | }); | ||
454 | |||
455 | // flush the finished frame and try again | ||
456 | buffer = buffer.subarray(i + frameLength - 1); | ||
457 | i = 1; | ||
458 | } | ||
459 | }; | ||
460 | }; | ||
461 | AacStream.prototype = new videojs.Hls.Stream(); | ||
462 | |||
463 | /** | ||
464 | * Constructs a single-track, ISO BMFF media segment from AAC data | ||
465 | * events. The output of this stream can be fed to a SourceBuffer | ||
466 | * configured with a suitable initialization segment. | ||
467 | */ | ||
468 | // TODO: share common code with VideoSegmentStream | ||
469 | AudioSegmentStream = function(track) { | ||
470 | var aacFrames = [], aacFramesLength = 0, sequenceNumber = 0; | ||
471 | AudioSegmentStream.prototype.init.call(this); | ||
472 | |||
473 | this.push = function(data) { | ||
474 | // buffer audio data until end() is called | ||
475 | aacFrames.push(data); | ||
476 | aacFramesLength += data.data.byteLength; | ||
477 | }; | ||
478 | |||
479 | this.end = function() { | ||
480 | var boxes, currentFrame, data, sample, i, mdat, moof; | ||
481 | // return early if no audio data has been observed | ||
482 | if (aacFramesLength === 0) { | ||
483 | return; | ||
484 | } | ||
485 | |||
486 | // concatenate the audio data to constuct the mdat | ||
487 | data = new Uint8Array(aacFramesLength); | ||
488 | track.samples = []; | ||
489 | i = 0; | ||
490 | while (aacFrames.length) { | ||
491 | currentFrame = aacFrames[0]; | ||
492 | sample = { | ||
493 | size: currentFrame.data.byteLength, | ||
494 | duration: 1024 // FIXME calculate for realz | ||
495 | }; | ||
496 | track.samples.push(sample); | ||
497 | |||
498 | data.set(currentFrame.data, i); | ||
499 | i += currentFrame.data.byteLength; | ||
500 | |||
501 | aacFrames.shift(); | ||
502 | } | ||
503 | aacFramesLength = 0; | ||
504 | mdat = mp4.mdat(data); | ||
505 | |||
506 | moof = mp4.moof(sequenceNumber, [track]); | ||
507 | boxes = new Uint8Array(moof.byteLength + mdat.byteLength); | ||
508 | |||
509 | // bump the sequence number for next time | ||
510 | sequenceNumber++; | ||
511 | |||
512 | boxes.set(moof); | ||
513 | boxes.set(mdat, moof.byteLength); | ||
514 | |||
515 | this.trigger('data', boxes); | ||
516 | }; | ||
517 | }; | ||
518 | AudioSegmentStream.prototype = new videojs.Hls.Stream(); | ||
519 | |||
520 | /** | ||
521 | * Accepts a NAL unit byte stream and unpacks the embedded NAL units. | ||
522 | */ | ||
523 | NalByteStream = function() { | ||
524 | var | ||
525 | syncPoint = 1, | ||
526 | i, | ||
527 | buffer; | ||
528 | NalByteStream.prototype.init.call(this); | ||
529 | |||
530 | this.push = function(data) { | ||
531 | var swapBuffer; | ||
532 | |||
533 | if (!buffer) { | ||
534 | buffer = data.data; | ||
535 | } else { | ||
536 | swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); | ||
537 | swapBuffer.set(buffer); | ||
538 | swapBuffer.set(data.data, buffer.byteLength); | ||
539 | buffer = swapBuffer; | ||
540 | } | ||
541 | |||
542 | // Rec. ITU-T H.264, Annex B | ||
543 | // scan for NAL unit boundaries | ||
544 | |||
545 | // a match looks like this: | ||
546 | // 0 0 1 .. NAL .. 0 0 1 | ||
547 | // ^ sync point ^ i | ||
548 | // or this: | ||
549 | // 0 0 1 .. NAL .. 0 0 0 | ||
550 | // ^ sync point ^ i | ||
551 | |||
552 | // advance the sync point to a NAL start, if necessary | ||
553 | for (; syncPoint < buffer.byteLength - 3; syncPoint++) { | ||
554 | if (buffer[syncPoint + 2] === 1) { | ||
555 | // the sync point is properly aligned | ||
556 | i = syncPoint + 5; | ||
557 | break; | ||
558 | } | ||
559 | } | ||
560 | |||
561 | while (i < buffer.byteLength) { | ||
562 | // look at the current byte to determine if we've hit the end of | ||
563 | // a NAL unit boundary | ||
564 | switch (buffer[i]) { | ||
565 | case 0: | ||
566 | // skip past non-sync sequences | ||
567 | if (buffer[i - 1] !== 0) { | ||
568 | i += 2; | ||
569 | break; | ||
570 | } else if (buffer[i - 2] !== 0) { | ||
571 | i++; | ||
572 | break; | ||
573 | } | ||
574 | |||
575 | // deliver the NAL unit | ||
576 | this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); | ||
577 | |||
578 | // drop trailing zeroes | ||
579 | do { | ||
580 | i++; | ||
581 | } while (buffer[i] !== 1 && i < buffer.length); | ||
582 | syncPoint = i - 2; | ||
583 | i += 3; | ||
584 | break; | ||
585 | case 1: | ||
586 | // skip past non-sync sequences | ||
587 | if (buffer[i - 1] !== 0 || | ||
588 | buffer[i - 2] !== 0) { | ||
589 | i += 3; | ||
590 | break; | ||
591 | } | ||
592 | |||
593 | // deliver the NAL unit | ||
594 | this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); | ||
595 | syncPoint = i - 2; | ||
596 | i += 3; | ||
597 | break; | ||
598 | default: | ||
599 | // the current byte isn't a one or zero, so it cannot be part | ||
600 | // of a sync sequence | ||
601 | i += 3; | ||
602 | break; | ||
603 | } | ||
604 | } | ||
605 | // filter out the NAL units that were delivered | ||
606 | buffer = buffer.subarray(syncPoint); | ||
607 | i -= syncPoint; | ||
608 | syncPoint = 0; | ||
609 | }; | ||
610 | |||
611 | this.end = function() { | ||
612 | // deliver the last buffered NAL unit | ||
613 | if (buffer && buffer.byteLength > 3) { | ||
614 | this.trigger('data', buffer.subarray(syncPoint + 3)); | ||
615 | } | ||
616 | }; | ||
617 | }; | ||
618 | NalByteStream.prototype = new videojs.Hls.Stream(); | ||
619 | |||
620 | /** | ||
621 | * Accepts input from a ElementaryStream and produces H.264 NAL unit data | ||
622 | * events. | ||
623 | */ | ||
624 | H264Stream = function() { | ||
625 | var | ||
626 | nalByteStream = new NalByteStream(), | ||
627 | self, | ||
628 | trackId, | ||
629 | currentPts, | ||
630 | currentDts, | ||
631 | |||
632 | readSequenceParameterSet, | ||
633 | skipScalingList; | ||
634 | |||
635 | H264Stream.prototype.init.call(this); | ||
636 | self = this; | ||
637 | |||
638 | this.push = function(packet) { | ||
639 | if (packet.type !== 'video') { | ||
640 | return; | ||
641 | } | ||
642 | trackId = packet.trackId; | ||
643 | currentPts = packet.pts; | ||
644 | currentDts = packet.dts; | ||
645 | |||
646 | nalByteStream.push(packet); | ||
647 | }; | ||
648 | |||
649 | nalByteStream.on('data', function(data) { | ||
650 | var event = { | ||
651 | trackId: trackId, | ||
652 | pts: currentPts, | ||
653 | dts: currentDts, | ||
654 | data: data | ||
655 | }; | ||
656 | switch (data[0] & 0x1f) { | ||
657 | |||
658 | case 0x05: | ||
659 | event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; | ||
660 | break; | ||
661 | case 0x07: | ||
662 | event.nalUnitType = 'seq_parameter_set_rbsp'; | ||
663 | event.config = readSequenceParameterSet(data.subarray(1)); | ||
664 | break; | ||
665 | case 0x08: | ||
666 | event.nalUnitType = 'pic_parameter_set_rbsp'; | ||
667 | break; | ||
668 | case 0x09: | ||
669 | event.nalUnitType = 'access_unit_delimiter_rbsp'; | ||
670 | break; | ||
671 | |||
672 | default: | ||
673 | break; | ||
674 | } | ||
675 | self.trigger('data', event); | ||
676 | }); | ||
677 | |||
678 | this.end = function() { | ||
679 | nalByteStream.end(); | ||
680 | }; | ||
681 | |||
682 | /** | ||
683 | * Advance the ExpGolomb decoder past a scaling list. The scaling | ||
684 | * list is optionally transmitted as part of a sequence parameter | ||
685 | * set and is not relevant to transmuxing. | ||
686 | * @param count {number} the number of entries in this scaling list | ||
687 | * @param expGolombDecoder {object} an ExpGolomb pointed to the | ||
688 | * start of a scaling list | ||
689 | * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 | ||
690 | */ | ||
691 | skipScalingList = function(count, expGolombDecoder) { | ||
692 | var | ||
693 | lastScale = 8, | ||
694 | nextScale = 8, | ||
695 | j, | ||
696 | deltaScale; | ||
697 | |||
698 | for (j = 0; j < count; j++) { | ||
699 | if (nextScale !== 0) { | ||
700 | deltaScale = expGolombDecoder.readExpGolomb(); | ||
701 | nextScale = (lastScale + deltaScale + 256) % 256; | ||
702 | } | ||
703 | |||
704 | lastScale = (nextScale === 0) ? lastScale : nextScale; | ||
705 | } | ||
706 | }; | ||
707 | |||
708 | /** | ||
709 | * Read a sequence parameter set and return some interesting video | ||
710 | * properties. A sequence parameter set is the H264 metadata that | ||
711 | * describes the properties of upcoming video frames. | ||
712 | * @param data {Uint8Array} the bytes of a sequence parameter set | ||
713 | * @return {object} an object with configuration parsed from the | ||
714 | * sequence parameter set, including the dimensions of the | ||
715 | * associated video frames. | ||
716 | */ | ||
717 | readSequenceParameterSet = function(data) { | ||
718 | var | ||
719 | frameCropLeftOffset = 0, | ||
720 | frameCropRightOffset = 0, | ||
721 | frameCropTopOffset = 0, | ||
722 | frameCropBottomOffset = 0, | ||
723 | expGolombDecoder, profileIdc, levelIdc, profileCompatibility, | ||
724 | chromaFormatIdc, picOrderCntType, | ||
725 | numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1, | ||
726 | picHeightInMapUnitsMinus1, | ||
727 | frameMbsOnlyFlag, | ||
728 | scalingListCount, | ||
729 | i; | ||
730 | |||
731 | expGolombDecoder = new videojs.Hls.ExpGolomb(data); | ||
732 | profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc | ||
733 | profileCompatibility = expGolombDecoder.readBits(5); // constraint_set[0-5]_flag | ||
734 | expGolombDecoder.skipBits(3); // u(1), reserved_zero_2bits u(2) | ||
735 | levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) | ||
736 | expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id | ||
737 | |||
738 | // some profiles have more optional data we don't need | ||
739 | if (profileIdc === 100 || | ||
740 | profileIdc === 110 || | ||
741 | profileIdc === 122 || | ||
742 | profileIdc === 244 || | ||
743 | profileIdc === 44 || | ||
744 | profileIdc === 83 || | ||
745 | profileIdc === 86 || | ||
746 | profileIdc === 118 || | ||
747 | profileIdc === 128) { | ||
748 | chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); | ||
749 | if (chromaFormatIdc === 3) { | ||
750 | expGolombDecoder.skipBits(1); // separate_colour_plane_flag | ||
751 | } | ||
752 | expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 | ||
753 | expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 | ||
754 | expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag | ||
755 | if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag | ||
756 | scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; | ||
757 | for (i = 0; i < scalingListCount; i++) { | ||
758 | if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ] | ||
759 | if (i < 6) { | ||
760 | skipScalingList(16, expGolombDecoder); | ||
761 | } else { | ||
762 | skipScalingList(64, expGolombDecoder); | ||
763 | } | ||
764 | } | ||
765 | } | ||
766 | } | ||
767 | } | ||
768 | |||
769 | expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 | ||
770 | picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); | ||
771 | |||
772 | if (picOrderCntType === 0) { | ||
773 | expGolombDecoder.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4 | ||
774 | } else if (picOrderCntType === 1) { | ||
775 | expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag | ||
776 | expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic | ||
777 | expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field | ||
778 | numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); | ||
779 | for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) { | ||
780 | expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] | ||
781 | } | ||
782 | } | ||
783 | |||
784 | expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames | ||
785 | expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag | ||
786 | |||
787 | picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); | ||
788 | picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); | ||
789 | |||
790 | frameMbsOnlyFlag = expGolombDecoder.readBits(1); | ||
791 | if (frameMbsOnlyFlag === 0) { | ||
792 | expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag | ||
793 | } | ||
794 | |||
795 | expGolombDecoder.skipBits(1); // direct_8x8_inference_flag | ||
796 | if (expGolombDecoder.readBoolean()) { // frame_cropping_flag | ||
797 | frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); | ||
798 | frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); | ||
799 | frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); | ||
800 | frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); | ||
801 | } | ||
802 | |||
803 | return { | ||
804 | profileIdc: profileIdc, | ||
805 | levelIdc: levelIdc, | ||
806 | profileCompatibility: profileCompatibility, | ||
807 | width: ((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2, | ||
808 | height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2) | ||
809 | }; | ||
810 | }; | ||
811 | |||
812 | }; | ||
813 | H264Stream.prototype = new videojs.Hls.Stream(); | ||
814 | |||
815 | /** | ||
816 | * Constructs a single-track, ISO BMFF media segment from H264 data | ||
817 | * events. The output of this stream can be fed to a SourceBuffer | ||
818 | * configured with a suitable initialization segment. | ||
819 | * @param track {object} track metadata configuration | ||
820 | */ | ||
821 | VideoSegmentStream = function(track) { | ||
822 | var | ||
823 | sequenceNumber = 0, | ||
824 | nalUnits = [], | ||
825 | nalUnitsLength = 0; | ||
826 | VideoSegmentStream.prototype.init.call(this); | ||
827 | |||
828 | this.push = function(data) { | ||
829 | // buffer video until end() is called | ||
830 | nalUnits.push(data); | ||
831 | nalUnitsLength += data.data.byteLength; | ||
832 | }; | ||
833 | |||
834 | this.end = function() { | ||
835 | var startUnit, currentNal, moof, mdat, boxes, i, data, view, sample; | ||
836 | |||
837 | // return early if no video data has been observed | ||
838 | if (nalUnitsLength === 0) { | ||
839 | return; | ||
840 | } | ||
841 | |||
842 | // concatenate the video data and construct the mdat | ||
843 | // first, we have to build the index from byte locations to | ||
844 | // samples (that is, frames) in the video data | ||
845 | data = new Uint8Array(nalUnitsLength + (4 * nalUnits.length)); | ||
846 | view = new DataView(data.buffer); | ||
847 | track.samples = []; | ||
848 | |||
849 | // see ISO/IEC 14496-12:2012, section 8.6.4.3 | ||
850 | sample = { | ||
851 | size: 0, | ||
852 | flags: { | ||
853 | isLeading: 0, | ||
854 | dependsOn: 1, | ||
855 | isDependedOn: 0, | ||
856 | hasRedundancy: 0, | ||
857 | degradationPriority: 0 | ||
858 | } | ||
859 | }; | ||
860 | i = 0; | ||
861 | while (nalUnits.length) { | ||
862 | currentNal = nalUnits[0]; | ||
863 | // flush the sample we've been building when a new sample is started | ||
864 | if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { | ||
865 | if (startUnit) { | ||
866 | // convert the duration to 90kHZ timescale to match the | ||
867 | // timescales specified in the init segment | ||
868 | sample.duration = (currentNal.dts - startUnit.dts) * 90; | ||
869 | track.samples.push(sample); | ||
870 | } | ||
871 | sample = { | ||
872 | size: 0, | ||
873 | flags: { | ||
874 | isLeading: 0, | ||
875 | dependsOn: 1, | ||
876 | isDependedOn: 0, | ||
877 | hasRedundancy: 0, | ||
878 | degradationPriority: 0 | ||
879 | }, | ||
880 | compositionTimeOffset: currentNal.pts - currentNal.dts | ||
881 | }; | ||
882 | startUnit = currentNal; | ||
883 | } | ||
884 | if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { | ||
885 | // the current sample is a key frame | ||
886 | sample.flags.dependsOn = 2; | ||
887 | } | ||
888 | sample.size += 4; // space for the NAL length | ||
889 | sample.size += currentNal.data.byteLength; | ||
890 | |||
891 | view.setUint32(i, currentNal.data.byteLength); | ||
892 | i += 4; | ||
893 | data.set(currentNal.data, i); | ||
894 | i += currentNal.data.byteLength; | ||
895 | |||
896 | nalUnits.shift(); | ||
897 | } | ||
898 | // record the last sample | ||
899 | if (track.samples.length) { | ||
900 | sample.duration = track.samples[track.samples.length - 1].duration; | ||
901 | } | ||
902 | track.samples.push(sample); | ||
903 | nalUnitsLength = 0; | ||
904 | mdat = mp4.mdat(data); | ||
905 | |||
906 | moof = mp4.moof(sequenceNumber, [track]); | ||
907 | |||
908 | // it would be great to allocate this array up front instead of | ||
909 | // throwing away hundreds of media segment fragments | ||
910 | boxes = new Uint8Array(moof.byteLength + mdat.byteLength); | ||
911 | |||
912 | // bump the sequence number for next time | ||
913 | sequenceNumber++; | ||
914 | |||
915 | boxes.set(moof); | ||
916 | boxes.set(mdat, moof.byteLength); | ||
917 | |||
918 | this.trigger('data', boxes); | ||
919 | }; | ||
920 | }; | ||
921 | VideoSegmentStream.prototype = new videojs.Hls.Stream(); | ||
922 | |||
923 | /** | ||
924 | * A Stream that expects MP2T binary data as input and produces | ||
925 | * corresponding media segments, suitable for use with Media Source | ||
926 | * Extension (MSE) implementations that support the ISO BMFF byte | ||
927 | * stream format, like Chrome. | ||
928 | * @see test/muxer/mse-demo.html for sample usage of a Transmuxer with | ||
929 | * MSE | ||
930 | */ | ||
931 | Transmuxer = function() { | ||
932 | var | ||
933 | self = this, | ||
934 | videoTrack, | ||
935 | audioTrack, | ||
936 | config, | ||
937 | pps, | ||
938 | |||
939 | packetStream, parseStream, elementaryStream, | ||
940 | aacStream, h264Stream, | ||
941 | videoSegmentStream, audioSegmentStream; | ||
942 | |||
943 | Transmuxer.prototype.init.call(this); | ||
944 | |||
945 | // set up the parsing pipeline | ||
946 | packetStream = new TransportPacketStream(); | ||
947 | parseStream = new TransportParseStream(); | ||
948 | elementaryStream = new ElementaryStream(); | ||
949 | aacStream = new AacStream(); | ||
950 | h264Stream = new H264Stream(); | ||
951 | |||
952 | packetStream.pipe(parseStream); | ||
953 | parseStream.pipe(elementaryStream); | ||
954 | elementaryStream.pipe(aacStream); | ||
955 | elementaryStream.pipe(h264Stream); | ||
956 | |||
957 | // handle incoming data events | ||
958 | h264Stream.on('data', function(data) { | ||
959 | // record the track config | ||
960 | if (data.nalUnitType === 'seq_parameter_set_rbsp' && | ||
961 | !config) { | ||
962 | config = data.config; | ||
963 | |||
964 | videoTrack.width = config.width; | ||
965 | videoTrack.height = config.height; | ||
966 | videoTrack.sps = [data.data]; | ||
967 | videoTrack.profileIdc = config.profileIdc; | ||
968 | videoTrack.levelIdc = config.levelIdc; | ||
969 | videoTrack.profileCompatibility = config.profileCompatibility; | ||
970 | |||
971 | // generate an init segment once all the metadata is available | ||
972 | if (pps) { | ||
973 | self.trigger('data', { | ||
974 | type: 'video', | ||
975 | data: videojs.mp4.initSegment([videoTrack]) | ||
976 | }); | ||
977 | } | ||
978 | } | ||
979 | if (data.nalUnitType === 'pic_parameter_set_rbsp' && | ||
980 | !pps) { | ||
981 | pps = data.data; | ||
982 | videoTrack.pps = [data.data]; | ||
983 | |||
984 | if (config) { | ||
985 | self.trigger('data', { | ||
986 | type: 'video', | ||
987 | data: videojs.mp4.initSegment([videoTrack]) | ||
988 | }); | ||
989 | } | ||
990 | } | ||
991 | }); | ||
992 | // generate an init segment based on the first audio sample | ||
993 | aacStream.on('data', function(data) { | ||
994 | if (audioTrack && audioTrack.channelcount === undefined) { | ||
995 | audioTrack.channelcount = data.channelcount; | ||
996 | audioTrack.samplerate = data.samplerate; | ||
997 | audioTrack.samplesize = data.samplesize; | ||
998 | self.trigger('data', { | ||
999 | type: 'audio', | ||
1000 | data: videojs.mp4.initSegment([audioTrack]) | ||
1001 | }); | ||
1002 | } | ||
1003 | }); | ||
1004 | // hook up the segment streams once track metadata is delivered | ||
1005 | elementaryStream.on('data', function(data) { | ||
1006 | var i, triggerData = function(type) { | ||
1007 | return function(segment) { | ||
1008 | self.trigger('data', { | ||
1009 | type: type, | ||
1010 | data: segment | ||
1011 | }); | ||
1012 | }; | ||
1013 | }; | ||
1014 | if (data.type === 'metadata') { | ||
1015 | i = data.tracks.length; | ||
1016 | |||
1017 | // scan the tracks listed in the metadata | ||
1018 | while (i--) { | ||
1019 | |||
1020 | // hook up the video segment stream to the first track with h264 data | ||
1021 | if (data.tracks[i].type === 'video' && !videoSegmentStream) { | ||
1022 | videoTrack = data.tracks[i]; | ||
1023 | videoSegmentStream = new VideoSegmentStream(videoTrack); | ||
1024 | h264Stream.pipe(videoSegmentStream); | ||
1025 | videoSegmentStream.on('data', triggerData('video')); | ||
1026 | break; | ||
1027 | } | ||
1028 | |||
1029 | // hook up the audio segment stream to the first track with aac data | ||
1030 | if (data.tracks[i].type === 'audio' && !audioSegmentStream) { | ||
1031 | audioTrack = data.tracks[i]; | ||
1032 | audioSegmentStream = new AudioSegmentStream(audioTrack); | ||
1033 | aacStream.pipe(audioSegmentStream); | ||
1034 | audioSegmentStream.on('data', triggerData('audio')); | ||
1035 | } | ||
1036 | } | ||
1037 | } | ||
1038 | }); | ||
1039 | |||
1040 | // feed incoming data to the front of the parsing pipeline | ||
1041 | this.push = function(data) { | ||
1042 | packetStream.push(data); | ||
1043 | }; | ||
1044 | // flush any buffered data | ||
1045 | this.end = function() { | ||
1046 | elementaryStream.end(); | ||
1047 | h264Stream.end(); | ||
1048 | if (videoSegmentStream) { | ||
1049 | videoSegmentStream.end(); | ||
1050 | } | ||
1051 | if (audioSegmentStream) { | ||
1052 | audioSegmentStream.end(); | ||
1053 | } | ||
1054 | }; | ||
1055 | }; | ||
1056 | Transmuxer.prototype = new videojs.Hls.Stream(); | ||
1057 | |||
1058 | window.videojs.mp2t = { | ||
1059 | PAT_PID: 0x0000, | ||
1060 | MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, | ||
1061 | H264_STREAM_TYPE: H264_STREAM_TYPE, | ||
1062 | ADTS_STREAM_TYPE: ADTS_STREAM_TYPE, | ||
1063 | TransportPacketStream: TransportPacketStream, | ||
1064 | TransportParseStream: TransportParseStream, | ||
1065 | ElementaryStream: ElementaryStream, | ||
1066 | VideoSegmentStream: VideoSegmentStream, | ||
1067 | Transmuxer: Transmuxer, | ||
1068 | AacStream: AacStream, | ||
1069 | H264Stream: H264Stream | ||
1070 | }; | ||
1071 | })(window, window.videojs); |
test/exp-golomb_test.js
deleted
100644 → 0
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 | buffer, | ||
24 | ExpGolomb = window.videojs.Hls.ExpGolomb, | ||
25 | expGolomb; | ||
26 | |||
27 | module('Exponential Golomb coding'); | ||
28 | |||
29 | test('small numbers are coded correctly', function() { | ||
30 | var | ||
31 | expected = [ | ||
32 | [0xF8, 0], | ||
33 | [0x5F, 1], | ||
34 | [0x7F, 2], | ||
35 | [0x27, 3], | ||
36 | [0x2F, 4], | ||
37 | [0x37, 5], | ||
38 | [0x3F, 6], | ||
39 | [0x11, 7], | ||
40 | [0x13, 8], | ||
41 | [0x15, 9] | ||
42 | ], | ||
43 | i = expected.length, | ||
44 | result; | ||
45 | |||
46 | while (i--) { | ||
47 | buffer = new Uint8Array([expected[i][0]]); | ||
48 | expGolomb = new ExpGolomb(buffer); | ||
49 | result = expGolomb.readUnsignedExpGolomb(); | ||
50 | equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]); | ||
51 | } | ||
52 | }); | ||
53 | |||
54 | test('drops working data as it is parsed', function() { | ||
55 | var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF])); | ||
56 | expGolomb.skipBits(8); | ||
57 | equal(8, expGolomb.bitsAvailable(), '8 bits remain'); | ||
58 | equal(0xFF, expGolomb.readBits(8), 'the second byte is read'); | ||
59 | }); | ||
60 | |||
61 | test('drops working data when skipping leading zeros', function() { | ||
62 | var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF])); | ||
63 | equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped'); | ||
64 | equal(8, expGolomb.bitsAvailable(), '8 bits remain'); | ||
65 | equal(0xFF, expGolomb.readBits(8), 'the second byte is read'); | ||
66 | }); | ||
67 | |||
68 | test('drops working data when skipping leading zeros', function() { | ||
69 | var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF])); | ||
70 | equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped'); | ||
71 | equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain'); | ||
72 | expGolomb.skipBits(1); | ||
73 | equal(0x5a, expGolomb.readBits(8), 'the next bits are read'); | ||
74 | }); | ||
75 | |||
76 | test('parses a sequence parameter set', function() { | ||
77 | var | ||
78 | sps = new Uint8Array([ | ||
79 | 0x27, 0x42, 0xe0, 0x0b, | ||
80 | 0xa9, 0x18, 0x60, 0x9d, | ||
81 | 0x80, 0x35, 0x06, 0x01, | ||
82 | 0x06, 0xb6, 0xc2, 0xb5, | ||
83 | 0xef, 0x7c, 0x04 | ||
84 | ]), | ||
85 | expGolomb = new ExpGolomb(sps); | ||
86 | |||
87 | strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS'); | ||
88 | strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66'); | ||
89 | strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct'); | ||
90 | |||
91 | expGolomb.skipBits(4); | ||
92 | strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11'); | ||
93 | strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0'); | ||
94 | strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1'); | ||
95 | strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0'); | ||
96 | strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3'); | ||
97 | strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2'); | ||
98 | strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false'); | ||
99 | strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11'); | ||
100 | strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8'); | ||
101 | strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true'); | ||
102 | strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true'); | ||
103 | strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false'); | ||
104 | }); | ||
105 | |||
106 | })(this); |
test/flv-tag_test.js
deleted
100644 → 0
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 FlvTag = window.videojs.Hls.FlvTag; | ||
23 | |||
24 | module('FLV tag'); | ||
25 | |||
26 | test('writeBytes with zero length writes the entire array', function() { | ||
27 | var | ||
28 | tag = new FlvTag(FlvTag.VIDEO_TAG), | ||
29 | headerLength = tag.length; | ||
30 | tag.writeBytes(new Uint8Array([0x1, 0x2, 0x3])); | ||
31 | |||
32 | equal(3 + headerLength, tag.length, '3 payload bytes are written'); | ||
33 | }); | ||
34 | |||
35 | test('writeShort writes a two byte sequence', function() { | ||
36 | var | ||
37 | tag = new FlvTag(FlvTag.VIDEO_TAG), | ||
38 | headerLength = tag.length; | ||
39 | tag.writeShort(0x0102); | ||
40 | |||
41 | equal(2 + headerLength, tag.length, '2 bytes are written'); | ||
42 | equal(0x0102, | ||
43 | new DataView(tag.bytes.buffer).getUint16(tag.length - 2), | ||
44 | 'the value is written'); | ||
45 | }); | ||
46 | |||
47 | test('writeBytes grows the internal byte array dynamically', function() { | ||
48 | var | ||
49 | tag = new FlvTag(FlvTag.VIDEO_TAG), | ||
50 | tooManyBytes = new Uint8Array(tag.bytes.byteLength + 1); | ||
51 | |||
52 | try { | ||
53 | tag.writeBytes(tooManyBytes); | ||
54 | ok(true, 'the buffer grew to fit the data'); | ||
55 | } catch(e) { | ||
56 | ok(!e, 'the buffer should grow'); | ||
57 | } | ||
58 | }); | ||
59 | |||
60 | })(this); |
test/h264-stream_test.js
deleted
100644 → 0
1 | (function(videojs) { | ||
2 | module('H264 Stream'); | ||
3 | |||
4 | var | ||
5 | nalUnitTypes = window.videojs.Hls.NALUnitType, | ||
6 | FlvTag = window.videojs.Hls.FlvTag, | ||
7 | |||
8 | accessUnitDelimiter = new Uint8Array([ | ||
9 | 0x00, | ||
10 | 0x00, | ||
11 | 0x01, | ||
12 | nalUnitTypes.access_unit_delimiter_rbsp | ||
13 | ]), | ||
14 | seqParamSet = new Uint8Array([ | ||
15 | 0x00, | ||
16 | 0x00, | ||
17 | 0x01, | ||
18 | 0x60 | nalUnitTypes.seq_parameter_set_rbsp, | ||
19 | 0x00, // profile_idc | ||
20 | 0x00, // constraint_set flags | ||
21 | 0x00, // level_idc | ||
22 | // seq_parameter_set_id ue(v) 0 => 1 | ||
23 | // log2_max_frame_num_minus4 ue(v) 1 => 010 | ||
24 | // pic_order_cnt_type ue(v) 0 => 1 | ||
25 | // log2_max_pic_order_cnt_lsb_minus4 ue(v) 1 => 010 | ||
26 | // max_num_ref_frames ue(v) 1 => 010 | ||
27 | // gaps_in_frame_num_value_allowed u(1) 0 | ||
28 | // pic_width_in_mbs_minus1 ue(v) 0 => 1 | ||
29 | // pic_height_in_map_units_minus1 ue(v) 0 => 1 | ||
30 | // frame_mbs_only_flag u(1) 1 | ||
31 | // direct_8x8_inference_flag u(1) 0 | ||
32 | // frame_cropping_flag u(1) 0 | ||
33 | // vui_parameters_present_flag u(1) 0 | ||
34 | // 1010 1010 0100 1110 00(00 0000) | ||
35 | 0xAA, | ||
36 | 0x4E, | ||
37 | 0x00 | ||
38 | ]); | ||
39 | |||
40 | test('metadata is generated for IDRs after a full NAL unit is written', function() { | ||
41 | var | ||
42 | h264Stream = new videojs.Hls.H264Stream(), | ||
43 | idr = new Uint8Array([ | ||
44 | 0x00, | ||
45 | 0x00, | ||
46 | 0x01, | ||
47 | nalUnitTypes.slice_layer_without_partitioning_rbsp_idr | ||
48 | ]); | ||
49 | |||
50 | h264Stream.setNextTimeStamp(0, 0, true); | ||
51 | h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength); | ||
52 | h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength); | ||
53 | h264Stream.writeBytes(idr, 0, idr.byteLength); | ||
54 | h264Stream.setNextTimeStamp(1, 1, true); | ||
55 | |||
56 | strictEqual(h264Stream.tags.length, 3, 'three tags are written'); | ||
57 | ok(FlvTag.isMetaData(h264Stream.tags[0].bytes), | ||
58 | 'metadata is written'); | ||
59 | ok(FlvTag.isVideoFrame(h264Stream.tags[1].bytes), | ||
60 | 'picture parameter set is written'); | ||
61 | ok(h264Stream.tags[2].keyFrame, 'key frame is written'); | ||
62 | }); | ||
63 | |||
64 | test('make sure we add metadata and extra data at the beginning of a stream', function() { | ||
65 | var | ||
66 | H264ExtraData = videojs.Hls.H264ExtraData, | ||
67 | oldExtraData = H264ExtraData.prototype.extraDataTag, | ||
68 | oldMetadata = H264ExtraData.prototype.metaDataTag, | ||
69 | h264Stream; | ||
70 | |||
71 | H264ExtraData.prototype.extraDataTag = function() { | ||
72 | return 'extraDataTag'; | ||
73 | }; | ||
74 | H264ExtraData.prototype.metaDataTag = function() { | ||
75 | return 'metaDataTag'; | ||
76 | }; | ||
77 | |||
78 | h264Stream = new videojs.Hls.H264Stream(); | ||
79 | |||
80 | h264Stream.setTimeStampOffset(0); | ||
81 | h264Stream.setNextTimeStamp(0, 0, true); | ||
82 | // the sps provides the metadata for the stream | ||
83 | h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength); | ||
84 | h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength); | ||
85 | |||
86 | // make sure that keyFrame is set to false but that we don't have any tags currently written out | ||
87 | h264Stream._h264Frame.keyFrame = false; | ||
88 | h264Stream.tags = []; | ||
89 | |||
90 | h264Stream.setNextTimeStamp(5, 5, true); | ||
91 | h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength); | ||
92 | // flush out the last tag | ||
93 | h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength); | ||
94 | |||
95 | strictEqual(h264Stream.tags.length, 4, 'three tags are ready'); | ||
96 | strictEqual(h264Stream.tags[0], 'metaDataTag', 'the first tag is the metaDataTag'); | ||
97 | strictEqual(h264Stream.tags[1], 'extraDataTag', 'the second tag is the extraDataTag'); | ||
98 | |||
99 | H264ExtraData.prototype.extraDataTag = oldExtraData; | ||
100 | H264ExtraData.prototype.metaDataTag = oldMetadata; | ||
101 | }); | ||
102 | |||
103 | })(window.videojs); |
... | @@ -77,25 +77,15 @@ module.exports = function(config) { | ... | @@ -77,25 +77,15 @@ module.exports = function(config) { |
77 | '../test/karma-qunit-shim.js', | 77 | '../test/karma-qunit-shim.js', |
78 | '../src/videojs-hls.js', | 78 | '../src/videojs-hls.js', |
79 | '../src/stream.js', | 79 | '../src/stream.js', |
80 | '../src/flv-tag.js', | ||
81 | '../src/exp-golomb.js', | ||
82 | '../src/h264-extradata.js', | ||
83 | '../src/h264-stream.js', | ||
84 | '../src/aac-stream.js', | ||
85 | '../src/metadata-stream.js', | ||
86 | '../src/segment-parser.js', | ||
87 | '../src/m3u8/m3u8-parser.js', | 80 | '../src/m3u8/m3u8-parser.js', |
88 | '../src/xhr.js', | 81 | '../src/xhr.js', |
89 | '../src/playlist.js', | 82 | '../src/playlist.js', |
90 | '../src/playlist-loader.js', | 83 | '../src/playlist-loader.js', |
91 | '../src/decrypter.js', | 84 | '../src/decrypter.js', |
92 | '../src/mp4-generator.js', | ||
93 | '../src/transmuxer.js', | ||
94 | '../tmp/manifests.js', | 85 | '../tmp/manifests.js', |
95 | '../tmp/expected.js', | 86 | '../tmp/expected.js', |
96 | 'tsSegment-bc.js', | 87 | 'tsSegment-bc.js', |
97 | '../src/bin-utils.js', | 88 | '../src/bin-utils.js', |
98 | '../test/muxer/js/mp4-inspector.js', | ||
99 | '../test/*.js', | 89 | '../test/*.js', |
100 | ], | 90 | ], |
101 | 91 | ... | ... |
... | @@ -42,25 +42,15 @@ module.exports = function(config) { | ... | @@ -42,25 +42,15 @@ module.exports = function(config) { |
42 | '../test/karma-qunit-shim.js', | 42 | '../test/karma-qunit-shim.js', |
43 | '../src/videojs-hls.js', | 43 | '../src/videojs-hls.js', |
44 | '../src/stream.js', | 44 | '../src/stream.js', |
45 | '../src/flv-tag.js', | ||
46 | '../src/exp-golomb.js', | ||
47 | '../src/h264-extradata.js', | ||
48 | '../src/h264-stream.js', | ||
49 | '../src/aac-stream.js', | ||
50 | '../src/metadata-stream.js', | ||
51 | '../src/segment-parser.js', | ||
52 | '../src/m3u8/m3u8-parser.js', | 45 | '../src/m3u8/m3u8-parser.js', |
53 | '../src/xhr.js', | 46 | '../src/xhr.js', |
54 | '../src/playlist.js', | 47 | '../src/playlist.js', |
55 | '../src/playlist-loader.js', | 48 | '../src/playlist-loader.js', |
56 | '../src/decrypter.js', | 49 | '../src/decrypter.js', |
57 | '../src/mp4-generator.js', | ||
58 | '../src/transmuxer.js', | ||
59 | '../tmp/manifests.js', | 50 | '../tmp/manifests.js', |
60 | '../tmp/expected.js', | 51 | '../tmp/expected.js', |
61 | 'tsSegment-bc.js', | 52 | 'tsSegment-bc.js', |
62 | '../src/bin-utils.js', | 53 | '../src/bin-utils.js', |
63 | '../test/muxer/js/mp4-inspector.js', | ||
64 | '../test/*.js', | 54 | '../test/*.js', |
65 | ], | 55 | ], |
66 | 56 | ... | ... |
test/metadata-stream_test.js
deleted
100644 → 0
1 | (function(window, videojs, undefined) { | ||
2 | 'use strict'; | ||
3 | /* | ||
4 | ======== A Handy Little QUnit Reference ======== | ||
5 | http://api.qunitjs.com/ | ||
6 | |||
7 | Test methods: | ||
8 | module(name, {[setup][ ,teardown]}) | ||
9 | test(name, callback) | ||
10 | expect(numberOfAssertions) | ||
11 | stop(increment) | ||
12 | start(decrement) | ||
13 | Test assertions: | ||
14 | ok(value, [message]) | ||
15 | equal(actual, expected, [message]) | ||
16 | notEqual(actual, expected, [message]) | ||
17 | deepEqual(actual, expected, [message]) | ||
18 | notDeepEqual(actual, expected, [message]) | ||
19 | strictEqual(actual, expected, [message]) | ||
20 | notStrictEqual(actual, expected, [message]) | ||
21 | throws(block, [expected], [message]) | ||
22 | */ | ||
23 | |||
24 | var metadataStream, stringToInts, stringToCString, id3Tag, id3Frame; | ||
25 | |||
26 | module('MetadataStream', { | ||
27 | setup: function() { | ||
28 | metadataStream = new videojs.Hls.MetadataStream(); | ||
29 | } | ||
30 | }); | ||
31 | |||
32 | test('can construct a MetadataStream', function() { | ||
33 | ok(metadataStream, 'does not return null'); | ||
34 | }); | ||
35 | |||
36 | stringToInts = function(string) { | ||
37 | var result = [], i; | ||
38 | for (i = 0; i < string.length; i++) { | ||
39 | result[i] = string.charCodeAt(i); | ||
40 | } | ||
41 | return result; | ||
42 | }; | ||
43 | |||
44 | stringToCString = function(string) { | ||
45 | return stringToInts(string).concat([0x00]); | ||
46 | }; | ||
47 | |||
48 | id3Tag = function() { | ||
49 | var | ||
50 | frames = Array.prototype.concat.apply([], Array.prototype.slice.call(arguments)), | ||
51 | result = stringToInts('ID3').concat([ | ||
52 | 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0) | ||
53 | 0x40, // flags. include an extended header | ||
54 | 0x00, 0x00, 0x00, 0x00, // size. set later | ||
55 | |||
56 | // extended header | ||
57 | 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC | ||
58 | 0x00, 0x00, // extended flags | ||
59 | 0x00, 0x00, 0x00, 0x02 // size of padding | ||
60 | ], frames), | ||
61 | size; | ||
62 | |||
63 | // size is stored as a sequence of four 7-bit integers with the | ||
64 | // high bit of each byte set to zero | ||
65 | size = result.length - 10; | ||
66 | result[6] = (size >>> 21) & 0x7f; | ||
67 | result[7] = (size >>> 14) & 0x7f; | ||
68 | result[8] = (size >>> 7) & 0x7f; | ||
69 | result[9] = (size) & 0x7f; | ||
70 | |||
71 | return result; | ||
72 | }; | ||
73 | |||
74 | id3Frame = function(type) { | ||
75 | var result = stringToInts(type).concat([ | ||
76 | 0x00, 0x00, 0x00, 0x00, // size | ||
77 | 0xe0, 0x00 // flags. tag/file alter preservation, read-only | ||
78 | ]), | ||
79 | size = result.length - 10; | ||
80 | |||
81 | // append the fields of the ID3 frame | ||
82 | result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1)); | ||
83 | |||
84 | // set the size | ||
85 | size = result.length - 10; | ||
86 | result[4] = (size >>> 24); | ||
87 | result[5] = (size >>> 16) & 0xff; | ||
88 | result[6] = (size >>> 8) & 0xff; | ||
89 | result[7] = (size) & 0xff; | ||
90 | |||
91 | return result; | ||
92 | }; | ||
93 | |||
94 | test('parses simple ID3 metadata out of PES packets', function() { | ||
95 | var | ||
96 | events = [], | ||
97 | wxxxPayload = [ | ||
98 | 0x00 // text encoding. ISO-8859-1 | ||
99 | ].concat(stringToCString('ad tag URL'), // description | ||
100 | stringToInts('http://example.com/ad?v=1234&q=7')), // value | ||
101 | id3Bytes, | ||
102 | size; | ||
103 | |||
104 | metadataStream.on('data', function(event) { | ||
105 | events.push(event); | ||
106 | }); | ||
107 | |||
108 | id3Bytes = new Uint8Array(stringToInts('ID3').concat([ | ||
109 | 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0) | ||
110 | 0x40, // flags. include an extended header | ||
111 | 0x00, 0x00, 0x00, 0x00, // size. set later | ||
112 | |||
113 | // extended header | ||
114 | 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC | ||
115 | 0x00, 0x00, // extended flags | ||
116 | 0x00, 0x00, 0x00, 0x02, // size of padding | ||
117 | |||
118 | // frame 0 | ||
119 | // http://id3.org/id3v2.3.0#User_defined_text_information_frame | ||
120 | ], id3Frame('WXXX', | ||
121 | wxxxPayload), // value | ||
122 | // frame 1 | ||
123 | // custom tag | ||
124 | id3Frame('XINF', | ||
125 | [ | ||
126 | 0x04, 0x03, 0x02, 0x01 // arbitrary data | ||
127 | ]), [ | ||
128 | 0x00, 0x00 // padding | ||
129 | ])); | ||
130 | |||
131 | // set header size field | ||
132 | size = id3Bytes.byteLength - 10; | ||
133 | id3Bytes[6] = (size >>> 21) & 0x7f; | ||
134 | id3Bytes[7] = (size >>> 14) & 0x7f; | ||
135 | id3Bytes[8] = (size >>> 7) & 0x7f; | ||
136 | id3Bytes[9] = (size) & 0x7f; | ||
137 | |||
138 | metadataStream.push({ | ||
139 | trackId: 7, | ||
140 | pts: 1000, | ||
141 | dts: 1000, | ||
142 | |||
143 | // header | ||
144 | data: id3Bytes | ||
145 | }); | ||
146 | |||
147 | equal(events.length, 1, 'parsed one tag'); | ||
148 | equal(events[0].frames.length, 2, 'parsed two frames'); | ||
149 | equal(events[0].frames[0].id, 'WXXX', 'parsed a WXXX frame'); | ||
150 | deepEqual(new Uint8Array(events[0].frames[0].data), | ||
151 | new Uint8Array(wxxxPayload), | ||
152 | 'attached the frame payload'); | ||
153 | equal(events[0].frames[1].id, 'XINF', 'parsed a user-defined frame'); | ||
154 | deepEqual(new Uint8Array(events[0].frames[1].data), | ||
155 | new Uint8Array([0x04, 0x03, 0x02, 0x01]), | ||
156 | 'attached the frame payload'); | ||
157 | equal(events[0].pts, 1000, 'did not modify the PTS'); | ||
158 | equal(events[0].dts, 1000, 'did not modify the PTS'); | ||
159 | }); | ||
160 | |||
161 | test('skips non-ID3 metadata events', function() { | ||
162 | var events = []; | ||
163 | metadataStream.on('data', function(event) { | ||
164 | events.push(event); | ||
165 | }); | ||
166 | |||
167 | metadataStream.push({ | ||
168 | trackId: 7, | ||
169 | pts: 1000, | ||
170 | dts: 1000, | ||
171 | |||
172 | // header | ||
173 | data: new Uint8Array([0]) | ||
174 | }); | ||
175 | |||
176 | equal(events.length, 0, 'did not emit an event'); | ||
177 | }); | ||
178 | |||
179 | // missing cases: | ||
180 | // unsynchronization | ||
181 | // CRC | ||
182 | // no extended header | ||
183 | // compressed frames | ||
184 | // encrypted frames | ||
185 | // frame groups | ||
186 | // too large/small tag size values | ||
187 | // too large/small frame size values | ||
188 | |||
189 | test('parses TXXX frames', function() { | ||
190 | var events = []; | ||
191 | metadataStream.on('data', function(event) { | ||
192 | events.push(event); | ||
193 | }); | ||
194 | |||
195 | metadataStream.push({ | ||
196 | trackId: 7, | ||
197 | pts: 1000, | ||
198 | dts: 900, | ||
199 | |||
200 | // header | ||
201 | data: new Uint8Array(id3Tag(id3Frame('TXXX', | ||
202 | 0x03, // utf-8 | ||
203 | stringToCString('get done'), | ||
204 | stringToCString('{ "key": "value" }')), | ||
205 | [0x00, 0x00])) | ||
206 | }); | ||
207 | |||
208 | equal(events.length, 1, 'parsed one tag'); | ||
209 | equal(events[0].frames.length, 1, 'parsed one frame'); | ||
210 | equal(events[0].frames[0].id, 'TXXX', 'parsed the frame id'); | ||
211 | equal(events[0].frames[0].description, 'get done', 'parsed the description'); | ||
212 | deepEqual(JSON.parse(events[0].frames[0].value), { key: 'value' }, 'parsed the value'); | ||
213 | }); | ||
214 | |||
215 | test('parses WXXX frames', function() { | ||
216 | var events = [], url = 'http://example.com/path/file?abc=7&d=4#ty'; | ||
217 | metadataStream.on('data', function(event) { | ||
218 | events.push(event); | ||
219 | }); | ||
220 | |||
221 | metadataStream.push({ | ||
222 | trackId: 7, | ||
223 | pts: 1000, | ||
224 | dts: 900, | ||
225 | |||
226 | // header | ||
227 | data: new Uint8Array(id3Tag(id3Frame('WXXX', | ||
228 | 0x03, // utf-8 | ||
229 | stringToCString(''), | ||
230 | stringToInts(url)), | ||
231 | [0x00, 0x00])) | ||
232 | }); | ||
233 | |||
234 | equal(events.length, 1, 'parsed one tag'); | ||
235 | equal(events[0].frames.length, 1, 'parsed one frame'); | ||
236 | equal(events[0].frames[0].id, 'WXXX', 'parsed the frame id'); | ||
237 | equal(events[0].frames[0].description, '', 'parsed the description'); | ||
238 | equal(events[0].frames[0].url, url, 'parsed the value'); | ||
239 | }); | ||
240 | |||
241 | test('parses TXXX frames with characters that have a single-digit hexadecimal representation', function() { | ||
242 | var events = [], value = String.fromCharCode(7); | ||
243 | metadataStream.on('data', function(event) { | ||
244 | events.push(event); | ||
245 | }); | ||
246 | |||
247 | metadataStream.push({ | ||
248 | trackId: 7, | ||
249 | pts: 1000, | ||
250 | dts: 900, | ||
251 | |||
252 | // header | ||
253 | data: new Uint8Array(id3Tag(id3Frame('TXXX', | ||
254 | 0x03, // utf-8 | ||
255 | stringToCString(''), | ||
256 | stringToCString(value)), | ||
257 | [0x00, 0x00])) | ||
258 | }); | ||
259 | |||
260 | equal(events[0].frames[0].value, | ||
261 | value, | ||
262 | 'parsed the single-digit character'); | ||
263 | }); | ||
264 | |||
265 | test('parses PRIV frames', function() { | ||
266 | var | ||
267 | events = [], | ||
268 | payload = stringToInts('arbitrary data may be included in the payload ' + | ||
269 | 'of a PRIV frame'); | ||
270 | |||
271 | metadataStream.on('data', function(event) { | ||
272 | events.push(event); | ||
273 | }); | ||
274 | |||
275 | metadataStream.push({ | ||
276 | trackId: 7, | ||
277 | pts: 1000, | ||
278 | dts: 900, | ||
279 | |||
280 | // header | ||
281 | data: new Uint8Array(id3Tag(id3Frame('PRIV', | ||
282 | stringToCString('priv-owner@example.com'), | ||
283 | payload))) | ||
284 | }); | ||
285 | |||
286 | equal(events.length, 1, 'parsed a tag'); | ||
287 | equal(events[0].frames.length, 1, 'parsed a frame'); | ||
288 | equal(events[0].frames[0].id, 'PRIV', 'frame id is PRIV'); | ||
289 | equal(events[0].frames[0].owner, 'priv-owner@example.com', 'parsed the owner'); | ||
290 | deepEqual(new Uint8Array(events[0].frames[0].privateData), | ||
291 | new Uint8Array(payload), | ||
292 | 'parsed the frame private data'); | ||
293 | |||
294 | }); | ||
295 | |||
296 | test('parses tags split across pushes', function() { | ||
297 | var | ||
298 | events = [], | ||
299 | owner = stringToCString('owner@example.com'), | ||
300 | payload = stringToInts('A TS packet is 188 bytes in length so that it can' + | ||
301 | ' be easily transmitted over ATM networks, an ' + | ||
302 | 'important medium at one time. We want to be sure' + | ||
303 | ' that ID3 frames larger than a TS packet are ' + | ||
304 | 'properly re-assembled.'), | ||
305 | tag = new Uint8Array(id3Tag(id3Frame('PRIV', owner, payload))), | ||
306 | front = tag.subarray(0, 100), | ||
307 | back = tag.subarray(100); | ||
308 | |||
309 | metadataStream.on('data', function(event) { | ||
310 | events.push(event); | ||
311 | }); | ||
312 | |||
313 | metadataStream.push({ | ||
314 | trackId: 7, | ||
315 | pts: 1000, | ||
316 | dts: 900, | ||
317 | data: front | ||
318 | }); | ||
319 | |||
320 | equal(events.length, 0, 'parsed zero tags'); | ||
321 | |||
322 | metadataStream.push({ | ||
323 | trackId: 7, | ||
324 | pts: 1000, | ||
325 | dts: 900, | ||
326 | data: back | ||
327 | }); | ||
328 | |||
329 | equal(events.length, 1, 'parsed a tag'); | ||
330 | equal(events[0].frames.length, 1, 'parsed a frame'); | ||
331 | equal(events[0].frames[0].data.byteLength, | ||
332 | owner.length + payload.length, | ||
333 | 'collected data across pushes'); | ||
334 | |||
335 | // parses subsequent fragmented tags | ||
336 | tag = new Uint8Array(id3Tag(id3Frame('PRIV', | ||
337 | owner, payload, payload))); | ||
338 | front = tag.subarray(0, 188); | ||
339 | back = tag.subarray(188); | ||
340 | metadataStream.push({ | ||
341 | trackId: 7, | ||
342 | pts: 2000, | ||
343 | dts: 2000, | ||
344 | data: front | ||
345 | }); | ||
346 | metadataStream.push({ | ||
347 | trackId: 7, | ||
348 | pts: 2000, | ||
349 | dts: 2000, | ||
350 | data: back | ||
351 | }); | ||
352 | equal(events.length, 2, 'parsed a subseqent frame'); | ||
353 | }); | ||
354 | |||
355 | test('ignores tags when the header is fragmented', function() { | ||
356 | |||
357 | var | ||
358 | events = [], | ||
359 | tag = new Uint8Array(id3Tag(id3Frame('PRIV', | ||
360 | stringToCString('owner@example.com'), | ||
361 | stringToInts('payload')))), | ||
362 | // split the 10-byte ID3 tag header in half | ||
363 | front = tag.subarray(0, 5), | ||
364 | back = tag.subarray(5); | ||
365 | |||
366 | metadataStream.on('data', function(event) { | ||
367 | events.push(event); | ||
368 | }); | ||
369 | |||
370 | metadataStream.push({ | ||
371 | trackId: 7, | ||
372 | pts: 1000, | ||
373 | dts: 900, | ||
374 | data: front | ||
375 | }); | ||
376 | metadataStream.push({ | ||
377 | trackId: 7, | ||
378 | pts: 1000, | ||
379 | dts: 900, | ||
380 | data: back | ||
381 | }); | ||
382 | |||
383 | equal(events.length, 0, 'parsed zero tags'); | ||
384 | |||
385 | metadataStream.push({ | ||
386 | trackId: 7, | ||
387 | pts: 1500, | ||
388 | dts: 1500, | ||
389 | data: new Uint8Array(id3Tag(id3Frame('PRIV', | ||
390 | stringToCString('owner2'), | ||
391 | stringToInts('payload2')))) | ||
392 | }); | ||
393 | equal(events.length, 1, 'parsed one tag'); | ||
394 | equal(events[0].frames[0].owner, 'owner2', 'dropped the first tag'); | ||
395 | }); | ||
396 | |||
397 | // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track | ||
398 | test('constructs the dispatch type', function() { | ||
399 | metadataStream = new videojs.Hls.MetadataStream({ | ||
400 | descriptor: new Uint8Array([0x03, 0x02, 0x01, 0x00]) | ||
401 | }); | ||
402 | |||
403 | equal(metadataStream.dispatchType, '1503020100', 'built the dispatch type'); | ||
404 | }); | ||
405 | |||
406 | })(window, window.videojs); |
test/mp4-generator_test.js
deleted
100644 → 0
1 | (function(window, videojs) { | ||
2 | 'use strict'; | ||
3 | /* | ||
4 | ======== A Handy Little QUnit Reference ======== | ||
5 | http://api.qunitjs.com/ | ||
6 | |||
7 | Test methods: | ||
8 | module(name, {[setup][ ,teardown]}) | ||
9 | test(name, callback) | ||
10 | expect(numberOfAssertions) | ||
11 | stop(increment) | ||
12 | start(decrement) | ||
13 | Test assertions: | ||
14 | ok(value, [message]) | ||
15 | equal(actual, expected, [message]) | ||
16 | notEqual(actual, expected, [message]) | ||
17 | deepEqual(actual, expected, [message]) | ||
18 | notDeepEqual(actual, expected, [message]) | ||
19 | strictEqual(actual, expected, [message]) | ||
20 | notStrictEqual(actual, expected, [message]) | ||
21 | throws(block, [expected], [message]) | ||
22 | */ | ||
23 | var | ||
24 | mp4 = videojs.mp4, | ||
25 | inspectMp4 = videojs.inspectMp4, | ||
26 | validateMvhd, validateTrak, validateTkhd, validateMdia, | ||
27 | validateMdhd, validateHdlr, validateMinf, validateDinf, | ||
28 | validateStbl, validateStsd, validateMvex, | ||
29 | validateVideoSample, validateAudioSample; | ||
30 | |||
31 | module('MP4 Generator'); | ||
32 | |||
33 | test('generates a BSMFF ftyp', function() { | ||
34 | var data = mp4.ftyp(), boxes; | ||
35 | |||
36 | ok(data, 'box is not null'); | ||
37 | |||
38 | boxes = inspectMp4(data); | ||
39 | equal(1, boxes.length, 'generated a single box'); | ||
40 | equal(boxes[0].type, 'ftyp', 'generated ftyp type'); | ||
41 | equal(boxes[0].size, data.byteLength, 'generated size'); | ||
42 | equal(boxes[0].majorBrand, 'isom', 'major version is "isom"'); | ||
43 | equal(boxes[0].minorVersion, 1, 'minor version is one'); | ||
44 | }); | ||
45 | |||
46 | validateMvhd = function(mvhd) { | ||
47 | equal(mvhd.type, 'mvhd', 'generated a mvhd'); | ||
48 | equal(mvhd.duration, 0xffffffff, 'wrote the maximum movie header duration'); | ||
49 | equal(mvhd.nextTrackId, 0xffffffff, 'wrote the max next track id'); | ||
50 | }; | ||
51 | |||
52 | validateTrak = function(trak, expected) { | ||
53 | expected = expected || {}; | ||
54 | equal(trak.type, 'trak', 'generated a trak'); | ||
55 | equal(trak.boxes.length, 2, 'generated two track sub boxes'); | ||
56 | |||
57 | validateTkhd(trak.boxes[0], expected); | ||
58 | validateMdia(trak.boxes[1], expected); | ||
59 | }; | ||
60 | |||
61 | validateTkhd = function(tkhd, expected) { | ||
62 | equal(tkhd.type, 'tkhd', 'generated a tkhd'); | ||
63 | equal(tkhd.trackId, 7, 'wrote the track id'); | ||
64 | deepEqual(tkhd.flags, new Uint8Array([0, 0, 7]), 'flags should equal 7'); | ||
65 | equal(tkhd.duration, | ||
66 | expected.duration || Math.pow(2, 32) - 1, | ||
67 | 'wrote duration into the track header'); | ||
68 | equal(tkhd.width, expected.width || 0, 'wrote width into the track header'); | ||
69 | equal(tkhd.height, expected.height || 0, 'wrote height into the track header'); | ||
70 | equal(tkhd.volume, 1, 'set volume to 1'); | ||
71 | }; | ||
72 | |||
73 | validateMdia = function(mdia, expected) { | ||
74 | equal(mdia.type, 'mdia', 'generated an mdia type'); | ||
75 | equal(mdia.boxes.length, 3, 'generated three track media sub boxes'); | ||
76 | |||
77 | validateMdhd(mdia.boxes[0], expected); | ||
78 | validateHdlr(mdia.boxes[1], expected); | ||
79 | validateMinf(mdia.boxes[2], expected); | ||
80 | }; | ||
81 | |||
82 | validateMdhd = function(mdhd, expected) { | ||
83 | equal(mdhd.type, 'mdhd', 'generate an mdhd type'); | ||
84 | equal(mdhd.language, 'und', 'wrote undetermined language'); | ||
85 | equal(mdhd.timescale, expected.timescale || 90000, 'wrote the timescale'); | ||
86 | equal(mdhd.duration, | ||
87 | expected.duration || Math.pow(2, 32) - 1, | ||
88 | 'wrote duration into the media header'); | ||
89 | }; | ||
90 | |||
91 | validateHdlr = function(hdlr, expected) { | ||
92 | equal(hdlr.type, 'hdlr', 'generate an hdlr type'); | ||
93 | if (expected.type !== 'audio') { | ||
94 | equal(hdlr.handlerType, 'vide', 'wrote a video handler'); | ||
95 | equal(hdlr.name, 'VideoHandler', 'wrote the handler name'); | ||
96 | } else { | ||
97 | equal(hdlr.handlerType, 'soun', 'wrote a sound handler'); | ||
98 | equal(hdlr.name, 'SoundHandler', 'wrote the sound handler name'); | ||
99 | } | ||
100 | }; | ||
101 | |||
102 | validateMinf = function(minf, expected) { | ||
103 | equal(minf.type, 'minf', 'generate an minf type'); | ||
104 | equal(minf.boxes.length, 3, 'generates three minf sub boxes'); | ||
105 | |||
106 | if (expected.type !== 'audio') { | ||
107 | deepEqual({ | ||
108 | type: 'vmhd', | ||
109 | size: 20, | ||
110 | version: 0, | ||
111 | flags: new Uint8Array([0, 0, 1]), | ||
112 | graphicsmode: 0, | ||
113 | opcolor: new Uint16Array([0, 0, 0]) | ||
114 | }, minf.boxes[0], 'generates a vhmd'); | ||
115 | } else { | ||
116 | deepEqual({ | ||
117 | type: 'smhd', | ||
118 | size: 16, | ||
119 | version: 0, | ||
120 | flags: new Uint8Array([0, 0, 0]), | ||
121 | balance: 0 | ||
122 | }, minf.boxes[0], 'generates an smhd'); | ||
123 | } | ||
124 | |||
125 | validateDinf(minf.boxes[1]); | ||
126 | validateStbl(minf.boxes[2], expected); | ||
127 | }; | ||
128 | |||
129 | validateDinf = function(dinf) { | ||
130 | deepEqual({ | ||
131 | type: 'dinf', | ||
132 | size: 36, | ||
133 | boxes: [{ | ||
134 | type: 'dref', | ||
135 | size: 28, | ||
136 | version: 0, | ||
137 | flags: new Uint8Array([0, 0, 0]), | ||
138 | dataReferences: [{ | ||
139 | type: 'url ', | ||
140 | size: 12, | ||
141 | version: 0, | ||
142 | flags: new Uint8Array([0, 0, 1]) | ||
143 | }] | ||
144 | }] | ||
145 | }, dinf, 'generates a dinf'); | ||
146 | }; | ||
147 | |||
148 | validateStbl = function(stbl, expected) { | ||
149 | equal(stbl.type, 'stbl', 'generates an stbl type'); | ||
150 | equal(stbl.boxes.length, 5, 'generated five stbl child boxes'); | ||
151 | |||
152 | validateStsd(stbl.boxes[0], expected); | ||
153 | deepEqual({ | ||
154 | type: 'stts', | ||
155 | size: 16, | ||
156 | version: 0, | ||
157 | flags: new Uint8Array([0, 0, 0]), | ||
158 | timeToSamples: [] | ||
159 | }, stbl.boxes[1], 'generated an stts'); | ||
160 | deepEqual({ | ||
161 | type: 'stsc', | ||
162 | size: 16, | ||
163 | version: 0, | ||
164 | flags: new Uint8Array([0, 0, 0]), | ||
165 | sampleToChunks: [] | ||
166 | }, stbl.boxes[2], 'generated an stsc'); | ||
167 | deepEqual({ | ||
168 | type: 'stsz', | ||
169 | version: 0, | ||
170 | size: 20, | ||
171 | flags: new Uint8Array([0, 0, 0]), | ||
172 | sampleSize: 0, | ||
173 | entries: [] | ||
174 | }, stbl.boxes[3], 'generated an stsz'); | ||
175 | deepEqual({ | ||
176 | type: 'stco', | ||
177 | size: 16, | ||
178 | version: 0, | ||
179 | flags: new Uint8Array([0, 0, 0]), | ||
180 | chunkOffsets: [] | ||
181 | }, stbl.boxes[4], 'generated and stco'); | ||
182 | }; | ||
183 | |||
184 | validateStsd = function(stsd, expected) { | ||
185 | equal(stsd.type, 'stsd', 'generated an stsd'); | ||
186 | equal(stsd.sampleDescriptions.length, 1, 'generated one sample'); | ||
187 | if (expected.type !== 'audio') { | ||
188 | validateVideoSample(stsd.sampleDescriptions[0]); | ||
189 | } else { | ||
190 | validateAudioSample(stsd.sampleDescriptions[0]); | ||
191 | } | ||
192 | }; | ||
193 | |||
194 | validateVideoSample = function(sample) { | ||
195 | deepEqual(sample, { | ||
196 | type: 'avc1', | ||
197 | size: 136, | ||
198 | dataReferenceIndex: 1, | ||
199 | width: 600, | ||
200 | height: 300, | ||
201 | horizresolution: 72, | ||
202 | vertresolution: 72, | ||
203 | frameCount: 1, | ||
204 | depth: 24, | ||
205 | config: [{ | ||
206 | type: 'avcC', | ||
207 | size: 30, | ||
208 | configurationVersion: 1, | ||
209 | avcProfileIndication: 3, | ||
210 | avcLevelIndication: 5, | ||
211 | profileCompatibility: 7, | ||
212 | lengthSizeMinusOne: 3, | ||
213 | sps: [new Uint8Array([ | ||
214 | 0, 1, 2 | ||
215 | ]), new Uint8Array([ | ||
216 | 3, 4, 5 | ||
217 | ])], | ||
218 | pps: [new Uint8Array([ | ||
219 | 6, 7, 8 | ||
220 | ])] | ||
221 | }, { | ||
222 | type: 'btrt', | ||
223 | size: 20, | ||
224 | bufferSizeDB: 1875072, | ||
225 | maxBitrate: 3000000, | ||
226 | avgBitrate: 3000000 | ||
227 | }] | ||
228 | }, 'generated a video sample'); | ||
229 | }; | ||
230 | |||
231 | validateAudioSample = function(sample) { | ||
232 | deepEqual(sample, { | ||
233 | type: 'mp4a', | ||
234 | size: 75, | ||
235 | dataReferenceIndex: 1, | ||
236 | channelcount: 2, | ||
237 | samplesize: 16, | ||
238 | samplerate: 48000, | ||
239 | streamDescriptor: { | ||
240 | type: 'esds', | ||
241 | version: 0, | ||
242 | flags: new Uint8Array([0, 0, 0]), | ||
243 | size: 39, | ||
244 | esId: 0, | ||
245 | streamPriority: 0, | ||
246 | // these values were hard-coded based on a working audio init segment | ||
247 | decoderConfig: { | ||
248 | avgBitrate: 56000, | ||
249 | maxBitrate: 56000, | ||
250 | bufferSize: 1536, | ||
251 | objectProfileIndication: 64, | ||
252 | streamType: 5 | ||
253 | } | ||
254 | } | ||
255 | }, 'generated an audio sample'); | ||
256 | }; | ||
257 | |||
258 | validateMvex = function(mvex, options) { | ||
259 | options = options || { | ||
260 | sampleDegradationPriority: 1 | ||
261 | }; | ||
262 | deepEqual({ | ||
263 | type: 'mvex', | ||
264 | size: 40, | ||
265 | boxes: [{ | ||
266 | type: 'trex', | ||
267 | size: 32, | ||
268 | version: 0, | ||
269 | flags: new Uint8Array([0, 0, 0]), | ||
270 | trackId: 7, | ||
271 | defaultSampleDescriptionIndex: 1, | ||
272 | defaultSampleDuration: 0, | ||
273 | defaultSampleSize: 0, | ||
274 | sampleDependsOn: 0, | ||
275 | sampleIsDependedOn: 0, | ||
276 | sampleHasRedundancy: 0, | ||
277 | samplePaddingValue: 0, | ||
278 | sampleIsDifferenceSample: true, | ||
279 | sampleDegradationPriority: options.sampleDegradationPriority | ||
280 | }] | ||
281 | }, mvex, 'writes a movie extends box'); | ||
282 | }; | ||
283 | |||
284 | test('generates a video moov', function() { | ||
285 | var | ||
286 | boxes, | ||
287 | data = mp4.moov([{ | ||
288 | id: 7, | ||
289 | duration: 100, | ||
290 | width: 600, | ||
291 | height: 300, | ||
292 | type: 'video', | ||
293 | profileIdc: 3, | ||
294 | levelIdc: 5, | ||
295 | profileCompatibility: 7, | ||
296 | sps: [new Uint8Array([0, 1, 2]), new Uint8Array([3, 4, 5])], | ||
297 | pps: [new Uint8Array([6, 7, 8])] | ||
298 | }]); | ||
299 | |||
300 | ok(data, 'box is not null'); | ||
301 | boxes = inspectMp4(data); | ||
302 | equal(boxes.length, 1, 'generated a single box'); | ||
303 | equal(boxes[0].type, 'moov', 'generated a moov type'); | ||
304 | equal(boxes[0].size, data.byteLength, 'generated size'); | ||
305 | equal(boxes[0].boxes.length, 3, 'generated three sub boxes'); | ||
306 | |||
307 | validateMvhd(boxes[0].boxes[0]); | ||
308 | validateTrak(boxes[0].boxes[1], { | ||
309 | duration: 100, | ||
310 | width: 600, | ||
311 | height: 300 | ||
312 | }); | ||
313 | validateMvex(boxes[0].boxes[2]); | ||
314 | }); | ||
315 | |||
316 | test('generates an audio moov', function() { | ||
317 | var | ||
318 | data = mp4.moov([{ | ||
319 | id: 7, | ||
320 | type: 'audio', | ||
321 | channelcount: 2, | ||
322 | samplerate: 48000, | ||
323 | samplesize: 16 | ||
324 | }]), | ||
325 | boxes; | ||
326 | |||
327 | ok(data, 'box is not null'); | ||
328 | boxes = inspectMp4(data); | ||
329 | equal(boxes.length, 1, 'generated a single box'); | ||
330 | equal(boxes[0].type, 'moov', 'generated a moov type'); | ||
331 | equal(boxes[0].size, data.byteLength, 'generated size'); | ||
332 | equal(boxes[0].boxes.length, 3, 'generated three sub boxes'); | ||
333 | |||
334 | validateMvhd(boxes[0].boxes[0]); | ||
335 | validateTrak(boxes[0].boxes[1], { | ||
336 | type: 'audio', | ||
337 | timescale: 48000 | ||
338 | }); | ||
339 | validateMvex(boxes[0].boxes[2], { | ||
340 | sampleDegradationPriority: 0 | ||
341 | }); | ||
342 | }); | ||
343 | |||
344 | test('generates a sound hdlr', function() { | ||
345 | var boxes, hdlr, | ||
346 | data = mp4.moov([{ | ||
347 | duration:100, | ||
348 | type: 'audio' | ||
349 | }]); | ||
350 | |||
351 | ok(data, 'box is not null'); | ||
352 | |||
353 | boxes = inspectMp4(data); | ||
354 | |||
355 | hdlr = boxes[0].boxes[1].boxes[1].boxes[1]; | ||
356 | equal(hdlr.type, 'hdlr', 'generate an hdlr type'); | ||
357 | equal(hdlr.handlerType, 'soun', 'wrote a sound handler'); | ||
358 | equal(hdlr.name, 'SoundHandler', 'wrote the handler name'); | ||
359 | }); | ||
360 | |||
361 | test('generates a video hdlr', function() { | ||
362 | var boxes, hdlr, | ||
363 | data = mp4.moov([{ | ||
364 | duration: 100, | ||
365 | width: 600, | ||
366 | height: 300, | ||
367 | type: 'video', | ||
368 | sps: [], | ||
369 | pps: [] | ||
370 | }]); | ||
371 | |||
372 | ok(data, 'box is not null'); | ||
373 | |||
374 | boxes = inspectMp4(data); | ||
375 | |||
376 | hdlr = boxes[0].boxes[1].boxes[1].boxes[1]; | ||
377 | equal(hdlr.type, 'hdlr', 'generate an hdlr type'); | ||
378 | equal(hdlr.handlerType, 'vide', 'wrote a video handler'); | ||
379 | equal(hdlr.name, 'VideoHandler', 'wrote the handler name'); | ||
380 | }); | ||
381 | |||
382 | test('generates an initialization segment', function() { | ||
383 | var | ||
384 | data = mp4.initSegment([{ | ||
385 | id: 1, | ||
386 | width: 600, | ||
387 | height: 300, | ||
388 | type: 'video', | ||
389 | sps: [new Uint8Array([0])], | ||
390 | pps: [new Uint8Array([1])] | ||
391 | }, { | ||
392 | id: 2, | ||
393 | type: 'audio' | ||
394 | }]), | ||
395 | init, mvhd, trak1, trak2, mvex; | ||
396 | |||
397 | init = videojs.inspectMp4(data); | ||
398 | equal(init.length, 2, 'generated two boxes'); | ||
399 | equal(init[0].type, 'ftyp', 'generated a ftyp box'); | ||
400 | equal(init[1].type, 'moov', 'generated a moov box'); | ||
401 | equal(init[1].boxes[0].duration, 0xffffffff, 'wrote a maximum duration'); | ||
402 | |||
403 | mvhd = init[1].boxes[0]; | ||
404 | equal(mvhd.type, 'mvhd', 'wrote an mvhd'); | ||
405 | |||
406 | trak1 = init[1].boxes[1]; | ||
407 | equal(trak1.type, 'trak', 'wrote a trak'); | ||
408 | equal(trak1.boxes[0].trackId, 1, 'wrote the first track id'); | ||
409 | equal(trak1.boxes[0].width, 600, 'wrote the first track width'); | ||
410 | equal(trak1.boxes[0].height, 300, 'wrote the first track height'); | ||
411 | equal(trak1.boxes[1].boxes[1].handlerType, 'vide', 'wrote the first track type'); | ||
412 | |||
413 | trak2 = init[1].boxes[2]; | ||
414 | equal(trak2.type, 'trak', 'wrote a trak'); | ||
415 | equal(trak2.boxes[0].trackId, 2, 'wrote the second track id'); | ||
416 | equal(trak2.boxes[1].boxes[1].handlerType, 'soun', 'wrote the second track type'); | ||
417 | |||
418 | mvex = init[1].boxes[3]; | ||
419 | equal(mvex.type, 'mvex', 'wrote an mvex'); | ||
420 | }); | ||
421 | |||
422 | test('generates a minimal moof', function() { | ||
423 | var | ||
424 | data = mp4.moof(7, [{ | ||
425 | id: 17, | ||
426 | samples: [{ | ||
427 | duration: 9000, | ||
428 | size: 10, | ||
429 | flags: { | ||
430 | isLeading: 0, | ||
431 | dependsOn: 2, | ||
432 | isDependedOn: 1, | ||
433 | hasRedundancy: 0, | ||
434 | paddingValue: 0, | ||
435 | isNonSyncSample: 0, | ||
436 | degradationPriority: 14 | ||
437 | }, | ||
438 | compositionTimeOffset: 500 | ||
439 | }, { | ||
440 | duration: 10000, | ||
441 | size: 11, | ||
442 | flags: { | ||
443 | isLeading: 0, | ||
444 | dependsOn: 1, | ||
445 | isDependedOn: 0, | ||
446 | hasRedundancy: 0, | ||
447 | paddingValue: 0, | ||
448 | isNonSyncSample: 0, | ||
449 | degradationPriority: 9 | ||
450 | }, | ||
451 | compositionTimeOffset: 1000 | ||
452 | }] | ||
453 | }]), | ||
454 | moof = videojs.inspectMp4(data), | ||
455 | trun, | ||
456 | sdtp; | ||
457 | |||
458 | equal(moof.length, 1, 'generated one box'); | ||
459 | equal(moof[0].type, 'moof', 'generated a moof box'); | ||
460 | equal(moof[0].boxes.length, 2, 'generated two child boxes'); | ||
461 | equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box'); | ||
462 | equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number'); | ||
463 | equal(moof[0].boxes[1].type, 'traf', 'generated a traf box'); | ||
464 | equal(moof[0].boxes[1].boxes.length, 4, 'generated track fragment info'); | ||
465 | equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box'); | ||
466 | equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id'); | ||
467 | equal(moof[0].boxes[1].boxes[0].baseDataOffset, undefined, 'did not set a base data offset'); | ||
468 | |||
469 | equal(moof[0].boxes[1].boxes[1].type, 'tfdt', 'generated a tfdt box'); | ||
470 | ok(moof[0].boxes[1].boxes[1].baseMediaDecodeTime >= 0, | ||
471 | 'media decode time is non-negative'); | ||
472 | |||
473 | trun = moof[0].boxes[1].boxes[2]; | ||
474 | equal(trun.type, 'trun', 'generated a trun box'); | ||
475 | equal(typeof trun.dataOffset, 'number', 'has a data offset'); | ||
476 | ok(trun.dataOffset >= 0, 'has a non-negative data offset'); | ||
477 | equal(trun.dataOffset, moof[0].size + 8, 'sets the data offset past the mdat header'); | ||
478 | equal(trun.samples.length, 2, 'wrote two samples'); | ||
479 | |||
480 | equal(trun.samples[0].duration, 9000, 'wrote a sample duration'); | ||
481 | equal(trun.samples[0].size, 10, 'wrote a sample size'); | ||
482 | deepEqual(trun.samples[0].flags, { | ||
483 | isLeading: 0, | ||
484 | dependsOn: 2, | ||
485 | isDependedOn: 1, | ||
486 | hasRedundancy: 0, | ||
487 | paddingValue: 0, | ||
488 | isNonSyncSample: 0, | ||
489 | degradationPriority: 14 | ||
490 | }, 'wrote the sample flags'); | ||
491 | equal(trun.samples[0].compositionTimeOffset, 500, 'wrote the composition time offset'); | ||
492 | |||
493 | equal(trun.samples[1].duration, 10000, 'wrote a sample duration'); | ||
494 | equal(trun.samples[1].size, 11, 'wrote a sample size'); | ||
495 | deepEqual(trun.samples[1].flags, { | ||
496 | isLeading: 0, | ||
497 | dependsOn: 1, | ||
498 | isDependedOn: 0, | ||
499 | hasRedundancy: 0, | ||
500 | paddingValue: 0, | ||
501 | isNonSyncSample: 0, | ||
502 | degradationPriority: 9 | ||
503 | }, 'wrote the sample flags'); | ||
504 | equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset'); | ||
505 | |||
506 | sdtp = moof[0].boxes[1].boxes[3]; | ||
507 | equal(sdtp.type, 'sdtp', 'generated an sdtp box'); | ||
508 | equal(sdtp.samples.length, 2, 'wrote two samples'); | ||
509 | deepEqual(sdtp.samples[0], { | ||
510 | dependsOn: 2, | ||
511 | isDependedOn: 1, | ||
512 | hasRedundancy: 0 | ||
513 | }, 'wrote the sample data table'); | ||
514 | deepEqual(sdtp.samples[1], { | ||
515 | dependsOn: 1, | ||
516 | isDependedOn: 0, | ||
517 | hasRedundancy: 0 | ||
518 | }, 'wrote the sample data table'); | ||
519 | }); | ||
520 | |||
521 | test('generates a moof for audio', function() { | ||
522 | var | ||
523 | data = mp4.moof(7, [{ | ||
524 | id: 17, | ||
525 | type: 'audio', | ||
526 | samples: [{ | ||
527 | duration: 9000, | ||
528 | size: 10 | ||
529 | }, { | ||
530 | duration: 10000, | ||
531 | size: 11 | ||
532 | }] | ||
533 | }]), | ||
534 | moof = videojs.inspectMp4(data), | ||
535 | trun; | ||
536 | |||
537 | deepEqual(moof[0].boxes[1].boxes.length, 3, 'generated three traf children'); | ||
538 | trun = moof[0].boxes[1].boxes[2]; | ||
539 | ok(trun, 'generated a trun'); | ||
540 | equal(trun.dataOffset, data.byteLength + 8, 'calculated the data offset'); | ||
541 | deepEqual(trun.samples, [{ | ||
542 | duration: 9000, | ||
543 | size: 10 | ||
544 | }, { | ||
545 | duration: 10000, | ||
546 | size: 11 | ||
547 | }], 'wrote simple audio samples'); | ||
548 | }); | ||
549 | |||
550 | test('can generate a traf without samples', function() { | ||
551 | var | ||
552 | data = mp4.moof(8, [{ | ||
553 | trackId: 13 | ||
554 | }]), | ||
555 | moof = videojs.inspectMp4(data); | ||
556 | |||
557 | equal(moof[0].boxes[1].boxes[2].samples.length, 0, 'generated no samples'); | ||
558 | }); | ||
559 | |||
560 | test('generates an mdat', function() { | ||
561 | var | ||
562 | data = mp4.mdat(new Uint8Array([1, 2, 3, 4])), | ||
563 | mdat = videojs.inspectMp4(data); | ||
564 | |||
565 | equal(mdat.length, 1, 'generated one box'); | ||
566 | equal(mdat[0].type, 'mdat', 'generated an mdat box'); | ||
567 | deepEqual(mdat[0].byteLength, 4, 'encapsulated the data'); | ||
568 | }); | ||
569 | |||
570 | })(window, window.videojs); |
test/mp4-inspector_test.js
deleted
100644 → 0
1 | (function(window, videojs) { | ||
2 | 'use strict'; | ||
3 | /* | ||
4 | ======== A Handy Little QUnit Reference ======== | ||
5 | http://api.qunitjs.com/ | ||
6 | |||
7 | Test methods: | ||
8 | module(name, {[setup][ ,teardown]}) | ||
9 | test(name, callback) | ||
10 | expect(numberOfAssertions) | ||
11 | stop(increment) | ||
12 | start(decrement) | ||
13 | Test assertions: | ||
14 | ok(value, [message]) | ||
15 | equal(actual, expected, [message]) | ||
16 | notEqual(actual, expected, [message]) | ||
17 | deepEqual(actual, expected, [message]) | ||
18 | notDeepEqual(actual, expected, [message]) | ||
19 | strictEqual(actual, expected, [message]) | ||
20 | notStrictEqual(actual, expected, [message]) | ||
21 | throws(block, [expected], [message]) | ||
22 | */ | ||
23 | var | ||
24 | Uint8Array = window.Uint8Array, | ||
25 | typeBytes = function(type) { | ||
26 | return [ | ||
27 | type.charCodeAt(0), | ||
28 | type.charCodeAt(1), | ||
29 | type.charCodeAt(2), | ||
30 | type.charCodeAt(3) | ||
31 | ]; | ||
32 | }, | ||
33 | box = function(type) { | ||
34 | var | ||
35 | array = Array.prototype.slice.call(arguments, 1), | ||
36 | result = [], | ||
37 | size, | ||
38 | i; | ||
39 | |||
40 | // "unwrap" any arrays that were passed as arguments | ||
41 | // e.g. box('etc', 1, [2, 3], 4) -> box('etc', 1, 2, 3, 4) | ||
42 | for (i = 0; i < array.length; i++) { | ||
43 | if (array[i] instanceof Array) { | ||
44 | array.splice.apply(array, [i, 1].concat(array[i])); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | size = 8 + array.length; | ||
49 | |||
50 | result[0] = (size & 0xFF000000) >> 24; | ||
51 | result[1] = (size & 0x00FF0000) >> 16; | ||
52 | result[2] = (size & 0x0000FF00) >> 8; | ||
53 | result[3] = size & 0xFF; | ||
54 | result = result.concat(typeBytes(type)); | ||
55 | result = result.concat(array); | ||
56 | return result; | ||
57 | }, | ||
58 | unityMatrix = [ | ||
59 | 0, 0, 0x10, 0, | ||
60 | 0, 0, 0, 0, | ||
61 | 0, 0, 0, 0, | ||
62 | |||
63 | 0, 0, 0, 0, | ||
64 | 0, 0, 0x10, 0, | ||
65 | 0, 0, 0, 0, | ||
66 | |||
67 | 0, 0, 0, 0, | ||
68 | 0, 0, 0, 0, | ||
69 | 0x40, 0, 0, 0 | ||
70 | ], | ||
71 | |||
72 | mvhd0 = box('mvhd', | ||
73 | 0x00, // version 0 | ||
74 | 0x00, 0x00, 0x00, // flags | ||
75 | 0x00, 0x00, 0x00, 0x01, // creation_time | ||
76 | 0x00, 0x00, 0x00, 0x02, // modification_time | ||
77 | 0x00, 0x00, 0x00, 0x3c, // timescale | ||
78 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
79 | 0x00, 0x01, 0x00, 0x00, // 1.0 rate | ||
80 | 0x01, 0x00, // 1.0 volume | ||
81 | 0x00, 0x00, // reserved | ||
82 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
83 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
84 | unityMatrix, | ||
85 | 0x00, 0x00, 0x00, 0x00, | ||
86 | 0x00, 0x00, 0x00, 0x00, | ||
87 | 0x00, 0x00, 0x00, 0x00, | ||
88 | 0x00, 0x00, 0x00, 0x00, | ||
89 | 0x00, 0x00, 0x00, 0x00, | ||
90 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
91 | 0x00, 0x00, 0x00, 0x02), | ||
92 | |||
93 | tkhd0 = box('tkhd', | ||
94 | 0x00, // version 0 | ||
95 | 0x00, 0x00, 0x00, // flags | ||
96 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
97 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
98 | 0x00, 0x00, 0x00, 0x01, // track_ID | ||
99 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
100 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
101 | 0x00, 0x00, 0x00, 0x00, | ||
102 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
103 | 0x00, 0x00, // layer | ||
104 | 0x00, 0x00, // alternate_group | ||
105 | 0x00, 0x00, // non-audio track volume | ||
106 | 0x00, 0x00, // reserved | ||
107 | unityMatrix, | ||
108 | 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed point | ||
109 | 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed point | ||
110 | mdhd0 = box('mdhd', | ||
111 | 0x00, // version 0 | ||
112 | 0x00, 0x00, 0x00, // flags | ||
113 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
114 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
115 | 0x00, 0x00, 0x00, 0x3c, // timescale | ||
116 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
117 | 0x15, 0xc7, // 'eng' language | ||
118 | 0x00, 0x00); | ||
119 | |||
120 | module('MP4 Inspector'); | ||
121 | |||
122 | test('produces an empty array for empty input', function() { | ||
123 | strictEqual(videojs.inspectMp4(new Uint8Array([])).length, 0, 'returned an empty array'); | ||
124 | }); | ||
125 | |||
126 | test('can parse a Box', function() { | ||
127 | var box = new Uint8Array([ | ||
128 | 0x00, 0x00, 0x00, 0x00, // size 0 | ||
129 | 0x00, 0x00, 0x00, 0x00 // boxtype 0 | ||
130 | ]); | ||
131 | deepEqual(videojs.inspectMp4(box), [{ | ||
132 | type: '\u0000\u0000\u0000\u0000', | ||
133 | size: 0, | ||
134 | data: box.subarray(box.byteLength) | ||
135 | }], 'parsed a Box'); | ||
136 | }); | ||
137 | |||
138 | test('can parse an ftyp', function() { | ||
139 | deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp', | ||
140 | 0x61, 0x76, 0x63, 0x31, // major brand | ||
141 | 0x00, 0x00, 0x00, 0x02, // minor version | ||
142 | 98, 111, 111, 112, // compatible brands | ||
143 | 98, 101, 101, 112 // compatible brands | ||
144 | ))), [{ | ||
145 | type: 'ftyp', | ||
146 | size: 4 * 6, | ||
147 | majorBrand: 'avc1', | ||
148 | minorVersion: 2, | ||
149 | compatibleBrands: ['boop', 'beep'] | ||
150 | }], 'parsed an ftyp'); | ||
151 | }); | ||
152 | |||
153 | test('can parse a pdin', function() { | ||
154 | deepEqual(videojs.inspectMp4(new Uint8Array(box('pdin', | ||
155 | 0x01, // version 1 | ||
156 | 0x01, 0x02, 0x03, // flags | ||
157 | 0x00, 0x00, 0x04, 0x00, // 1024 = 0x400 bytes/second rate | ||
158 | 0x00, 0x00, 0x00, 0x01 // initial delay | ||
159 | ))), [{ | ||
160 | size: 20, | ||
161 | type: 'pdin', | ||
162 | version: 1, | ||
163 | flags: new Uint8Array([1, 2, 3]), | ||
164 | rate: 1024, | ||
165 | initialDelay: 1 | ||
166 | }], 'parsed a pdin'); | ||
167 | }); | ||
168 | |||
169 | test('can parse an mdat', function() { | ||
170 | var mdat = new Uint8Array(box('mdat', | ||
171 | 0, 0, 0, 4, // length | ||
172 | 0x01, 0x02, 0x03, 0x04 // data | ||
173 | )); | ||
174 | deepEqual(videojs.inspectMp4(mdat), [{ | ||
175 | size: 16, | ||
176 | type: 'mdat', | ||
177 | nals: [ | ||
178 | 'slice_layer_without_partitioning_rbsp' | ||
179 | ], | ||
180 | byteLength: 8 | ||
181 | }], 'parsed an mdat'); | ||
182 | }); | ||
183 | |||
184 | test('can parse a free or skip', function() { | ||
185 | var | ||
186 | free = new Uint8Array(box('free', | ||
187 | 0x01, 0x02, 0x03, 0x04)), // data | ||
188 | skip = new Uint8Array(box('skip', | ||
189 | 0x01, 0x02, 0x03, 0x04)); // data | ||
190 | |||
191 | deepEqual(videojs.inspectMp4(free), [{ | ||
192 | size: 12, | ||
193 | type: 'free', | ||
194 | data: free.subarray(free.byteLength - 4) | ||
195 | }], 'parsed a free'); | ||
196 | deepEqual(videojs.inspectMp4(skip), [{ | ||
197 | size: 12, | ||
198 | type: 'skip', | ||
199 | data: skip.subarray(skip.byteLength - 4) | ||
200 | }], 'parsed a skip'); | ||
201 | }); | ||
202 | |||
203 | test('can parse a version 0 mvhd', function() { | ||
204 | deepEqual(videojs.inspectMp4(new Uint8Array(mvhd0)), [{ | ||
205 | type: 'mvhd', | ||
206 | version: 0, | ||
207 | flags: new Uint8Array([0, 0, 0]), | ||
208 | creationTime: new Date(1000 - 2082844800000), | ||
209 | modificationTime: new Date(2000 - 2082844800000), | ||
210 | timescale: 60, | ||
211 | duration: 600, | ||
212 | rate: 1, | ||
213 | volume: 1, | ||
214 | matrix: new Uint32Array(unityMatrix), | ||
215 | size: 108, | ||
216 | nextTrackId: 2 | ||
217 | }]); | ||
218 | }); | ||
219 | |||
220 | test('can parse a version 0 tkhd', function() { | ||
221 | deepEqual(videojs.inspectMp4(new Uint8Array(tkhd0)), [{ | ||
222 | type: 'tkhd', | ||
223 | version: 0, | ||
224 | flags: new Uint8Array([0, 0, 0]), | ||
225 | creationTime: new Date(2000 - 2082844800000), | ||
226 | modificationTime: new Date(3000 - 2082844800000), | ||
227 | size: 92, | ||
228 | trackId: 1, | ||
229 | duration: 600, | ||
230 | layer: 0, | ||
231 | alternateGroup: 0, | ||
232 | volume: 0, | ||
233 | matrix: new Uint32Array(unityMatrix), | ||
234 | width: 300, | ||
235 | height: 150 | ||
236 | }]); | ||
237 | }); | ||
238 | |||
239 | test('can parse a version 0 mdhd', function() { | ||
240 | deepEqual(videojs.inspectMp4(new Uint8Array(mdhd0)), [{ | ||
241 | type: 'mdhd', | ||
242 | version: 0, | ||
243 | flags: new Uint8Array([0, 0, 0]), | ||
244 | creationTime: new Date(2000 - 2082844800000), | ||
245 | modificationTime: new Date(3000 - 2082844800000), | ||
246 | size: 32, | ||
247 | timescale: 60, | ||
248 | duration: 600, | ||
249 | language: 'eng' | ||
250 | }]); | ||
251 | }); | ||
252 | |||
253 | test('can parse a moov', function() { | ||
254 | var data = | ||
255 | box('moov', | ||
256 | box('mvhd', | ||
257 | 0x01, // version 1 | ||
258 | 0x00, 0x00, 0x00, // flags | ||
259 | 0x00, 0x00, 0x00, 0x00, | ||
260 | 0x00, 0x00, 0x00, 0x01, // creation_time | ||
261 | 0x00, 0x00, 0x00, 0x00, | ||
262 | 0x00, 0x00, 0x00, 0x02, // modification_time | ||
263 | 0x00, 0x00, 0x00, 0x3c, // timescale | ||
264 | 0x00, 0x00, 0x00, 0x00, | ||
265 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
266 | 0x00, 0x01, 0x00, 0x00, // 1.0 rate | ||
267 | 0x01, 0x00, // 1.0 volume | ||
268 | 0x00, 0x00, // reserved | ||
269 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
270 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
271 | unityMatrix, | ||
272 | 0x00, 0x00, 0x00, 0x00, | ||
273 | 0x00, 0x00, 0x00, 0x00, | ||
274 | 0x00, 0x00, 0x00, 0x00, | ||
275 | 0x00, 0x00, 0x00, 0x00, | ||
276 | 0x00, 0x00, 0x00, 0x00, | ||
277 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
278 | 0x00, 0x00, 0x00, 0x02), // next_track_ID | ||
279 | box('trak', | ||
280 | box('tkhd', | ||
281 | 0x01, // version 1 | ||
282 | 0x00, 0x00, 0x00, // flags | ||
283 | 0x00, 0x00, 0x00, 0x00, | ||
284 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
285 | 0x00, 0x00, 0x00, 0x00, | ||
286 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
287 | 0x00, 0x00, 0x00, 0x01, // track_ID | ||
288 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
289 | 0x00, 0x00, 0x00, 0x00, | ||
290 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
291 | 0x00, 0x00, 0x00, 0x00, | ||
292 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
293 | 0x00, 0x00, // layer | ||
294 | 0x00, 0x00, // alternate_group | ||
295 | 0x00, 0x00, // non-audio track volume | ||
296 | 0x00, 0x00, // reserved | ||
297 | unityMatrix, | ||
298 | 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point | ||
299 | 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point | ||
300 | box('mdia', | ||
301 | box('mdhd', | ||
302 | 0x01, // version 1 | ||
303 | 0x00, 0x00, 0x00, // flags | ||
304 | 0x00, 0x00, 0x00, 0x00, | ||
305 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
306 | 0x00, 0x00, 0x00, 0x00, | ||
307 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
308 | 0x00, 0x00, 0x00, 0x3c, // timescale | ||
309 | 0x00, 0x00, 0x00, 0x00, | ||
310 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
311 | 0x15, 0xc7, // 'eng' language | ||
312 | 0x00, 0x00), | ||
313 | box('hdlr', | ||
314 | 0x01, // version 1 | ||
315 | 0x00, 0x00, 0x00, // flags | ||
316 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
317 | typeBytes('vide'), // handler_type | ||
318 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
319 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
320 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
321 | typeBytes('one'), 0x00), // name | ||
322 | box('minf', | ||
323 | box('dinf', | ||
324 | box('dref', | ||
325 | 0x01, // version 1 | ||
326 | 0x00, 0x00, 0x00, // flags | ||
327 | 0x00, 0x00, 0x00, 0x01, // entry_count | ||
328 | box('url ', | ||
329 | 0x00, // version | ||
330 | 0x00, 0x00, 0x01))), // flags | ||
331 | box('stbl', | ||
332 | box('stsd', | ||
333 | 0x01, // version 1 | ||
334 | 0x00, 0x00, 0x00, // flags | ||
335 | 0x00, 0x00, 0x00, 0x00), // entry_count | ||
336 | box('stts', | ||
337 | 0x01, // version 1 | ||
338 | 0x00, 0x00, 0x00, // flags | ||
339 | 0x00, 0x00, 0x00, 0x01, // entry_count | ||
340 | 0x00, 0x00, 0x00, 0x01, // sample_count | ||
341 | 0x00, 0x00, 0x00, 0x01), // sample_delta | ||
342 | box('stsc', | ||
343 | 0x01, // version 1 | ||
344 | 0x00, 0x00, 0x00, // flags | ||
345 | 0x00, 0x00, 0x00, 0x01, // entry_count | ||
346 | 0x00, 0x00, 0x00, 0x02, // first_chunk | ||
347 | 0x00, 0x00, 0x00, 0x03, // samples_per_chunk | ||
348 | 0x00, 0x00, 0x00, 0x01), // sample_description_index | ||
349 | box('stco', | ||
350 | 0x01, // version 1 | ||
351 | 0x00, 0x00, 0x00, // flags | ||
352 | 0x00, 0x00, 0x00, 0x01, // entry_count | ||
353 | 0x00, 0x00, 0x00, 0x01)))))); // chunk_offset | ||
354 | |||
355 | |||
356 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ | ||
357 | type: 'moov', | ||
358 | size: 469, | ||
359 | boxes: [{ | ||
360 | type: 'mvhd', | ||
361 | version: 1, | ||
362 | flags: new Uint8Array([0, 0, 0]), | ||
363 | creationTime: new Date(1000 - 2082844800000), | ||
364 | modificationTime: new Date(2000 - 2082844800000), | ||
365 | timescale: 60, | ||
366 | duration: 600, | ||
367 | rate: 1, | ||
368 | size: 120, | ||
369 | volume: 1, | ||
370 | matrix: new Uint32Array(unityMatrix), | ||
371 | nextTrackId: 2 | ||
372 | }, { | ||
373 | type: 'trak', | ||
374 | size: 341, | ||
375 | boxes: [{ | ||
376 | type: 'tkhd', | ||
377 | flags: new Uint8Array([0, 0, 0]), | ||
378 | version: 1, | ||
379 | creationTime: new Date(2000 - 2082844800000), | ||
380 | modificationTime: new Date(3000 - 2082844800000), | ||
381 | size: 104, | ||
382 | trackId: 1, | ||
383 | duration: 600, | ||
384 | layer: 0, | ||
385 | alternateGroup: 0, | ||
386 | volume: 0, | ||
387 | matrix: new Uint32Array(unityMatrix), | ||
388 | width: 300, | ||
389 | height: 150 | ||
390 | }, { | ||
391 | type: 'mdia', | ||
392 | size: 229, | ||
393 | boxes: [{ | ||
394 | type: 'mdhd', | ||
395 | version: 1, | ||
396 | flags: new Uint8Array([0, 0, 0]), | ||
397 | creationTime: new Date(2000 - 2082844800000), | ||
398 | modificationTime: new Date(3000 - 2082844800000), | ||
399 | timescale: 60, | ||
400 | duration: 600, | ||
401 | language: 'eng', | ||
402 | size: 44 | ||
403 | }, { | ||
404 | type: 'hdlr', | ||
405 | version: 1, | ||
406 | flags: new Uint8Array([0, 0, 0]), | ||
407 | handlerType: 'vide', | ||
408 | name: 'one', | ||
409 | size: 37 | ||
410 | }, { | ||
411 | type: 'minf', | ||
412 | size: 140, | ||
413 | boxes: [{ | ||
414 | type: 'dinf', | ||
415 | size: 36, | ||
416 | boxes: [{ | ||
417 | type: 'dref', | ||
418 | size: 28, | ||
419 | version: 1, | ||
420 | flags: new Uint8Array([0, 0, 0]), | ||
421 | dataReferences: [{ | ||
422 | type: 'url ', | ||
423 | size: 12, | ||
424 | version: 0, | ||
425 | flags: new Uint8Array([0, 0, 1]) | ||
426 | }], | ||
427 | }] | ||
428 | }, { | ||
429 | type: 'stbl', | ||
430 | size: 96, | ||
431 | boxes: [{ | ||
432 | type: 'stsd', | ||
433 | size: 16, | ||
434 | version: 1, | ||
435 | flags: new Uint8Array([0, 0, 0]), | ||
436 | sampleDescriptions: [], | ||
437 | }, { | ||
438 | type: 'stts', | ||
439 | size: 24, | ||
440 | version: 1, | ||
441 | flags: new Uint8Array([0, 0, 0]), | ||
442 | timeToSamples: [{ | ||
443 | sampleCount: 1, | ||
444 | sampleDelta: 1 | ||
445 | }] | ||
446 | }, { | ||
447 | type: 'stsc', | ||
448 | version: 1, | ||
449 | flags: new Uint8Array([0, 0, 0]), | ||
450 | sampleToChunks: [{ | ||
451 | firstChunk: 2, | ||
452 | samplesPerChunk: 3, | ||
453 | sampleDescriptionIndex: 1 | ||
454 | }], | ||
455 | size: 28 | ||
456 | }, { | ||
457 | type: 'stco', | ||
458 | size: 20, | ||
459 | version: 1, | ||
460 | flags: new Uint8Array([0, 0, 0]), | ||
461 | chunkOffsets: [1] | ||
462 | }] | ||
463 | }] | ||
464 | }] | ||
465 | }] | ||
466 | }] | ||
467 | }], 'parsed a moov'); | ||
468 | }); | ||
469 | |||
470 | test('can parse an mvex', function() { | ||
471 | var mvex = | ||
472 | box('mvex', | ||
473 | box('trex', | ||
474 | 0x00, // version | ||
475 | 0x00, 0x00, 0x00, // flags | ||
476 | 0x00, 0x00, 0x00, 0x01, // track_ID | ||
477 | 0x00, 0x00, 0x00, 0x01, // default_sample_description_index | ||
478 | 0x00, 0x00, 0x00, 0x02, // default_sample_duration | ||
479 | 0x00, 0x00, 0x00, 0x03, // default_sample_size | ||
480 | 0x00, 0x61, 0x00, 0x01)); // default_sample_flags | ||
481 | deepEqual(videojs.inspectMp4(new Uint8Array(mvex)), [{ | ||
482 | type: 'mvex', | ||
483 | size: 40, | ||
484 | boxes: [{ | ||
485 | type: 'trex', | ||
486 | size: 32, | ||
487 | version: 0, | ||
488 | flags: new Uint8Array([0, 0, 0]), | ||
489 | trackId: 1, | ||
490 | defaultSampleDescriptionIndex: 1, | ||
491 | defaultSampleDuration: 2, | ||
492 | defaultSampleSize: 3, | ||
493 | sampleDependsOn: 0, | ||
494 | sampleIsDependedOn: 1, | ||
495 | sampleHasRedundancy: 2, | ||
496 | samplePaddingValue: 0, | ||
497 | sampleIsDifferenceSample: true, | ||
498 | sampleDegradationPriority: 1 | ||
499 | }] | ||
500 | }], 'parsed an mvex'); | ||
501 | }); | ||
502 | |||
503 | test('can parse a video stsd', function() { | ||
504 | var data = box('stsd', | ||
505 | 0x00, // version 0 | ||
506 | 0x00, 0x00, 0x00, // flags | ||
507 | 0x00, 0x00, 0x00, 0x01, | ||
508 | box('avc1', | ||
509 | 0x00, 0x00, 0x00, | ||
510 | 0x00, 0x00, 0x00, // reserved | ||
511 | 0x00, 0x01, // data_reference_index | ||
512 | 0x00, 0x00, // pre_defined | ||
513 | 0x00, 0x00, // reserved | ||
514 | 0x00, 0x00, 0x00, 0x00, | ||
515 | 0x00, 0x00, 0x00, 0x00, | ||
516 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
517 | 0x01, 0x2c, // width = 300 | ||
518 | 0x00, 0x96, // height = 150 | ||
519 | 0x00, 0x48, 0x00, 0x00, // horizresolution | ||
520 | 0x00, 0x48, 0x00, 0x00, // vertresolution | ||
521 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
522 | 0x00, 0x01, // frame_count | ||
523 | 0x04, | ||
524 | typeBytes('avc1'), | ||
525 | 0x00, 0x00, 0x00, 0x00, | ||
526 | 0x00, 0x00, 0x00, 0x00, | ||
527 | 0x00, 0x00, 0x00, 0x00, | ||
528 | 0x00, 0x00, 0x00, 0x00, | ||
529 | 0x00, 0x00, 0x00, 0x00, | ||
530 | 0x00, 0x00, 0x00, 0x00, | ||
531 | 0x00, 0x00, 0x00, // compressorname | ||
532 | 0x00, 0x18, // depth = 24 | ||
533 | 0x11, 0x11, // pre_defined | ||
534 | box('avcC', | ||
535 | 0x01, // configurationVersion | ||
536 | 0x00, // AVCProfileIndication?? | ||
537 | 0x00, // profile_compatibility | ||
538 | 0x00, // AVCLevelIndication | ||
539 | 0x1c, // lengthSizeMinusOne | ||
540 | 0xe1, // numOfSequenceParameterSets | ||
541 | 0x00, 0x01, // sequenceParameterSetLength | ||
542 | 0x00, // "SPS" | ||
543 | 0x02, // numOfPictureParameterSets | ||
544 | 0x00, 0x02, // pictureParameterSetLength | ||
545 | 0x01, 0x02, // "PPS" | ||
546 | 0x00, 0x01, // pictureParameterSetLength | ||
547 | 0xff), // "PPS" | ||
548 | box('btrt', | ||
549 | 0x00, 0x00, 0x00, 0x00, // bufferSizeDB | ||
550 | 0x00, 0x00, 0x00, 0x01, // maxBitrate | ||
551 | 0x00, 0x00, 0x00, 0x01))); // avgBitrate | ||
552 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ | ||
553 | type: 'stsd', | ||
554 | size: 147, | ||
555 | version: 0, | ||
556 | flags: new Uint8Array([0, 0, 0]), | ||
557 | sampleDescriptions: [{ | ||
558 | type: 'avc1', | ||
559 | size: 131, | ||
560 | dataReferenceIndex: 1, | ||
561 | width: 300, | ||
562 | height: 150, | ||
563 | horizresolution: 72, | ||
564 | vertresolution: 72, | ||
565 | frameCount: 1, | ||
566 | depth: 24, | ||
567 | config: [{ | ||
568 | type: 'avcC', | ||
569 | size: 25, | ||
570 | configurationVersion: 1, | ||
571 | avcProfileIndication: 0, | ||
572 | profileCompatibility: 0, | ||
573 | avcLevelIndication: 0, | ||
574 | lengthSizeMinusOne: 0, | ||
575 | sps: [new Uint8Array(1)], | ||
576 | pps: [new Uint8Array([1, 2]), | ||
577 | new Uint8Array([0xff])] | ||
578 | }, { | ||
579 | type: 'btrt', | ||
580 | size: 20, | ||
581 | bufferSizeDB: 0, | ||
582 | maxBitrate: 1, | ||
583 | avgBitrate: 1 | ||
584 | }] | ||
585 | }] | ||
586 | }]); | ||
587 | }); | ||
588 | |||
589 | test('can parse an audio stsd', function() { | ||
590 | var data = box('stsd', | ||
591 | 0x00, // version 0 | ||
592 | 0x00, 0x00, 0x00, // flags | ||
593 | 0x00, 0x00, 0x00, 0x01, // entry_count | ||
594 | box('mp4a', | ||
595 | 0x00, 0x00, 0x00, | ||
596 | 0x00, 0x00, 0x00, // reserved | ||
597 | 0x00, 0x01, // data_reference_index | ||
598 | 0x00, 0x00, 0x00, 0x00, | ||
599 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
600 | 0x00, 0x02, // channelcount | ||
601 | 0x00, 0x10, // samplesize | ||
602 | 0x00, 0x00, // pre_defined | ||
603 | 0x00, 0x00, // reserved | ||
604 | 0xbb, 0x80, 0x00, 0x00, // samplerate, fixed-point 16.16 | ||
605 | box('esds', | ||
606 | 0x00, // version 0 | ||
607 | 0x00, 0x00, 0x00, // flags | ||
608 | 0x03, // tag, ES_DescrTag | ||
609 | 0x00, // length | ||
610 | 0x00, 0x01, // ES_ID | ||
611 | 0x00, // streamDependenceFlag, URL_Flag, reserved, streamPriority | ||
612 | |||
613 | // DecoderConfigDescriptor | ||
614 | 0x04, // tag, DecoderConfigDescrTag | ||
615 | 0x0d, // length | ||
616 | 0x40, // objectProfileIndication, AAC Main | ||
617 | 0x15, // streamType, AudioStream. upstream, reserved | ||
618 | 0x00, 0x00, 0xff, // bufferSizeDB | ||
619 | 0x00, 0x00, 0x00, 0xff, // maxBitrate | ||
620 | 0x00, 0x00, 0x00, 0xaa, // avgBitrate | ||
621 | |||
622 | // DecoderSpecificInfo | ||
623 | 0x05, // tag, DecoderSpecificInfoTag | ||
624 | 0x02, // length | ||
625 | 0x11, 0x90, 0x06, 0x01, 0x02))); // decoder specific info | ||
626 | |||
627 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ | ||
628 | version: 0, | ||
629 | flags: new Uint8Array([0, 0, 0]), | ||
630 | type: 'stsd', | ||
631 | size: 91, | ||
632 | sampleDescriptions: [{ | ||
633 | type: 'mp4a', | ||
634 | dataReferenceIndex: 1, | ||
635 | channelcount: 2, | ||
636 | samplesize: 16, | ||
637 | samplerate: 48000, | ||
638 | size: 75, | ||
639 | streamDescriptor: { | ||
640 | type: 'esds', | ||
641 | version: 0, | ||
642 | size: 39, | ||
643 | flags: new Uint8Array([0, 0, 0]), | ||
644 | esId: 1, | ||
645 | streamPriority: 0, | ||
646 | decoderConfig: { | ||
647 | objectProfileIndication: 0x40, | ||
648 | streamType: 0x05, | ||
649 | bufferSize: 0xff, | ||
650 | maxBitrate: 0xff, | ||
651 | avgBitrate: 0xaa | ||
652 | } | ||
653 | } | ||
654 | }] | ||
655 | }], 'parsed an audio stsd'); | ||
656 | }); | ||
657 | |||
658 | test('can parse an styp', function() { | ||
659 | deepEqual(videojs.inspectMp4(new Uint8Array(box('styp', | ||
660 | 0x61, 0x76, 0x63, 0x31, // major brand | ||
661 | 0x00, 0x00, 0x00, 0x02, // minor version | ||
662 | 98, 111, 111, 112 // compatible brands | ||
663 | ))), [{ | ||
664 | type: 'styp', | ||
665 | size: 4 * 5, | ||
666 | majorBrand: 'avc1', | ||
667 | minorVersion: 2, | ||
668 | compatibleBrands: ['boop'] | ||
669 | }], 'parsed an styp'); | ||
670 | }); | ||
671 | |||
672 | test('can parse a vmhd', function() { | ||
673 | var data = box('vmhd', | ||
674 | 0x00, // version | ||
675 | 0x00, 0x00, 0x00, // flags | ||
676 | 0x00, 0x00, // graphicsmode | ||
677 | 0x00, 0x00, | ||
678 | 0x00, 0x00, | ||
679 | 0x00, 0x00); // opcolor | ||
680 | |||
681 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
682 | [{ | ||
683 | type: 'vmhd', | ||
684 | size: 20, | ||
685 | version: 0, | ||
686 | flags: new Uint8Array([0, 0, 0]), | ||
687 | graphicsmode: 0, | ||
688 | opcolor: new Uint16Array([0, 0, 0]) | ||
689 | }]); | ||
690 | }); | ||
691 | |||
692 | test('can parse an stsz', function() { | ||
693 | var data = box('stsz', | ||
694 | 0x00, // version | ||
695 | 0x00, 0x00, 0x00, // flags | ||
696 | 0x00, 0x00, 0x00, 0x00, // sample_size | ||
697 | 0x00, 0x00, 0x00, 0x03, // sample_count | ||
698 | 0x00, 0x00, 0x00, 0x01, // entry_size | ||
699 | 0x00, 0x00, 0x00, 0x02, // entry_size | ||
700 | 0x00, 0x00, 0x00, 0x03); // entry_size | ||
701 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
702 | [{ | ||
703 | type: 'stsz', | ||
704 | size: 32, | ||
705 | version: 0, | ||
706 | flags: new Uint8Array([0, 0, 0]), | ||
707 | sampleSize: 0, | ||
708 | entries: [1, 2, 3] | ||
709 | }]); | ||
710 | }); | ||
711 | |||
712 | test('can parse a moof', function() { | ||
713 | var data = box('moof', | ||
714 | box('mfhd', | ||
715 | 0x00, // version | ||
716 | 0x00, 0x00, 0x00, // flags | ||
717 | 0x00, 0x00, 0x00, 0x04), // sequence_number | ||
718 | box('traf', | ||
719 | box('tfhd', | ||
720 | 0x00, // version | ||
721 | 0x00, 0x00, 0x3b, // flags | ||
722 | 0x00, 0x00, 0x00, 0x01, // track_ID | ||
723 | 0x00, 0x00, 0x00, 0x00, | ||
724 | 0x00, 0x00, 0x00, 0x01, // base_data_offset | ||
725 | 0x00, 0x00, 0x00, 0x02, // sample_description_index | ||
726 | 0x00, 0x00, 0x00, 0x03, // default_sample_duration, | ||
727 | 0x00, 0x00, 0x00, 0x04, // default_sample_size | ||
728 | 0x00, 0x00, 0x00, 0x05))); // default_sample_flags | ||
729 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
730 | [{ | ||
731 | type: 'moof', | ||
732 | size: 72, | ||
733 | boxes: [{ | ||
734 | type: 'mfhd', | ||
735 | size: 16, | ||
736 | version: 0, | ||
737 | flags: new Uint8Array([0, 0, 0]), | ||
738 | sequenceNumber: 4 | ||
739 | }, | ||
740 | { | ||
741 | type: 'traf', | ||
742 | size: 48, | ||
743 | boxes: [{ | ||
744 | type: 'tfhd', | ||
745 | version: 0, | ||
746 | size: 40, | ||
747 | flags: new Uint8Array([0x00, 0, 0x3b]), | ||
748 | trackId: 1, | ||
749 | baseDataOffset: 1, | ||
750 | sampleDescriptionIndex: 2, | ||
751 | defaultSampleDuration: 3, | ||
752 | defaultSampleSize: 4, | ||
753 | defaultSampleFlags: 5 | ||
754 | }] | ||
755 | }] | ||
756 | }]); | ||
757 | }); | ||
758 | |||
759 | test('can parse a trun', function() { | ||
760 | var data = box('trun', | ||
761 | 0x00, // version | ||
762 | 0x00, 0x0b, 0x05, // flags | ||
763 | 0x00, 0x00, 0x00, 0x02, // sample_count | ||
764 | 0x00, 0x00, 0x00, 0x01, // data_offset | ||
765 | // first_sample_flags | ||
766 | // r:0000 il:10 sdo:01 sido:10 shr:01 spv:111 snss:1 | ||
767 | // dp:1111 1110 1101 1100 | ||
768 | 0x09, 0x9f, 0xfe, 0xdc, | ||
769 | |||
770 | 0x00, 0x00, 0x00, 0x09, // sample_duration | ||
771 | 0x00, 0x00, 0x00, 0xff, // sample_size | ||
772 | 0x00, 0x00, 0x00, 0x00, // sample_composition_time_offset | ||
773 | |||
774 | 0x00, 0x00, 0x00, 0x08, // sample_duration | ||
775 | 0x00, 0x00, 0x00, 0xfe, // sample_size | ||
776 | 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset | ||
777 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
778 | [{ | ||
779 | type: 'trun', | ||
780 | version: 0, | ||
781 | size: 48, | ||
782 | flags: new Uint8Array([0, 0x0b, 0x05]), | ||
783 | dataOffset: 1, | ||
784 | samples: [{ | ||
785 | duration: 9, | ||
786 | size: 0xff, | ||
787 | flags: { | ||
788 | isLeading: 2, | ||
789 | dependsOn: 1, | ||
790 | isDependedOn: 2, | ||
791 | hasRedundancy: 1, | ||
792 | paddingValue: 7, | ||
793 | isNonSyncSample: 1, | ||
794 | degradationPriority: 0xfedc, | ||
795 | }, | ||
796 | compositionTimeOffset: 0 | ||
797 | }, { | ||
798 | duration: 8, | ||
799 | size: 0xfe, | ||
800 | compositionTimeOffset: 0 | ||
801 | }] | ||
802 | }]); | ||
803 | }); | ||
804 | |||
805 | test('can parse a trun with per-sample flags', function() { | ||
806 | var data = box('trun', | ||
807 | 0x00, // version | ||
808 | 0x00, 0x0f, 0x00, // flags | ||
809 | 0x00, 0x00, 0x00, 0x01, // sample_count | ||
810 | |||
811 | 0x00, 0x00, 0x00, 0x09, // sample_duration | ||
812 | 0x00, 0x00, 0x00, 0xff, // sample_size | ||
813 | // sample_flags | ||
814 | // r:0000 il:00 sdo:01, sido:11 shr:00 spv:010 snss:0 | ||
815 | // dp: 0001 0010 0011 0100 | ||
816 | 0x01, 0xc4, 0x12, 0x34, | ||
817 | 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset | ||
818 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
819 | [{ | ||
820 | type: 'trun', | ||
821 | version: 0, | ||
822 | size: 32, | ||
823 | flags: new Uint8Array([0, 0x0f, 0x00]), | ||
824 | samples: [{ | ||
825 | duration: 9, | ||
826 | size: 0xff, | ||
827 | flags: { | ||
828 | isLeading: 0, | ||
829 | dependsOn: 1, | ||
830 | isDependedOn: 3, | ||
831 | hasRedundancy: 0, | ||
832 | paddingValue: 2, | ||
833 | isNonSyncSample: 0, | ||
834 | degradationPriority: 0x1234 | ||
835 | }, | ||
836 | compositionTimeOffset: 0 | ||
837 | }] | ||
838 | }]); | ||
839 | |||
840 | }); | ||
841 | |||
842 | test('can parse an sdtp', function() { | ||
843 | var data = box('sdtp', | ||
844 | 0x00, // version | ||
845 | 0x00, 0x00, 0x00, // flags | ||
846 | // reserved + sample_depends_on + | ||
847 | // sample_is_dependend_on + sample_has_redundancy | ||
848 | 0x15, | ||
849 | // reserved + sample_depends_on + | ||
850 | // sample_is_dependend_on + sample_has_redundancy | ||
851 | 0x27); | ||
852 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ | ||
853 | type: 'sdtp', | ||
854 | version: 0, | ||
855 | flags: new Uint8Array([0, 0, 0]), | ||
856 | size: 14, | ||
857 | samples: [{ | ||
858 | dependsOn: 1, | ||
859 | isDependedOn: 1, | ||
860 | hasRedundancy: 1 | ||
861 | }, { | ||
862 | dependsOn: 2, | ||
863 | isDependedOn: 1, | ||
864 | hasRedundancy: 3 | ||
865 | }] | ||
866 | }]); | ||
867 | }); | ||
868 | |||
869 | test('can parse a sidx', function(){ | ||
870 | var data = box('sidx', | ||
871 | 0x00, // version | ||
872 | 0x00, 0x00, 0x00, // flags | ||
873 | 0x00, 0x00, 0x00, 0x02, // reference_ID | ||
874 | 0x00, 0x00, 0x00, 0x01, // timescale | ||
875 | 0x01, 0x02, 0x03, 0x04, // earliest_presentation_time | ||
876 | 0x00, 0x00, 0x00, 0x00, // first_offset | ||
877 | 0x00, 0x00, // reserved | ||
878 | 0x00, 0x02, // reference_count | ||
879 | // first reference | ||
880 | 0x80, 0x00, 0x00, 0x07, // reference_type(1) + referenced_size(31) | ||
881 | 0x00, 0x00, 0x00, 0x08, // subsegment_duration | ||
882 | 0x80, 0x00, 0x00, 0x09, // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28) | ||
883 | // second reference | ||
884 | 0x00, 0x00, 0x00, 0x03, // reference_type(1) + referenced_size(31) | ||
885 | 0x00, 0x00, 0x00, 0x04, // subsegment_duration | ||
886 | 0x10, 0x00, 0x00, 0x05 // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28) | ||
887 | ); | ||
888 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
889 | [{ | ||
890 | type: 'sidx', | ||
891 | version: 0, | ||
892 | flags: new Uint8Array([0, 0x00, 0x00]), | ||
893 | timescale: 1, | ||
894 | earliestPresentationTime: 0x01020304, | ||
895 | firstOffset: 0, | ||
896 | referenceId: 2, | ||
897 | size: 56, | ||
898 | references: [{ | ||
899 | referenceType: 1, | ||
900 | referencedSize: 7, | ||
901 | subsegmentDuration: 8, | ||
902 | startsWithSap: true, | ||
903 | sapType: 0, | ||
904 | sapDeltaTime: 9 | ||
905 | },{ | ||
906 | referenceType: 0, | ||
907 | referencedSize: 3, | ||
908 | subsegmentDuration: 4, | ||
909 | startsWithSap: false, | ||
910 | sapType: 1, | ||
911 | sapDeltaTime: 5 | ||
912 | }] | ||
913 | |||
914 | }]); | ||
915 | }); | ||
916 | |||
917 | test('can parse an smhd', function() { | ||
918 | var data = box('smhd', | ||
919 | 0x00, // version | ||
920 | 0x00, 0x00, 0x00, // flags | ||
921 | 0x00, 0xff, // balance, fixed-point 8.8 | ||
922 | 0x00, 0x00); // reserved | ||
923 | |||
924 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
925 | [{ | ||
926 | type: 'smhd', | ||
927 | size: 16, | ||
928 | version: 0, | ||
929 | flags: new Uint8Array([0, 0, 0]), | ||
930 | balance: 0xff / Math.pow(2, 8) | ||
931 | }], | ||
932 | 'parsed an smhd'); | ||
933 | }); | ||
934 | |||
935 | test('can parse a tfdt', function() { | ||
936 | var data = box('tfdt', | ||
937 | 0x00, // version | ||
938 | 0x00, 0x00, 0x00, // flags | ||
939 | 0x01, 0x02, 0x03, 0x04); // baseMediaDecodeTime | ||
940 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
941 | [{ | ||
942 | type: 'tfdt', | ||
943 | version: 0, | ||
944 | size: 16, | ||
945 | flags: new Uint8Array([0, 0, 0]), | ||
946 | baseMediaDecodeTime: 0x01020304 | ||
947 | }]); | ||
948 | }); | ||
949 | |||
950 | test('can parse a series of boxes', function() { | ||
951 | var ftyp = [ | ||
952 | 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24 | ||
953 | ].concat(typeBytes('ftyp')).concat([ | ||
954 | 0x69, 0x73, 0x6f, 0x6d, // major brand | ||
955 | 0x00, 0x00, 0x00, 0x02, // minor version | ||
956 | 98, 101, 101, 112, // compatible brands | ||
957 | 98, 111, 111, 112, // compatible brands | ||
958 | ]); | ||
959 | |||
960 | deepEqual(videojs.inspectMp4(new Uint8Array(ftyp.concat(ftyp))), | ||
961 | [{ | ||
962 | type: 'ftyp', | ||
963 | size: 4 * 6, | ||
964 | majorBrand: 'isom', | ||
965 | minorVersion: 2, | ||
966 | compatibleBrands: ['beep', 'boop'] | ||
967 | },{ | ||
968 | type: 'ftyp', | ||
969 | size: 4 * 6, | ||
970 | majorBrand: 'isom', | ||
971 | minorVersion: 2, | ||
972 | compatibleBrands: ['beep', 'boop'] | ||
973 | }], | ||
974 | 'parsed two boxes in series'); | ||
975 | |||
976 | }); | ||
977 | |||
978 | })(window, window.videojs); |
test/muxer/css/main.css
deleted
100644 → 0
1 | /* ========================================================================== | ||
2 | HTML5 Boilerplate styles - h5bp.com (generated via initializr.com) | ||
3 | ========================================================================== */ | ||
4 | |||
5 | html, | ||
6 | button, | ||
7 | input, | ||
8 | select, | ||
9 | textarea { | ||
10 | color: #222; | ||
11 | } | ||
12 | |||
13 | body { | ||
14 | font-size: 1em; | ||
15 | line-height: 1.4; | ||
16 | } | ||
17 | |||
18 | ::-moz-selection { | ||
19 | background: #b3d4fc; | ||
20 | text-shadow: none; | ||
21 | } | ||
22 | |||
23 | ::selection { | ||
24 | background: #b3d4fc; | ||
25 | text-shadow: none; | ||
26 | } | ||
27 | |||
28 | hr { | ||
29 | display: block; | ||
30 | height: 1px; | ||
31 | border: 0; | ||
32 | border-top: 1px solid #ccc; | ||
33 | margin: 1em 0; | ||
34 | padding: 0; | ||
35 | } | ||
36 | |||
37 | img { | ||
38 | vertical-align: middle; | ||
39 | } | ||
40 | |||
41 | fieldset { | ||
42 | border: 0; | ||
43 | margin: 0; | ||
44 | padding: 0; | ||
45 | } | ||
46 | |||
47 | textarea { | ||
48 | resize: vertical; | ||
49 | } | ||
50 | |||
51 | .chromeframe { | ||
52 | margin: 0.2em 0; | ||
53 | background: #ccc; | ||
54 | color: #000; | ||
55 | padding: 0.2em 0; | ||
56 | } | ||
57 | |||
58 | |||
59 | /* ===== Initializr Styles ================================================== | ||
60 | Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template | ||
61 | ========================================================================== */ | ||
62 | |||
63 | body { | ||
64 | font: 16px/26px Helvetica, Helvetica Neue, Arial; | ||
65 | } | ||
66 | |||
67 | .wrapper { | ||
68 | width: 90%; | ||
69 | margin: 0 5%; | ||
70 | } | ||
71 | |||
72 | /* =================== | ||
73 | ALL: Orange Theme | ||
74 | =================== */ | ||
75 | |||
76 | .header-container { | ||
77 | border-bottom: 20px solid #e44d26; | ||
78 | } | ||
79 | |||
80 | .footer-container, | ||
81 | .main aside { | ||
82 | border-top: 20px solid #e44d26; | ||
83 | } | ||
84 | |||
85 | .header-container, | ||
86 | .footer-container, | ||
87 | .main aside { | ||
88 | background: #f16529; | ||
89 | } | ||
90 | |||
91 | .title { | ||
92 | color: white; | ||
93 | } | ||
94 | |||
95 | /* ============== | ||
96 | MOBILE: Menu | ||
97 | ============== */ | ||
98 | |||
99 | nav ul { | ||
100 | margin: 0; | ||
101 | padding: 0; | ||
102 | } | ||
103 | |||
104 | nav a { | ||
105 | display: block; | ||
106 | margin-bottom: 10px; | ||
107 | padding: 15px 0; | ||
108 | |||
109 | text-align: center; | ||
110 | text-decoration: none; | ||
111 | font-weight: bold; | ||
112 | |||
113 | color: white; | ||
114 | background: #e44d26; | ||
115 | } | ||
116 | |||
117 | nav a:hover, | ||
118 | nav a:visited { | ||
119 | color: white; | ||
120 | } | ||
121 | |||
122 | nav a:hover { | ||
123 | text-decoration: underline; | ||
124 | } | ||
125 | |||
126 | /* ============== | ||
127 | MOBILE: Main | ||
128 | ============== */ | ||
129 | |||
130 | .main { | ||
131 | padding: 30px 0; | ||
132 | } | ||
133 | |||
134 | .main article h1 { | ||
135 | font-size: 2em; | ||
136 | } | ||
137 | |||
138 | .main aside { | ||
139 | color: white; | ||
140 | padding: 0px 5% 10px; | ||
141 | } | ||
142 | |||
143 | .footer-container footer { | ||
144 | color: white; | ||
145 | padding: 20px 0; | ||
146 | } | ||
147 | |||
148 | /* =============== | ||
149 | ALL: IE Fixes | ||
150 | =============== */ | ||
151 | |||
152 | .ie7 .title { | ||
153 | padding-top: 20px; | ||
154 | } | ||
155 | |||
156 | /* ========================================================================== | ||
157 | Author's custom styles | ||
158 | ========================================================================== */ | ||
159 | |||
160 | section { | ||
161 | clear: both; | ||
162 | } | ||
163 | |||
164 | form label { | ||
165 | display: block; | ||
166 | } | ||
167 | |||
168 | .result-wrapper { | ||
169 | float: left; | ||
170 | margin: 10px 0; | ||
171 | min-width: 422px; | ||
172 | width: 50%; | ||
173 | } | ||
174 | |||
175 | .result { | ||
176 | border: thin solid #aaa; | ||
177 | border-radius: 5px; | ||
178 | font-size: 10px; | ||
179 | line-height: 15px; | ||
180 | margin: 0 5px; | ||
181 | padding: 0 10px; | ||
182 | } | ||
183 | |||
184 | /* ========================================================================== | ||
185 | Media Queries | ||
186 | ========================================================================== */ | ||
187 | |||
188 | @media only screen and (min-width: 480px) { | ||
189 | |||
190 | /* ==================== | ||
191 | INTERMEDIATE: Menu | ||
192 | ==================== */ | ||
193 | |||
194 | nav a { | ||
195 | float: left; | ||
196 | width: 27%; | ||
197 | margin: 0 1.7%; | ||
198 | padding: 25px 2%; | ||
199 | margin-bottom: 0; | ||
200 | } | ||
201 | |||
202 | nav li:first-child a { | ||
203 | margin-left: 0; | ||
204 | } | ||
205 | |||
206 | nav li:last-child a { | ||
207 | margin-right: 0; | ||
208 | } | ||
209 | |||
210 | /* ======================== | ||
211 | INTERMEDIATE: IE Fixes | ||
212 | ======================== */ | ||
213 | |||
214 | nav ul li { | ||
215 | display: inline; | ||
216 | } | ||
217 | |||
218 | .oldie nav a { | ||
219 | margin: 0 0.7%; | ||
220 | } | ||
221 | } | ||
222 | |||
223 | @media only screen and (min-width: 768px) { | ||
224 | |||
225 | /* ==================== | ||
226 | WIDE: CSS3 Effects | ||
227 | ==================== */ | ||
228 | |||
229 | .header-container, | ||
230 | .main aside { | ||
231 | -webkit-box-shadow: 0 5px 10px #aaa; | ||
232 | -moz-box-shadow: 0 5px 10px #aaa; | ||
233 | box-shadow: 0 5px 10px #aaa; | ||
234 | } | ||
235 | |||
236 | /* ============ | ||
237 | WIDE: Menu | ||
238 | ============ */ | ||
239 | |||
240 | .title { | ||
241 | float: left; | ||
242 | } | ||
243 | |||
244 | nav { | ||
245 | float: right; | ||
246 | width: 38%; | ||
247 | } | ||
248 | |||
249 | /* ============ | ||
250 | WIDE: Main | ||
251 | ============ */ | ||
252 | |||
253 | .main article { | ||
254 | float: left; | ||
255 | width: 100%; | ||
256 | } | ||
257 | } | ||
258 | |||
259 | @media only screen and (min-width: 1140px) { | ||
260 | |||
261 | /* =============== | ||
262 | Maximal Width | ||
263 | =============== */ | ||
264 | |||
265 | .wrapper { | ||
266 | width: 1026px; /* 1140px - 10% for margins */ | ||
267 | margin: 0 auto; | ||
268 | } | ||
269 | } | ||
270 | |||
271 | /* ========================================================================== | ||
272 | Helper classes | ||
273 | ========================================================================== */ | ||
274 | |||
275 | .ir { | ||
276 | background-color: transparent; | ||
277 | border: 0; | ||
278 | overflow: hidden; | ||
279 | *text-indent: -9999px; | ||
280 | } | ||
281 | |||
282 | .ir:before { | ||
283 | content: ""; | ||
284 | display: block; | ||
285 | width: 0; | ||
286 | height: 150%; | ||
287 | } | ||
288 | |||
289 | .hidden { | ||
290 | display: none !important; | ||
291 | visibility: hidden; | ||
292 | } | ||
293 | |||
294 | .visuallyhidden { | ||
295 | border: 0; | ||
296 | clip: rect(0 0 0 0); | ||
297 | height: 1px; | ||
298 | margin: -1px; | ||
299 | overflow: hidden; | ||
300 | padding: 0; | ||
301 | position: absolute; | ||
302 | width: 1px; | ||
303 | } | ||
304 | |||
305 | .visuallyhidden.focusable:active, | ||
306 | .visuallyhidden.focusable:focus { | ||
307 | clip: auto; | ||
308 | height: auto; | ||
309 | margin: 0; | ||
310 | overflow: visible; | ||
311 | position: static; | ||
312 | width: auto; | ||
313 | } | ||
314 | |||
315 | .invisible { | ||
316 | visibility: hidden; | ||
317 | } | ||
318 | |||
319 | .clearfix:before, | ||
320 | .clearfix:after { | ||
321 | content: " "; | ||
322 | display: table; | ||
323 | } | ||
324 | |||
325 | .clearfix:after { | ||
326 | clear: both; | ||
327 | } | ||
328 | |||
329 | .clearfix { | ||
330 | *zoom: 1; | ||
331 | } | ||
332 | |||
333 | /* ========================================================================== | ||
334 | Print styles | ||
335 | ========================================================================== */ | ||
336 | |||
337 | @media print { | ||
338 | * { | ||
339 | background: transparent !important; | ||
340 | color: #000 !important; /* Black prints faster: h5bp.com/s */ | ||
341 | box-shadow: none !important; | ||
342 | text-shadow: none !important; | ||
343 | } | ||
344 | |||
345 | a, | ||
346 | a:visited { | ||
347 | text-decoration: underline; | ||
348 | } | ||
349 | |||
350 | a[href]:after { | ||
351 | content: " (" attr(href) ")"; | ||
352 | } | ||
353 | |||
354 | abbr[title]:after { | ||
355 | content: " (" attr(title) ")"; | ||
356 | } | ||
357 | |||
358 | /* | ||
359 | * Don't show links for images, or javascript/internal links | ||
360 | */ | ||
361 | |||
362 | .ir a:after, | ||
363 | a[href^="javascript:"]:after, | ||
364 | a[href^="#"]:after { | ||
365 | content: ""; | ||
366 | } | ||
367 | |||
368 | pre, | ||
369 | blockquote { | ||
370 | border: 1px solid #999; | ||
371 | page-break-inside: avoid; | ||
372 | } | ||
373 | |||
374 | thead { | ||
375 | display: table-header-group; /* h5bp.com/t */ | ||
376 | } | ||
377 | |||
378 | tr, | ||
379 | img { | ||
380 | page-break-inside: avoid; | ||
381 | } | ||
382 | |||
383 | img { | ||
384 | max-width: 100% !important; | ||
385 | } | ||
386 | |||
387 | @page { | ||
388 | margin: 0.5cm; | ||
389 | } | ||
390 | |||
391 | p, | ||
392 | h2, | ||
393 | h3 { | ||
394 | orphans: 3; | ||
395 | widows: 3; | ||
396 | } | ||
397 | |||
398 | h2, | ||
399 | h3 { | ||
400 | page-break-after: avoid; | ||
401 | } | ||
402 | } | ||
403 | |||
404 | /** | ||
405 | * Diff styling | ||
406 | */ | ||
407 | #comparison p { | ||
408 | white-space: normal; | ||
409 | } | ||
410 | #comparison pre { | ||
411 | font-size: 90%; | ||
412 | } | ||
413 | ins, del { | ||
414 | text-decoration: none; | ||
415 | } | ||
416 | ins { | ||
417 | background-color: #C6E746; | ||
418 | } | ||
419 | |||
420 | del { | ||
421 | background-color: #EE5757 | ||
422 | } | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
test/muxer/css/normalize.css
deleted
100644 → 0
1 | /*! normalize.css v1.1.2 | MIT License | git.io/normalize */ | ||
2 | |||
3 | /* ========================================================================== | ||
4 | HTML5 display definitions | ||
5 | ========================================================================== */ | ||
6 | |||
7 | /** | ||
8 | * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. | ||
9 | */ | ||
10 | |||
11 | article, | ||
12 | aside, | ||
13 | details, | ||
14 | figcaption, | ||
15 | figure, | ||
16 | footer, | ||
17 | header, | ||
18 | hgroup, | ||
19 | main, | ||
20 | nav, | ||
21 | section, | ||
22 | summary { | ||
23 | display: block; | ||
24 | } | ||
25 | |||
26 | /** | ||
27 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. | ||
28 | */ | ||
29 | |||
30 | audio, | ||
31 | canvas, | ||
32 | video { | ||
33 | display: inline-block; | ||
34 | *display: inline; | ||
35 | *zoom: 1; | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * Prevent modern browsers from displaying `audio` without controls. | ||
40 | * Remove excess height in iOS 5 devices. | ||
41 | */ | ||
42 | |||
43 | audio:not([controls]) { | ||
44 | display: none; | ||
45 | height: 0; | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. | ||
50 | * Known issue: no IE 6 support. | ||
51 | */ | ||
52 | |||
53 | [hidden] { | ||
54 | display: none; | ||
55 | } | ||
56 | |||
57 | /* ========================================================================== | ||
58 | Base | ||
59 | ========================================================================== */ | ||
60 | |||
61 | /** | ||
62 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using | ||
63 | * `em` units. | ||
64 | * 2. Prevent iOS text size adjust after orientation change, without disabling | ||
65 | * user zoom. | ||
66 | */ | ||
67 | |||
68 | html { | ||
69 | font-size: 100%; /* 1 */ | ||
70 | -ms-text-size-adjust: 100%; /* 2 */ | ||
71 | -webkit-text-size-adjust: 100%; /* 2 */ | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * Address `font-family` inconsistency between `textarea` and other form | ||
76 | * elements. | ||
77 | */ | ||
78 | |||
79 | html, | ||
80 | button, | ||
81 | input, | ||
82 | select, | ||
83 | textarea { | ||
84 | font-family: sans-serif; | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * Address margins handled incorrectly in IE 6/7. | ||
89 | */ | ||
90 | |||
91 | body { | ||
92 | margin: 0; | ||
93 | } | ||
94 | |||
95 | /* ========================================================================== | ||
96 | Links | ||
97 | ========================================================================== */ | ||
98 | |||
99 | /** | ||
100 | * Address `outline` inconsistency between Chrome and other browsers. | ||
101 | */ | ||
102 | |||
103 | a:focus { | ||
104 | outline: thin dotted; | ||
105 | } | ||
106 | |||
107 | /** | ||
108 | * Improve readability when focused and also mouse hovered in all browsers. | ||
109 | */ | ||
110 | |||
111 | a:active, | ||
112 | a:hover { | ||
113 | outline: 0; | ||
114 | } | ||
115 | |||
116 | /* ========================================================================== | ||
117 | Typography | ||
118 | ========================================================================== */ | ||
119 | |||
120 | /** | ||
121 | * Address font sizes and margins set differently in IE 6/7. | ||
122 | * Address font sizes within `section` and `article` in Firefox 4+, Safari 5, | ||
123 | * and Chrome. | ||
124 | */ | ||
125 | |||
126 | h1 { | ||
127 | font-size: 2em; | ||
128 | margin: 0.67em 0; | ||
129 | } | ||
130 | |||
131 | h2 { | ||
132 | font-size: 1.5em; | ||
133 | margin: 0.83em 0; | ||
134 | } | ||
135 | |||
136 | h3 { | ||
137 | font-size: 1.17em; | ||
138 | margin: 1em 0; | ||
139 | } | ||
140 | |||
141 | h4 { | ||
142 | font-size: 1em; | ||
143 | margin: 1.33em 0; | ||
144 | } | ||
145 | |||
146 | h5 { | ||
147 | font-size: 0.83em; | ||
148 | margin: 1.67em 0; | ||
149 | } | ||
150 | |||
151 | h6 { | ||
152 | font-size: 0.67em; | ||
153 | margin: 2.33em 0; | ||
154 | } | ||
155 | |||
156 | /** | ||
157 | * Address styling not present in IE 7/8/9, Safari 5, and Chrome. | ||
158 | */ | ||
159 | |||
160 | abbr[title] { | ||
161 | border-bottom: 1px dotted; | ||
162 | } | ||
163 | |||
164 | /** | ||
165 | * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. | ||
166 | */ | ||
167 | |||
168 | b, | ||
169 | strong { | ||
170 | font-weight: bold; | ||
171 | } | ||
172 | |||
173 | blockquote { | ||
174 | margin: 1em 40px; | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * Address styling not present in Safari 5 and Chrome. | ||
179 | */ | ||
180 | |||
181 | dfn { | ||
182 | font-style: italic; | ||
183 | } | ||
184 | |||
185 | /** | ||
186 | * Address differences between Firefox and other browsers. | ||
187 | * Known issue: no IE 6/7 normalization. | ||
188 | */ | ||
189 | |||
190 | hr { | ||
191 | -moz-box-sizing: content-box; | ||
192 | box-sizing: content-box; | ||
193 | height: 0; | ||
194 | } | ||
195 | |||
196 | /** | ||
197 | * Address styling not present in IE 6/7/8/9. | ||
198 | */ | ||
199 | |||
200 | mark { | ||
201 | background: #ff0; | ||
202 | color: #000; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * Address margins set differently in IE 6/7. | ||
207 | */ | ||
208 | |||
209 | p, | ||
210 | pre { | ||
211 | margin: 1em 0; | ||
212 | } | ||
213 | |||
214 | /** | ||
215 | * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. | ||
216 | */ | ||
217 | |||
218 | code, | ||
219 | kbd, | ||
220 | pre, | ||
221 | samp { | ||
222 | font-family: monospace, serif; | ||
223 | _font-family: 'courier new', monospace; | ||
224 | font-size: 1em; | ||
225 | } | ||
226 | |||
227 | /** | ||
228 | * Improve readability of pre-formatted text in all browsers. | ||
229 | */ | ||
230 | |||
231 | pre { | ||
232 | white-space: pre; | ||
233 | white-space: pre-wrap; | ||
234 | word-wrap: break-word; | ||
235 | } | ||
236 | |||
237 | /** | ||
238 | * Address CSS quotes not supported in IE 6/7. | ||
239 | */ | ||
240 | |||
241 | q { | ||
242 | quotes: none; | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * Address `quotes` property not supported in Safari 4. | ||
247 | */ | ||
248 | |||
249 | q:before, | ||
250 | q:after { | ||
251 | content: ''; | ||
252 | content: none; | ||
253 | } | ||
254 | |||
255 | /** | ||
256 | * Address inconsistent and variable font size in all browsers. | ||
257 | */ | ||
258 | |||
259 | small { | ||
260 | font-size: 80%; | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. | ||
265 | */ | ||
266 | |||
267 | sub, | ||
268 | sup { | ||
269 | font-size: 75%; | ||
270 | line-height: 0; | ||
271 | position: relative; | ||
272 | vertical-align: baseline; | ||
273 | } | ||
274 | |||
275 | sup { | ||
276 | top: -0.5em; | ||
277 | } | ||
278 | |||
279 | sub { | ||
280 | bottom: -0.25em; | ||
281 | } | ||
282 | |||
283 | /* ========================================================================== | ||
284 | Lists | ||
285 | ========================================================================== */ | ||
286 | |||
287 | /** | ||
288 | * Address margins set differently in IE 6/7. | ||
289 | */ | ||
290 | |||
291 | dl, | ||
292 | menu, | ||
293 | ol, | ||
294 | ul { | ||
295 | margin: 1em 0; | ||
296 | } | ||
297 | |||
298 | dd { | ||
299 | margin: 0 0 0 40px; | ||
300 | } | ||
301 | |||
302 | /** | ||
303 | * Address paddings set differently in IE 6/7. | ||
304 | */ | ||
305 | |||
306 | menu, | ||
307 | ol, | ||
308 | ul { | ||
309 | padding: 0 0 0 40px; | ||
310 | } | ||
311 | |||
312 | /** | ||
313 | * Correct list images handled incorrectly in IE 7. | ||
314 | */ | ||
315 | |||
316 | nav ul, | ||
317 | nav ol { | ||
318 | list-style: none; | ||
319 | list-style-image: none; | ||
320 | } | ||
321 | |||
322 | /* ========================================================================== | ||
323 | Embedded content | ||
324 | ========================================================================== */ | ||
325 | |||
326 | /** | ||
327 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. | ||
328 | * 2. Improve image quality when scaled in IE 7. | ||
329 | */ | ||
330 | |||
331 | img { | ||
332 | border: 0; /* 1 */ | ||
333 | -ms-interpolation-mode: bicubic; /* 2 */ | ||
334 | } | ||
335 | |||
336 | /** | ||
337 | * Correct overflow displayed oddly in IE 9. | ||
338 | */ | ||
339 | |||
340 | svg:not(:root) { | ||
341 | overflow: hidden; | ||
342 | } | ||
343 | |||
344 | /* ========================================================================== | ||
345 | Figures | ||
346 | ========================================================================== */ | ||
347 | |||
348 | /** | ||
349 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. | ||
350 | */ | ||
351 | |||
352 | figure { | ||
353 | margin: 0; | ||
354 | } | ||
355 | |||
356 | /* ========================================================================== | ||
357 | Forms | ||
358 | ========================================================================== */ | ||
359 | |||
360 | /** | ||
361 | * Correct margin displayed oddly in IE 6/7. | ||
362 | */ | ||
363 | |||
364 | form { | ||
365 | margin: 0; | ||
366 | } | ||
367 | |||
368 | /** | ||
369 | * Define consistent border, margin, and padding. | ||
370 | */ | ||
371 | |||
372 | fieldset { | ||
373 | border: 1px solid #c0c0c0; | ||
374 | margin: 0 2px; | ||
375 | padding: 0.35em 0.625em 0.75em; | ||
376 | } | ||
377 | |||
378 | /** | ||
379 | * 1. Correct color not being inherited in IE 6/7/8/9. | ||
380 | * 2. Correct text not wrapping in Firefox 3. | ||
381 | * 3. Correct alignment displayed oddly in IE 6/7. | ||
382 | */ | ||
383 | |||
384 | legend { | ||
385 | border: 0; /* 1 */ | ||
386 | padding: 0; | ||
387 | white-space: normal; /* 2 */ | ||
388 | *margin-left: -7px; /* 3 */ | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * 1. Correct font size not being inherited in all browsers. | ||
393 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, | ||
394 | * and Chrome. | ||
395 | * 3. Improve appearance and consistency in all browsers. | ||
396 | */ | ||
397 | |||
398 | button, | ||
399 | input, | ||
400 | select, | ||
401 | textarea { | ||
402 | font-size: 100%; /* 1 */ | ||
403 | margin: 0; /* 2 */ | ||
404 | vertical-align: baseline; /* 3 */ | ||
405 | *vertical-align: middle; /* 3 */ | ||
406 | } | ||
407 | |||
408 | /** | ||
409 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in | ||
410 | * the UA stylesheet. | ||
411 | */ | ||
412 | |||
413 | button, | ||
414 | input { | ||
415 | line-height: normal; | ||
416 | } | ||
417 | |||
418 | /** | ||
419 | * Address inconsistent `text-transform` inheritance for `button` and `select`. | ||
420 | * All other form control elements do not inherit `text-transform` values. | ||
421 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. | ||
422 | * Correct `select` style inheritance in Firefox 4+ and Opera. | ||
423 | */ | ||
424 | |||
425 | button, | ||
426 | select { | ||
427 | text-transform: none; | ||
428 | } | ||
429 | |||
430 | /** | ||
431 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` | ||
432 | * and `video` controls. | ||
433 | * 2. Correct inability to style clickable `input` types in iOS. | ||
434 | * 3. Improve usability and consistency of cursor style between image-type | ||
435 | * `input` and others. | ||
436 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs. | ||
437 | * Known issue: inner spacing remains in IE 6. | ||
438 | */ | ||
439 | |||
440 | button, | ||
441 | html input[type="button"], /* 1 */ | ||
442 | input[type="reset"], | ||
443 | input[type="submit"] { | ||
444 | -webkit-appearance: button; /* 2 */ | ||
445 | cursor: pointer; /* 3 */ | ||
446 | *overflow: visible; /* 4 */ | ||
447 | } | ||
448 | |||
449 | /** | ||
450 | * Re-set default cursor for disabled elements. | ||
451 | */ | ||
452 | |||
453 | button[disabled], | ||
454 | html input[disabled] { | ||
455 | cursor: default; | ||
456 | } | ||
457 | |||
458 | /** | ||
459 | * 1. Address box sizing set to content-box in IE 8/9. | ||
460 | * 2. Remove excess padding in IE 8/9. | ||
461 | * 3. Remove excess padding in IE 7. | ||
462 | * Known issue: excess padding remains in IE 6. | ||
463 | */ | ||
464 | |||
465 | input[type="checkbox"], | ||
466 | input[type="radio"] { | ||
467 | box-sizing: border-box; /* 1 */ | ||
468 | padding: 0; /* 2 */ | ||
469 | *height: 13px; /* 3 */ | ||
470 | *width: 13px; /* 3 */ | ||
471 | } | ||
472 | |||
473 | /** | ||
474 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. | ||
475 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome | ||
476 | * (include `-moz` to future-proof). | ||
477 | */ | ||
478 | |||
479 | input[type="search"] { | ||
480 | -webkit-appearance: textfield; /* 1 */ | ||
481 | -moz-box-sizing: content-box; | ||
482 | -webkit-box-sizing: content-box; /* 2 */ | ||
483 | box-sizing: content-box; | ||
484 | } | ||
485 | |||
486 | /** | ||
487 | * Remove inner padding and search cancel button in Safari 5 and Chrome | ||
488 | * on OS X. | ||
489 | */ | ||
490 | |||
491 | input[type="search"]::-webkit-search-cancel-button, | ||
492 | input[type="search"]::-webkit-search-decoration { | ||
493 | -webkit-appearance: none; | ||
494 | } | ||
495 | |||
496 | /** | ||
497 | * Remove inner padding and border in Firefox 3+. | ||
498 | */ | ||
499 | |||
500 | button::-moz-focus-inner, | ||
501 | input::-moz-focus-inner { | ||
502 | border: 0; | ||
503 | padding: 0; | ||
504 | } | ||
505 | |||
506 | /** | ||
507 | * 1. Remove default vertical scrollbar in IE 6/7/8/9. | ||
508 | * 2. Improve readability and alignment in all browsers. | ||
509 | */ | ||
510 | |||
511 | textarea { | ||
512 | overflow: auto; /* 1 */ | ||
513 | vertical-align: top; /* 2 */ | ||
514 | } | ||
515 | |||
516 | /* ========================================================================== | ||
517 | Tables | ||
518 | ========================================================================== */ | ||
519 | |||
520 | /** | ||
521 | * Remove most spacing between table cells. | ||
522 | */ | ||
523 | |||
524 | table { | ||
525 | border-collapse: collapse; | ||
526 | border-spacing: 0; | ||
527 | } |
test/muxer/css/normalize.min.css
deleted
100644 → 0
1 | /*! normalize.css v1.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
test/muxer/index.html
deleted
100644 → 0
1 | <!DOCTYPE html> | ||
2 | <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> | ||
3 | <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> | ||
4 | <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> | ||
5 | <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> | ||
6 | <head> | ||
7 | <meta charset="utf-8"> | ||
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||
9 | <title></title> | ||
10 | <meta name="description" content=""> | ||
11 | <meta name="viewport" content="width=device-width"> | ||
12 | |||
13 | <link rel="stylesheet" href="css/normalize.min.css"> | ||
14 | <link rel="stylesheet" href="css/main.css"> | ||
15 | |||
16 | <script src="js/vendor/modernizr-2.6.2.min.js"></script> | ||
17 | </head> | ||
18 | <body> | ||
19 | <!--[if lt IE 7]> | ||
20 | <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p> | ||
21 | <![endif]--> | ||
22 | |||
23 | <div class="header-container"> | ||
24 | <header class="wrapper clearfix"> | ||
25 | <h1 class="title">Transmux Analyzer</h1> | ||
26 | </header> | ||
27 | </div> | ||
28 | |||
29 | <div class="main-container"> | ||
30 | <div class="main wrapper clearfix"> | ||
31 | |||
32 | <article> | ||
33 | <header> | ||
34 | <p> | ||
35 | This page can help you compare the results of the | ||
36 | transmuxing performed by videojs-contrib-hls with a known, | ||
37 | working file produced by another tool. You could use | ||
38 | ffmpeg to transform an MPEG-2 transport stream into an FLV | ||
39 | with a command like this: | ||
40 | <pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre> | ||
41 | </p> | ||
42 | <p> | ||
43 | This page only compares FLVs files. There is | ||
44 | a <a href="mp4.html">similar utility</a> for testing the mp4 | ||
45 | conversion. | ||
46 | </p> | ||
47 | </header> | ||
48 | <section> | ||
49 | <h2>Inputs</h2> | ||
50 | <form id="inputs"> | ||
51 | <fieldset> | ||
52 | <label> | ||
53 | Your original MP2T segment: | ||
54 | <input type="file" id="original"> | ||
55 | </label> | ||
56 | <label> | ||
57 | Key (optional): | ||
58 | <input type="text" id="key"> | ||
59 | </label> | ||
60 | <label> | ||
61 | IV (optional): | ||
62 | <input type="text" id="iv"> | ||
63 | </label> | ||
64 | </fieldset> | ||
65 | <label> | ||
66 | A working, FLV version of the underlying stream | ||
67 | produced by another tool: | ||
68 | <input type="file" id="working"> | ||
69 | </label> | ||
70 | </form> | ||
71 | </section> | ||
72 | <section> | ||
73 | <h2>Tag Comparison</h2> | ||
74 | <div class="result-wrapper"> | ||
75 | <h3>videojs-contrib-hls</h3> | ||
76 | <ol class="vjs-tags"> | ||
77 | </ol> | ||
78 | </div> | ||
79 | <div class="result-wrapper"> | ||
80 | <h3>Working</h3> | ||
81 | <ol class="working-tags"> | ||
82 | </ol> | ||
83 | </div> | ||
84 | </section> | ||
85 | <section> | ||
86 | <h2>Results</h2> | ||
87 | <div class="result-wrapper"> | ||
88 | <h3>videojs-contrib-hls</h3> | ||
89 | <div class="vjs-hls-output result"> | ||
90 | <p> | ||
91 | The results of transmuxing your input file with | ||
92 | videojs-contrib-hls will show up here. | ||
93 | </p> | ||
94 | </div> | ||
95 | </div> | ||
96 | <div class="result-wrapper"> | ||
97 | <h3>Working</h3> | ||
98 | <div class="working-output result"> | ||
99 | <p> | ||
100 | The "good" version of the file will show up here. | ||
101 | </p> | ||
102 | </div> | ||
103 | </div> | ||
104 | </section> | ||
105 | </article> | ||
106 | |||
107 | </div> <!-- #main --> | ||
108 | </div> <!-- #main-container --> | ||
109 | |||
110 | <div class="footer-container"> | ||
111 | <footer class="wrapper"> | ||
112 | <h3>footer</h3> | ||
113 | </footer> | ||
114 | </div> | ||
115 | |||
116 | |||
117 | <script> | ||
118 | window.videojs = { | ||
119 | Hls: {} | ||
120 | }; | ||
121 | </script> | ||
122 | <!-- transmuxing --> | ||
123 | <script src="../../src/stream.js"></script> | ||
124 | <script src="../../src/flv-tag.js"></script> | ||
125 | <script src="../../src/exp-golomb.js"></script> | ||
126 | <script src="../../src/h264-stream.js"></script> | ||
127 | <script src="../../src/aac-stream.js"></script> | ||
128 | <script src="../../src/metadata-stream.js"></script> | ||
129 | <script src="../../src/segment-parser.js"></script> | ||
130 | <script src="../../node_modules/pkcs7/dist/pkcs7.unpad.js"></script> | ||
131 | <script src="../../src/decrypter.js"></script> | ||
132 | |||
133 | <script src="../../src/bin-utils.js"></script> | ||
134 | <script> | ||
135 | var inputs = document.getElementById('inputs'), | ||
136 | original = document.getElementById('original'), | ||
137 | key = document.getElementById('key'), | ||
138 | iv = document.getElementById('iv'), | ||
139 | working = document.getElementById('working'), | ||
140 | |||
141 | vjsTags = document.querySelector('.vjs-tags'), | ||
142 | workingTags = document.querySelector('.working-tags'), | ||
143 | |||
144 | vjsOutput = document.querySelector('.vjs-hls-output'), | ||
145 | workingOutput = document.querySelector('.working-output'), | ||
146 | |||
147 | tagTypes = { | ||
148 | 0x08: 'audio', | ||
149 | 0x09: 'video', | ||
150 | 0x12: 'metadata' | ||
151 | }; | ||
152 | |||
153 | videojs.log = console.log.bind(console); | ||
154 | |||
155 | original.addEventListener('change', function() { | ||
156 | var reader = new FileReader(); | ||
157 | reader.addEventListener('loadend', function() { | ||
158 | var parser = new videojs.Hls.SegmentParser(), | ||
159 | tags = [parser.getFlvHeader()], | ||
160 | tag, | ||
161 | bytes, | ||
162 | hex, | ||
163 | li, | ||
164 | byteLength = 0, | ||
165 | data, | ||
166 | i, | ||
167 | pos; | ||
168 | |||
169 | // clear old tag info | ||
170 | vjsTags.innerHTML = ''; | ||
171 | |||
172 | // optionally, decrypt the segment | ||
173 | if (key.value && iv.value) { | ||
174 | bytes = videojs.Hls.decrypt(new Uint8Array(reader.result), | ||
175 | key.value.match(/([0-9a-f]{8})/gi).map(function(e) { | ||
176 | return parseInt(e, 16); | ||
177 | }), | ||
178 | iv.value.match(/([0-9a-f]{8})/gi).map(function(e) { | ||
179 | return parseInt(e, 16); | ||
180 | })); | ||
181 | } else { | ||
182 | bytes = new Uint8Array(reader.result); | ||
183 | } | ||
184 | |||
185 | parser.parseSegmentBinaryData(bytes); | ||
186 | |||
187 | // collect all the tags | ||
188 | while (parser.tagsAvailable()) { | ||
189 | tag = parser.getNextTag(); | ||
190 | tags.push(tag.bytes); | ||
191 | li = document.createElement('li'); | ||
192 | li.innerHTML = tagTypes[tag.bytes[0]] + ': ' + tag.bytes.byteLength + 'B'; | ||
193 | vjsTags.appendChild(li); | ||
194 | } | ||
195 | // create a uint8array for the entire segment and copy everything over | ||
196 | i = tags.length; | ||
197 | while (i--) { | ||
198 | byteLength += tags[i].byteLength; | ||
199 | } | ||
200 | data = new Uint8Array(byteLength); | ||
201 | i = tags.length; | ||
202 | pos = byteLength; | ||
203 | while (i--) { | ||
204 | pos -= tags[i].byteLength; | ||
205 | data.set(tags[i], pos); | ||
206 | } | ||
207 | |||
208 | hex = '<pre>' | ||
209 | hex += videojs.Hls.utils.hexDump(data); | ||
210 | hex += '</pre>' | ||
211 | |||
212 | vjsOutput.innerHTML = hex; | ||
213 | }); | ||
214 | reader.readAsArrayBuffer(this.files[0]); | ||
215 | }, false); | ||
216 | |||
217 | working.addEventListener('change', function() { | ||
218 | var reader = new FileReader(); | ||
219 | reader.addEventListener('loadend', function() { | ||
220 | var hex = '<pre>', | ||
221 | bytes = new Uint8Array(reader.result), | ||
222 | i = 9, // header | ||
223 | dataSize, | ||
224 | li; | ||
225 | |||
226 | // clear old tag info | ||
227 | workingTags.innerHTML = ''; | ||
228 | |||
229 | // traverse the tags | ||
230 | i += 4; // previous tag size | ||
231 | while (i < bytes.byteLength) { | ||
232 | dataSize = bytes[i + 1] << 16; | ||
233 | dataSize |= bytes[i + 2] << 8; | ||
234 | dataSize |= bytes[i + 3]; | ||
235 | dataSize += 11; | ||
236 | |||
237 | li = document.createElement('li'); | ||
238 | li.innerHTML = tagTypes[bytes[i]] + ': ' + dataSize + 'B'; | ||
239 | workingTags.appendChild(li); | ||
240 | |||
241 | i += dataSize; // tag size | ||
242 | i += 4; // previous tag size | ||
243 | } | ||
244 | |||
245 | // output the hex dump | ||
246 | hex += videojs.Hls.utils.hexDump(bytes); | ||
247 | hex += '</pre>'; | ||
248 | workingOutput.innerHTML = hex; | ||
249 | }); | ||
250 | reader.readAsArrayBuffer(this.files[0]); | ||
251 | }, false); | ||
252 | </script> | ||
253 | <script type="text/plain"> | ||
254 | // map nal_unit_types to friendly names | ||
255 | console.log([ | ||
256 | 'unspecified', | ||
257 | 'slice_layer_without_partitioning', | ||
258 | 'slice_data_partition_a_layer', | ||
259 | 'slice_data_partition_b_layer', | ||
260 | 'slice_data_partition_c_layer', | ||
261 | 'slice_layer_without_partitioning_idr', | ||
262 | 'sei', | ||
263 | 'seq_parameter_set', | ||
264 | 'pic_parameter_set', | ||
265 | 'access_unit_delimiter', | ||
266 | 'end_of_seq', | ||
267 | 'end_of_stream', | ||
268 | 'filler', | ||
269 | 'seq_parameter_set_ext', | ||
270 | 'prefix_nal_unit', | ||
271 | 'subset_seq_parameter_set', | ||
272 | 'reserved', | ||
273 | 'reserved', | ||
274 | 'reserved' | ||
275 | ][nalUnitType]); | ||
276 | </script> | ||
277 | </body> | ||
278 | </html> |
test/muxer/js/main.js
deleted
100644 → 0
test/muxer/js/mdat.js
deleted
100644 → 0
1 | var moof = [0,0,0,16,109,102,104,100,0,0,0,0,0,0,0,1,0,0,0,164,116,114,97,102,0,0,0,16,116,102,104,100,0,2,0,0,0,0,0,1,0,0,0,16,116,102,100,116,0,0,0,0,0,0,0,0,0,0,0,124,116,114,117,110,0,0,2,5,0,0,0,25,0,0,0,196,0,0,0,0,0,0,12,219,0,0,0,183,0,0,0,172,0,0,0,182,0,0,0,183,0,0,0,198,0,0,0,146,0,0,0,176,0,0,0,190,0,0,0,169,0,0,0,243,0,0,0,174,0,0,0,131,0,0,0,161,0,0,0,144,0,0,0,163,0,0,0,128,0,0,0,174,0,0,0,194,0,0,0,133,0,0,0,183,0,0,0,154,0,0,0,150,0,0,0,169,0,0,0,157]; | ||
2 | var mdat = [0,0,2,151,6,5,255,255,147,220,69,233,189,230,217,72,183,150,44,216,32,217,35,238,239,120,50,54,52,32,45,32,99,111,114,101,32,49,49,56,32,114,50,48,56,53,32,56,97,54,50,56,51,53,32,45,32,72,46,50,54,52,47,77,80,69,71,45,52,32,65,86,67,32,99,111,100,101,99,32,45,32,67,111,112,121,108,101,102,116,32,50,48,48,51,45,50,48,49,49,32,45,32,104,116,116,112,58,47,47,119,119,119,46,118,105,100,101,111,108,97,110,46,111,114,103,47,120,50,54,52,46,104,116,109,108,32,45,32,111,112,116,105,111,110,115,58,32,99,97,98,97,99,61,48,32,114,101,102,61,51,32,100,101,98,108,111,99,107,61,49,58,48,58,48,32,97,110,97,108,121,115,101,61,48,120,49,58,48,120,49,49,49,32,109,101,61,104,101,120,32,115,117,98,109,101,61,55,32,112,115,121,61,49,32,112,115,121,95,114,100,61,49,46,48,48,58,48,46,48,48,32,109,105,120,101,100,95,114,101,102,61,49,32,109,101,95,114,97,110,103,101,61,49,54,32,99,104,114,111,109,97,95,109,101,61,49,32,116,114,101,108,108,105,115,61,49,32,56,120,56,100,99,116,61,48,32,99,113,109,61,48,32,100,101,97,100,122,111,110,101,61,50,49,44,49,49,32,102,97,115,116,95,112,115,107,105,112,61,49,32,99,104,114,111,109,97,95,113,112,95,111,102,102,115,101,116,61,45,50,32,116,104,114,101,97,100,115,61,51,32,115,108,105,99,101,100,95,116,104,114,101,97,100,115,61,48,32,110,114,61,48,32,100,101,99,105,109,97,116,101,61,49,32,105,110,116,101,114,108,97,99,101,100,61,48,32,98,108,117,114,97,121,95,99,111,109,112,97,116,61,48,32,99,111,110,115,116,114,97,105,110,101,100,95,105,110,116,114,97,61,48,32,98,102,114,97,109,101,115,61,48,32,119,101,105,103,104,116,112,61,48,32,107,101,121,105,110,116,61,50,53,32,107,101,121,105,110,116,95,109,105,110,61,49,51,32,115,99,101,110,101,99,117,116,61,52,48,32,105,110,116,114,97,95,114,101,102,114,101,115,104,61,48,32,114,99,95,108,111,111,107,97,104,101,97,100,61,52,48,32,114,99,61,99,98,114,32,109,98,116,114,101,101,61,49,32,98,105,116,114,97,116,101,61,52,56,32,114,97,116,101,116,111,108,61,49,46,48,32,113,99,111,109,112,61,48,46,54,48,32,113,112,109,105,110,61,48,32,113,112,109,97,120,61,54,57,32,113,112,115,116,101,112,61,52,32,118,98,118,95,109,97,120,114,97,116,101,61,52,56,32,118,98,118,95,98,117,102,115,105,122,101,61,50,48,48,48,32,110,97,108,95,104,114,100,61,110,111,110,101,32,105,112,95,114,97,116,105,111,61,49,46,52,48,32,97,113,61,49,58,49,46,48,48,0,128,0,0,10,60,101,136,132,25,201,6,34,128,0,195,76,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,157,117,215,93,117,215,93,117,215,93,117,215,93,117,215,93,117,215,93,117,215,93,117,215,95,255,222,238,80,168,38,192,35,16,144,238,68,216,105,218,55,168,0,162,196,199,82,63,12,225,73,161,167,199,63,255,119,220,120,86,40,104,195,139,254,42,238,91,250,153,255,255,85,225,81,64,72,185,204,154,55,71,225,39,244,108,42,251,175,255,254,171,134,8,60,160,192,171,24,186,235,174,186,235,174,186,235,174,186,235,254,68,82,105,120,193,48,3,216,90,173,113,18,222,1,212,208,33,57,132,214,13,165,0,49,178,166,201,246,232,31,159,114,199,225,248,120,43,94,172,137,32,75,180,11,247,223,215,60,255,225,240,208,6,179,209,69,200,98,8,78,127,56,167,193,4,10,86,12,135,128,50,171,244,132,239,23,93,117,215,93,117,255,255,174,21,39,0,34,47,146,109,95,99,121,145,164,252,245,215,93,127,218,223,102,56,80,108,162,144,4,45,36,192,200,180,217,171,107,207,0,5,118,124,132,86,222,3,228,95,255,135,132,133,95,63,201,94,237,216,117,215,93,253,52,167,211,72,160,150,220,152,252,49,23,119,68,166,120,38,182,158,46,186,235,174,186,235,237,7,14,24,92,172,85,41,52,237,176,36,7,36,243,235,141,132,30,250,171,62,170,183,14,146,109,247,254,0,216,166,193,132,65,114,198,145,17,236,4,181,80,52,183,125,238,240,182,55,155,38,215,252,231,217,97,81,252,12,81,121,32,222,64,100,123,224,117,17,49,226,133,65,77,215,128,8,79,114,61,95,127,46,199,213,220,111,165,80,103,234,179,148,4,110,94,206,245,255,196,106,207,10,148,0,134,53,178,28,75,239,243,54,131,72,65,84,194,41,207,235,70,173,59,32,134,180,210,191,128,35,215,219,55,10,106,126,109,51,86,17,157,104,20,103,247,214,44,196,132,32,28,171,255,255,206,255,213,147,97,133,76,214,102,231,231,241,72,156,56,161,87,89,7,57,85,100,65,255,32,110,253,1,25,209,163,106,105,49,152,90,243,182,81,195,130,157,24,201,118,229,255,196,106,39,10,138,240,68,214,2,163,144,173,82,203,73,174,70,10,101,231,250,63,97,37,178,3,121,219,125,96,145,38,57,34,37,63,255,240,160,68,109,118,202,61,227,203,210,102,142,153,173,76,111,174,186,235,254,31,252,40,104,20,109,73,102,245,50,154,153,215,235,174,186,235,174,190,19,221,127,225,35,195,124,193,179,16,239,119,251,25,126,181,143,140,216,122,35,194,225,107,76,143,235,124,22,30,202,27,52,188,80,232,136,132,137,94,110,191,59,210,95,223,177,114,10,191,252,37,187,154,7,140,177,57,255,61,186,4,194,206,129,208,186,187,46,52,63,207,183,245,54,237,191,235,221,35,76,197,84,140,63,186,52,41,129,82,166,149,255,25,149,23,231,53,175,219,243,232,67,11,191,252,37,252,130,111,2,166,217,10,0,94,211,194,101,84,68,17,31,35,242,222,132,139,208,130,103,95,84,113,195,68,84,93,169,150,64,204,207,71,253,211,174,141,133,58,135,205,35,27,187,30,176,178,60,125,14,145,16,145,43,206,175,130,255,194,66,51,199,59,5,57,117,243,84,108,168,126,150,206,255,93,135,142,157,191,20,156,122,213,80,172,179,98,143,80,240,154,158,36,146,159,145,86,194,237,251,110,225,169,127,194,66,29,151,107,53,213,93,120,115,180,1,184,180,21,41,249,151,77,200,172,235,159,179,35,35,123,180,189,31,190,255,79,130,82,20,8,128,212,22,177,22,61,232,247,30,120,3,79,232,150,179,54,225,239,11,174,186,235,255,183,177,182,20,52,2,228,166,105,102,193,76,2,71,210,89,246,226,238,235,245,215,93,117,215,93,119,223,125,255,210,31,240,248,94,2,25,231,73,255,255,47,210,129,224,115,207,22,18,209,12,2,92,233,134,237,191,192,43,214,64,109,19,255,8,48,37,118,186,235,255,255,224,132,188,0,126,81,156,120,8,99,90,123,107,101,41,126,19,52,38,229,238,98,61,176,47,32,11,119,32,54,137,225,215,93,117,215,93,117,215,93,117,215,95,234,112,255,135,197,224,7,75,35,133,156,62,179,243,170,233,134,170,56,177,31,4,254,52,151,134,30,112,37,243,126,6,143,41,153,143,255,250,172,84,120,32,142,236,197,21,248,182,229,187,255,254,239,194,176,33,18,156,200,41,70,241,254,52,125,104,216,72,212,245,255,238,69,191,248,75,128,27,108,204,4,130,175,242,117,231,180,128,23,218,89,154,72,63,147,174,186,235,174,186,235,175,255,244,30,20,62,0,9,26,140,152,187,140,173,72,149,57,9,16,162,250,87,66,43,56,215,99,158,154,53,171,127,255,219,76,111,60,203,19,244,247,127,143,144,0,152,42,176,51,6,179,229,217,225,160,149,17,207,16,87,78,248,9,225,95,163,21,113,70,213,129,40,107,116,216,109,178,200,144,31,117,163,49,39,243,81,141,245,71,73,230,249,246,126,239,209,240,255,168,97,180,45,192,178,117,74,198,219,174,10,140,240,22,146,16,77,184,39,91,18,251,142,15,32,71,84,247,230,161,195,240,0,168,132,78,73,49,199,205,5,192,67,254,193,46,21,175,76,229,19,0,26,100,168,43,230,37,9,66,51,3,90,66,243,186,94,187,232,26,151,217,89,85,99,255,86,166,46,38,206,80,185,255,88,94,45,103,228,8,76,122,227,98,250,34,184,114,238,200,171,26,49,98,139,255,103,87,173,64,119,90,66,176,207,147,189,132,144,105,120,183,18,140,253,105,131,221,240,202,191,246,169,111,254,223,160,138,73,34,149,138,238,62,184,175,27,224,184,253,44,134,172,190,120,26,228,92,187,202,254,192,220,79,205,151,190,216,230,3,7,219,166,104,91,206,38,255,233,85,85,143,136,231,102,196,227,158,181,26,17,82,208,205,91,9,69,255,185,238,98,122,133,85,19,132,125,95,251,34,196,231,154,186,208,233,249,168,247,255,127,106,63,32,51,67,163,1,190,176,66,92,198,212,84,186,39,254,119,31,56,251,223,4,166,108,149,89,78,236,0,180,201,156,173,94,153,111,254,252,17,108,29,69,70,221,79,156,2,201,254,41,118,45,169,255,216,93,195,83,174,18,107,125,167,192,226,217,88,184,46,127,238,193,177,38,168,69,139,159,223,114,99,37,109,32,53,38,184,207,253,205,158,205,231,141,74,171,76,139,140,193,242,246,180,56,178,144,18,162,41,16,43,255,155,120,145,25,202,221,219,239,184,14,234,58,129,128,223,214,124,110,155,150,205,118,175,251,192,14,148,58,73,44,180,15,29,201,184,122,56,103,137,231,0,45,144,127,145,96,157,75,37,3,180,104,193,173,166,114,208,214,159,223,214,123,73,163,0,5,117,61,156,14,52,198,157,135,213,215,211,130,136,185,173,120,160,245,142,245,169,208,22,106,52,233,254,183,121,49,53,105,86,96,66,175,35,148,64,126,18,240,88,215,207,255,251,73,32,112,196,160,73,215,74,223,186,240,6,64,7,80,27,252,60,91,228,206,175,251,35,194,137,161,5,148,166,55,254,195,187,101,2,100,39,246,127,205,191,128,128,243,166,153,163,24,82,210,161,152,149,209,204,101,111,187,252,105,224,236,86,165,56,18,115,255,255,19,11,171,107,34,156,76,111,152,1,108,42,188,73,174,110,189,157,143,172,198,139,246,182,99,88,234,237,171,127,161,133,33,241,63,39,31,194,101,244,58,204,82,50,28,62,166,246,128,220,58,42,111,17,86,140,183,219,133,4,72,74,141,148,103,54,62,213,36,74,68,137,87,255,208,253,117,215,93,117,215,93,117,255,255,8,66,135,225,191,64,31,51,34,6,19,247,9,99,159,227,240,8,11,56,216,140,0,171,94,141,16,247,76,216,98,241,23,172,125,190,96,61,54,112,80,180,192,176,239,166,82,195,32,168,103,107,73,19,16,119,126,122,54,227,251,62,187,145,247,164,96,213,91,12,71,194,230,205,4,77,193,43,38,167,48,98,200,67,194,185,211,85,240,127,235,140,66,34,35,190,3,243,47,131,132,172,43,63,255,85,46,12,155,217,238,172,55,73,48,115,152,251,227,16,194,71,82,93,43,20,25,8,144,50,139,168,52,255,242,75,9,170,96,249,57,6,239,15,154,49,205,152,41,18,214,74,42,204,86,193,72,239,245,120,127,195,224,24,7,10,192,157,166,143,46,49,16,103,240,10,28,252,128,38,98,217,40,152,86,240,248,65,34,153,30,175,230,88,23,251,139,26,107,20,55,246,234,167,193,135,235,50,68,92,135,184,126,0,41,205,147,8,129,210,235,224,31,154,244,105,117,255,253,224,104,210,82,92,144,252,7,24,71,87,220,90,79,141,97,177,21,191,216,120,146,204,15,63,140,157,223,73,11,152,130,71,124,161,189,225,253,132,239,73,84,11,55,230,109,209,166,131,160,248,153,53,167,193,252,84,86,133,241,126,252,103,226,229,213,170,59,131,255,246,166,96,221,56,138,82,119,81,126,210,136,228,36,142,8,148,27,191,220,34,22,228,86,109,140,200,181,129,226,39,82,198,74,115,255,43,57,17,28,243,227,119,223,73,132,73,57,22,140,177,53,246,255,143,75,122,68,86,182,39,217,212,20,152,234,175,239,251,108,128,166,114,66,141,192,137,255,193,219,140,13,81,113,178,233,155,82,69,68,113,13,204,180,31,192,12,70,64,60,68,247,212,6,130,174,179,151,13,188,29,109,18,3,119,208,43,37,115,65,5,204,144,184,33,24,69,30,254,54,205,201,63,115,80,217,127,127,244,67,49,80,247,168,139,249,83,255,123,114,1,170,237,67,99,234,219,144,215,4,18,236,169,215,251,223,213,178,113,148,52,75,191,239,242,96,125,81,77,29,40,219,89,162,35,20,198,227,120,31,248,154,52,64,85,194,230,91,22,142,6,104,173,3,77,147,26,164,111,254,42,84,1,204,72,61,140,96,5,191,5,174,85,179,228,84,8,175,2,58,99,107,14,98,44,13,255,46,67,245,94,70,125,53,90,241,209,237,160,203,225,219,3,37,181,152,21,246,12,25,243,249,20,173,224,251,164,130,119,149,151,188,64,118,134,136,243,192,253,26,236,17,244,3,6,161,56,241,241,227,58,192,119,65,2,85,69,101,194,147,105,35,212,64,201,135,43,129,87,131,254,24,153,225,6,226,250,93,242,223,246,161,232,38,235,82,149,186,208,131,255,224,119,239,223,128,8,91,254,167,223,255,193,54,146,56,131,228,158,254,240,20,219,226,181,174,62,91,97,110,120,231,111,181,135,34,0,55,131,15,194,73,33,218,198,76,123,70,176,25,60,252,123,173,180,121,252,31,255,220,98,1,198,120,0,43,164,153,155,68,70,100,1,68,194,33,145,96,191,16,42,212,112,86,184,139,195,63,221,241,194,72,146,175,1,192,89,134,143,188,80,73,161,214,37,255,238,53,252,5,209,205,66,96,119,255,3,135,207,225,15,193,0,155,16,161,27,102,216,136,122,189,255,93,117,215,255,255,194,65,87,125,223,120,99,44,240,251,44,240,198,89,225,246,88,127,99,45,199,31,255,239,15,178,207,12,101,158,31,101,158,24,203,15,195,236,183,199,255,199,225,140,183,143,255,247,135,217,103,134,50,195,251,236,183,31,255,247,134,50,207,15,178,207,12,101,158,31,101,158,24,203,64,0,0,0,179,65,154,56,59,131,107,152,241,64,0,64,43,20,0,4,2,190,82,5,192,0,247,128,198,49,192,39,2,192,0,244,164,248,64,96,240,64,253,159,205,107,125,155,241,99,57,170,177,157,93,248,182,8,109,152,126,10,240,110,177,6,248,127,240,66,43,128,99,196,97,211,109,246,255,15,66,111,243,73,209,139,47,134,99,29,98,48,1,125,106,153,161,178,253,244,246,249,131,47,164,243,35,56,252,184,75,220,168,36,246,188,21,127,32,59,136,136,162,27,251,125,184,80,240,104,149,63,146,34,151,230,114,9,227,27,21,177,0,74,89,242,49,208,115,240,219,227,245,103,207,211,115,8,63,2,158,55,17,226,60,71,136,241,29,97,10,194,21,132,43,8,86,16,196,120,143,17,226,60,0,0,0,168,65,154,84,12,224,221,231,129,36,38,44,176,156,92,6,185,112,11,92,114,7,113,149,193,72,145,228,141,60,127,205,254,21,63,130,15,192,7,170,117,110,189,221,229,54,108,180,121,249,13,255,246,99,133,13,192,72,185,113,236,236,105,199,251,34,10,8,193,67,254,41,138,92,65,225,119,155,218,91,49,124,72,107,130,57,27,177,97,254,203,237,159,136,39,218,16,75,110,35,42,240,11,80,206,48,189,1,147,138,63,9,220,40,66,39,69,239,255,37,151,128,21,215,218,239,174,131,83,97,234,147,152,139,131,185,72,39,128,140,212,213,233,155,255,252,61,12,164,199,246,126,48,158,247,251,17,144,94,14,242,82,182,121,212,65,248,25,224,0,0,0,178,65,154,96,87,7,15,124,167,192,18,53,31,218,6,61,141,142,205,92,19,19,230,174,168,39,229,227,2,0,10,183,113,149,63,192,33,216,167,15,46,9,194,65,237,121,45,194,101,248,20,66,226,40,92,212,161,241,238,98,34,255,252,60,83,1,63,20,186,196,19,128,31,102,153,100,123,224,20,114,141,131,206,60,51,158,194,203,247,183,231,102,23,132,143,61,32,3,45,244,60,16,178,23,173,139,117,202,43,115,77,225,30,169,193,69,83,167,122,34,225,89,115,17,17,127,252,61,113,245,236,79,23,228,40,6,169,7,167,17,57,117,35,16,11,22,133,167,87,140,121,239,247,190,205,100,221,143,232,90,43,151,59,7,215,105,70,45,255,62,105,143,190,3,53,194,24,142,34,0,0,0,179,65,154,128,71,2,57,249,75,248,177,32,81,128,135,144,161,22,7,178,4,222,253,152,69,227,177,153,255,238,253,116,5,64,33,150,0,44,80,105,154,147,173,10,20,107,95,176,88,0,45,226,145,39,130,197,244,232,73,63,111,242,226,114,1,36,194,184,8,186,247,21,130,0,32,141,65,35,67,126,2,249,243,225,57,91,87,247,132,123,173,150,190,111,183,137,39,167,152,115,248,140,4,122,245,231,190,135,220,13,90,146,206,43,92,154,19,138,155,201,89,120,3,24,115,44,221,231,129,54,141,235,85,175,136,184,70,93,226,96,129,188,33,86,192,236,175,218,229,255,204,4,108,79,17,228,8,194,47,4,57,43,99,132,137,155,13,255,26,175,110,223,191,40,142,179,1,2,6,136,0,0,0,194,65,154,160,71,6,190,44,94,96,48,227,45,145,132,143,200,39,175,41,56,2,85,90,52,141,108,37,167,137,196,159,240,168,17,247,135,244,17,9,4,251,172,84,36,195,134,252,4,198,205,227,198,31,52,64,90,255,207,196,37,124,70,0,85,146,38,50,154,206,248,37,104,141,18,45,51,251,254,239,48,220,0,33,2,237,5,20,244,140,183,91,5,92,219,29,77,26,202,43,11,118,66,98,193,45,112,187,113,59,217,57,204,38,12,191,250,208,37,196,10,224,140,49,200,67,126,11,119,45,252,62,12,4,21,94,166,225,41,245,228,13,99,252,25,6,51,241,110,246,200,126,180,86,192,210,182,41,58,202,137,128,197,160,158,202,159,239,202,154,101,163,143,119,235,65,93,245,23,174,196,84,174,39,58,72,225,245,173,138,86,250,120,27,224,0,0,0,142,65,154,192,71,2,179,114,196,98,132,121,125,240,152,59,30,12,199,225,7,6,235,232,46,155,71,19,30,2,192,210,165,133,37,172,253,26,9,190,112,167,226,60,64,232,16,174,150,108,134,173,184,93,60,158,253,173,195,145,236,39,148,176,38,110,234,96,1,153,116,166,138,242,246,182,129,241,41,94,220,78,83,30,28,103,223,55,248,255,133,68,97,150,91,216,219,47,55,253,247,30,8,53,18,250,202,8,66,192,92,17,199,124,43,114,211,2,186,97,51,85,93,236,159,198,123,35,72,180,177,177,116,126,200,56,152,190,184,251,180,13,240,0,0,0,172,65,154,224,220,10,205,33,17,115,113,223,97,66,123,88,177,225,111,143,17,0,55,155,49,17,89,92,140,34,78,190,200,12,74,147,26,139,188,129,15,254,242,50,129,239,151,37,98,248,0,94,87,77,175,86,4,116,45,66,214,162,61,99,27,127,147,179,30,1,187,116,141,230,255,21,245,24,84,64,38,125,75,248,18,249,191,7,26,233,226,111,255,215,10,151,5,155,82,0,125,189,81,183,51,124,63,248,80,108,1,0,88,95,191,99,163,209,160,43,110,72,247,117,224,13,234,183,30,225,12,101,108,49,113,47,19,244,53,91,63,55,144,108,8,201,106,103,230,21,197,173,237,108,80,140,74,109,116,189,25,148,223,242,41,93,242,27,232,217,224,104,128,0,0,0,186,65,155,0,220,10,205,175,133,43,16,34,0,192,135,54,189,189,200,125,206,235,108,6,116,18,246,85,235,38,127,189,27,75,215,183,234,97,56,5,158,35,50,80,43,183,4,246,125,151,214,254,81,24,78,163,48,232,148,215,100,136,145,24,117,201,57,76,88,36,236,211,252,223,250,174,120,32,247,14,207,44,252,107,246,136,65,98,154,160,238,150,28,53,9,120,180,183,215,137,49,164,239,81,174,107,194,61,136,134,184,36,45,62,255,2,215,154,105,101,174,27,94,121,173,20,170,97,77,29,87,255,135,207,28,44,188,199,85,207,234,188,19,155,203,146,231,55,194,203,85,99,108,96,184,2,22,18,77,214,61,49,209,100,173,232,163,216,98,25,144,1,111,18,252,197,239,129,65,249,190,234,141,243,254,6,120,0,0,0,165,65,155,32,71,2,179,245,133,11,232,173,213,8,21,9,5,57,230,2,56,148,20,189,247,242,19,241,58,207,111,193,42,162,42,252,188,149,148,184,1,115,100,52,157,237,228,4,244,208,215,234,155,123,241,56,147,97,151,191,4,32,132,167,224,71,95,159,248,250,216,192,86,56,226,153,36,244,245,126,241,155,223,253,176,101,50,156,115,63,159,10,19,31,25,60,240,230,255,58,159,193,1,122,223,2,13,152,78,0,123,221,125,87,191,159,132,150,12,0,131,136,25,6,174,87,175,4,61,98,107,3,52,165,33,248,145,176,8,235,248,251,219,7,255,228,160,196,237,151,27,94,233,195,128,22,245,179,65,168,137,184,31,224,84,128,0,0,0,239,65,155,64,71,2,179,195,60,220,0,147,92,254,223,250,216,62,99,33,14,228,126,77,255,175,224,128,94,27,218,114,225,255,127,190,19,39,181,98,70,138,120,129,130,2,16,3,199,41,63,71,214,191,3,56,206,136,250,213,83,198,58,98,184,96,29,115,161,214,92,173,70,96,99,69,192,239,175,178,206,34,43,224,32,67,250,231,243,142,172,55,182,224,48,232,52,252,227,205,128,140,205,207,200,33,89,187,48,76,28,165,237,101,55,193,71,85,133,142,116,119,13,109,175,94,118,19,156,217,80,65,86,165,63,29,228,128,22,49,10,23,179,167,53,136,20,138,201,196,45,72,31,253,225,5,106,109,183,125,246,32,191,97,202,53,135,148,199,52,249,233,48,130,120,214,63,183,159,250,48,220,51,22,39,194,24,68,223,73,255,195,226,163,166,94,109,62,105,63,139,19,201,41,189,193,50,208,165,224,3,242,155,135,70,113,109,61,242,192,67,120,35,32,215,135,159,132,188,132,2,24,161,13,160,255,225,136,16,32,0,0,0,170,65,155,96,87,2,183,102,224,89,165,0,17,238,159,109,76,41,228,55,213,34,157,128,144,176,30,127,238,76,242,192,4,44,211,223,245,173,148,192,34,20,196,204,192,34,20,196,205,189,132,242,154,4,205,221,76,92,8,107,104,15,137,74,246,226,113,230,203,24,46,229,47,80,17,49,239,70,217,128,150,102,145,246,194,31,152,75,141,125,159,126,228,147,16,242,248,68,21,226,57,97,26,210,252,252,37,249,169,76,62,148,193,57,97,50,136,206,48,99,175,124,4,143,71,225,31,36,16,251,177,96,0,83,130,104,196,70,151,193,240,47,252,64,184,9,141,180,48,1,111,37,142,101,228,71,220,215,211,192,233,66,161,228,201,119,175,15,19,192,129,0,0,0,127,65,155,128,87,2,181,112,165,100,52,0,134,39,4,108,206,82,180,11,82,210,68,0,117,69,199,175,246,80,106,169,48,12,44,99,159,243,250,42,132,133,58,177,126,6,239,41,176,2,164,166,218,221,238,128,219,76,138,115,249,158,39,10,248,67,30,222,246,41,191,222,183,216,90,132,254,69,137,7,225,16,18,121,248,76,223,10,127,195,226,120,213,56,20,37,22,243,182,20,48,127,64,27,191,22,40,234,35,104,50,11,117,184,42,34,139,71,129,143,172,139,22,51,71,224,66,128,0,0,0,157,65,155,160,87,3,87,144,208,17,25,156,21,13,152,240,46,65,222,185,111,101,9,24,120,214,205,93,143,83,63,153,77,63,21,54,26,185,64,18,47,73,157,35,31,0,65,181,83,235,121,77,193,229,106,56,17,191,210,21,210,206,194,112,249,132,224,47,141,126,113,60,115,244,200,32,20,182,254,35,18,229,202,223,172,194,81,190,23,247,223,171,210,149,125,246,42,79,104,166,43,74,175,15,249,37,49,191,225,239,18,44,80,16,194,64,40,115,240,153,189,161,242,150,9,163,244,255,26,164,252,38,104,126,177,5,194,19,60,89,147,40,233,131,190,183,78,253,224,128,249,79,4,47,92,112,33,64,0,0,0,140,65,155,192,87,6,250,195,247,155,192,36,253,125,156,248,174,226,111,32,136,22,159,86,19,46,115,234,254,64,175,59,65,199,49,141,106,115,246,92,104,57,87,166,214,75,157,238,178,147,0,46,226,68,185,89,192,111,52,103,173,86,207,19,133,12,124,1,106,159,146,89,225,6,173,108,117,137,36,6,249,13,237,45,173,240,247,132,35,36,19,243,97,11,37,21,17,97,51,91,229,254,30,41,189,243,240,145,142,170,204,216,235,226,188,212,225,212,137,153,235,172,71,172,177,252,223,226,186,248,32,18,82,135,250,112,91,114,223,129,14,0,0,0,159,65,155,224,87,2,181,102,241,179,190,240,157,100,17,0,53,90,66,59,247,166,223,191,74,214,202,4,243,68,74,176,186,185,251,70,188,197,132,204,239,96,12,34,87,141,51,8,241,229,137,230,94,247,90,202,43,130,187,110,133,74,233,183,91,46,228,156,32,99,151,193,11,67,139,31,228,48,71,173,61,53,179,127,255,146,132,159,246,255,251,16,24,142,255,235,207,188,8,0,175,52,155,229,254,18,44,252,178,249,225,51,121,74,196,95,137,240,223,155,23,252,255,193,198,18,242,4,96,4,57,106,121,146,247,230,42,175,199,85,224,156,103,10,4,205,198,245,148,55,129,47,155,241,14,166,88,255,2,36,0,0,0,124,65,154,0,87,2,181,253,44,39,228,17,0,99,95,101,95,124,67,216,204,130,141,43,87,123,46,119,95,149,49,183,176,158,82,64,153,187,169,128,13,255,162,201,251,247,189,173,160,62,37,43,219,136,190,18,23,226,120,4,121,24,189,191,225,10,216,162,85,203,163,180,113,226,77,62,251,204,191,132,64,176,8,129,164,60,94,27,247,193,128,41,207,194,47,193,36,35,136,97,193,169,119,227,127,244,2,32,4,110,35,132,176,176,64,129,60,33,106,95,133,194,48,34,64,0,0,0,170,65,154,32,87,2,181,102,240,9,203,70,151,249,167,132,201,245,247,228,17,9,249,24,9,145,5,223,207,64,183,242,130,143,200,45,58,79,124,64,36,253,148,39,251,170,191,209,227,18,187,203,192,1,187,45,249,241,178,189,47,76,77,188,95,245,249,215,175,67,133,147,4,150,165,153,54,144,11,212,55,219,251,92,188,109,236,66,123,135,246,141,53,94,255,204,126,1,30,190,218,240,43,2,64,88,13,5,147,182,105,177,236,191,47,195,37,4,158,248,212,175,96,104,194,57,36,13,194,102,201,175,83,255,127,127,172,16,135,144,128,128,73,102,191,182,104,176,82,200,67,88,194,37,90,2,247,127,224,66,125,1,58,249,252,252,35,254,92,8,176,0,0,0,190,65,154,64,103,2,181,126,35,9,214,67,64,38,226,151,163,226,153,253,214,181,179,133,156,135,203,25,15,188,243,89,107,116,253,179,75,32,89,190,0,64,50,242,11,72,190,220,37,8,102,103,169,68,232,255,111,41,184,112,201,24,19,42,62,37,214,238,87,152,95,2,107,212,201,191,241,125,198,21,25,166,122,130,3,58,50,8,207,4,99,170,38,248,123,223,194,184,11,79,84,114,11,120,224,254,63,201,20,199,39,35,230,112,195,224,251,249,159,235,192,103,186,228,221,190,196,89,110,141,168,240,222,161,163,246,76,110,253,95,123,1,205,224,8,94,41,72,80,104,4,41,170,237,249,60,122,96,100,87,198,176,63,0,71,47,200,82,230,109,195,222,38,190,239,135,248,120,66,60,177,159,95,208,4,3,215,9,159,129,98,0,0,0,129,65,154,96,103,2,189,147,9,249,4,64,7,58,225,200,74,114,18,173,80,104,244,223,213,214,206,42,216,191,207,249,151,169,149,66,119,186,202,76,0,209,69,10,120,252,220,4,245,163,62,212,187,244,222,22,12,32,28,41,13,211,48,148,197,29,87,123,230,135,155,5,255,0,225,226,117,155,252,3,0,225,89,236,125,150,215,251,243,127,128,64,60,42,83,224,203,71,66,203,124,33,91,20,48,139,232,126,5,187,191,203,248,128,247,144,104,18,158,105,103,255,129,227,66,56,76,252,11,16,0,0,0,179,65,154,128,87,2,111,150,120,110,37,226,185,184,34,184,122,108,190,108,1,213,136,177,184,0,53,73,102,81,224,0,0,64,27,49,127,58,174,167,22,103,255,12,154,8,175,65,170,246,57,213,98,4,64,24,22,245,79,215,60,230,48,98,109,58,34,45,130,196,158,52,1,141,84,33,152,148,70,229,184,63,178,144,255,249,245,121,184,4,196,245,118,192,86,158,137,95,30,255,118,178,155,15,119,97,253,108,217,13,108,30,32,110,65,97,236,106,211,143,126,171,246,1,128,144,94,2,34,218,111,207,204,171,173,71,254,18,10,95,153,157,159,81,213,155,85,135,235,225,63,86,248,251,200,122,183,125,211,175,114,117,123,255,189,136,27,20,17,223,253,71,252,249,252,71,136,224,107,128,0,0,0,150,65,154,160,103,2,183,155,192,2,30,233,246,202,101,232,81,96,6,23,171,227,35,60,168,192,97,173,242,16,45,158,50,28,25,69,55,252,120,20,132,8,21,142,205,13,44,241,86,124,69,1,255,195,79,4,64,146,131,55,19,127,169,215,80,133,72,63,43,248,89,211,27,228,54,241,241,101,130,206,228,63,2,91,10,81,169,196,61,158,7,173,221,243,198,221,183,176,157,148,210,216,17,231,44,238,103,142,146,55,59,207,216,6,195,155,235,255,195,229,218,230,58,172,15,235,225,226,119,8,61,26,71,76,46,38,208,241,239,35,91,243,191,114,81,252,71,136,224,107,128,0,0,0,146,65,154,192,103,2,57,252,191,38,8,65,176,49,2,48,71,128,50,95,3,134,120,190,0,91,154,22,208,170,62,168,55,210,243,213,120,173,101,255,203,224,10,170,198,65,55,94,245,36,144,195,16,66,10,165,152,197,255,254,30,132,235,254,61,255,153,78,121,252,206,112,248,140,124,100,252,18,70,83,144,208,9,201,68,206,84,41,74,131,239,61,246,246,85,140,238,176,173,10,116,47,91,59,221,229,38,2,81,207,150,254,204,37,119,134,126,234,176,36,0,152,239,204,103,56,31,207,195,217,225,132,43,125,131,113,21,95,105,244,127,63,136,239,168,26,32,0,0,0,165,65,154,224,87,6,190,83,224,1,218,159,240,88,229,55,216,34,250,115,161,158,176,131,240,70,8,2,70,128,22,152,174,215,248,182,0,77,199,110,96,224,128,230,176,137,193,41,126,176,110,4,40,32,30,48,191,26,152,174,191,141,174,131,222,28,29,32,161,127,132,252,129,120,23,78,136,254,95,243,5,62,154,124,16,7,57,56,207,33,96,75,77,245,92,66,197,106,62,205,236,165,69,11,74,150,124,183,154,214,188,220,5,187,93,128,70,9,201,147,55,217,111,41,178,140,60,158,219,165,228,240,134,139,225,188,119,199,191,82,77,130,242,147,251,250,255,184,63,211,41,199,91,7,80,239,155,27,191,103,123,1,163,240,54,192,0,0,0,145,65,155,0,87,6,135,247,228,139,46,224,3,233,94,198,82,128,75,0,17,102,68,109,63,35,64,31,4,50,0,0,3,0,128,116,240,0,16,14,252,161,15,132,37,30,97,62,2,193,135,64,2,58,253,189,155,109,134,30,184,10,8,47,18,8,153,249,190,126,49,222,87,231,204,127,52,41,244,244,195,218,120,203,201,128,1,251,68,190,119,228,51,49,204,76,136,172,67,105,158,63,161,211,215,205,12,109,182,175,148,152,3,218,62,72,2,207,88,15,200,156,60,73,54,59,76,33,74,224,206,188,122,19,127,118,250,63,5,91,152,92,119,220,8,112,0,0,0,4,104,203,140,178]; | ||
3 | var mdat_n = [0,0,0,1,9,240,0,0,0,1,39,66,224,11,169,24,96,157,128,53,6,1,6,182,194,181,239,124,4,0,0,0,1,40,222,9,136,0,0,1,6,0,7,130,220,108,0,0,3,0,64,128,0,0,1,6,5,17,3,135,244,78,205,10,75,220,161,148,58,195,212,155,23,31,0,128,0,0,1,37,184,32,32,71,255,252,224,158,40,0,8,52,252,194,144,0,68,56,189,149,17,89,122,21,191,230,42,39,1,133,76,0,53,152,211,185,10,132,55,104,162,22,43,163,142,38,18,196,102,59,65,74,226,190,208,213,237,25,146,204,90,161,62,215,80,111,128,55,135,230,8,121,77,214,206,207,162,149,193,123,30,214,4,100,57,114,69,74,166,255,64,151,71,62,159,63,175,64,209,25,173,108,182,76,249,73,96,72,189,208,149,255,180,63,0,6,44,4,169,148,195,173,86,197,183,252,172,95,10,96,218,157,30,233,229,56,3,243,81,162,247,125,2,91,62,31,166,144,125,248,6,189,54,202,191,175,149,253,254,69,201,6,242,191,157,0,78,99,162,46,102,83,248,202,170,61,164,133,153,136,252,210,227,189,37,106,35,223,251,9,21,154,173,14,243,219,200,230,86,85,69,88,79,191,185,157,94,254,117,93,16,159,255,131,172,87,249,255,90,41,40,190,199,123,251,63,177,53,15,132,104,48,121,255,52,230,216,204,24,255,190,220,126,122,9,122,136,173,240,217,212,143,95,81,12,21,237,81,212,254,129,33,12,115,188,107,24,0,212,129,64,0,116,16,6,106,28,153,228,108,234,78,221,107,215,184,13,2,234,54,47,50,248,25,21,191,231,227,85,82,146,248,127,242,120,178,246,24,127,134,0,9,25,144,145,92,170,196,71,116,128,5,206,6,60,145,214,67,128,123,161,13,134,0,47,148,223,47,182,248,23,70,6,122,16,32,222,220,223,224,112,136,6,13,50,27,29,181,238,135,32,53,113,181,198,98,225,177,218,190,120,115,199,137,230,2,248,5,87,13,131,196,198,154,224,83,252,35,210,4,100,62,9,121,153,6,120,0,198,236,34,0,147,198,135,97,75,232,16,248,16,88,137,10,142,153,239,181,81,224,58,70,147,62,161,245,214,167,23,192,149,158,28,210,243,22,34,196,90,103,13,128,30,6,68,124,143,159,240,167,224,224,186,211,30,50,29,253,199,152,189,120,127,255,236,1,16,247,180,126,143,203,108,145,240,233,255,251,231,220,127,255,239,252,119,255,248,19,111,247,255,34,106,228,127,161,213,13,25,226,178,97,146,75,32,162,100,5,17,133,87,235,79,165,24,31,251,173,214,112,92,244,153,139,219,82,160,17,98,154,213,24,128,5,36,69,47,2,252,73,64,3,209,153,18,219,117,191,255,255,24,19,66,73,252,45,169,255,90,29,228,106,195,93,100,22,255,216,28,31,247,198,170,94,252,58,156,191,255,167,225,50,57,71,192,68,221,199,189,216,174,96,39,166,122,83,22,96,176,90,210,146,124,226,252,241,135,239,0,89,210,15,53,40,62,123,133,183,80,179,141,21,149,22,207,124,163,59,199,249,215,130,66,151,178,203,137,95,253,239,8,79,248,67,238,189,255,255,254,142,52,111,174,235,98,0,64,42,240,168,4,213,244,158,236,188,76,49,215,60,156,25,9,172,255,243,240,255,1,145,94,141,66,36,16,207,252,127,247,64,160,53,0,87,75,68,57,78,104,159,255,3,198,169,236,145,108,71,5,157,135,30,175,120,241,212,153,146,39,255,252,86,21,39,0,8,130,249,41,158,147,221,6,193,72,150,191,1,180,142,200,200,204,207,127,249,252,43,192,70,217,19,26,49,123,98,63,61,33,58,137,94,149,78,223,255,97,142,181,29,128,2,109,52,147,105,38,155,255,240,0,157,6,153,28,172,194,44,165,225,254,148,47,64,225,66,3,39,216,71,212,142,127,235,107,121,187,76,6,112,124,48,134,47,196,32,255,94,192,219,45,184,56,174,16,134,247,185,16,14,159,240,60,213,200,24,206,236,247,255,138,35,2,35,11,238,253,136,171,199,202,23,36,4,44,195,77,110,233,110,157,87,90,200,193,162,169,52,123,238,246,255,253,164,255,96,215,24,56,15,216,150,192,140,163,136,247,196,183,192,32,24,38,216,170,239,252,245,82,48,193,162,100,13,231,243,114,13,183,204,29,141,239,153,230,177,184,133,98,41,197,131,107,121,253,251,77,3,242,94,33,12,225,183,223,245,40,176,0,16,27,9,113,67,69,221,149,181,240,4,45,235,1,168,154,245,225,66,142,184,82,186,152,62,177,123,160,227,101,254,207,236,183,176,69,8,16,71,17,239,111,184,192,25,64,2,77,173,177,32,111,176,161,212,130,17,35,243,26,128,3,37,2,8,150,93,131,15,93,91,8,1,152,0,37,204,75,211,251,30,59,56,195,37,8,11,110,217,65,167,13,194,253,4,54,77,103,7,12,34,111,151,146,33,40,255,31,255,131,206,215,246,125,102,190,150,251,255,132,203,132,80,95,227,129,70,63,3,143,255,125,255,140,12,242,15,255,84,188,40,128,193,174,167,192,0,0,1,37,6,238,8,8,47,248,125,40,145,81,64,0,65,14,53,78,1,71,30,0,2,0,58,160,0,8,6,128,3,23,128,97,29,160,108,27,165,103,247,207,5,5,115,240,149,118,37,34,207,140,214,63,220,3,139,197,101,81,157,183,223,255,248,120,75,255,12,112,240,197,8,135,12,11,122,254,193,25,13,10,36,105,100,171,95,231,238,91,137,154,134,168,134,23,105,193,52,81,118,146,204,110,111,251,137,143,194,168,153,176,86,175,173,245,25,231,160,23,55,244,31,240,248,135,241,46,218,217,27,44,134,0,2,0,14,51,0,3,0,211,33,34,170,235,147,149,201,138,166,67,115,239,113,22,96,105,193,119,156,68,95,112,225,223,236,189,48,92,164,156,33,47,255,41,162,154,247,255,73,252,248,124,158,30,0,217,32,244,181,248,243,4,102,153,52,235,156,233,249,159,60,90,248,19,229,218,176,187,42,252,240,22,97,103,150,100,123,139,191,237,83,142,187,249,127,105,180,150,95,65,255,254,156,102,3,136,71,34,103,192,5,228,39,23,241,202,106,213,124,4,249,9,197,216,162,24,246,202,128,11,200,226,144,29,49,201,1,88,200,193,225,144,214,233,187,92,208,234,41,226,81,144,214,205,0,192,48,12,3,224,235,103,45,240,216,0,42,215,31,63,191,52,34,170,33,89,153,148,192,229,18,249,240,196,223,71,223,80,99,76,81,110,184,137,190,193,0,0,128,25,0,0,234,8,0,32,130,184,90,64,26,9,237,46,98,19,136,83,229,52,240,18,59,6,175,211,100,142,113,122,255,255,4,24,153,233,37,136,113,127,119,192,134,9,28,226,169,206,45,183,124,16,88,70,0,232,57,1,80,50,231,152,41,18,66,70,67,253,231,176,244,255,252,71,17,133,120,9,236,136,103,120,129,24,75,127,171,132,162,204,65,74,123,246,252,225,61,103,30,58,74,97,47,159,26,17,85,156,92,237,33,143,108,235,212,56,4,0,35,16,26,153,56,13,165,101,97,93,248,13,61,89,99,209,63,117,159,231,224,45,62,52,34,186,20,140,230,84,47,253,230,66,41,102,81,57,121,49,152,244,246,72,113,167,67,205,119,45,254,6,149,131,249,82,249,83,225,94,128,24,140,71,171,134,214,218,27,72,105,26,243,123,180,35,136,18,213,16,141,102,249,228,67,33,239,194,122,245,11,164,121,33,145,213,200,71,41,204,58,21,192,56,108,142,241,140,225,217,230,128,79,129,18,14,120,173,43,173,15,132,39,75,0,48,211,181,181,158,242,134,78,238,100,126,42,154,164,121,70,42,234,36,108,146,167,177,93,175,5,165,196,36,111,242,90,250,127,206,145,152,43,224,234,107,25,135,9,212,206,195,160,150,50,253,221,200,139,47,81,188,0,27,235,96,120,8,209,82,89,23,222,191,249,165,255,31,194,175,4,12,37,125,154,69,56,185,155,224,20,118,13,95,229,203,148,226,211,46,195,54,114,142,166,48,148,253,240,19,241,9,69,252,41,79,127,191,208,135,201,130,41,217,169,181,137,149,119,101,239,169,231,224,72,253,4,119,79,95,224,160,191,53,147,248,116,232,12,161,160,10,236,189,49,145,31,128,158,250,23,177,206,24,54,167,102,71,240,221,120,78,126,190,168,224,247,149,43,114,126,255,102,129,228,35,241,40,70,125,95,113,128,247,31,63,172,159,25,134,9,214,254,255,33,131,226,136,205,81,247,254,213,153,76,18,226,157,227,7,195,106,251,202,35,148,74,192,171,179,208,128,247,222,74,226,8,221,142,159,158,181,47,4,225,118,97,14,255,186,41,8,227,200,23,63,152,64,106,14,185,168,58,235,51,235,1,218,9,175,251,235,35,46,128,103,178,132,55,191,234,1,90,128,175,154,151,219,229,46,224,126,221,221,235,160,56,143,255,9,23,74,17,229,163,195,10,16,155,255,190,210,132,121,121,194,188,193,13,67,247,193,243,170,7,63,180,123,25,22,9,95,255,128,7,248,88,2,8,37,93,216,127,17,193,255,248,75,134,59,234,145,196,16,243,212,63,246,222,246,96,42,8,80,166,171,251,238,67,211,109,155,0,42,2,49,40,41,143,103,147,48,0,16,0,192,8,158,167,15,9,187,191,252,144,126,184,176,34,45,16,135,117,91,99,101,32,16,67,206,235,223,252,67,7,255,225,47,91,126,192,184,67,36,99,85,109,127,0,5,0,98,167,138,98,15,236,247,128,0,128,4,0,100,101,42,125,44,253,152,0,16,6,245,202,81,254,95,252,87,80,255,132,180,123,152,0,8,1,1,118,70,63,191,158,211,220,240,0,32,228,182,69,72,186,255,211,249,160,0,135,52,153,21,131,43,141,23,100,2,41,193,100,98,14,188,62,1,0,255,194,91,132,136,35,184,20,87,8,155,159,100,2,213,138,112,44,138,96,143,119,255,201,239,232,41,36,192,0,64,5,128,61,221,237,42,42,79,0,1,0,16,7,159,181,253,191,252,0,15,178,48,45,56,180,219,240,108,3,154,134,126,69,211,191,193,231,160,223,23,13,246,191,241,29,27,255,132,184,164,203,0,6,224,23,4,90,15,252,245,146,33,160,21,99,240,198,160,127,207,127,56,68,3,106,174,14,152,112,28,191,247,98,128,121,86,40,20,78,176,139,127,223,252]; | ||
4 | var box = new Uint8Array( moof.concat(mdat) ); |
test/muxer/js/mp4-inspector.js
deleted
100644 → 0
1 | (function(window, videojs) { | ||
2 | 'use strict'; | ||
3 | |||
4 | var | ||
5 | DataView = window.DataView, | ||
6 | /** | ||
7 | * Returns the string representation of an ASCII encoded four byte buffer. | ||
8 | * @param buffer {Uint8Array} a four-byte buffer to translate | ||
9 | * @return {string} the corresponding string | ||
10 | */ | ||
11 | parseType = function(buffer) { | ||
12 | var result = ''; | ||
13 | result += String.fromCharCode(buffer[0]); | ||
14 | result += String.fromCharCode(buffer[1]); | ||
15 | result += String.fromCharCode(buffer[2]); | ||
16 | result += String.fromCharCode(buffer[3]); | ||
17 | return result; | ||
18 | }, | ||
19 | parseMp4Date = function(seconds) { | ||
20 | return new Date(seconds * 1000 - 2082844800000); | ||
21 | }, | ||
22 | parseSampleFlags = function(flags) { | ||
23 | return { | ||
24 | isLeading: (flags[0] & 0x0c) >>> 2, | ||
25 | dependsOn: flags[0] & 0x03, | ||
26 | isDependedOn: (flags[1] & 0xc0) >>> 6, | ||
27 | hasRedundancy: (flags[1] & 0x30) >>> 4, | ||
28 | paddingValue: (flags[1] & 0x0e) >>> 1, | ||
29 | isNonSyncSample: flags[1] & 0x01, | ||
30 | degradationPriority: (flags[2] << 8) | flags[3] | ||
31 | }; | ||
32 | }, | ||
33 | nalParse = function(avcStream) { | ||
34 | var | ||
35 | avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), | ||
36 | result = [], | ||
37 | i, | ||
38 | length; | ||
39 | for (i = 0; i < avcStream.length; i += length) { | ||
40 | length = avcView.getUint32(i); | ||
41 | i += 4; | ||
42 | |||
43 | // bail if this doesn't appear to be an H264 stream | ||
44 | if (length <= 0) { | ||
45 | return; | ||
46 | } | ||
47 | |||
48 | switch(avcStream[i] & 0x1F) { | ||
49 | case 0x01: | ||
50 | result.push('slice_layer_without_partitioning_rbsp'); | ||
51 | break; | ||
52 | case 0x05: | ||
53 | result.push('slice_layer_without_partitioning_rbsp_idr'); | ||
54 | break; | ||
55 | case 0x06: | ||
56 | result.push('sei_rbsp'); | ||
57 | break; | ||
58 | case 0x07: | ||
59 | result.push('seq_parameter_set_rbsp'); | ||
60 | break; | ||
61 | case 0x08: | ||
62 | result.push('pic_parameter_set_rbsp'); | ||
63 | break; | ||
64 | case 0x09: | ||
65 | result.push('access_unit_delimiter_rbsp'); | ||
66 | break; | ||
67 | default: | ||
68 | result.push(avcStream[i] & 0x1F); | ||
69 | break; | ||
70 | } | ||
71 | } | ||
72 | return result; | ||
73 | }, | ||
74 | |||
75 | // registry of handlers for individual mp4 box types | ||
76 | parse = { | ||
77 | // codingname, not a first-class box type. stsd entries share the | ||
78 | // same format as real boxes so the parsing infrastructure can be | ||
79 | // shared | ||
80 | avc1: function(data) { | ||
81 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
82 | return { | ||
83 | dataReferenceIndex: view.getUint16(6), | ||
84 | width: view.getUint16(24), | ||
85 | height: view.getUint16(26), | ||
86 | horizresolution: view.getUint16(28) + (view.getUint16(30) / 16), | ||
87 | vertresolution: view.getUint16(32) + (view.getUint16(34) / 16), | ||
88 | frameCount: view.getUint16(40), | ||
89 | depth: view.getUint16(74), | ||
90 | config: videojs.inspectMp4(data.subarray(78, data.byteLength)) | ||
91 | }; | ||
92 | }, | ||
93 | avcC: function(data) { | ||
94 | var | ||
95 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
96 | result = { | ||
97 | configurationVersion: data[0], | ||
98 | avcProfileIndication: data[1], | ||
99 | profileCompatibility: data[2], | ||
100 | avcLevelIndication: data[3], | ||
101 | lengthSizeMinusOne: data[4] & 0x03, | ||
102 | sps: [], | ||
103 | pps: [] | ||
104 | }, | ||
105 | numOfSequenceParameterSets = data[5] & 0x1f, | ||
106 | numOfPictureParameterSets, | ||
107 | nalSize, | ||
108 | offset, | ||
109 | i; | ||
110 | |||
111 | // iterate past any SPSs | ||
112 | offset = 6; | ||
113 | for (i = 0; i < numOfSequenceParameterSets; i++) { | ||
114 | nalSize = view.getUint16(offset); | ||
115 | offset += 2; | ||
116 | result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); | ||
117 | offset += nalSize; | ||
118 | } | ||
119 | // iterate past any PPSs | ||
120 | numOfPictureParameterSets = data[offset]; | ||
121 | offset++; | ||
122 | for (i = 0; i < numOfPictureParameterSets; i++) { | ||
123 | nalSize = view.getUint16(offset); | ||
124 | offset += 2; | ||
125 | result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); | ||
126 | offset += nalSize; | ||
127 | } | ||
128 | return result; | ||
129 | }, | ||
130 | btrt: function(data) { | ||
131 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
132 | return { | ||
133 | bufferSizeDB: view.getUint32(0), | ||
134 | maxBitrate: view.getUint32(4), | ||
135 | avgBitrate: view.getUint32(8) | ||
136 | }; | ||
137 | }, | ||
138 | esds: function(data) { | ||
139 | return { | ||
140 | version: data[0], | ||
141 | flags: new Uint8Array(data.subarray(1, 4)), | ||
142 | esId: (data[6] << 8) | data[7], | ||
143 | streamPriority: data[8] & 0x1f, | ||
144 | decoderConfig: { | ||
145 | objectProfileIndication: data[11], | ||
146 | streamType: (data[12] >>> 2) & 0x3f, | ||
147 | bufferSize: (data[13] << 16) | (data[14] << 8) | data[15], | ||
148 | maxBitrate: (data[16] << 24) | | ||
149 | (data[17] << 16) | | ||
150 | (data[18] << 8) | | ||
151 | data[19], | ||
152 | avgBitrate: (data[20] << 24) | | ||
153 | (data[21] << 16) | | ||
154 | (data[22] << 8) | | ||
155 | data[23] | ||
156 | } | ||
157 | }; | ||
158 | }, | ||
159 | ftyp: function(data) { | ||
160 | var | ||
161 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
162 | result = { | ||
163 | majorBrand: parseType(data.subarray(0, 4)), | ||
164 | minorVersion: view.getUint32(4), | ||
165 | compatibleBrands: [] | ||
166 | }, | ||
167 | i = 8; | ||
168 | while (i < data.byteLength) { | ||
169 | result.compatibleBrands.push(parseType(data.subarray(i, i + 4))); | ||
170 | i += 4; | ||
171 | } | ||
172 | return result; | ||
173 | }, | ||
174 | dinf: function(data) { | ||
175 | return { | ||
176 | boxes: videojs.inspectMp4(data) | ||
177 | }; | ||
178 | }, | ||
179 | dref: function(data) { | ||
180 | return { | ||
181 | version: data[0], | ||
182 | flags: new Uint8Array(data.subarray(1, 4)), | ||
183 | dataReferences: videojs.inspectMp4(data.subarray(8)) | ||
184 | }; | ||
185 | }, | ||
186 | hdlr: function(data) { | ||
187 | var | ||
188 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
189 | language, | ||
190 | result = { | ||
191 | version: view.getUint8(0), | ||
192 | flags: new Uint8Array(data.subarray(1, 4)), | ||
193 | handlerType: parseType(data.subarray(8, 12)), | ||
194 | name: '' | ||
195 | }, | ||
196 | i = 8; | ||
197 | |||
198 | // parse out the name field | ||
199 | for (i = 24; i < data.byteLength; i++) { | ||
200 | if (data[i] === 0x00) { | ||
201 | // the name field is null-terminated | ||
202 | i++; | ||
203 | break; | ||
204 | } | ||
205 | result.name += String.fromCharCode(data[i]); | ||
206 | } | ||
207 | // decode UTF-8 to javascript's internal representation | ||
208 | // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html | ||
209 | result.name = window.decodeURIComponent(window.escape(result.name)); | ||
210 | |||
211 | return result; | ||
212 | }, | ||
213 | mdat: function(data) { | ||
214 | return { | ||
215 | byteLength: data.byteLength, | ||
216 | nals: nalParse(data) | ||
217 | }; | ||
218 | }, | ||
219 | mdhd: function(data) { | ||
220 | var | ||
221 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
222 | i = 4, | ||
223 | language, | ||
224 | result = { | ||
225 | version: view.getUint8(0), | ||
226 | flags: new Uint8Array(data.subarray(1, 4)), | ||
227 | language: '' | ||
228 | }; | ||
229 | if (result.version === 1) { | ||
230 | i += 4; | ||
231 | result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes | ||
232 | i += 8; | ||
233 | result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes | ||
234 | i += 4; | ||
235 | result.timescale = view.getUint32(i); | ||
236 | i += 8; | ||
237 | result.duration = view.getUint32(i); // truncating top 4 bytes | ||
238 | } else { | ||
239 | result.creationTime = parseMp4Date(view.getUint32(i)); | ||
240 | i += 4; | ||
241 | result.modificationTime = parseMp4Date(view.getUint32(i)); | ||
242 | i += 4; | ||
243 | result.timescale = view.getUint32(i); | ||
244 | i += 4; | ||
245 | result.duration = view.getUint32(i); | ||
246 | } | ||
247 | i += 4; | ||
248 | // language is stored as an ISO-639-2/T code in an array of three 5-bit fields | ||
249 | // each field is the packed difference between its ASCII value and 0x60 | ||
250 | language = view.getUint16(i); | ||
251 | result.language += String.fromCharCode((language >> 10) + 0x60); | ||
252 | result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60); | ||
253 | result.language += String.fromCharCode((language & 0x1f) + 0x60); | ||
254 | |||
255 | return result; | ||
256 | }, | ||
257 | mdia: function(data) { | ||
258 | return { | ||
259 | boxes: videojs.inspectMp4(data) | ||
260 | }; | ||
261 | }, | ||
262 | mfhd: function(data) { | ||
263 | return { | ||
264 | version: data[0], | ||
265 | flags: new Uint8Array(data.subarray(1, 4)), | ||
266 | sequenceNumber: (data[4] << 24) | | ||
267 | (data[5] << 16) | | ||
268 | (data[6] << 8) | | ||
269 | (data[7]) | ||
270 | }; | ||
271 | }, | ||
272 | minf: function(data) { | ||
273 | return { | ||
274 | boxes: videojs.inspectMp4(data) | ||
275 | }; | ||
276 | }, | ||
277 | // codingname, not a first-class box type. stsd entries share the | ||
278 | // same format as real boxes so the parsing infrastructure can be | ||
279 | // shared | ||
280 | mp4a: function(data) { | ||
281 | var | ||
282 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
283 | result = { | ||
284 | // 6 bytes reserved | ||
285 | dataReferenceIndex: view.getUint16(6), | ||
286 | // 4 + 4 bytes reserved | ||
287 | channelcount: view.getUint16(16), | ||
288 | samplesize: view.getUint16(18), | ||
289 | // 2 bytes pre_defined | ||
290 | // 2 bytes reserved | ||
291 | samplerate: view.getUint16(24) + (view.getUint16(26) / 65536) | ||
292 | }; | ||
293 | |||
294 | // if there are more bytes to process, assume this is an ISO/IEC | ||
295 | // 14496-14 MP4AudioSampleEntry and parse the ESDBox | ||
296 | if (data.byteLength > 28) { | ||
297 | result.streamDescriptor = videojs.inspectMp4(data.subarray(28))[0]; | ||
298 | } | ||
299 | return result; | ||
300 | }, | ||
301 | moof: function(data) { | ||
302 | return { | ||
303 | boxes: videojs.inspectMp4(data) | ||
304 | }; | ||
305 | }, | ||
306 | moov: function(data) { | ||
307 | return { | ||
308 | boxes: videojs.inspectMp4(data) | ||
309 | }; | ||
310 | }, | ||
311 | mvex: function(data) { | ||
312 | return { | ||
313 | boxes: videojs.inspectMp4(data) | ||
314 | }; | ||
315 | }, | ||
316 | mvhd: function(data) { | ||
317 | var | ||
318 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
319 | i = 4, | ||
320 | result = { | ||
321 | version: view.getUint8(0), | ||
322 | flags: new Uint8Array(data.subarray(1, 4)) | ||
323 | }; | ||
324 | |||
325 | if (result.version === 1) { | ||
326 | i += 4; | ||
327 | result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes | ||
328 | i += 8; | ||
329 | result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes | ||
330 | i += 4; | ||
331 | result.timescale = view.getUint32(i); | ||
332 | i += 8 | ||
333 | result.duration = view.getUint32(i); // truncating top 4 bytes | ||
334 | } else { | ||
335 | result.creationTime = parseMp4Date(view.getUint32(i)); | ||
336 | i += 4; | ||
337 | result.modificationTime = parseMp4Date(view.getUint32(i)); | ||
338 | i += 4; | ||
339 | result.timescale = view.getUint32(i); | ||
340 | i += 4; | ||
341 | result.duration = view.getUint32(i); | ||
342 | } | ||
343 | i += 4; | ||
344 | |||
345 | // convert fixed-point, base 16 back to a number | ||
346 | result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16); | ||
347 | i += 4; | ||
348 | result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8); | ||
349 | i += 2; | ||
350 | i += 2; | ||
351 | i += 2 * 4; | ||
352 | result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4))); | ||
353 | i += 9 * 4; | ||
354 | i += 6 * 4; | ||
355 | result.nextTrackId = view.getUint32(i); | ||
356 | return result; | ||
357 | }, | ||
358 | pdin: function(data) { | ||
359 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
360 | return { | ||
361 | version: view.getUint8(0), | ||
362 | flags: new Uint8Array(data.subarray(1, 4)), | ||
363 | rate: view.getUint32(4), | ||
364 | initialDelay: view.getUint32(8) | ||
365 | }; | ||
366 | }, | ||
367 | sdtp: function(data) { | ||
368 | var | ||
369 | result = { | ||
370 | version: data[0], | ||
371 | flags: new Uint8Array(data.subarray(1, 4)), | ||
372 | samples: [] | ||
373 | }, i; | ||
374 | |||
375 | for (i = 4; i < data.byteLength; i++) { | ||
376 | result.samples.push({ | ||
377 | dependsOn: (data[i] & 0x30) >> 4, | ||
378 | isDependedOn: (data[i] & 0x0c) >> 2, | ||
379 | hasRedundancy: data[i] & 0x03 | ||
380 | }); | ||
381 | } | ||
382 | return result; | ||
383 | }, | ||
384 | sidx: function(data) { | ||
385 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
386 | result = { | ||
387 | version: data[0], | ||
388 | flags: new Uint8Array(data.subarray(1, 4)), | ||
389 | references: [], | ||
390 | referenceId: view.getUint32(4), | ||
391 | timescale: view.getUint32(8), | ||
392 | earliestPresentationTime: view.getUint32(12), | ||
393 | firstOffset: view.getUint32(16) | ||
394 | }, | ||
395 | referenceCount = view.getUint16(22), | ||
396 | i; | ||
397 | |||
398 | for (i = 24; referenceCount; i += 12, referenceCount-- ) { | ||
399 | result.references.push({ | ||
400 | referenceType: (data[i] & 0x80) >>> 7, | ||
401 | referencedSize: view.getUint32(i) & 0x7FFFFFFF, | ||
402 | subsegmentDuration: view.getUint32(i + 4), | ||
403 | startsWithSap: !!(data[i + 8] & 0x80), | ||
404 | sapType: (data[i + 8] & 0x70) >>> 4, | ||
405 | sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF | ||
406 | }); | ||
407 | } | ||
408 | |||
409 | return result; | ||
410 | }, | ||
411 | smhd: function(data) { | ||
412 | return { | ||
413 | version: data[0], | ||
414 | flags: new Uint8Array(data.subarray(1, 4)), | ||
415 | balance: data[4] + (data[5] / 256) | ||
416 | }; | ||
417 | }, | ||
418 | stbl: function(data) { | ||
419 | return { | ||
420 | boxes: videojs.inspectMp4(data) | ||
421 | }; | ||
422 | }, | ||
423 | stco: function(data) { | ||
424 | var | ||
425 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
426 | result = { | ||
427 | version: data[0], | ||
428 | flags: new Uint8Array(data.subarray(1, 4)), | ||
429 | chunkOffsets: [] | ||
430 | }, | ||
431 | entryCount = view.getUint32(4), | ||
432 | i; | ||
433 | for (i = 8; entryCount; i += 4, entryCount--) { | ||
434 | result.chunkOffsets.push(view.getUint32(i)); | ||
435 | } | ||
436 | return result; | ||
437 | }, | ||
438 | stsc: function(data) { | ||
439 | var | ||
440 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
441 | entryCount = view.getUint32(4), | ||
442 | result = { | ||
443 | version: data[0], | ||
444 | flags: new Uint8Array(data.subarray(1, 4)), | ||
445 | sampleToChunks: [] | ||
446 | }, | ||
447 | i; | ||
448 | for (i = 8; entryCount; i += 12, entryCount--) { | ||
449 | result.sampleToChunks.push({ | ||
450 | firstChunk: view.getUint32(i), | ||
451 | samplesPerChunk: view.getUint32(i + 4), | ||
452 | sampleDescriptionIndex: view.getUint32(i + 8) | ||
453 | }); | ||
454 | } | ||
455 | return result; | ||
456 | }, | ||
457 | stsd: function(data) { | ||
458 | return { | ||
459 | version: data[0], | ||
460 | flags: new Uint8Array(data.subarray(1, 4)), | ||
461 | sampleDescriptions: videojs.inspectMp4(data.subarray(8)) | ||
462 | }; | ||
463 | }, | ||
464 | stsz: function(data) { | ||
465 | var | ||
466 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
467 | result = { | ||
468 | version: data[0], | ||
469 | flags: new Uint8Array(data.subarray(1, 4)), | ||
470 | sampleSize: view.getUint32(4), | ||
471 | entries: [] | ||
472 | }, | ||
473 | i; | ||
474 | for (i = 12; i < data.byteLength; i += 4) { | ||
475 | result.entries.push(view.getUint32(i)); | ||
476 | } | ||
477 | return result; | ||
478 | }, | ||
479 | stts: function(data) { | ||
480 | var | ||
481 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
482 | result = { | ||
483 | version: data[0], | ||
484 | flags: new Uint8Array(data.subarray(1, 4)), | ||
485 | timeToSamples: [] | ||
486 | }, | ||
487 | entryCount = view.getUint32(4), | ||
488 | i; | ||
489 | |||
490 | for (i = 8; entryCount; i += 8, entryCount--) { | ||
491 | result.timeToSamples.push({ | ||
492 | sampleCount: view.getUint32(i), | ||
493 | sampleDelta: view.getUint32(i + 4) | ||
494 | }); | ||
495 | } | ||
496 | return result; | ||
497 | }, | ||
498 | styp: function(data) { | ||
499 | return parse.ftyp(data); | ||
500 | }, | ||
501 | tfdt: function(data) { | ||
502 | return { | ||
503 | version: data[0], | ||
504 | flags: new Uint8Array(data.subarray(1, 4)), | ||
505 | baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] | ||
506 | }; | ||
507 | }, | ||
508 | tfhd: function(data) { | ||
509 | var | ||
510 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
511 | result = { | ||
512 | version: data[0], | ||
513 | flags: new Uint8Array(data.subarray(1, 4)), | ||
514 | trackId: view.getUint32(4) | ||
515 | }, | ||
516 | baseDataOffsetPresent = result.flags[2] & 0x01, | ||
517 | sampleDescriptionIndexPresent = result.flags[2] & 0x02, | ||
518 | defaultSampleDurationPresent = result.flags[2] & 0x08, | ||
519 | defaultSampleSizePresent = result.flags[2] & 0x10, | ||
520 | defaultSampleFlagsPresent = result.flags[2] & 0x20, | ||
521 | i; | ||
522 | |||
523 | i = 8; | ||
524 | if (baseDataOffsetPresent) { | ||
525 | i += 4; // truncate top 4 bytes | ||
526 | result.baseDataOffset = view.getUint32(12); | ||
527 | i += 4; | ||
528 | } | ||
529 | if (sampleDescriptionIndexPresent) { | ||
530 | result.sampleDescriptionIndex = view.getUint32(i); | ||
531 | i += 4; | ||
532 | } | ||
533 | if (defaultSampleDurationPresent) { | ||
534 | result.defaultSampleDuration = view.getUint32(i); | ||
535 | i += 4; | ||
536 | } | ||
537 | if (defaultSampleSizePresent) { | ||
538 | result.defaultSampleSize = view.getUint32(i); | ||
539 | i += 4; | ||
540 | } | ||
541 | if (defaultSampleFlagsPresent) { | ||
542 | result.defaultSampleFlags = view.getUint32(i); | ||
543 | } | ||
544 | return result; | ||
545 | }, | ||
546 | tkhd: function(data) { | ||
547 | var | ||
548 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
549 | i = 4, | ||
550 | result = { | ||
551 | version: view.getUint8(0), | ||
552 | flags: new Uint8Array(data.subarray(1, 4)), | ||
553 | }; | ||
554 | if (result.version === 1) { | ||
555 | i += 4; | ||
556 | result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes | ||
557 | i += 8; | ||
558 | result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes | ||
559 | i += 4; | ||
560 | result.trackId = view.getUint32(i); | ||
561 | i += 4; | ||
562 | i += 8; | ||
563 | result.duration = view.getUint32(i); // truncating top 4 bytes | ||
564 | } else { | ||
565 | result.creationTime = parseMp4Date(view.getUint32(i)); | ||
566 | i += 4; | ||
567 | result.modificationTime = parseMp4Date(view.getUint32(i)); | ||
568 | i += 4; | ||
569 | result.trackId = view.getUint32(i); | ||
570 | i += 4; | ||
571 | i += 4; | ||
572 | result.duration = view.getUint32(i); | ||
573 | } | ||
574 | i += 4; | ||
575 | i += 2 * 4; | ||
576 | result.layer = view.getUint16(i); | ||
577 | i += 2; | ||
578 | result.alternateGroup = view.getUint16(i); | ||
579 | i += 2; | ||
580 | // convert fixed-point, base 16 back to a number | ||
581 | result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8); | ||
582 | i += 2; | ||
583 | i += 2; | ||
584 | result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4))); | ||
585 | i += 9 * 4; | ||
586 | result.width = view.getUint16(i) + (view.getUint16(i + 2) / 16); | ||
587 | i += 4; | ||
588 | result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16); | ||
589 | return result; | ||
590 | }, | ||
591 | traf: function(data) { | ||
592 | return { | ||
593 | boxes: videojs.inspectMp4(data) | ||
594 | }; | ||
595 | }, | ||
596 | trak: function(data) { | ||
597 | return { | ||
598 | boxes: videojs.inspectMp4(data) | ||
599 | }; | ||
600 | }, | ||
601 | trex: function(data) { | ||
602 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
603 | return { | ||
604 | version: data[0], | ||
605 | flags: new Uint8Array(data.subarray(1, 4)), | ||
606 | trackId: view.getUint32(4), | ||
607 | defaultSampleDescriptionIndex: view.getUint32(8), | ||
608 | defaultSampleDuration: view.getUint32(12), | ||
609 | defaultSampleSize: view.getUint32(16), | ||
610 | sampleDependsOn: data[20] & 0x03, | ||
611 | sampleIsDependedOn: (data[21] & 0xc0) >> 6, | ||
612 | sampleHasRedundancy: (data[21] & 0x30) >> 4, | ||
613 | samplePaddingValue: (data[21] & 0x0e) >> 1, | ||
614 | sampleIsDifferenceSample: !!(data[21] & 0x01), | ||
615 | sampleDegradationPriority: view.getUint16(22) | ||
616 | }; | ||
617 | }, | ||
618 | trun: function(data) { | ||
619 | var | ||
620 | result = { | ||
621 | version: data[0], | ||
622 | flags: new Uint8Array(data.subarray(1, 4)), | ||
623 | samples: [] | ||
624 | }, | ||
625 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
626 | dataOffsetPresent = result.flags[2] & 0x01, | ||
627 | firstSampleFlagsPresent = result.flags[2] & 0x04, | ||
628 | sampleDurationPresent = result.flags[1] & 0x01, | ||
629 | sampleSizePresent = result.flags[1] & 0x02, | ||
630 | sampleFlagsPresent = result.flags[1] & 0x04, | ||
631 | sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, | ||
632 | sampleCount = view.getUint32(4), | ||
633 | offset = 8, | ||
634 | sample; | ||
635 | |||
636 | if (dataOffsetPresent) { | ||
637 | result.dataOffset = view.getUint32(offset); | ||
638 | offset += 4; | ||
639 | } | ||
640 | if (firstSampleFlagsPresent && sampleCount) { | ||
641 | sample = { | ||
642 | flags: parseSampleFlags(data.subarray(offset, offset + 4)) | ||
643 | }; | ||
644 | offset += 4; | ||
645 | if (sampleDurationPresent) { | ||
646 | sample.duration = view.getUint32(offset); | ||
647 | offset += 4; | ||
648 | } | ||
649 | if (sampleSizePresent) { | ||
650 | sample.size = view.getUint32(offset); | ||
651 | offset += 4; | ||
652 | } | ||
653 | if (sampleCompositionTimeOffsetPresent) { | ||
654 | sample.compositionTimeOffset = view.getUint32(offset); | ||
655 | offset += 4; | ||
656 | } | ||
657 | result.samples.push(sample); | ||
658 | sampleCount--; | ||
659 | }; | ||
660 | while (sampleCount--) { | ||
661 | sample = {}; | ||
662 | if (sampleDurationPresent) { | ||
663 | sample.duration = view.getUint32(offset); | ||
664 | offset += 4; | ||
665 | } | ||
666 | if (sampleSizePresent) { | ||
667 | sample.size = view.getUint32(offset); | ||
668 | offset += 4; | ||
669 | } | ||
670 | if (sampleFlagsPresent) { | ||
671 | sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); | ||
672 | offset += 4; | ||
673 | } | ||
674 | if (sampleCompositionTimeOffsetPresent) { | ||
675 | sample.compositionTimeOffset = view.getUint32(offset); | ||
676 | offset += 4; | ||
677 | } | ||
678 | result.samples.push(sample); | ||
679 | } | ||
680 | return result; | ||
681 | }, | ||
682 | 'url ': function(data) { | ||
683 | return { | ||
684 | version: data[0], | ||
685 | flags: new Uint8Array(data.subarray(1, 4)) | ||
686 | }; | ||
687 | }, | ||
688 | vmhd: function(data) { | ||
689 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
690 | return { | ||
691 | version: data[0], | ||
692 | flags: new Uint8Array(data.subarray(1, 4)), | ||
693 | graphicsmode: view.getUint16(4), | ||
694 | opcolor: new Uint16Array([view.getUint16(6), | ||
695 | view.getUint16(8), | ||
696 | view.getUint16(10)]) | ||
697 | }; | ||
698 | } | ||
699 | }; | ||
700 | |||
701 | /** | ||
702 | * Return a javascript array of box objects parsed from an ISO base | ||
703 | * media file. | ||
704 | * @param data {Uint8Array} the binary data of the media to be inspected | ||
705 | * @return {array} a javascript array of potentially nested box objects | ||
706 | */ | ||
707 | videojs.inspectMp4 = function(data) { | ||
708 | var | ||
709 | i = 0, | ||
710 | result = [], | ||
711 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
712 | size, | ||
713 | type, | ||
714 | end, | ||
715 | box; | ||
716 | |||
717 | while (i < data.byteLength) { | ||
718 | // parse box data | ||
719 | size = view.getUint32(i), | ||
720 | type = parseType(data.subarray(i + 4, i + 8)); | ||
721 | end = size > 1 ? i + size : data.byteLength; | ||
722 | |||
723 | // parse type-specific data | ||
724 | box = (parse[type] || function(data) { | ||
725 | return { | ||
726 | data: data | ||
727 | }; | ||
728 | })(data.subarray(i + 8, end)); | ||
729 | box.size = size; | ||
730 | box.type = type; | ||
731 | |||
732 | // store this box and move to the next | ||
733 | result.push(box); | ||
734 | i = end; | ||
735 | } | ||
736 | return result; | ||
737 | }; | ||
738 | |||
739 | /** | ||
740 | * Returns a textual representation of the javascript represtentation | ||
741 | * of an MP4 file. You can use it as an alternative to | ||
742 | * JSON.stringify() to compare inspected MP4s. | ||
743 | * @param inspectedMp4 {array} the parsed array of boxes in an MP4 | ||
744 | * file | ||
745 | * @param depth {number} (optional) the number of ancestor boxes of | ||
746 | * the elements of inspectedMp4. Assumed to be zero if unspecified. | ||
747 | * @return {string} a text representation of the parsed MP4 | ||
748 | */ | ||
749 | videojs.textifyMp4 = function(inspectedMp4, depth) { | ||
750 | var indent; | ||
751 | depth = depth || 0; | ||
752 | indent = Array(depth * 2 + 1).join(' '); | ||
753 | |||
754 | // iterate over all the boxes | ||
755 | return inspectedMp4.map(function(box, index) { | ||
756 | |||
757 | // list the box type first at the current indentation level | ||
758 | return indent + box.type + '\n' + | ||
759 | |||
760 | // the type is already included and handle child boxes separately | ||
761 | Object.keys(box).filter(function(key) { | ||
762 | return key !== 'type' && key !== 'boxes'; | ||
763 | |||
764 | // output all the box properties | ||
765 | }).map(function(key) { | ||
766 | var prefix = indent + ' ' + key + ': ', | ||
767 | value = box[key]; | ||
768 | |||
769 | // print out raw bytes as hexademical | ||
770 | if (value instanceof Uint8Array || value instanceof Uint32Array) { | ||
771 | var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)) | ||
772 | .map(function(byte) { | ||
773 | return ' ' + ('00' + byte.toString(16)).slice(-2); | ||
774 | }).join('').match(/.{1,24}/g); | ||
775 | if (!bytes) { | ||
776 | return prefix + '<>'; | ||
777 | } | ||
778 | if (bytes.length === 1) { | ||
779 | return prefix + '<' + bytes.join('').slice(1) + '>'; | ||
780 | } | ||
781 | return prefix + '<\n' + bytes.map(function(line) { | ||
782 | return indent + ' ' + line; | ||
783 | }).join('\n') + '\n' + indent + ' >'; | ||
784 | } | ||
785 | |||
786 | // stringify generic objects | ||
787 | return prefix + | ||
788 | JSON.stringify(value, null, 2) | ||
789 | .split('\n').map(function(line, index) { | ||
790 | if (index === 0) { | ||
791 | return line; | ||
792 | } | ||
793 | return indent + ' ' + line; | ||
794 | }).join('\n'); | ||
795 | }).join('\n') + | ||
796 | |||
797 | // recursively textify the child boxes | ||
798 | (box.boxes ? '\n' + videojs.textifyMp4(box.boxes, depth + 1) : ''); | ||
799 | }).join('\n'); | ||
800 | }; | ||
801 | |||
802 | })(window, window.videojs); |
1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD | ||
2 | * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load | ||
3 | */ | ||
4 | ;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function p(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return r.shivMethods?n(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+l().join().replace(/\w+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(r,b.frag)}function q(a){a||(a=b);var c=m(a);return r.shivCSS&&!f&&!c.hasCSS&&(c.hasCSS=!!k(a,"article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}")),j||p(a,c),a}var c=a.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,f,g="_html5shiv",h=0,i={},j;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))}; |
test/muxer/min-m4s.js
deleted
100644 → 0
1 | // a minimal set of boxes assembled to form a valid BMFF media segment | ||
2 | var mfhd = [ | ||
3 | 0, 0, 0, 16, 109, 102, 104, 100, 0, 0, 0, 0, 0, 0, 0, 1 | ||
4 | ]; // sequence number 1 | ||
5 | var tfhd = [ | ||
6 | 0, 0, 0, 16, 116, 102, 104, 100, 0, 2, 0, 0, 0, 0, 0, 1 | ||
7 | ]; // track id 1 | ||
8 | var tfdt = [ | ||
9 | 0, 0, 0, 16, 116, 102, 100, 116, 0, 0, 0, 0, 0, 0, 0, 0 | ||
10 | ]; | ||
11 | var trun = [ | ||
12 | 0, 0, 0, 124, 116, 114, 117, 110, 0, 0, 2, 5, 0, 0, 0, 25, 0, 0, 0, | ||
13 | 196, 0, 0, 0, 0, 0, 0, 12, 219, 0, 0, 0, 183, 0, 0, 0, 172, 0, 0, 0, | ||
14 | 182, 0, 0, 0, 183, 0, 0, 0, 198, 0, 0, 0, 146, 0, 0, 0, 176, 0, 0, | ||
15 | 0, 190, 0, 0, 0, 169, 0, 0, 0, 243, 0, 0, 0, 174, 0, 0, 0, 131, 0, | ||
16 | 0, 0, 161, 0, 0, 0, 144, 0, 0, 0, 163, 0, 0, 0, 128, 0, 0, 0, 174, | ||
17 | 0, 0, 0, 194, 0, 0, 0, 133, 0, 0, 0, 183, 0, 0, 0, 154, 0, 0, 0, | ||
18 | 150, 0, 0, 0, 169, 0, 0, 0, 157 | ||
19 | ]; | ||
20 | var mdat = [ | ||
21 | 0, 0, 28, 188, 109, 100, 97, 116, 0, 0, 2, 151, 6, 5, 255, 255, 147, | ||
22 | 220, 69, 233, 189, 230, 217, 72, 183, 150, 44, 216, 32, 217, 35, 238, | ||
23 | 239, 120, 50, 54, 52, 32, 45, 32, 99, 111, 114, 101, 32, 49, 49, 56, | ||
24 | 32, 114, 50, 48, 56, 53, 32, 56, 97, 54, 50, 56, 51, 53, 32, 45, 32, | ||
25 | 72, 46, 50, 54, 52, 47, 77, 80, 69, 71, 45, 52, 32, 65, 86, 67, 32, | ||
26 | 99, 111, 100, 101, 99, 32, 45, 32, 67, 111, 112, 121, 108, 101, 102, | ||
27 | 116, 32, 50, 48, 48, 51, 45, 50, 48, 49, 49, 32, 45, 32, 104, 116, | ||
28 | 116, 112, 58, 47, 47, 119, 119, 119, 46, 118, 105, 100, 101, 111, 108, | ||
29 | 97, 110, 46, 111, 114, 103, 47, 120, 50, 54, 52, 46, 104, 116, 109, | ||
30 | 108, 32, 45, 32, 111, 112, 116, 105, 111, 110, 115, 58, 32, 99, 97, | ||
31 | 98, 97, 99, 61, 48, 32, 114, 101, 102, 61, 51, 32, 100, 101, 98, 108, | ||
32 | 111, 99, 107, 61, 49, 58, 48, 58, 48, 32, 97, 110, 97, 108, 121, 115, | ||
33 | 101, 61, 48, 120, 49, 58, 48, 120, 49, 49, 49, 32, 109, 101, 61, 104, | ||
34 | 101, 120, 32, 115, 117, 98, 109, 101, 61, 55, 32, 112, 115, 121, 61, | ||
35 | 49, 32, 112, 115, 121, 95, 114, 100, 61, 49, 46, 48, 48, 58, 48, 46, | ||
36 | 48, 48, 32, 109, 105, 120, 101, 100, 95, 114, 101, 102, 61, 49, 32, | ||
37 | 109, 101, 95, 114, 97, 110, 103, 101, 61, 49, 54, 32, 99, 104, 114, | ||
38 | 111, 109, 97, 95, 109, 101, 61, 49, 32, 116, 114, 101, 108, 108, 105, | ||
39 | 115, 61, 49, 32, 56, 120, 56, 100, 99, 116, 61, 48, 32, 99, 113, 109, | ||
40 | 61, 48, 32, 100, 101, 97, 100, 122, 111, 110, 101, 61, 50, 49, 44, 49, | ||
41 | 49, 32, 102, 97, 115, 116, 95, 112, 115, 107, 105, 112, 61, 49, 32, | ||
42 | 99, 104, 114, 111, 109, 97, 95, 113, 112, 95, 111, 102, 102, 115, 101, | ||
43 | 116, 61, 45, 50, 32, 116, 104, 114, 101, 97, 100, 115, 61, 51, 32, | ||
44 | 115, 108, 105, 99, 101, 100, 95, 116, 104, 114, 101, 97, 100, 115, 61, | ||
45 | 48, 32, 110, 114, 61, 48, 32, 100, 101, 99, 105, 109, 97, 116, 101, | ||
46 | 61, 49, 32, 105, 110, 116, 101, 114, 108, 97, 99, 101, 100, 61, 48, | ||
47 | 32, 98, 108, 117, 114, 97, 121, 95, 99, 111, 109, 112, 97, 116, 61, | ||
48 | 48, 32, 99, 111, 110, 115, 116, 114, 97, 105, 110, 101, 100, 95, 105, | ||
49 | 110, 116, 114, 97, 61, 48, 32, 98, 102, 114, 97, 109, 101, 115, 61, | ||
50 | 48, 32, 119, 101, 105, 103, 104, 116, 112, 61, 48, 32, 107, 101, 121, | ||
51 | 105, 110, 116, 61, 50, 53, 32, 107, 101, 121, 105, 110, 116, 95, 109, | ||
52 | 105, 110, 61, 49, 51, 32, 115, 99, 101, 110, 101, 99, 117, 116, 61, | ||
53 | 52, 48, 32, 105, 110, 116, 114, 97, 95, 114, 101, 102, 114, 101, 115, | ||
54 | 104, 61, 48, 32, 114, 99, 95, 108, 111, 111, 107, 97, 104, 101, 97, | ||
55 | 100, 61, 52, 48, 32, 114, 99, 61, 99, 98, 114, 32, 109, 98, 116, 114, | ||
56 | 101, 101, 61, 49, 32, 98, 105, 116, 114, 97, 116, 101, 61, 52, 56, 32, | ||
57 | 114, 97, 116, 101, 116, 111, 108, 61, 49, 46, 48, 32, 113, 99, 111, | ||
58 | 109, 112, 61, 48, 46, 54, 48, 32, 113, 112, 109, 105, 110, 61, 48, 32, | ||
59 | 113, 112, 109, 97, 120, 61, 54, 57, 32, 113, 112, 115, 116, 101, 112, | ||
60 | 61, 52, 32, 118, 98, 118, 95, 109, 97, 120, 114, 97, 116, 101, 61, 52, | ||
61 | 56, 32, 118, 98, 118, 95, 98, 117, 102, 115, 105, 122, 101, 61, 50, | ||
62 | 48, 48, 48, 32, 110, 97, 108, 95, 104, 114, 100, 61, 110, 111, 110, | ||
63 | 101, 32, 105, 112, 95, 114, 97, 116, 105, 111, 61, 49, 46, 52, 48, 32, | ||
64 | 97, 113, 61, 49, 58, 49, 46, 48, 48, 0, 128, 0, 0, 10, 60, 101, 136, | ||
65 | 132, 25, 201, 6, 34, 128, 0, 195, 76, 156, 156, 156, 156, 156, 156, | ||
66 | 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 157, 117, | ||
67 | 215, 93, 117, 215, 93, 117, 215, 93, 117, 215, 93, 117, 215, 93, 117, | ||
68 | 215, 93, 117, 215, 93, 117, 215, 95, 255, 222, 238, 80, 168, 38, 192, | ||
69 | 35, 16, 144, 238, 68, 216, 105, 218, 55, 168, 0, 162, 196, 199, 82, | ||
70 | 63, 12, 225, 73, 161, 167, 199, 63, 255, 119, 220, 120, 86, 40, 104, | ||
71 | 195, 139, 254, 42, 238, 91, 250, 153, 255, 255, 85, 225, 81, 64, 72, | ||
72 | 185, 204, 154, 55, 71, 225, 39, 244, 108, 42, 251, 175, 255, 254, 171, | ||
73 | 134, 8, 60, 160, 192, 171, 24, 186, 235, 174, 186, 235, 174, 186, 235, | ||
74 | 174, 186, 235, 254, 68, 82, 105, 120, 193, 48, 3, 216, 90, 173, 113, | ||
75 | 18, 222, 1, 212, 208, 33, 57, 132, 214, 13, 165, 0, 49, 178, 166, 201, | ||
76 | 246, 232, 31, 159, 114, 199, 225, 248, 120, 43, 94, 172, 137, 32, 75, | ||
77 | 180, 11, 247, 223, 215, 60, 255, 225, 240, 208, 6, 179, 209, 69, 200, | ||
78 | 98, 8, 78, 127, 56, 167, 193, 4, 10, 86, 12, 135, 128, 50, 171, 244, | ||
79 | 132, 239, 23, 93, 117, 215, 93, 117, 255, 255, 174, 21, 39, 0, 34, 47, | ||
80 | 146, 109, 95, 99, 121, 145, 164, 252, 245, 215, 93, 127, 218, 223, | ||
81 | 102, 56, 80, 108, 162, 144, 4, 45, 36, 192, 200, 180, 217, 171, 107, | ||
82 | 207, 0, 5, 118, 124, 132, 86, 222, 3, 228, 95, 255, 135, 132, 133, 95, | ||
83 | 63, 201, 94, 237, 216, 117, 215, 93, 253, 52, 167, 211, 72, 160, 150, | ||
84 | 220, 152, 252, 49, 23, 119, 68, 166, 120, 38, 182, 158, 46, 186, 235, | ||
85 | 174, 186, 235, 237, 7, 14, 24, 92, 172, 85, 41, 52, 237, 176, 36, 7, | ||
86 | 36, 243, 235, 141, 132, 30, 250, 171, 62, 170, 183, 14, 146, 109, 247, | ||
87 | 254, 0, 216, 166, 193, 132, 65, 114, 198, 145, 17, 236, 4, 181, 80, | ||
88 | 52, 183, 125, 238, 240, 182, 55, 155, 38, 215, 252, 231, 217, 97, 81, | ||
89 | 252, 12, 81, 121, 32, 222, 64, 100, 123, 224, 117, 17, 49, 226, 133, | ||
90 | 65, 77, 215, 128, 8, 79, 114, 61, 95, 127, 46, 199, 213, 220, 111, | ||
91 | 165, 80, 103, 234, 179, 148, 4, 110, 94, 206, 245, 255, 196, 106, 207, | ||
92 | 10, 148, 0, 134, 53, 178, 28, 75, 239, 243, 54, 131, 72, 65, 84, 194, | ||
93 | 41, 207, 235, 70, 173, 59, 32, 134, 180, 210, 191, 128, 35, 215, 219, | ||
94 | 55, 10, 106, 126, 109, 51, 86, 17, 157, 104, 20, 103, 247, 214, 44, | ||
95 | 196, 132, 32, 28, 171, 255, 255, 206, 255, 213, 147, 97, 133, 76, 214, | ||
96 | 102, 231, 231, 241, 72, 156, 56, 161, 87, 89, 7, 57, 85, 100, 65, 255, | ||
97 | 32, 110, 253, 1, 25, 209, 163, 106, 105, 49, 152, 90, 243, 182, 81, | ||
98 | 195, 130, 157, 24, 201, 118, 229, 255, 196, 106, 39, 10, 138, 240, 68, | ||
99 | 214, 2, 163, 144, 173, 82, 203, 73, 174, 70, 10, 101, 231, 250, 63, | ||
100 | 97, 37, 178, 3, 121, 219, 125, 96, 145, 38, 57, 34, 37, 63, 255, 240, | ||
101 | 160, 68, 109, 118, 202, 61, 227, 203, 210, 102, 142, 153, 173, 76, | ||
102 | 111, 174, 186, 235, 254, 31, 252, 40, 104, 20, 109, 73, 102, 245, 50, | ||
103 | 154, 153, 215, 235, 174, 186, 235, 174, 190, 19, 221, 127, 225, 35, | ||
104 | 195, 124, 193, 179, 16, 239, 119, 251, 25, 126, 181, 143, 140, 216, | ||
105 | 122, 35, 194, 225, 107, 76, 143, 235, 124, 22, 30, 202, 27, 52, 188, | ||
106 | 80, 232, 136, 132, 137, 94, 110, 191, 59, 210, 95, 223, 177, 114, 10, | ||
107 | 191, 252, 37, 187, 154, 7, 140, 177, 57, 255, 61, 186, 4, 194, 206, | ||
108 | 129, 208, 186, 187, 46, 52, 63, 207, 183, 245, 54, 237, 191, 235, 221, | ||
109 | 35, 76, 197, 84, 140, 63, 186, 52, 41, 129, 82, 166, 149, 255, 25, | ||
110 | 149, 23, 231, 53, 175, 219, 243, 232, 67, 11, 191, 252, 37, 252, 130, | ||
111 | 111, 2, 166, 217, 10, 0, 94, 211, 194, 101, 84, 68, 17, 31, 35, 242, | ||
112 | 222, 132, 139, 208, 130, 103, 95, 84, 113, 195, 68, 84, 93, 169, 150, | ||
113 | 64, 204, 207, 71, 253, 211, 174, 141, 133, 58, 135, 205, 35, 27, 187, | ||
114 | 30, 176, 178, 60, 125, 14, 145, 16, 145, 43, 206, 175, 130, 255, 194, | ||
115 | 66, 51, 199, 59, 5, 57, 117, 243, 84, 108, 168, 126, 150, 206, 255, | ||
116 | 93, 135, 142, 157, 191, 20, 156, 122, 213, 80, 172, 179, 98, 143, 80, | ||
117 | 240, 154, 158, 36, 146, 159, 145, 86, 194, 237, 251, 110, 225, 169, | ||
118 | 127, 194, 66, 29, 151, 107, 53, 213, 93, 120, 115, 180, 1, 184, 180, | ||
119 | 21, 41, 249, 151, 77, 200, 172, 235, 159, 179, 35, 35, 123, 180, 189, | ||
120 | 31, 190, 255, 79, 130, 82, 20, 8, 128, 212, 22, 177, 22, 61, 232, 247, | ||
121 | 30, 120, 3, 79, 232, 150, 179, 54, 225, 239, 11, 174, 186, 235, 255, | ||
122 | 183, 177, 182, 20, 52, 2, 228, 166, 105, 102, 193, 76, 2, 71, 210, 89, | ||
123 | 246, 226, 238, 235, 245, 215, 93, 117, 215, 93, 119, 223, 125, 255, | ||
124 | 210, 31, 240, 248, 94, 2, 25, 231, 73, 255, 255, 47, 210, 129, 224, | ||
125 | 115, 207, 22, 18, 209, 12, 2, 92, 233, 134, 237, 191, 192, 43, 214, | ||
126 | 64, 109, 19, 255, 8, 48, 37, 118, 186, 235, 255, 255, 224, 132, 188, | ||
127 | 0, 126, 81, 156, 120, 8, 99, 90, 123, 107, 101, 41, 126, 19, 52, 38, | ||
128 | 229, 238, 98, 61, 176, 47, 32, 11, 119, 32, 54, 137, 225, 215, 93, | ||
129 | 117, 215, 93, 117, 215, 93, 117, 215, 95, 234, 112, 255, 135, 197, | ||
130 | 224, 7, 75, 35, 133, 156, 62, 179, 243, 170, 233, 134, 170, 56, 177, | ||
131 | 31, 4, 254, 52, 151, 134, 30, 112, 37, 243, 126, 6, 143, 41, 153, 143, | ||
132 | 255, 250, 172, 84, 120, 32, 142, 236, 197, 21, 248, 182, 229, 187, | ||
133 | 255, 254, 239, 194, 176, 33, 18, 156, 200, 41, 70, 241, 254, 52, 125, | ||
134 | 104, 216, 72, 212, 245, 255, 238, 69, 191, 248, 75, 128, 27, 108, 204, | ||
135 | 4, 130, 175, 242, 117, 231, 180, 128, 23, 218, 89, 154, 72, 63, 147, | ||
136 | 174, 186, 235, 174, 186, 235, 175, 255, 244, 30, 20, 62, 0, 9, 26, | ||
137 | 140, 152, 187, 140, 173, 72, 149, 57, 9, 16, 162, 250, 87, 66, 43, 56, | ||
138 | 215, 99, 158, 154, 53, 171, 127, 255, 219, 76, 111, 60, 203, 19, 244, | ||
139 | 247, 127, 143, 144, 0, 152, 42, 176, 51, 6, 179, 229, 217, 225, 160, | ||
140 | 149, 17, 207, 16, 87, 78, 248, 9, 225, 95, 163, 21, 113, 70, 213, 129, | ||
141 | 40, 107, 116, 216, 109, 178, 200, 144, 31, 117, 163, 49, 39, 243, 81, | ||
142 | 141, 245, 71, 73, 230, 249, 246, 126, 239, 209, 240, 255, 168, 97, | ||
143 | 180, 45, 192, 178, 117, 74, 198, 219, 174, 10, 140, 240, 22, 146, 16, | ||
144 | 77, 184, 39, 91, 18, 251, 142, 15, 32, 71, 84, 247, 230, 161, 195, | ||
145 | 240, 0, 168, 132, 78, 73, 49, 199, 205, 5, 192, 67, 254, 193, 46, 21, | ||
146 | 175, 76, 229, 19, 0, 26, 100, 168, 43, 230, 37, 9, 66, 51, 3, 90, 66, | ||
147 | 243, 186, 94, 187, 232, 26, 151, 217, 89, 85, 99, 255, 86, 166, 46, | ||
148 | 38, 206, 80, 185, 255, 88, 94, 45, 103, 228, 8, 76, 122, 227, 98, 250, | ||
149 | 34, 184, 114, 238, 200, 171, 26, 49, 98, 139, 255, 103, 87, 173, 64, | ||
150 | 119, 90, 66, 176, 207, 147, 189, 132, 144, 105, 120, 183, 18, 140, | ||
151 | 253, 105, 131, 221, 240, 202, 191, 246, 169, 111, 254, 223, 160, 138, | ||
152 | 73, 34, 149, 138, 238, 62, 184, 175, 27, 224, 184, 253, 44, 134, 172, | ||
153 | 190, 120, 26, 228, 92, 187, 202, 254, 192, 220, 79, 205, 151, 190, | ||
154 | 216, 230, 3, 7, 219, 166, 104, 91, 206, 38, 255, 233, 85, 85, 143, | ||
155 | 136, 231, 102, 196, 227, 158, 181, 26, 17, 82, 208, 205, 91, 9, 69, | ||
156 | 255, 185, 238, 98, 122, 133, 85, 19, 132, 125, 95, 251, 34, 196, 231, | ||
157 | 154, 186, 208, 233, 249, 168, 247, 255, 127, 106, 63, 32, 51, 67, 163, | ||
158 | 1, 190, 176, 66, 92, 198, 212, 84, 186, 39, 254, 119, 31, 56, 251, | ||
159 | 223, 4, 166, 108, 149, 89, 78, 236, 0, 180, 201, 156, 173, 94, 153, | ||
160 | 111, 254, 252, 17, 108, 29, 69, 70, 221, 79, 156, 2, 201, 254, 41, | ||
161 | 118, 45, 169, 255, 216, 93, 195, 83, 174, 18, 107, 125, 167, 192, 226, | ||
162 | 217, 88, 184, 46, 127, 238, 193, 177, 38, 168, 69, 139, 159, 223, 114, | ||
163 | 99, 37, 109, 32, 53, 38, 184, 207, 253, 205, 158, 205, 231, 141, 74, | ||
164 | 171, 76, 139, 140, 193, 242, 246, 180, 56, 178, 144, 18, 162, 41, 16, | ||
165 | 43, 255, 155, 120, 145, 25, 202, 221, 219, 239, 184, 14, 234, 58, 129, | ||
166 | 128, 223, 214, 124, 110, 155, 150, 205, 118, 175, 251, 192, 14, 148, | ||
167 | 58, 73, 44, 180, 15, 29, 201, 184, 122, 56, 103, 137, 231, 0, 45, 144, | ||
168 | 127, 145, 96, 157, 75, 37, 3, 180, 104, 193, 173, 166, 114, 208, 214, | ||
169 | 159, 223, 214, 123, 73, 163, 0, 5, 117, 61, 156, 14, 52, 198, 157, | ||
170 | 135, 213, 215, 211, 130, 136, 185, 173, 120, 160, 245, 142, 245, 169, | ||
171 | 208, 22, 106, 52, 233, 254, 183, 121, 49, 53, 105, 86, 96, 66, 175, | ||
172 | 35, 148, 64, 126, 18, 240, 88, 215, 207, 255, 251, 73, 32, 112, 196, | ||
173 | 160, 73, 215, 74, 223, 186, 240, 6, 64, 7, 80, 27, 252, 60, 91, 228, | ||
174 | 206, 175, 251, 35, 194, 137, 161, 5, 148, 166, 55, 254, 195, 187, 101, | ||
175 | 2, 100, 39, 246, 127, 205, 191, 128, 128, 243, 166, 153, 163, 24, 82, | ||
176 | 210, 161, 152, 149, 209, 204, 101, 111, 187, 252, 105, 224, 236, 86, | ||
177 | 165, 56, 18, 115, 255, 255, 19, 11, 171, 107, 34, 156, 76, 111, 152, | ||
178 | 1, 108, 42, 188, 73, 174, 110, 189, 157, 143, 172, 198, 139, 246, 182, | ||
179 | 99, 88, 234, 237, 171, 127, 161, 133, 33, 241, 63, 39, 31, 194, 101, | ||
180 | 244, 58, 204, 82, 50, 28, 62, 166, 246, 128, 220, 58, 42, 111, 17, 86, | ||
181 | 140, 183, 219, 133, 4, 72, 74, 141, 148, 103, 54, 62, 213, 36, 74, 68, | ||
182 | 137, 87, 255, 208, 253, 117, 215, 93, 117, 215, 93, 117, 255, 255, 8, | ||
183 | 66, 135, 225, 191, 64, 31, 51, 34, 6, 19, 247, 9, 99, 159, 227, 240, | ||
184 | 8, 11, 56, 216, 140, 0, 171, 94, 141, 16, 247, 76, 216, 98, 241, 23, | ||
185 | 172, 125, 190, 96, 61, 54, 112, 80, 180, 192, 176, 239, 166, 82, 195, | ||
186 | 32, 168, 103, 107, 73, 19, 16, 119, 126, 122, 54, 227, 251, 62, 187, | ||
187 | 145, 247, 164, 96, 213, 91, 12, 71, 194, 230, 205, 4, 77, 193, 43, 38, | ||
188 | 167, 48, 98, 200, 67, 194, 185, 211, 85, 240, 127, 235, 140, 66, 34, | ||
189 | 35, 190, 3, 243, 47, 131, 132, 172, 43, 63, 255, 85, 46, 12, 155, 217, | ||
190 | 238, 172, 55, 73, 48, 115, 152, 251, 227, 16, 194, 71, 82, 93, 43, 20, | ||
191 | 25, 8, 144, 50, 139, 168, 52, 255, 242, 75, 9, 170, 96, 249, 57, 6, | ||
192 | 239, 15, 154, 49, 205, 152, 41, 18, 214, 74, 42, 204, 86, 193, 72, | ||
193 | 239, 245, 120, 127, 195, 224, 24, 7, 10, 192, 157, 166, 143, 46, 49, | ||
194 | 16, 103, 240, 10, 28, 252, 128, 38, 98, 217, 40, 152, 86, 240, 248, | ||
195 | 65, 34, 153, 30, 175, 230, 88, 23, 251, 139, 26, 107, 20, 55, 246, | ||
196 | 234, 167, 193, 135, 235, 50, 68, 92, 135, 184, 126, 0, 41, 205, 147, | ||
197 | 8, 129, 210, 235, 224, 31, 154, 244, 105, 117, 255, 253, 224, 104, | ||
198 | 210, 82, 92, 144, 252, 7, 24, 71, 87, 220, 90, 79, 141, 97, 177, 21, | ||
199 | 191, 216, 120, 146, 204, 15, 63, 140, 157, 223, 73, 11, 152, 130, 71, | ||
200 | 124, 161, 189, 225, 253, 132, 239, 73, 84, 11, 55, 230, 109, 209, 166, | ||
201 | 131, 160, 248, 153, 53, 167, 193, 252, 84, 86, 133, 241, 126, 252, | ||
202 | 103, 226, 229, 213, 170, 59, 131, 255, 246, 166, 96, 221, 56, 138, 82, | ||
203 | 119, 81, 126, 210, 136, 228, 36, 142, 8, 148, 27, 191, 220, 34, 22, | ||
204 | 228, 86, 109, 140, 200, 181, 129, 226, 39, 82, 198, 74, 115, 255, 43, | ||
205 | 57, 17, 28, 243, 227, 119, 223, 73, 132, 73, 57, 22, 140, 177, 53, | ||
206 | 246, 255, 143, 75, 122, 68, 86, 182, 39, 217, 212, 20, 152, 234, 175, | ||
207 | 239, 251, 108, 128, 166, 114, 66, 141, 192, 137, 255, 193, 219, 140, | ||
208 | 13, 81, 113, 178, 233, 155, 82, 69, 68, 113, 13, 204, 180, 31, 192, | ||
209 | 12, 70, 64, 60, 68, 247, 212, 6, 130, 174, 179, 151, 13, 188, 29, 109, | ||
210 | 18, 3, 119, 208, 43, 37, 115, 65, 5, 204, 144, 184, 33, 24, 69, 30, | ||
211 | 254, 54, 205, 201, 63, 115, 80, 217, 127, 127, 244, 67, 49, 80, 247, | ||
212 | 168, 139, 249, 83, 255, 123, 114, 1, 170, 237, 67, 99, 234, 219, 144, | ||
213 | 215, 4, 18, 236, 169, 215, 251, 223, 213, 178, 113, 148, 52, 75, 191, | ||
214 | 239, 242, 96, 125, 81, 77, 29, 40, 219, 89, 162, 35, 20, 198, 227, | ||
215 | 120, 31, 248, 154, 52, 64, 85, 194, 230, 91, 22, 142, 6, 104, 173, 3, | ||
216 | 77, 147, 26, 164, 111, 254, 42, 84, 1, 204, 72, 61, 140, 96, 5, 191, | ||
217 | 5, 174, 85, 179, 228, 84, 8, 175, 2, 58, 99, 107, 14, 98, 44, 13, 255, | ||
218 | 46, 67, 245, 94, 70, 125, 53, 90, 241, 209, 237, 160, 203, 225, 219, | ||
219 | 3, 37, 181, 152, 21, 246, 12, 25, 243, 249, 20, 173, 224, 251, 164, | ||
220 | 130, 119, 149, 151, 188, 64, 118, 134, 136, 243, 192, 253, 26, 236, | ||
221 | 17, 244, 3, 6, 161, 56, 241, 241, 227, 58, 192, 119, 65, 2, 85, 69, | ||
222 | 101, 194, 147, 105, 35, 212, 64, 201, 135, 43, 129, 87, 131, 254, 24, | ||
223 | 153, 225, 6, 226, 250, 93, 242, 223, 246, 161, 232, 38, 235, 82, 149, | ||
224 | 186, 208, 131, 255, 224, 119, 239, 223, 128, 8, 91, 254, 167, 223, | ||
225 | 255, 193, 54, 146, 56, 131, 228, 158, 254, 240, 20, 219, 226, 181, | ||
226 | 174, 62, 91, 97, 110, 120, 231, 111, 181, 135, 34, 0, 55, 131, 15, | ||
227 | 194, 73, 33, 218, 198, 76, 123, 70, 176, 25, 60, 252, 123, 173, 180, | ||
228 | 121, 252, 31, 255, 220, 98, 1, 198, 120, 0, 43, 164, 153, 155, 68, 70, | ||
229 | 100, 1, 68, 194, 33, 145, 96, 191, 16, 42, 212, 112, 86, 184, 139, | ||
230 | 195, 63, 221, 241, 194, 72, 146, 175, 1, 192, 89, 134, 143, 188, 80, | ||
231 | 73, 161, 214, 37, 255, 238, 53, 252, 5, 209, 205, 66, 96, 119, 255, 3, | ||
232 | 135, 207, 225, 15, 193, 0, 155, 16, 161, 27, 102, 216, 136, 122, 189, | ||
233 | 255, 93, 117, 215, 255, 255, 194, 65, 87, 125, 223, 120, 99, 44, 240, | ||
234 | 251, 44, 240, 198, 89, 225, 246, 88, 127, 99, 45, 199, 31, 255, 239, | ||
235 | 15, 178, 207, 12, 101, 158, 31, 101, 158, 24, 203, 15, 195, 236, 183, | ||
236 | 199, 255, 199, 225, 140, 183, 143, 255, 247, 135, 217, 103, 134, 50, | ||
237 | 195, 251, 236, 183, 31, 255, 247, 134, 50, 207, 15, 178, 207, 12, 101, | ||
238 | 158, 31, 101, 158, 24, 203, 64, 0, 0, 0, 179, 65, 154, 56, 59, 131, | ||
239 | 107, 152, 241, 64, 0, 64, 43, 20, 0, 4, 2, 190, 82, 5, 192, 0, 247, | ||
240 | 128, 198, 49, 192, 39, 2, 192, 0, 244, 164, 248, 64, 96, 240, 64, 253, | ||
241 | 159, 205, 107, 125, 155, 241, 99, 57, 170, 177, 157, 93, 248, 182, 8, | ||
242 | 109, 152, 126, 10, 240, 110, 177, 6, 248, 127, 240, 66, 43, 128, 99, | ||
243 | 196, 97, 211, 109, 246, 255, 15, 66, 111, 243, 73, 209, 139, 47, 134, | ||
244 | 99, 29, 98, 48, 1, 125, 106, 153, 161, 178, 253, 244, 246, 249, 131, | ||
245 | 47, 164, 243, 35, 56, 252, 184, 75, 220, 168, 36, 246, 188, 21, 127, | ||
246 | 32, 59, 136, 136, 162, 27, 251, 125, 184, 80, 240, 104, 149, 63, 146, | ||
247 | 34, 151, 230, 114, 9, 227, 27, 21, 177, 0, 74, 89, 242, 49, 208, 115, | ||
248 | 240, 219, 227, 245, 103, 207, 211, 115, 8, 63, 2, 158, 55, 17, 226, | ||
249 | 60, 71, 136, 241, 29, 97, 10, 194, 21, 132, 43, 8, 86, 16, 196, 120, | ||
250 | 143, 17, 226, 60, 0, 0, 0, 168, 65, 154, 84, 12, 224, 221, 231, 129, | ||
251 | 36, 38, 44, 176, 156, 92, 6, 185, 112, 11, 92, 114, 7, 113, 149, 193, | ||
252 | 72, 145, 228, 141, 60, 127, 205, 254, 21, 63, 130, 15, 192, 7, 170, | ||
253 | 117, 110, 189, 221, 229, 54, 108, 180, 121, 249, 13, 255, 246, 99, | ||
254 | 133, 13, 192, 72, 185, 113, 236, 236, 105, 199, 251, 34, 10, 8, 193, | ||
255 | 67, 254, 41, 138, 92, 65, 225, 119, 155, 218, 91, 49, 124, 72, 107, | ||
256 | 130, 57, 27, 177, 97, 254, 203, 237, 159, 136, 39, 218, 16, 75, 110, | ||
257 | 35, 42, 240, 11, 80, 206, 48, 189, 1, 147, 138, 63, 9, 220, 40, 66, | ||
258 | 39, 69, 239, 255, 37, 151, 128, 21, 215, 218, 239, 174, 131, 83, 97, | ||
259 | 234, 147, 152, 139, 131, 185, 72, 39, 128, 140, 212, 213, 233, 155, | ||
260 | 255, 252, 61, 12, 164, 199, 246, 126, 48, 158, 247, 251, 17, 144, 94, | ||
261 | 14, 242, 82, 182, 121, 212, 65, 248, 25, 224, 0, 0, 0, 178, 65, 154, | ||
262 | 96, 87, 7, 15, 124, 167, 192, 18, 53, 31, 218, 6, 61, 141, 142, 205, | ||
263 | 92, 19, 19, 230, 174, 168, 39, 229, 227, 2, 0, 10, 183, 113, 149, 63, | ||
264 | 192, 33, 216, 167, 15, 46, 9, 194, 65, 237, 121, 45, 194, 101, 248, | ||
265 | 20, 66, 226, 40, 92, 212, 161, 241, 238, 98, 34, 255, 252, 60, 83, 1, | ||
266 | 63, 20, 186, 196, 19, 128, 31, 102, 153, 100, 123, 224, 20, 114, 141, | ||
267 | 131, 206, 60, 51, 158, 194, 203, 247, 183, 231, 102, 23, 132, 143, 61, | ||
268 | 32, 3, 45, 244, 60, 16, 178, 23, 173, 139, 117, 202, 43, 115, 77, 225, | ||
269 | 30, 169, 193, 69, 83, 167, 122, 34, 225, 89, 115, 17, 17, 127, 252, | ||
270 | 61, 113, 245, 236, 79, 23, 228, 40, 6, 169, 7, 167, 17, 57, 117, 35, | ||
271 | 16, 11, 22, 133, 167, 87, 140, 121, 239, 247, 190, 205, 100, 221, 143, | ||
272 | 232, 90, 43, 151, 59, 7, 215, 105, 70, 45, 255, 62, 105, 143, 190, 3, | ||
273 | 53, 194, 24, 142, 34, 0, 0, 0, 179, 65, 154, 128, 71, 2, 57, 249, 75, | ||
274 | 248, 177, 32, 81, 128, 135, 144, 161, 22, 7, 178, 4, 222, 253, 152, | ||
275 | 69, 227, 177, 153, 255, 238, 253, 116, 5, 64, 33, 150, 0, 44, 80, 105, | ||
276 | 154, 147, 173, 10, 20, 107, 95, 176, 88, 0, 45, 226, 145, 39, 130, | ||
277 | 197, 244, 232, 73, 63, 111, 242, 226, 114, 1, 36, 194, 184, 8, 186, | ||
278 | 247, 21, 130, 0, 32, 141, 65, 35, 67, 126, 2, 249, 243, 225, 57, 91, | ||
279 | 87, 247, 132, 123, 173, 150, 190, 111, 183, 137, 39, 167, 152, 115, | ||
280 | 248, 140, 4, 122, 245, 231, 190, 135, 220, 13, 90, 146, 206, 43, 92, | ||
281 | 154, 19, 138, 155, 201, 89, 120, 3, 24, 115, 44, 221, 231, 129, 54, | ||
282 | 141, 235, 85, 175, 136, 184, 70, 93, 226, 96, 129, 188, 33, 86, 192, | ||
283 | 236, 175, 218, 229, 255, 204, 4, 108, 79, 17, 228, 8, 194, 47, 4, 57, | ||
284 | 43, 99, 132, 137, 155, 13, 255, 26, 175, 110, 223, 191, 40, 142, 179, | ||
285 | 1, 2, 6, 136, 0, 0, 0, 194, 65, 154, 160, 71, 6, 190, 44, 94, 96, 48, | ||
286 | 227, 45, 145, 132, 143, 200, 39, 175, 41, 56, 2, 85, 90, 52, 141, 108, | ||
287 | 37, 167, 137, 196, 159, 240, 168, 17, 247, 135, 244, 17, 9, 4, 251, | ||
288 | 172, 84, 36, 195, 134, 252, 4, 198, 205, 227, 198, 31, 52, 64, 90, | ||
289 | 255, 207, 196, 37, 124, 70, 0, 85, 146, 38, 50, 154, 206, 248, 37, | ||
290 | 104, 141, 18, 45, 51, 251, 254, 239, 48, 220, 0, 33, 2, 237, 5, 20, | ||
291 | 244, 140, 183, 91, 5, 92, 219, 29, 77, 26, 202, 43, 11, 118, 66, 98, | ||
292 | 193, 45, 112, 187, 113, 59, 217, 57, 204, 38, 12, 191, 250, 208, 37, | ||
293 | 196, 10, 224, 140, 49, 200, 67, 126, 11, 119, 45, 252, 62, 12, 4, 21, | ||
294 | 94, 166, 225, 41, 245, 228, 13, 99, 252, 25, 6, 51, 241, 110, 246, | ||
295 | 200, 126, 180, 86, 192, 210, 182, 41, 58, 202, 137, 128, 197, 160, | ||
296 | 158, 202, 159, 239, 202, 154, 101, 163, 143, 119, 235, 65, 93, 245, | ||
297 | 23, 174, 196, 84, 174, 39, 58, 72, 225, 245, 173, 138, 86, 250, 120, | ||
298 | 27, 224, 0, 0, 0, 142, 65, 154, 192, 71, 2, 179, 114, 196, 98, 132, | ||
299 | 121, 125, 240, 152, 59, 30, 12, 199, 225, 7, 6, 235, 232, 46, 155, 71, | ||
300 | 19, 30, 2, 192, 210, 165, 133, 37, 172, 253, 26, 9, 190, 112, 167, | ||
301 | 226, 60, 64, 232, 16, 174, 150, 108, 134, 173, 184, 93, 60, 158, 253, | ||
302 | 173, 195, 145, 236, 39, 148, 176, 38, 110, 234, 96, 1, 153, 116, 166, | ||
303 | 138, 242, 246, 182, 129, 241, 41, 94, 220, 78, 83, 30, 28, 103, 223, | ||
304 | 55, 248, 255, 133, 68, 97, 150, 91, 216, 219, 47, 55, 253, 247, 30, 8, | ||
305 | 53, 18, 250, 202, 8, 66, 192, 92, 17, 199, 124, 43, 114, 211, 2, 186, | ||
306 | 97, 51, 85, 93, 236, 159, 198, 123, 35, 72, 180, 177, 177, 116, 126, | ||
307 | 200, 56, 152, 190, 184, 251, 180, 13, 240, 0, 0, 0, 172, 65, 154, 224, | ||
308 | 220, 10, 205, 33, 17, 115, 113, 223, 97, 66, 123, 88, 177, 225, 111, | ||
309 | 143, 17, 0, 55, 155, 49, 17, 89, 92, 140, 34, 78, 190, 200, 12, 74, | ||
310 | 147, 26, 139, 188, 129, 15, 254, 242, 50, 129, 239, 151, 37, 98, 248, | ||
311 | 0, 94, 87, 77, 175, 86, 4, 116, 45, 66, 214, 162, 61, 99, 27, 127, | ||
312 | 147, 179, 30, 1, 187, 116, 141, 230, 255, 21, 245, 24, 84, 64, 38, | ||
313 | 125, 75, 248, 18, 249, 191, 7, 26, 233, 226, 111, 255, 215, 10, 151, | ||
314 | 5, 155, 82, 0, 125, 189, 81, 183, 51, 124, 63, 248, 80, 108, 1, 0, 88, | ||
315 | 95, 191, 99, 163, 209, 160, 43, 110, 72, 247, 117, 224, 13, 234, 183, | ||
316 | 30, 225, 12, 101, 108, 49, 113, 47, 19, 244, 53, 91, 63, 55, 144, 108, | ||
317 | 8, 201, 106, 103, 230, 21, 197, 173, 237, 108, 80, 140, 74, 109, 116, | ||
318 | 189, 25, 148, 223, 242, 41, 93, 242, 27, 232, 217, 224, 104, 128, 0, | ||
319 | 0, 0, 186, 65, 155, 0, 220, 10, 205, 175, 133, 43, 16, 34, 0, 192, | ||
320 | 135, 54, 189, 189, 200, 125, 206, 235, 108, 6, 116, 18, 246, 85, 235, | ||
321 | 38, 127, 189, 27, 75, 215, 183, 234, 97, 56, 5, 158, 35, 50, 80, 43, | ||
322 | 183, 4, 246, 125, 151, 214, 254, 81, 24, 78, 163, 48, 232, 148, 215, | ||
323 | 100, 136, 145, 24, 117, 201, 57, 76, 88, 36, 236, 211, 252, 223, 250, | ||
324 | 174, 120, 32, 247, 14, 207, 44, 252, 107, 246, 136, 65, 98, 154, 160, | ||
325 | 238, 150, 28, 53, 9, 120, 180, 183, 215, 137, 49, 164, 239, 81, 174, | ||
326 | 107, 194, 61, 136, 134, 184, 36, 45, 62, 255, 2, 215, 154, 105, 101, | ||
327 | 174, 27, 94, 121, 173, 20, 170, 97, 77, 29, 87, 255, 135, 207, 28, 44, | ||
328 | 188, 199, 85, 207, 234, 188, 19, 155, 203, 146, 231, 55, 194, 203, 85, | ||
329 | 99, 108, 96, 184, 2, 22, 18, 77, 214, 61, 49, 209, 100, 173, 232, 163, | ||
330 | 216, 98, 25, 144, 1, 111, 18, 252, 197, 239, 129, 65, 249, 190, 234, | ||
331 | 141, 243, 254, 6, 120, 0, 0, 0, 165, 65, 155, 32, 71, 2, 179, 245, | ||
332 | 133, 11, 232, 173, 213, 8, 21, 9, 5, 57, 230, 2, 56, 148, 20, 189, | ||
333 | 247, 242, 19, 241, 58, 207, 111, 193, 42, 162, 42, 252, 188, 149, 148, | ||
334 | 184, 1, 115, 100, 52, 157, 237, 228, 4, 244, 208, 215, 234, 155, 123, | ||
335 | 241, 56, 147, 97, 151, 191, 4, 32, 132, 167, 224, 71, 95, 159, 248, | ||
336 | 250, 216, 192, 86, 56, 226, 153, 36, 244, 245, 126, 241, 155, 223, | ||
337 | 253, 176, 101, 50, 156, 115, 63, 159, 10, 19, 31, 25, 60, 240, 230, | ||
338 | 255, 58, 159, 193, 1, 122, 223, 2, 13, 152, 78, 0, 123, 221, 125, 87, | ||
339 | 191, 159, 132, 150, 12, 0, 131, 136, 25, 6, 174, 87, 175, 4, 61, 98, | ||
340 | 107, 3, 52, 165, 33, 248, 145, 176, 8, 235, 248, 251, 219, 7, 255, | ||
341 | 228, 160, 196, 237, 151, 27, 94, 233, 195, 128, 22, 245, 179, 65, 168, | ||
342 | 137, 184, 31, 224, 84, 128, 0, 0, 0, 239, 65, 155, 64, 71, 2, 179, | ||
343 | 195, 60, 220, 0, 147, 92, 254, 223, 250, 216, 62, 99, 33, 14, 228, | ||
344 | 126, 77, 255, 175, 224, 128, 94, 27, 218, 114, 225, 255, 127, 190, 19, | ||
345 | 39, 181, 98, 70, 138, 120, 129, 130, 2, 16, 3, 199, 41, 63, 71, 214, | ||
346 | 191, 3, 56, 206, 136, 250, 213, 83, 198, 58, 98, 184, 96, 29, 115, | ||
347 | 161, 214, 92, 173, 70, 96, 99, 69, 192, 239, 175, 178, 206, 34, 43, | ||
348 | 224, 32, 67, 250, 231, 243, 142, 172, 55, 182, 224, 48, 232, 52, 252, | ||
349 | 227, 205, 128, 140, 205, 207, 200, 33, 89, 187, 48, 76, 28, 165, 237, | ||
350 | 101, 55, 193, 71, 85, 133, 142, 116, 119, 13, 109, 175, 94, 118, 19, | ||
351 | 156, 217, 80, 65, 86, 165, 63, 29, 228, 128, 22, 49, 10, 23, 179, 167, | ||
352 | 53, 136, 20, 138, 201, 196, 45, 72, 31, 253, 225, 5, 106, 109, 183, | ||
353 | 125, 246, 32, 191, 97, 202, 53, 135, 148, 199, 52, 249, 233, 48, 130, | ||
354 | 120, 214, 63, 183, 159, 250, 48, 220, 51, 22, 39, 194, 24, 68, 223, | ||
355 | 73, 255, 195, 226, 163, 166, 94, 109, 62, 105, 63, 139, 19, 201, 41, | ||
356 | 189, 193, 50, 208, 165, 224, 3, 242, 155, 135, 70, 113, 109, 61, 242, | ||
357 | 192, 67, 120, 35, 32, 215, 135, 159, 132, 188, 132, 2, 24, 161, 13, | ||
358 | 160, 255, 225, 136, 16, 32, 0, 0, 0, 170, 65, 155, 96, 87, 2, 183, | ||
359 | 102, 224, 89, 165, 0, 17, 238, 159, 109, 76, 41, 228, 55, 213, 34, | ||
360 | 157, 128, 144, 176, 30, 127, 238, 76, 242, 192, 4, 44, 211, 223, 245, | ||
361 | 173, 148, 192, 34, 20, 196, 204, 192, 34, 20, 196, 205, 189, 132, 242, | ||
362 | 154, 4, 205, 221, 76, 92, 8, 107, 104, 15, 137, 74, 246, 226, 113, | ||
363 | 230, 203, 24, 46, 229, 47, 80, 17, 49, 239, 70, 217, 128, 150, 102, | ||
364 | 145, 246, 194, 31, 152, 75, 141, 125, 159, 126, 228, 147, 16, 242, | ||
365 | 248, 68, 21, 226, 57, 97, 26, 210, 252, 252, 37, 249, 169, 76, 62, | ||
366 | 148, 193, 57, 97, 50, 136, 206, 48, 99, 175, 124, 4, 143, 71, 225, 31, | ||
367 | 36, 16, 251, 177, 96, 0, 83, 130, 104, 196, 70, 151, 193, 240, 47, | ||
368 | 252, 64, 184, 9, 141, 180, 48, 1, 111, 37, 142, 101, 228, 71, 220, | ||
369 | 215, 211, 192, 233, 66, 161, 228, 201, 119, 175, 15, 19, 192, 129, 0, | ||
370 | 0, 0, 127, 65, 155, 128, 87, 2, 181, 112, 165, 100, 52, 0, 134, 39, 4, | ||
371 | 108, 206, 82, 180, 11, 82, 210, 68, 0, 117, 69, 199, 175, 246, 80, | ||
372 | 106, 169, 48, 12, 44, 99, 159, 243, 250, 42, 132, 133, 58, 177, 126, | ||
373 | 6, 239, 41, 176, 2, 164, 166, 218, 221, 238, 128, 219, 76, 138, 115, | ||
374 | 249, 158, 39, 10, 248, 67, 30, 222, 246, 41, 191, 222, 183, 216, 90, | ||
375 | 132, 254, 69, 137, 7, 225, 16, 18, 121, 248, 76, 223, 10, 127, 195, | ||
376 | 226, 120, 213, 56, 20, 37, 22, 243, 182, 20, 48, 127, 64, 27, 191, 22, | ||
377 | 40, 234, 35, 104, 50, 11, 117, 184, 42, 34, 139, 71, 129, 143, 172, | ||
378 | 139, 22, 51, 71, 224, 66, 128, 0, 0, 0, 157, 65, 155, 160, 87, 3, 87, | ||
379 | 144, 208, 17, 25, 156, 21, 13, 152, 240, 46, 65, 222, 185, 111, 101, | ||
380 | 9, 24, 120, 214, 205, 93, 143, 83, 63, 153, 77, 63, 21, 54, 26, 185, | ||
381 | 64, 18, 47, 73, 157, 35, 31, 0, 65, 181, 83, 235, 121, 77, 193, 229, | ||
382 | 106, 56, 17, 191, 210, 21, 210, 206, 194, 112, 249, 132, 224, 47, 141, | ||
383 | 126, 113, 60, 115, 244, 200, 32, 20, 182, 254, 35, 18, 229, 202, 223, | ||
384 | 172, 194, 81, 190, 23, 247, 223, 171, 210, 149, 125, 246, 42, 79, 104, | ||
385 | 166, 43, 74, 175, 15, 249, 37, 49, 191, 225, 239, 18, 44, 80, 16, 194, | ||
386 | 64, 40, 115, 240, 153, 189, 161, 242, 150, 9, 163, 244, 255, 26, 164, | ||
387 | 252, 38, 104, 126, 177, 5, 194, 19, 60, 89, 147, 40, 233, 131, 190, | ||
388 | 183, 78, 253, 224, 128, 249, 79, 4, 47, 92, 112, 33, 64, 0, 0, 0, 140, | ||
389 | 65, 155, 192, 87, 6, 250, 195, 247, 155, 192, 36, 253, 125, 156, 248, | ||
390 | 174, 226, 111, 32, 136, 22, 159, 86, 19, 46, 115, 234, 254, 64, 175, | ||
391 | 59, 65, 199, 49, 141, 106, 115, 246, 92, 104, 57, 87, 166, 214, 75, | ||
392 | 157, 238, 178, 147, 0, 46, 226, 68, 185, 89, 192, 111, 52, 103, 173, | ||
393 | 86, 207, 19, 133, 12, 124, 1, 106, 159, 146, 89, 225, 6, 173, 108, | ||
394 | 117, 137, 36, 6, 249, 13, 237, 45, 173, 240, 247, 132, 35, 36, 19, | ||
395 | 243, 97, 11, 37, 21, 17, 97, 51, 91, 229, 254, 30, 41, 189, 243, 240, | ||
396 | 145, 142, 170, 204, 216, 235, 226, 188, 212, 225, 212, 137, 153, 235, | ||
397 | 172, 71, 172, 177, 252, 223, 226, 186, 248, 32, 18, 82, 135, 250, 112, | ||
398 | 91, 114, 223, 129, 14, 0, 0, 0, 159, 65, 155, 224, 87, 2, 181, 102, | ||
399 | 241, 179, 190, 240, 157, 100, 17, 0, 53, 90, 66, 59, 247, 166, 223, | ||
400 | 191, 74, 214, 202, 4, 243, 68, 74, 176, 186, 185, 251, 70, 188, 197, | ||
401 | 132, 204, 239, 96, 12, 34, 87, 141, 51, 8, 241, 229, 137, 230, 94, | ||
402 | 247, 90, 202, 43, 130, 187, 110, 133, 74, 233, 183, 91, 46, 228, 156, | ||
403 | 32, 99, 151, 193, 11, 67, 139, 31, 228, 48, 71, 173, 61, 53, 179, 127, | ||
404 | 255, 146, 132, 159, 246, 255, 251, 16, 24, 142, 255, 235, 207, 188, 8, | ||
405 | 0, 175, 52, 155, 229, 254, 18, 44, 252, 178, 249, 225, 51, 121, 74, | ||
406 | 196, 95, 137, 240, 223, 155, 23, 252, 255, 193, 198, 18, 242, 4, 96, | ||
407 | 4, 57, 106, 121, 146, 247, 230, 42, 175, 199, 85, 224, 156, 103, 10, | ||
408 | 4, 205, 198, 245, 148, 55, 129, 47, 155, 241, 14, 166, 88, 255, 2, 36, | ||
409 | 0, 0, 0, 124, 65, 154, 0, 87, 2, 181, 253, 44, 39, 228, 17, 0, 99, 95, | ||
410 | 101, 95, 124, 67, 216, 204, 130, 141, 43, 87, 123, 46, 119, 95, 149, | ||
411 | 49, 183, 176, 158, 82, 64, 153, 187, 169, 128, 13, 255, 162, 201, 251, | ||
412 | 247, 189, 173, 160, 62, 37, 43, 219, 136, 190, 18, 23, 226, 120, 4, | ||
413 | 121, 24, 189, 191, 225, 10, 216, 162, 85, 203, 163, 180, 113, 226, 77, | ||
414 | 62, 251, 204, 191, 132, 64, 176, 8, 129, 164, 60, 94, 27, 247, 193, | ||
415 | 128, 41, 207, 194, 47, 193, 36, 35, 136, 97, 193, 169, 119, 227, 127, | ||
416 | 244, 2, 32, 4, 110, 35, 132, 176, 176, 64, 129, 60, 33, 106, 95, 133, | ||
417 | 194, 48, 34, 64, 0, 0, 0, 170, 65, 154, 32, 87, 2, 181, 102, 240, 9, | ||
418 | 203, 70, 151, 249, 167, 132, 201, 245, 247, 228, 17, 9, 249, 24, 9, | ||
419 | 145, 5, 223, 207, 64, 183, 242, 130, 143, 200, 45, 58, 79, 124, 64, | ||
420 | 36, 253, 148, 39, 251, 170, 191, 209, 227, 18, 187, 203, 192, 1, 187, | ||
421 | 45, 249, 241, 178, 189, 47, 76, 77, 188, 95, 245, 249, 215, 175, 67, | ||
422 | 133, 147, 4, 150, 165, 153, 54, 144, 11, 212, 55, 219, 251, 92, 188, | ||
423 | 109, 236, 66, 123, 135, 246, 141, 53, 94, 255, 204, 126, 1, 30, 190, | ||
424 | 218, 240, 43, 2, 64, 88, 13, 5, 147, 182, 105, 177, 236, 191, 47, 195, | ||
425 | 37, 4, 158, 248, 212, 175, 96, 104, 194, 57, 36, 13, 194, 102, 201, | ||
426 | 175, 83, 255, 127, 127, 172, 16, 135, 144, 128, 128, 73, 102, 191, | ||
427 | 182, 104, 176, 82, 200, 67, 88, 194, 37, 90, 2, 247, 127, 224, 66, | ||
428 | 125, 1, 58, 249, 252, 252, 35, 254, 92, 8, 176, 0, 0, 0, 190, 65, 154, | ||
429 | 64, 103, 2, 181, 126, 35, 9, 214, 67, 64, 38, 226, 151, 163, 226, 153, | ||
430 | 253, 214, 181, 179, 133, 156, 135, 203, 25, 15, 188, 243, 89, 107, | ||
431 | 116, 253, 179, 75, 32, 89, 190, 0, 64, 50, 242, 11, 72, 190, 220, 37, | ||
432 | 8, 102, 103, 169, 68, 232, 255, 111, 41, 184, 112, 201, 24, 19, 42, | ||
433 | 62, 37, 214, 238, 87, 152, 95, 2, 107, 212, 201, 191, 241, 125, 198, | ||
434 | 21, 25, 166, 122, 130, 3, 58, 50, 8, 207, 4, 99, 170, 38, 248, 123, | ||
435 | 223, 194, 184, 11, 79, 84, 114, 11, 120, 224, 254, 63, 201, 20, 199, | ||
436 | 39, 35, 230, 112, 195, 224, 251, 249, 159, 235, 192, 103, 186, 228, | ||
437 | 221, 190, 196, 89, 110, 141, 168, 240, 222, 161, 163, 246, 76, 110, | ||
438 | 253, 95, 123, 1, 205, 224, 8, 94, 41, 72, 80, 104, 4, 41, 170, 237, | ||
439 | 249, 60, 122, 96, 100, 87, 198, 176, 63, 0, 71, 47, 200, 82, 230, 109, | ||
440 | 195, 222, 38, 190, 239, 135, 248, 120, 66, 60, 177, 159, 95, 208, 4, | ||
441 | 3, 215, 9, 159, 129, 98, 0, 0, 0, 129, 65, 154, 96, 103, 2, 189, 147, | ||
442 | 9, 249, 4, 64, 7, 58, 225, 200, 74, 114, 18, 173, 80, 104, 244, 223, | ||
443 | 213, 214, 206, 42, 216, 191, 207, 249, 151, 169, 149, 66, 119, 186, | ||
444 | 202, 76, 0, 209, 69, 10, 120, 252, 220, 4, 245, 163, 62, 212, 187, | ||
445 | 244, 222, 22, 12, 32, 28, 41, 13, 211, 48, 148, 197, 29, 87, 123, 230, | ||
446 | 135, 155, 5, 255, 0, 225, 226, 117, 155, 252, 3, 0, 225, 89, 236, 125, | ||
447 | 150, 215, 251, 243, 127, 128, 64, 60, 42, 83, 224, 203, 71, 66, 203, | ||
448 | 124, 33, 91, 20, 48, 139, 232, 126, 5, 187, 191, 203, 248, 128, 247, | ||
449 | 144, 104, 18, 158, 105, 103, 255, 129, 227, 66, 56, 76, 252, 11, 16, | ||
450 | 0, 0, 0, 179, 65, 154, 128, 87, 2, 111, 150, 120, 110, 37, 226, 185, | ||
451 | 184, 34, 184, 122, 108, 190, 108, 1, 213, 136, 177, 184, 0, 53, 73, | ||
452 | 102, 81, 224, 0, 0, 64, 27, 49, 127, 58, 174, 167, 22, 103, 255, 12, | ||
453 | 154, 8, 175, 65, 170, 246, 57, 213, 98, 4, 64, 24, 22, 245, 79, 215, | ||
454 | 60, 230, 48, 98, 109, 58, 34, 45, 130, 196, 158, 52, 1, 141, 84, 33, | ||
455 | 152, 148, 70, 229, 184, 63, 178, 144, 255, 249, 245, 121, 184, 4, 196, | ||
456 | 245, 118, 192, 86, 158, 137, 95, 30, 255, 118, 178, 155, 15, 119, 97, | ||
457 | 253, 108, 217, 13, 108, 30, 32, 110, 65, 97, 236, 106, 211, 143, 126, | ||
458 | 171, 246, 1, 128, 144, 94, 2, 34, 218, 111, 207, 204, 171, 173, 71, | ||
459 | 254, 18, 10, 95, 153, 157, 159, 81, 213, 155, 85, 135, 235, 225, 63, | ||
460 | 86, 248, 251, 200, 122, 183, 125, 211, 175, 114, 117, 123, 255, 189, | ||
461 | 136, 27, 20, 17, 223, 253, 71, 252, 249, 252, 71, 136, 224, 107, 128, | ||
462 | 0, 0, 0, 150, 65, 154, 160, 103, 2, 183, 155, 192, 2, 30, 233, 246, | ||
463 | 202, 101, 232, 81, 96, 6, 23, 171, 227, 35, 60, 168, 192, 97, 173, | ||
464 | 242, 16, 45, 158, 50, 28, 25, 69, 55, 252, 120, 20, 132, 8, 21, 142, | ||
465 | 205, 13, 44, 241, 86, 124, 69, 1, 255, 195, 79, 4, 64, 146, 131, 55, | ||
466 | 19, 127, 169, 215, 80, 133, 72, 63, 43, 248, 89, 211, 27, 228, 54, | ||
467 | 241, 241, 101, 130, 206, 228, 63, 2, 91, 10, 81, 169, 196, 61, 158, 7, | ||
468 | 173, 221, 243, 198, 221, 183, 176, 157, 148, 210, 216, 17, 231, 44, | ||
469 | 238, 103, 142, 146, 55, 59, 207, 216, 6, 195, 155, 235, 255, 195, 229, | ||
470 | 218, 230, 58, 172, 15, 235, 225, 226, 119, 8, 61, 26, 71, 76, 46, 38, | ||
471 | 208, 241, 239, 35, 91, 243, 191, 114, 81, 252, 71, 136, 224, 107, 128, | ||
472 | 0, 0, 0, 146, 65, 154, 192, 103, 2, 57, 252, 191, 38, 8, 65, 176, 49, | ||
473 | 2, 48, 71, 128, 50, 95, 3, 134, 120, 190, 0, 91, 154, 22, 208, 170, | ||
474 | 62, 168, 55, 210, 243, 213, 120, 173, 101, 255, 203, 224, 10, 170, | ||
475 | 198, 65, 55, 94, 245, 36, 144, 195, 16, 66, 10, 165, 152, 197, 255, | ||
476 | 254, 30, 132, 235, 254, 61, 255, 153, 78, 121, 252, 206, 112, 248, | ||
477 | 140, 124, 100, 252, 18, 70, 83, 144, 208, 9, 201, 68, 206, 84, 41, 74, | ||
478 | 131, 239, 61, 246, 246, 85, 140, 238, 176, 173, 10, 116, 47, 91, 59, | ||
479 | 221, 229, 38, 2, 81, 207, 150, 254, 204, 37, 119, 134, 126, 234, 176, | ||
480 | 36, 0, 152, 239, 204, 103, 56, 31, 207, 195, 217, 225, 132, 43, 125, | ||
481 | 131, 113, 21, 95, 105, 244, 127, 63, 136, 239, 168, 26, 32, 0, 0, 0, | ||
482 | 165, 65, 154, 224, 87, 6, 190, 83, 224, 1, 218, 159, 240, 88, 229, 55, | ||
483 | 216, 34, 250, 115, 161, 158, 176, 131, 240, 70, 8, 2, 70, 128, 22, | ||
484 | 152, 174, 215, 248, 182, 0, 77, 199, 110, 96, 224, 128, 230, 176, 137, | ||
485 | 193, 41, 126, 176, 110, 4, 40, 32, 30, 48, 191, 26, 152, 174, 191, | ||
486 | 141, 174, 131, 222, 28, 29, 32, 161, 127, 132, 252, 129, 120, 23, 78, | ||
487 | 136, 254, 95, 243, 5, 62, 154, 124, 16, 7, 57, 56, 207, 33, 96, 75, | ||
488 | 77, 245, 92, 66, 197, 106, 62, 205, 236, 165, 69, 11, 74, 150, 124, | ||
489 | 183, 154, 214, 188, 220, 5, 187, 93, 128, 70, 9, 201, 147, 55, 217, | ||
490 | 111, 41, 178, 140, 60, 158, 219, 165, 228, 240, 134, 139, 225, 188, | ||
491 | 119, 199, 191, 82, 77, 130, 242, 147, 251, 250, 255, 184, 63, 211, 41, | ||
492 | 199, 91, 7, 80, 239, 155, 27, 191, 103, 123, 1, 163, 240, 54, 192, 0, | ||
493 | 0, 0, 145, 65, 155, 0, 87, 6, 135, 247, 228, 139, 46, 224, 3, 233, 94, | ||
494 | 198, 82, 128, 75, 0, 17, 102, 68, 109, 63, 35, 64, 31, 4, 50, 0, 0, 3, | ||
495 | 0, 128, 116, 240, 0, 16, 14, 252, 161, 15, 132, 37, 30, 97, 62, 2, | ||
496 | 193, 135, 64, 2, 58, 253, 189, 155, 109, 134, 30, 184, 10, 8, 47, 18, | ||
497 | 8, 153, 249, 190, 126, 49, 222, 87, 231, 204, 127, 52, 41, 244, 244, | ||
498 | 195, 218, 120, 203, 201, 128, 1, 251, 68, 190, 119, 228, 51, 49, 204, | ||
499 | 76, 136, 172, 67, 105, 158, 63, 161, 211, 215, 205, 12, 109, 182, 175, | ||
500 | 148, 152, 3, 218, 62, 72, 2, 207, 88, 15, 200, 156, 60, 73, 54, 59, | ||
501 | 76, 33, 74, 224, 206, 188, 122, 19, 127, 118, 250, 63, 5, 91, 152, 92, | ||
502 | 119, 220, 8, 112, 0, 0, 0, 4, 104, 203, 140, 178 | ||
503 | ]; | ||
504 | |||
505 | var trafLength = 8 + tfdt.length + tfdt.length + trun.length; | ||
506 | var m4s = [ | ||
507 | 8 + mfhd.length + trafLength, // size | ||
508 | 109, 111, 111, 102 // 'moof' | ||
509 | ] | ||
510 | .concat(mfhd) | ||
511 | .concat([ | ||
512 | trafLength, // size | ||
513 | 116, 114, 97, 102 // 'traf' | ||
514 | ].concat(tfhd).concat(tfdt).concat(trun)) | ||
515 | .concat(mdat); |
test/muxer/mp4.html
deleted
100644 → 0
1 | <!DOCTYPE html> | ||
2 | <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> | ||
3 | <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> | ||
4 | <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> | ||
5 | <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> | ||
6 | <head> | ||
7 | <meta charset="utf-8"> | ||
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||
9 | <title></title> | ||
10 | <meta name="description" content=""> | ||
11 | <meta name="viewport" content="width=device-width"> | ||
12 | |||
13 | <link rel="stylesheet" href="css/normalize.min.css"> | ||
14 | <link rel="stylesheet" href="css/main.css"> | ||
15 | |||
16 | <script src="js/vendor/modernizr-2.6.2.min.js"></script> | ||
17 | </head> | ||
18 | <body> | ||
19 | <!--[if lt IE 7]> | ||
20 | <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p> | ||
21 | <![endif]--> | ||
22 | |||
23 | <div class="header-container"> | ||
24 | <header class="wrapper clearfix"> | ||
25 | <h1 class="title">Transmux Analyzer</h1> | ||
26 | </header> | ||
27 | </div> | ||
28 | |||
29 | <div class="main-container"> | ||
30 | <div class="main wrapper clearfix"> | ||
31 | |||
32 | <article> | ||
33 | <header> | ||
34 | <p> | ||
35 | This page can help you inspect the results of the | ||
36 | transmuxing to mp4 files performed by | ||
37 | videojs-contrib-hls. It's still a bit tricky to create a | ||
38 | MSE-compatible fragmented MP4. We've had luck | ||
39 | with <a href="http://www.bento4.com/developers/dash/">Bento4</a> | ||
40 | and ffmpeg. If you have both of those utilities installed, | ||
41 | you can create a working MP4 like this: | ||
42 | <pre> | ||
43 | ffmpeg -i movie.ts -vn -codec copy -absf aac_adtstoasc movie-audio.mp4 | ||
44 | mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio.m4s | ||
45 | </pre> | ||
46 | <small>Looking for the <a href="index.html">FLV tool</a>?</small> | ||
47 | </header> | ||
48 | <section id="video-place"> | ||
49 | </section> | ||
50 | <section> | ||
51 | <h2>Inputs</h2> | ||
52 | <form id="inputs"> | ||
53 | <label> | ||
54 | Your original MP2T segment: | ||
55 | <input type="file" id="original"> | ||
56 | </label> | ||
57 | <label> | ||
58 | A working, MP4 version of the underlying stream | ||
59 | produced by another tool: | ||
60 | <input type="file" id="working"> | ||
61 | </label> | ||
62 | </form> | ||
63 | </section> | ||
64 | <section> | ||
65 | <h2>Comparision</h2> | ||
66 | <div id="comparison"> | ||
67 | A diff of the structure of the two MP4s will appear here | ||
68 | once you've specified an input TS file and a known working | ||
69 | MP4. | ||
70 | </div> | ||
71 | </section> | ||
72 | <section> | ||
73 | <h2>Structure</h2> | ||
74 | <div class="result-wrapper"> | ||
75 | <h3>videojs-contrib-hls</h3> | ||
76 | <pre class="vjs-boxes"> | ||
77 | </pre> | ||
78 | </div> | ||
79 | <div class="result-wrapper"> | ||
80 | <h3>Working</h3> | ||
81 | <pre class="working-boxes"></pre> | ||
82 | </div> | ||
83 | </section> | ||
84 | <section> | ||
85 | <h2>Results</h2> | ||
86 | <div class="result-wrapper"> | ||
87 | <h3>videojs-contrib-hls</h3> | ||
88 | <div class="vjs-hls-output result"> | ||
89 | <p> | ||
90 | The results of transmuxing your input file with | ||
91 | videojs-contrib-hls will show up here. | ||
92 | </p> | ||
93 | </div> | ||
94 | </div> | ||
95 | <div class="result-wrapper"> | ||
96 | <h3>Working</h3> | ||
97 | <div class="working-output result"> | ||
98 | <p> | ||
99 | The "good" version of the file will show up here. | ||
100 | </p> | ||
101 | </div> | ||
102 | </div> | ||
103 | </section> | ||
104 | </article> | ||
105 | |||
106 | </div> <!-- #main --> | ||
107 | </div> <!-- #main-container --> | ||
108 | |||
109 | <div class="footer-container"> | ||
110 | <footer class="wrapper"> | ||
111 | <h3>footer</h3> | ||
112 | </footer> | ||
113 | </div> | ||
114 | |||
115 | |||
116 | <script> | ||
117 | window.videojs = window.videojs || { | ||
118 | Hls: {} | ||
119 | }; | ||
120 | </script> | ||
121 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/stream.js"></script> | ||
122 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-generator.js"></script> | ||
123 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/transmuxer.js"></script> | ||
124 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-inspector.js"></script> | ||
125 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/flv-tag.js"></script> | ||
126 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/exp-golomb.js"></script> | ||
127 | |||
128 | <script src="../../src/bin-utils.js"></script> | ||
129 | |||
130 | <!-- Include QUnit for object diffs --> | ||
131 | <script src="../../node_modules/qunitjs/qunit/qunit.js"></script> | ||
132 | <script> | ||
133 | /* | ||
134 | MOSTLY STOLEN FROM https://w3c.github.io/media-source/#examples | ||
135 | */ | ||
136 | function setupMSE (videoElement, getNextVideoSegment, getNextAudioSegment) { | ||
137 | function onSourceOpen(videoTag, e) { | ||
138 | var | ||
139 | initVideoSegment = getNextVideoSegment(), | ||
140 | initAudioSegment = getNextAudioSegment(), | ||
141 | numberInited = 0, | ||
142 | videoBuffer, audioBuffer, | ||
143 | mediaSource = e.target; | ||
144 | |||
145 | if (mediaSource.sourceBuffers.length > 0) | ||
146 | return; | ||
147 | |||
148 | if (initVideoSegment) { | ||
149 | videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d401f'); | ||
150 | } | ||
151 | if (initAudioSegment) { | ||
152 | audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'); | ||
153 | } | ||
154 | |||
155 | videoTag.addEventListener('progress', onProgress.bind(videoTag, mediaSource)); | ||
156 | |||
157 | if (initVideoSegment == null && initAudioSegment == null) { | ||
158 | // Error fetching the initialization segment. Signal end of stream with an error. | ||
159 | mediaSource.endOfStream("network"); | ||
160 | return; | ||
161 | } | ||
162 | |||
163 | // Append the initialization segment. | ||
164 | var firstAppendHandler = function(e) { | ||
165 | var sourceBuffer = e.target; | ||
166 | sourceBuffer.removeEventListener('updateend', firstAppendHandler); | ||
167 | |||
168 | // Append some initial media data. | ||
169 | if (++numberInited === 2) { | ||
170 | onProgress(mediaSource, e); | ||
171 | } | ||
172 | }; | ||
173 | |||
174 | if (videoBuffer) { | ||
175 | videoBuffer.addEventListener('updateend', firstAppendHandler); | ||
176 | } | ||
177 | if (audioBuffer) { | ||
178 | audioBuffer.addEventListener('updateend', firstAppendHandler); | ||
179 | } | ||
180 | |||
181 | if (initVideoSegment) { | ||
182 | videoBuffer.appendBuffer(initVideoSegment); | ||
183 | } | ||
184 | if (initAudioSegment) { | ||
185 | audioBuffer.appendBuffer(initAudioSegment); | ||
186 | } | ||
187 | } | ||
188 | |||
189 | function appendNextMediaSegment(getNextMediaSegment, mediaSource, sourceBuffer) { | ||
190 | if (mediaSource.readyState == "closed") { | ||
191 | return; | ||
192 | } | ||
193 | |||
194 | var mediaSegment = getNextMediaSegment(); | ||
195 | // If we have run out of stream data, then signal end of stream. | ||
196 | if (mediaSegment == null) { | ||
197 | // mediaSource.endOfStream("network"); | ||
198 | return false; | ||
199 | } | ||
200 | |||
201 | // Make sure the previous append is not still pending. | ||
202 | if (sourceBuffer.updating) { | ||
203 | return false; | ||
204 | } | ||
205 | |||
206 | // NOTE: If mediaSource.readyState == “ended”, this appendBuffer() call will | ||
207 | // cause mediaSource.readyState to transition to "open". The web application | ||
208 | // should be prepared to handle multiple “sourceopen” events. | ||
209 | sourceBuffer.appendBuffer(mediaSegment); | ||
210 | return true; | ||
211 | } | ||
212 | /* | ||
213 | function onSeeking(mediaSource, e) { | ||
214 | var video = e.target; | ||
215 | |||
216 | if (mediaSource.readyState == "open") { | ||
217 | // Abort current segment append. | ||
218 | mediaSource.sourceBuffers[0].abort(); | ||
219 | } | ||
220 | |||
221 | // Notify the media segment loading code to start fetching data at the | ||
222 | // new playback position. | ||
223 | SeekToMediaSegmentAt(video.currentTime); | ||
224 | |||
225 | // Append a media segment from the new playback position. | ||
226 | appendNextMediaSegment(mediaSource); | ||
227 | } | ||
228 | */ | ||
229 | function onProgress(mediaSource, e) { | ||
230 | (appendNextMediaSegment(getNextVideoSegment, mediaSource, mediaSource.sourceBuffers[0]) && | ||
231 | appendNextMediaSegment(getNextAudioSegment, mediaSource, mediaSource.sourceBuffers[1])); | ||
232 | } | ||
233 | |||
234 | var mediaSource = new MediaSource(); | ||
235 | mediaSource.addEventListener('sourceopen', onSourceOpen.bind(this, videoElement)); | ||
236 | videoElement.src = window.URL.createObjectURL(mediaSource); | ||
237 | } | ||
238 | function getSegment (segmentArray) { | ||
239 | var segment = segmentArray.shift(); | ||
240 | if (segment) { | ||
241 | return segment.data; | ||
242 | } | ||
243 | return null; | ||
244 | } | ||
245 | </script> | ||
246 | <script> | ||
247 | var inputs = document.getElementById('inputs'), | ||
248 | original = document.getElementById('original'), | ||
249 | working = document.getElementById('working'), | ||
250 | |||
251 | vjsParsed, | ||
252 | workingParsed, | ||
253 | diffParsed, | ||
254 | vjsBytes, | ||
255 | workingBytes, | ||
256 | |||
257 | vjsBoxes = document.querySelector('.vjs-boxes'), | ||
258 | workingBoxes = document.querySelector('.working-boxes'), | ||
259 | |||
260 | vjsOutput = document.querySelector('.vjs-hls-output'), | ||
261 | workingOutput = document.querySelector('.working-output'), | ||
262 | |||
263 | video = document.createElement('video'), | ||
264 | mediaSource = new MediaSource(); | ||
265 | |||
266 | document.querySelector('#video-place').appendChild(video); | ||
267 | |||
268 | logevent = function(event) { | ||
269 | console.log(event.type); | ||
270 | }; | ||
271 | |||
272 | // output a diff of the two parsed MP4s | ||
273 | diffParsed = function() { | ||
274 | var comparison, diff, transmuxed; | ||
275 | if (!vjsParsed || !workingParsed) { | ||
276 | // wait until both inputs have been provided | ||
277 | return; | ||
278 | } | ||
279 | comparison = document.querySelector('#comparison'); | ||
280 | if (workingParsed[0].type === 'moof') { | ||
281 | diff = '<h3>Media Segment Comparision</h3>'; | ||
282 | transmuxed = vjsParsed.slice(2); | ||
283 | } else if (workingParsed.length === 2) { | ||
284 | diff = '<h3>Init Segment Comparision</h3>'; | ||
285 | transmuxed = vjsParsed.slice(0, 2); | ||
286 | } else { | ||
287 | diff = '<h3>General Comparision</h3>'; | ||
288 | transmuxed = vjsParsed; | ||
289 | } | ||
290 | diff += '<p>A <del>red background</del> indicates ' + | ||
291 | 'properties present in the transmuxed file but missing from the ' + | ||
292 | 'working version. A <ins>green background</ins> indicates ' + | ||
293 | 'properties present in the working version but missing in the ' + | ||
294 | 'transmuxed output.</p>'; | ||
295 | diff += '<pre class="mp4-diff">' + | ||
296 | QUnit.diff(muxjs.textifyMp4(transmuxed, null, ' '), | ||
297 | muxjs.textifyMp4(workingParsed, null, ' ')) + | ||
298 | '</pre>'; | ||
299 | |||
300 | comparison.innerHTML = diff; | ||
301 | }; | ||
302 | |||
303 | mediaSource.addEventListener('sourceopen', function() { | ||
304 | var | ||
305 | buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'); | ||
306 | //buffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'); | ||
307 | buffer.addEventListener('updatestart', logevent); | ||
308 | buffer.addEventListener('updateend', logevent); | ||
309 | buffer.addEventListener('error', logevent); | ||
310 | window.vjsMediaSource = mediaSource; | ||
311 | window.vjsSourceBuffer = buffer; | ||
312 | window.vjsVideo = video; | ||
313 | }); | ||
314 | mediaSource.addEventListener('error', logevent); | ||
315 | mediaSource.addEventListener('opened', logevent); | ||
316 | mediaSource.addEventListener('closed', logevent); | ||
317 | mediaSource.addEventListener('sourceended', logevent); | ||
318 | video.src = URL.createObjectURL(mediaSource); | ||
319 | video.addEventListener('error', console.log.bind(console)); | ||
320 | |||
321 | |||
322 | // muxjs.log = console.log.bind(console); | ||
323 | |||
324 | original.addEventListener('change', function() { | ||
325 | var reader = new FileReader(), | ||
326 | videoBuffer = [], | ||
327 | audioBuffer = []; | ||
328 | |||
329 | reader.addEventListener('loadend', function() { | ||
330 | var segment = new Uint8Array(reader.result), | ||
331 | transmuxer = new muxjs.mp2t.Transmuxer(), | ||
332 | videoSegments = [], | ||
333 | audioSegments = [], | ||
334 | videoBytesLength = 0, | ||
335 | audioBytesLength = 0, | ||
336 | decodeMe, | ||
337 | bytes, | ||
338 | i, j, | ||
339 | hex = ''; | ||
340 | |||
341 | // transmux the MPEG-TS data to BMFF segments | ||
342 | transmuxer.on('data', function(segment) { | ||
343 | if (segment.type === 'video') { | ||
344 | videoSegments.push(segment); | ||
345 | videoBytesLength += segment.data.byteLength; | ||
346 | } else { | ||
347 | audioSegments.push(segment); | ||
348 | audioBytesLength += segment.data.byteLength; | ||
349 | } | ||
350 | }); | ||
351 | |||
352 | transmuxer.push(segment); | ||
353 | transmuxer.end(); | ||
354 | // XXX - switch to select video/audio to show | ||
355 | decodeMe = videoSegments; | ||
356 | bytes = new Uint8Array(videoBytesLength); | ||
357 | |||
358 | for (j = 0, i = 0; j < decodeMe.length; j++) { | ||
359 | bytes.set(decodeMe[j].data, i); | ||
360 | i += decodeMe[j].byteLength; | ||
361 | } | ||
362 | |||
363 | vjsBytes = bytes; | ||
364 | vjsParsed = muxjs.inspectMp4(bytes); | ||
365 | console.log('transmuxed', vjsParsed); | ||
366 | diffParsed(); | ||
367 | |||
368 | // XXX - set one of videoSegments or audioSegments below to an | ||
369 | // empty array to only test one stream | ||
370 | |||
371 | setupMSE(video, | ||
372 | getSegment.bind(null, videoSegments), | ||
373 | getSegment.bind(null, audioSegments)); | ||
374 | |||
375 | // clear old box info | ||
376 | vjsBoxes.innerHTML = muxjs.textifyMp4(vjsParsed, null, ' '); | ||
377 | |||
378 | // write out the result | ||
379 | hex += '<pre>'; | ||
380 | hex += 'nothing to see here'; | ||
381 | hex += '</pre>'; | ||
382 | vjsOutput.innerHTML = hex; | ||
383 | |||
384 | video.play(); | ||
385 | }); | ||
386 | |||
387 | reader.readAsArrayBuffer(this.files[0]); | ||
388 | }, false); | ||
389 | |||
390 | working.addEventListener('change', function() { | ||
391 | var reader = new FileReader(); | ||
392 | reader.addEventListener('loadend', function() { | ||
393 | var hex = '', | ||
394 | bytes = new Uint8Array(reader.result); | ||
395 | |||
396 | |||
397 | workingBytes = bytes; | ||
398 | workingParsed = muxjs.inspectMp4(bytes); | ||
399 | console.log('working', workingParsed); | ||
400 | diffParsed(); | ||
401 | |||
402 | // clear old box info | ||
403 | workingBoxes.innerHTML = muxjs.textifyMp4(workingParsed, null, ' '); | ||
404 | |||
405 | // output the hex dump | ||
406 | hex += '<pre>'; | ||
407 | hex += videojs.Hls.utils.hexDump(bytes); | ||
408 | hex += '</pre>'; | ||
409 | workingOutput.innerHTML = hex; | ||
410 | |||
411 | // XXX Media Sources Testing | ||
412 | /* setupMSE(video, | ||
413 | getSegment.bind(null, []), | ||
414 | getSegment.bind(null, [{data: bytes}]));*/ | ||
415 | //window.vjsSourceBuffer.appendBuffer(bytes); | ||
416 | }); | ||
417 | reader.readAsArrayBuffer(this.files[0]); | ||
418 | }, false); | ||
419 | </script> | ||
420 | </body> | ||
421 | </html> |
test/muxer/mse-demo.html
deleted
100644 → 0
1 | <!DOCTYPE html> | ||
2 | <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> | ||
3 | <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> | ||
4 | <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> | ||
5 | <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> | ||
6 | <head> | ||
7 | <meta charset="utf-8"> | ||
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||
9 | <title>HLS Media Sources Demo</title> | ||
10 | <meta name="description" content=""> | ||
11 | <meta name="viewport" content="width=device-width"> | ||
12 | |||
13 | <link rel="stylesheet" href="css/normalize.min.css"> | ||
14 | <link rel="stylesheet" href="css/main.css"> | ||
15 | |||
16 | <script src="js/vendor/modernizr-2.6.2.min.js"></script> | ||
17 | </head> | ||
18 | <body> | ||
19 | <!--[if lt IE 7]> | ||
20 | <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p> | ||
21 | <![endif]--> | ||
22 | |||
23 | <div class="header-container"> | ||
24 | <header class="wrapper clearfix"> | ||
25 | <h1 class="title">Media Sources Demo</h1> | ||
26 | </header> | ||
27 | </div> | ||
28 | |||
29 | <div class="main-container"> | ||
30 | <div class="main wrapper clearfix"> | ||
31 | |||
32 | <article> | ||
33 | <header> | ||
34 | <p> | ||
35 | videojs-contrib-hls can convert MPEG transport streams | ||
36 | into Media Source Extension-compatible media segments on | ||
37 | the fly. Check out the demo below in Chrome! | ||
38 | </p> | ||
39 | </header> | ||
40 | <section> | ||
41 | <video id=demo width=640 height=360 controls> | ||
42 | <p> | ||
43 | Your browser is too old to view this demo. How about | ||
44 | upgrading? | ||
45 | </p> | ||
46 | </video> | ||
47 | </section> | ||
48 | </article> | ||
49 | |||
50 | </div> <!-- #main --> | ||
51 | </div> <!-- #main-container --> | ||
52 | |||
53 | <div class="footer-container"> | ||
54 | <footer class="wrapper"> | ||
55 | </footer> | ||
56 | </div> | ||
57 | |||
58 | |||
59 | <script src="zencoder-haze-1.js"></script> | ||
60 | <!-- transmuxing --> | ||
61 | <script> | ||
62 | window.videojs = window.videojs || { | ||
63 | Hls: {} | ||
64 | }; | ||
65 | </script> | ||
66 | <script src="../../src/stream.js"></script> | ||
67 | <script src="../../src/mp4-generator.js"></script> | ||
68 | <script src="../../src/transmuxer.js"></script> | ||
69 | <script src="../../src/exp-golomb.js"></script> | ||
70 | <script src="js/mp4-inspector.js"></script> | ||
71 | |||
72 | <script src="../../src/bin-utils.js"></script> | ||
73 | <script> | ||
74 | var mediaSource = new MediaSource(), | ||
75 | demo = document.getElementById('demo'); | ||
76 | |||
77 | // setup the media source | ||
78 | mediaSource.addEventListener('sourceopen', function() { | ||
79 | var videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'), | ||
80 | audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'), | ||
81 | transmuxer = new videojs.mp2t.Transmuxer(), | ||
82 | videoSegments = [], | ||
83 | audioSegments = []; | ||
84 | |||
85 | // expose the machinery for debugging | ||
86 | window.vjsMediaSource = mediaSource; | ||
87 | window.vjsSourceBuffer = videoBuffer; | ||
88 | window.vjsVideo = demo; | ||
89 | |||
90 | // transmux the MPEG-TS data to BMFF segments | ||
91 | transmuxer.on('data', function(segment) { | ||
92 | if (segment.type === 'video') { | ||
93 | videoSegments.push(segment); | ||
94 | } else { | ||
95 | audioSegments.push(segment); | ||
96 | } | ||
97 | }); | ||
98 | transmuxer.push(hazeVideo); | ||
99 | transmuxer.end(); | ||
100 | |||
101 | // buffer up the video data | ||
102 | videoBuffer.appendBuffer(videoSegments.shift().data); | ||
103 | videoBuffer.addEventListener('updateend', function() { | ||
104 | if (videoSegments.length) { | ||
105 | videoBuffer.appendBuffer(videoSegments.shift().data); | ||
106 | } | ||
107 | }); | ||
108 | |||
109 | // buffer up the audio data | ||
110 | audioBuffer.appendBuffer(audioSegments.shift().data); | ||
111 | audioBuffer.addEventListener('updateend', function() { | ||
112 | if (audioSegments.length) { | ||
113 | audioBuffer.appendBuffer(audioSegments.shift().data); | ||
114 | } | ||
115 | }); | ||
116 | }); | ||
117 | demo.src = URL.createObjectURL(mediaSource); | ||
118 | demo.addEventListener('error', console.log.bind(console)); | ||
119 | </script> | ||
120 | </body> | ||
121 | </html> |
test/muxer/parts.html
deleted
100644 → 0
1 | <!DOCTYPE html> | ||
2 | <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> | ||
3 | <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> | ||
4 | <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> | ||
5 | <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> | ||
6 | <head> | ||
7 | <meta charset="utf-8"> | ||
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||
9 | <title></title> | ||
10 | <meta name="description" content=""> | ||
11 | <meta name="viewport" content="width=device-width"> | ||
12 | |||
13 | <link rel="stylesheet" href="css/normalize.min.css"> | ||
14 | <link rel="stylesheet" href="css/main.css"> | ||
15 | |||
16 | <script src="js/vendor/modernizr-2.6.2.min.js"></script> | ||
17 | |||
18 | <script> | ||
19 | window.videojs = window.videojs || { | ||
20 | Hls: {} | ||
21 | }; | ||
22 | </script> | ||
23 | <!--<script src="min-m4s.js"></script>--> | ||
24 | <script src="./js/mdat.js"></script> | ||
25 | <script src="../../src/mp4-generator.js"></script> | ||
26 | </head> | ||
27 | <body> | ||
28 | <!--[if lt IE 7]> | ||
29 | <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p> | ||
30 | <![endif]--> | ||
31 | |||
32 | <div class="header-container"> | ||
33 | <header class="wrapper clearfix"> | ||
34 | <h1 class="title">Transmux Analyzer</h1> | ||
35 | </header> | ||
36 | </div> | ||
37 | |||
38 | <div class="main-container"> | ||
39 | <div class="main wrapper clearfix"> | ||
40 | |||
41 | <article> | ||
42 | <header> | ||
43 | <p> | ||
44 | This page will show a video player that loads a header of an mp4 followed by an mp4 segment. | ||
45 | </p> | ||
46 | </header> | ||
47 | <section> | ||
48 | <video controls autoplay width="320" height="240"></video> | ||
49 | </section> | ||
50 | </article> | ||
51 | |||
52 | </div> <!-- #main --> | ||
53 | </div> <!-- #main-container --> | ||
54 | |||
55 | <div class="footer-container"> | ||
56 | <footer class="wrapper"> | ||
57 | <h3></h3> | ||
58 | </footer> | ||
59 | </div> | ||
60 | |||
61 | |||
62 | |||
63 | |||
64 | |||
65 | <script> | ||
66 | var ms, | ||
67 | sourceBuffer, | ||
68 | video; | ||
69 | |||
70 | function closed() { | ||
71 | console.log("MediaSource Closed."); | ||
72 | } | ||
73 | |||
74 | var done = false; | ||
75 | function loadFragment() { | ||
76 | if (done) { | ||
77 | return; | ||
78 | } | ||
79 | |||
80 | console.log("LOAD FRAGMENT"); | ||
81 | |||
82 | var moov = videojs.mp4.moov(100, 600, 300, "video"); | ||
83 | var moof = videojs.mp4.moof([{trackId: 1}]); | ||
84 | var frag = Array.prototype.slice.call(moov); | ||
85 | frag = frag.concat(Array.prototype.slice.call(moof)); | ||
86 | frag = frag.concat(Array.prototype.slice.call(mdat)); | ||
87 | |||
88 | |||
89 | |||
90 | sourceBuffer.appendBuffer( new Uint8Array( frag ) ); | ||
91 | |||
92 | done = true; | ||
93 | |||
94 | } | ||
95 | |||
96 | function loadMdat(){ | ||
97 | console.log('mdat', mdat); | ||
98 | setTimeout( function(){ | ||
99 | sourceBuffer.appendBuffer(new Uint8Amdat); | ||
100 | },0); | ||
101 | |||
102 | } | ||
103 | |||
104 | function loadInit() { | ||
105 | console.log("LOAD INIT"); | ||
106 | var req = new XMLHttpRequest(); | ||
107 | req.responseType = "arraybuffer"; | ||
108 | req.open("GET", "./ts2/Header.m4s", true); | ||
109 | |||
110 | req.onload = function() { | ||
111 | console.log("INIT DONE LOADING"); | ||
112 | sourceBuffer.appendBuffer(new Uint8Array(req.response)); | ||
113 | sourceBuffer.addEventListener('update', loadFragment); | ||
114 | }; | ||
115 | |||
116 | req.onerror = function () { | ||
117 | window.alert("Could not load init."); | ||
118 | }; | ||
119 | |||
120 | req.send(); | ||
121 | |||
122 | } | ||
123 | |||
124 | function opened() { | ||
125 | |||
126 | console.log("OPENED"); | ||
127 | sourceBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.42c00d"'); | ||
128 | |||
129 | sourceBuffer.addEventListener('updateend', console.log.bind(console)); | ||
130 | sourceBuffer.addEventListener('updatestart', console.log.bind(console)); | ||
131 | sourceBuffer.addEventListener('update', console.log.bind(console)); | ||
132 | loadInit(); | ||
133 | } | ||
134 | |||
135 | function load() { | ||
136 | console.log("START"); | ||
137 | window.MediaSource = window.MediaSource || window.WebKitMediaSource; | ||
138 | |||
139 | ms = new window.MediaSource(); | ||
140 | |||
141 | video = document.querySelector('video'); | ||
142 | video.src = window.URL.createObjectURL(ms); | ||
143 | |||
144 | ms.addEventListener('webkitsourceopen', opened, false); | ||
145 | ms.addEventListener('webkitsourceclose', closed, false); | ||
146 | |||
147 | ms.addEventListener('sourceopen', opened, false); | ||
148 | ms.addEventListener('sourceclose', closed, false); | ||
149 | |||
150 | ms.addEventListener('update', console.log.bind(console)); | ||
151 | ms.addEventListener('updateend', console.log.bind(console)); | ||
152 | } | ||
153 | load(); | ||
154 | </script> | ||
155 | </body> | ||
156 | </html> |
test/muxer/player.html
deleted
100644 → 0
1 | <!DOCTYPE html> | ||
2 | <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> | ||
3 | <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> | ||
4 | <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> | ||
5 | <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> | ||
6 | <head> | ||
7 | <meta charset="utf-8"> | ||
8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||
9 | <title></title> | ||
10 | <meta name="description" content=""> | ||
11 | <meta name="viewport" content="width=device-width"> | ||
12 | |||
13 | <link rel="stylesheet" href="css/normalize.min.css"> | ||
14 | <link rel="stylesheet" href="css/main.css"> | ||
15 | |||
16 | <script src="js/vendor/modernizr-2.6.2.min.js"></script> | ||
17 | </head> | ||
18 | <body> | ||
19 | <!--[if lt IE 7]> | ||
20 | <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p> | ||
21 | <![endif]--> | ||
22 | |||
23 | <div class="header-container"> | ||
24 | <header class="wrapper clearfix"> | ||
25 | <h1 class="title">MP4 Media Sources API Test</h1> | ||
26 | </header> | ||
27 | </div> | ||
28 | |||
29 | <div class="main-container"> | ||
30 | <div class="main wrapper clearfix"> | ||
31 | |||
32 | <article> | ||
33 | <header> | ||
34 | <p> | ||
35 | This page is used to demo the playing of an mp4 file via the mediaSources API | ||
36 | </p> | ||
37 | </header> | ||
38 | <section> | ||
39 | <h2>Inputs</h2> | ||
40 | <form id="inputs"> | ||
41 | <label> | ||
42 | Your original MP2T segment: | ||
43 | <input type="file" id="original"> | ||
44 | </label> | ||
45 | <label> | ||
46 | A working, MP4 version of the underlying stream | ||
47 | produced by another tool: | ||
48 | <input type="file" id="working"> | ||
49 | </label> | ||
50 | </form> | ||
51 | </section> | ||
52 | <section> | ||
53 | |||
54 | <video controls width="320" height="240">> | ||
55 | <source src="sintel_trailer-480p.mp4" type="video/mp4"> | ||
56 | Your browser does not support the <code>video</code> element. | ||
57 | </video> | ||
58 | </section> | ||
59 | </article> | ||
60 | |||
61 | </div> <!-- #main --> | ||
62 | </div> <!-- #main-container --> | ||
63 | |||
64 | <div class="footer-container"> | ||
65 | <footer class="wrapper"> | ||
66 | <h3></h3> | ||
67 | </footer> | ||
68 | </div> | ||
69 | |||
70 | <script> | ||
71 | var inputs = document.getElementById('inputs'), | ||
72 | original = document.getElementById('original'), | ||
73 | working = document.getElementById('working'), | ||
74 | |||
75 | video = document.createElement('video'); | ||
76 | |||
77 | |||
78 | working.addEventListener('change', function() { | ||
79 | console.log('File Selectd'); | ||
80 | var reader = new FileReader(); | ||
81 | reader.addEventListener('loadend', function() { | ||
82 | console.log('File has been loaded'); | ||
83 | var bytes = new Uint8Array(reader.result), | ||
84 | ms = new window.MediaSource(), | ||
85 | video = document.querySelector('video'); | ||
86 | |||
87 | video.addEventListener("error", function onError(err) { | ||
88 | console.dir("error", err, video.error); | ||
89 | }); | ||
90 | |||
91 | video.pause(); | ||
92 | video.src = window.URL.createObjectURL(ms); | ||
93 | |||
94 | var onMediaSourceOpen = function() { | ||
95 | console.log('on media open'); | ||
96 | ms.removeEventListener('sourceopen', onMediaSourceOpen); | ||
97 | var videoBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.4D400D"'); | ||
98 | videoBuffer.appendBuffer(bytes); | ||
99 | |||
100 | var audioBuffer = ms.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'); | ||
101 | }; | ||
102 | |||
103 | ms.addEventListener('sourceopen', onMediaSourceOpen); | ||
104 | |||
105 | console.log('appended bytes', bytes); | ||
106 | }); | ||
107 | reader.readAsArrayBuffer(this.files[0]); | ||
108 | }); | ||
109 | |||
110 | </script> | ||
111 | |||
112 | </body> | ||
113 | </html> |
test/muxer/ts2/Header_boxes.json
deleted
100644 → 0
1 | [ | ||
2 | { | ||
3 | "type": "ftyp" | ||
4 | }, | ||
5 | { | ||
6 | "type": "free" | ||
7 | }, | ||
8 | { | ||
9 | "boxes": [ | ||
10 | { | ||
11 | "type": "mvhd" | ||
12 | }, | ||
13 | { | ||
14 | "boxes": [ | ||
15 | { | ||
16 | "type": "mehd" | ||
17 | }, | ||
18 | { | ||
19 | "type": "trex" | ||
20 | } | ||
21 | ], | ||
22 | "type": "mvex" | ||
23 | }, | ||
24 | { | ||
25 | "boxes": [ | ||
26 | { | ||
27 | "type": "tkhd" | ||
28 | }, | ||
29 | { | ||
30 | "boxes": [ | ||
31 | { | ||
32 | "type": "mdhd" | ||
33 | }, | ||
34 | { | ||
35 | |||
36 | "type": "hdlr" | ||
37 | }, | ||
38 | { | ||
39 | "boxes": [ | ||
40 | { | ||
41 | "type": "vmhd" | ||
42 | }, | ||
43 | { | ||
44 | "boxes": [ | ||
45 | { | ||
46 | "dataReferences": [ | ||
47 | { | ||
48 | "type": "url " | ||
49 | } | ||
50 | ], | ||
51 | "type": "dref" | ||
52 | } | ||
53 | ], | ||
54 | "type": "dinf" | ||
55 | }, | ||
56 | { | ||
57 | "boxes": [ | ||
58 | { | ||
59 | "sampleDescriptions": [ | ||
60 | { | ||
61 | "config": [ | ||
62 | { | ||
63 | "type": "avcC" | ||
64 | }, | ||
65 | { | ||
66 | "type": "btrt" | ||
67 | } | ||
68 | ], | ||
69 | "type": "avc1" | ||
70 | } | ||
71 | ], | ||
72 | "type": "stsd" | ||
73 | }, | ||
74 | { | ||
75 | "type": "stts" | ||
76 | }, | ||
77 | { | ||
78 | "type": "stsc" | ||
79 | }, | ||
80 | { | ||
81 | "type": "stsz" | ||
82 | }, | ||
83 | { | ||
84 | "type": "stco" | ||
85 | } | ||
86 | ], | ||
87 | "type": "stbl" | ||
88 | } | ||
89 | ], | ||
90 | "type": "minf" | ||
91 | } | ||
92 | ], | ||
93 | "type": "mdia" | ||
94 | } | ||
95 | ], | ||
96 | "type": "trak" | ||
97 | } | ||
98 | ], | ||
99 | "type": "moov" | ||
100 | } | ||
101 | ] | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
test/muxer/zencoder-haze-1.js
deleted
100644 → 0
This diff could not be displayed because it is too large.
test/segment-parser.js
deleted
100644 → 0
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 | parser, | ||
24 | |||
25 | expectedHeader = [ | ||
26 | 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, | ||
27 | 0x09, 0x00, 0x00, 0x00, 0x00 | ||
28 | ], | ||
29 | |||
30 | mergeOptions = window.videojs.mergeOptions, | ||
31 | |||
32 | makePat, | ||
33 | makePsi, | ||
34 | makePmt, | ||
35 | makePacket, | ||
36 | |||
37 | testAudioTag, | ||
38 | testVideoTag, | ||
39 | testScriptTag, | ||
40 | asciiFromBytes, | ||
41 | testScriptString, | ||
42 | testScriptEcmaArray, | ||
43 | testNalUnit; | ||
44 | |||
45 | module('segment parser', { | ||
46 | setup: function() { | ||
47 | parser = new window.videojs.Hls.SegmentParser(); | ||
48 | } | ||
49 | }); | ||
50 | |||
51 | test('creates an flv header', function() { | ||
52 | var header = Array.prototype.slice.call(parser.getFlvHeader()); | ||
53 | ok(header, 'the header is truthy'); | ||
54 | equal(9 + 4, header.length, 'the header length is correct'); | ||
55 | equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"'); | ||
56 | equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"'); | ||
57 | equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"'); | ||
58 | |||
59 | deepEqual(expectedHeader, header, 'the rest of the header is correct'); | ||
60 | }); | ||
61 | |||
62 | // Create a PMT packet | ||
63 | // @return {Array} bytes | ||
64 | makePmt = function(options) { | ||
65 | var | ||
66 | result = [], | ||
67 | pcr = options.pcr || 0, | ||
68 | entryCount = 0, | ||
69 | k, | ||
70 | sectionLength; | ||
71 | |||
72 | for (k in options.pids) { | ||
73 | if (k !== 'pcr') { | ||
74 | entryCount++; | ||
75 | } | ||
76 | } | ||
77 | // table_id | ||
78 | result.push(0x02); | ||
79 | // section_syntax_indicator '0' reserved section_length | ||
80 | // 13 + (program_info_length) + (n * 5 + ES_info_length[n]) | ||
81 | sectionLength = 13 + (5 * entryCount) + 17; | ||
82 | result.push(0x80 | (0xF00 & sectionLength >>> 8)); | ||
83 | result.push(sectionLength & 0xFF); | ||
84 | // program_number | ||
85 | result.push(0x00); | ||
86 | result.push(0x01); | ||
87 | // reserved version_number current_next_indicator | ||
88 | result.push(0x01); | ||
89 | // section_number | ||
90 | result.push(0x00); | ||
91 | // last_section_number | ||
92 | result.push(0x00); | ||
93 | // reserved PCR_PID | ||
94 | result.push(0xe0 | (pcr & (0x1f << 8))); | ||
95 | result.push(pcr & 0xff); | ||
96 | // reserved program_info_length | ||
97 | result.push(0xf0); | ||
98 | result.push(0x11); // hard-coded 17 byte descriptor | ||
99 | // program descriptors | ||
100 | result = result.concat([ | ||
101 | 0x25, 0x0f, 0xff, 0xff, | ||
102 | 0x49, 0x44, 0x33, 0x20, | ||
103 | 0xff, 0x49, 0x44, 0x33, | ||
104 | 0x20, 0x00, 0x1f, 0x00, | ||
105 | 0x01 | ||
106 | ]); | ||
107 | for (k in options.pids) { | ||
108 | // stream_type | ||
109 | result.push(options.pids[k]); | ||
110 | // reserved elementary_PID | ||
111 | result.push(0xe0 | (k & 0x1f00) >>> 8); | ||
112 | result.push(k & 0xff); | ||
113 | // reserved ES_info_length | ||
114 | result.push(0xf0); | ||
115 | result.push(0x00); // ES_info_length = 0 | ||
116 | } | ||
117 | // CRC_32 | ||
118 | result.push([0x00, 0x00, 0x00, 0x00]); // invalid CRC but we don't check it | ||
119 | return result; | ||
120 | }; | ||
121 | |||
122 | // Create a PAT packet | ||
123 | // @return {Array} bytes | ||
124 | makePat = function(options) { | ||
125 | var | ||
126 | result = [], | ||
127 | programEntries = [], | ||
128 | sectionLength, | ||
129 | k; | ||
130 | |||
131 | // build the program entries first | ||
132 | for (k in options.programs) { | ||
133 | // program_number | ||
134 | programEntries.push((k & 0xFF00) >>> 8); | ||
135 | programEntries.push(k & 0x00FF); | ||
136 | // reserved program_map_pid | ||
137 | programEntries.push((options.programs[k] & 0x1f00) >>> 8); | ||
138 | programEntries.push(options.programs[k] & 0xff); | ||
139 | } | ||
140 | sectionLength = programEntries.length + 5 + 4; | ||
141 | |||
142 | // table_id | ||
143 | result.push(0x00); | ||
144 | // section_syntax_indicator '0' reserved section_length | ||
145 | result.push(0x80 | ((0x300 & sectionLength) >>> 8)); | ||
146 | result.push(0xff & sectionLength); // section_length | ||
147 | // transport_stream_id | ||
148 | result.push(0x00); | ||
149 | result.push(0x00); | ||
150 | // reserved version_number current_next_indicator | ||
151 | result.push(0x01); // current_next_indicator is 1 | ||
152 | // section_number | ||
153 | result.push(0x00); | ||
154 | // last_section_number | ||
155 | result.push(0x00); | ||
156 | // program entries | ||
157 | result = result.concat(programEntries); | ||
158 | return result; | ||
159 | }; | ||
160 | |||
161 | // Create a PAT or PMT packet based on the specified options | ||
162 | // @return {Array} bytes | ||
163 | makePsi = function(options) { | ||
164 | var result = []; | ||
165 | |||
166 | // pointer_field | ||
167 | if (options.payloadUnitStartIndicator) { | ||
168 | result.push(0x00); | ||
169 | } | ||
170 | if (options.programs) { | ||
171 | return result.concat(makePat(options)); | ||
172 | } | ||
173 | return result.concat(makePmt(options)); | ||
174 | }; | ||
175 | |||
176 | // Construct an M2TS packet | ||
177 | // @return {Array} bytes | ||
178 | makePacket = function(options) { | ||
179 | var | ||
180 | result = [], | ||
181 | settings = mergeOptions({ | ||
182 | payloadUnitStartIndicator: true, | ||
183 | pid: 0x00 | ||
184 | }, options); | ||
185 | |||
186 | // header | ||
187 | // sync_byte | ||
188 | result.push(0x47); | ||
189 | // transport_error_indicator payload_unit_start_indicator transport_priority PID | ||
190 | result.push((settings.pid & 0x1f) << 8 | | ||
191 | (settings.payloadUnitStartIndicator ? 0x40 : 0x00)); | ||
192 | result.push(settings.pid & 0xff); | ||
193 | // transport_scrambling_control adaptation_field_control continuity_counter | ||
194 | result.push(0x10); | ||
195 | result = result.concat(makePsi(settings)); | ||
196 | |||
197 | // ensure the resulting packet is the correct size | ||
198 | result.length = window.videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH; | ||
199 | return result; | ||
200 | }; | ||
201 | |||
202 | test('parses PMTs with program descriptors', function() { | ||
203 | var | ||
204 | h264Type = window.videojs.Hls.SegmentParser.STREAM_TYPES.h264, | ||
205 | adtsType = window.videojs.Hls.SegmentParser.STREAM_TYPES.adts; | ||
206 | |||
207 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | ||
208 | programs: { | ||
209 | 0x01: [0x01] | ||
210 | } | ||
211 | }).concat(makePacket({ | ||
212 | pid: 0x01, | ||
213 | pids: { | ||
214 | 0x02: h264Type, // h264 video | ||
215 | 0x03: adtsType // adts audio | ||
216 | } | ||
217 | })))); | ||
218 | |||
219 | strictEqual(parser.stream.pmtPid, 0x01, 'PMT PID is 1'); | ||
220 | strictEqual(parser.stream.programMapTable[h264Type], 0x02, 'video is PID 2'); | ||
221 | strictEqual(parser.stream.programMapTable[adtsType], 0x03, 'audio is PID 3'); | ||
222 | }); | ||
223 | |||
224 | test('ignores network information specific data (NIT) in the PAT', function() { | ||
225 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | ||
226 | programs: { | ||
227 | 0x01: [0x01], | ||
228 | 0x00: [0x00] // a NIT has a reserved PID of 0x00 | ||
229 | } | ||
230 | }))); | ||
231 | |||
232 | ok(true, 'did not throw when a NIT is encountered'); | ||
233 | }); | ||
234 | |||
235 | test('ignores packets with PCR pids', function() { | ||
236 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | ||
237 | programs: { | ||
238 | 0x01: [0x01] | ||
239 | } | ||
240 | }).concat(makePacket({ | ||
241 | pid: 0x01, | ||
242 | pcr: 0x02 | ||
243 | })))); | ||
244 | |||
245 | equal(parser.stream.programMapTable.pcrPid, 0x02, 'parsed the PCR pid'); | ||
246 | }); | ||
247 | |||
248 | test('recognizes metadata streams', function() { | ||
249 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | ||
250 | programs: { | ||
251 | 0x01: [0x01] | ||
252 | } | ||
253 | }).concat(makePacket({ | ||
254 | pid: 0x01, | ||
255 | pids: { | ||
256 | // Rec. ITU-T H.222.0 (06/2012), Table 2-34 | ||
257 | 0x02: 0x15 // Metadata carried in PES packets | ||
258 | } | ||
259 | })))); | ||
260 | |||
261 | equal(parser.stream.programMapTable[0x15], 0x02, 'metadata is PID 2'); | ||
262 | }); | ||
263 | |||
264 | test('recognizes subsequent metadata packets after the payload start', function() { | ||
265 | var packets = []; | ||
266 | parser.metadataStream.push = function(packet) { | ||
267 | packets.push(packet); | ||
268 | }; | ||
269 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | ||
270 | programs: { | ||
271 | 0x01: [0x01] | ||
272 | } | ||
273 | }).concat(makePacket({ | ||
274 | pid: 0x01, | ||
275 | pids: { | ||
276 | // Rec. ITU-T H.222.0 (06/2012), Table 2-34 | ||
277 | 0x02: 0x15 // Metadata carried in PES packets | ||
278 | } | ||
279 | })).concat(makePacket({ | ||
280 | pid: 0x02, | ||
281 | payloadUnitStartIndicator: false | ||
282 | })))); | ||
283 | |||
284 | equal(packets.length, 1, 'parsed non-payload metadata packet'); | ||
285 | }); | ||
286 | |||
287 | test('returns undefined for PTS stats when a track is missing', function() { | ||
288 | parser.parseSegmentBinaryData(new Uint8Array(makePacket({ | ||
289 | programs: { | ||
290 | 0x01: [0x01] | ||
291 | } | ||
292 | }))); | ||
293 | |||
294 | strictEqual(parser.stats.h264Tags(), 0, 'no video tags yet'); | ||
295 | strictEqual(parser.stats.aacTags(), 0, 'no audio tags yet'); | ||
296 | }); | ||
297 | |||
298 | test('parses the first bipbop segment', function() { | ||
299 | parser.parseSegmentBinaryData(window.bcSegment); | ||
300 | |||
301 | ok(parser.tagsAvailable(), 'tags are available'); | ||
302 | }); | ||
303 | |||
304 | testAudioTag = function(tag) { | ||
305 | var | ||
306 | byte = tag.bytes[11], | ||
307 | format = (byte & 0xF0) >>> 4, | ||
308 | soundRate = byte & 0x03, | ||
309 | soundSize = (byte & 0x2) >>> 1, | ||
310 | soundType = byte & 0x1, | ||
311 | aacPacketType = tag.bytes[12]; | ||
312 | |||
313 | equal(10, format, 'the audio format is aac'); | ||
314 | equal(3, soundRate, 'the sound rate is 44kHhz'); | ||
315 | equal(1, soundSize, 'the sound size is 16-bit samples'); | ||
316 | equal(1, soundType, 'the sound type is stereo'); | ||
317 | |||
318 | ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type'); | ||
319 | }; | ||
320 | |||
321 | testVideoTag = function (tag) { | ||
322 | var | ||
323 | byte = tag.bytes[11], | ||
324 | frameType = (byte & 0xF0) >>> 4, | ||
325 | codecId = byte & 0x0F, | ||
326 | packetType = tag.bytes[12], | ||
327 | compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8; | ||
328 | |||
329 | // payload starts at tag.bytes[16] | ||
330 | |||
331 | // XXX: I'm not sure that frame types 3-5 are invalid | ||
332 | ok(frameType === 1 || frameType === 2, | ||
333 | 'the frame type should be valid'); | ||
334 | |||
335 | equal(7, codecId, 'the codec ID is AVC for h264'); | ||
336 | ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]'); | ||
337 | if (packetType !== 1) { | ||
338 | equal(0, | ||
339 | compositionTime, | ||
340 | 'the composition time is zero for non-NALU packets'); | ||
341 | } | ||
342 | |||
343 | // TODO: the rest of the bytes are an NLU unit | ||
344 | if (packetType === 0) { | ||
345 | // AVC decoder configuration record | ||
346 | } else { | ||
347 | // NAL units | ||
348 | testNalUnit(tag.bytes.subarray(16)); | ||
349 | } | ||
350 | }; | ||
351 | |||
352 | testNalUnit = function(bytes) { | ||
353 | var | ||
354 | nalHeader = bytes[0]; | ||
355 | // unitType = nalHeader & 0x1F; | ||
356 | |||
357 | equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0'); | ||
358 | // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something'); | ||
359 | // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0'); | ||
360 | // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22'); | ||
361 | }; | ||
362 | |||
363 | |||
364 | asciiFromBytes = function(bytes) { | ||
365 | var | ||
366 | string = [], | ||
367 | i = bytes.byteLength; | ||
368 | |||
369 | while (i--) { | ||
370 | string[i] = String.fromCharCode(bytes[i]); | ||
371 | } | ||
372 | return string.join(''); | ||
373 | }; | ||
374 | |||
375 | testScriptString = function(tag, offset, expected) { | ||
376 | var | ||
377 | type = tag.bytes[offset], | ||
378 | stringLength = tag.view.getUint16(offset + 1), | ||
379 | string; | ||
380 | |||
381 | equal(2, type, 'the script element is of string type'); | ||
382 | equal(stringLength, expected.length, 'the script string length is correct'); | ||
383 | string = asciiFromBytes(tag.bytes.subarray(offset + 3, | ||
384 | offset + 3 + stringLength)); | ||
385 | equal(expected, string, 'the string value is "' + expected + '"'); | ||
386 | }; | ||
387 | |||
388 | testScriptEcmaArray = function(tag, start) { | ||
389 | var | ||
390 | numItems = tag.view.getUint32(start), | ||
391 | i = numItems, | ||
392 | offset = start + 4, | ||
393 | length, | ||
394 | type; | ||
395 | |||
396 | while (i--) { | ||
397 | length = tag.view.getUint16(offset); | ||
398 | |||
399 | // advance offset to the property value | ||
400 | offset += 2 + length; | ||
401 | |||
402 | type = tag.bytes[offset]; | ||
403 | ok(type === 1 || type === 0, | ||
404 | 'the ecma array property value type is number or boolean'); | ||
405 | offset++; | ||
406 | if (type) { | ||
407 | // boolean | ||
408 | ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1, | ||
409 | 'the script boolean value is 0 or 1'); | ||
410 | offset++; | ||
411 | } else { | ||
412 | // number | ||
413 | ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN'); | ||
414 | offset += 8; | ||
415 | } | ||
416 | } | ||
417 | equal(tag.bytes[offset], 0, 'the property array terminator is valid'); | ||
418 | equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid'); | ||
419 | equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid'); | ||
420 | }; | ||
421 | |||
422 | testScriptTag = function(tag) { | ||
423 | testScriptString(tag, 11, 'onMetaData'); | ||
424 | |||
425 | // the onMetaData object is stored as an 'ecma array', an array with non- | ||
426 | // integer indices (i.e. a dictionary or hash-map). | ||
427 | equal(8, tag.bytes[24], 'onMetaData is of ecma array type'); | ||
428 | testScriptEcmaArray(tag, 25); | ||
429 | }; | ||
430 | |||
431 | test('the flv tags are well-formed', function() { | ||
432 | var | ||
433 | byte, | ||
434 | tag, | ||
435 | type, | ||
436 | minVideoPts, | ||
437 | maxVideoPts, | ||
438 | minAudioPts, | ||
439 | maxAudioPts, | ||
440 | currentPts = 0, | ||
441 | lastTime = 0; | ||
442 | parser.parseSegmentBinaryData(window.bcSegment); | ||
443 | |||
444 | minVideoPts = parser.stats.minVideoPts(); | ||
445 | maxVideoPts = parser.stats.maxVideoPts(); | ||
446 | minAudioPts = parser.stats.minAudioPts(); | ||
447 | maxAudioPts = parser.stats.maxAudioPts(); | ||
448 | |||
449 | while (parser.tagsAvailable()) { | ||
450 | tag = parser.getNextTag(); | ||
451 | type = tag.bytes[0]; | ||
452 | |||
453 | ok(tag.pts >= currentPts, 'presentation time stamps are increasing'); | ||
454 | currentPts = tag.pts; | ||
455 | |||
456 | // generic flv headers | ||
457 | switch (type) { | ||
458 | case 8: ok(true, 'the type is audio'); | ||
459 | ok(minAudioPts <= currentPts, 'not less than minimum audio PTS'); | ||
460 | ok(maxAudioPts >= currentPts, 'not greater than max audio PTS'); | ||
461 | break; | ||
462 | case 9: ok(true, 'the type is video'); | ||
463 | ok(minVideoPts <= currentPts, 'not less than minimum video PTS'); | ||
464 | ok(maxVideoPts >= currentPts, 'not greater than max video PTS'); | ||
465 | break; | ||
466 | case 18: ok(true, 'the type is script'); | ||
467 | break; | ||
468 | default: ok(false, 'the type (' + type + ') is unrecognized'); | ||
469 | } | ||
470 | |||
471 | byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8; | ||
472 | equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct'); | ||
473 | |||
474 | byte = tag.view.getUint32(5) & 0xFFFFFF00; | ||
475 | ok(byte >= lastTime, | ||
476 | 'timestamp is increasing. last pts: ' + lastTime + ' this pts: ' + byte); | ||
477 | lastTime = byte; | ||
478 | |||
479 | // tag type-specific headers | ||
480 | ({ | ||
481 | 8: testAudioTag, | ||
482 | 9: testVideoTag, | ||
483 | 18: testScriptTag | ||
484 | })[type](tag); | ||
485 | |||
486 | // previous tag size | ||
487 | equal(tag.bytes.byteLength - 4, | ||
488 | tag.view.getUint32(tag.bytes.byteLength - 4), | ||
489 | 'the size of the previous tag is correct'); | ||
490 | } | ||
491 | }); | ||
492 | })(window); |
test/transmuxer_test.js
deleted
100644 → 0
1 | (function(window, videojs) { | ||
2 | 'use strict'; | ||
3 | /* | ||
4 | ======== A Handy Little QUnit Reference ======== | ||
5 | http://api.qunitjs.com/ | ||
6 | |||
7 | Test methods: | ||
8 | module(name, {[setup][ ,teardown]}) | ||
9 | test(name, callback) | ||
10 | expect(numberOfAssertions) | ||
11 | stop(increment) | ||
12 | start(decrement) | ||
13 | Test assertions: | ||
14 | ok(value, [message]) | ||
15 | equal(actual, expected, [message]) | ||
16 | notEqual(actual, expected, [message]) | ||
17 | deepEqual(actual, expected, [message]) | ||
18 | notDeepEqual(actual, expected, [message]) | ||
19 | strictEqual(actual, expected, [message]) | ||
20 | notStrictEqual(actual, expected, [message]) | ||
21 | throws(block, [expected], [message]) | ||
22 | */ | ||
23 | var | ||
24 | TransportPacketStream = videojs.mp2t.TransportPacketStream, | ||
25 | transportPacketStream, | ||
26 | TransportParseStream = videojs.mp2t.TransportParseStream, | ||
27 | transportParseStream, | ||
28 | ElementaryStream = videojs.mp2t.ElementaryStream, | ||
29 | elementaryStream, | ||
30 | H264Stream = videojs.mp2t.H264Stream, | ||
31 | h264Stream, | ||
32 | VideoSegmentStream = videojs.mp2t.VideoSegmentStream, | ||
33 | videoSegmentStream, | ||
34 | AacStream = videojs.mp2t.AacStream, | ||
35 | aacStream, | ||
36 | Transmuxer = videojs.mp2t.Transmuxer, | ||
37 | transmuxer, | ||
38 | |||
39 | MP2T_PACKET_LENGTH = videojs.mp2t.MP2T_PACKET_LENGTH, | ||
40 | H264_STREAM_TYPE = videojs.mp2t.H264_STREAM_TYPE, | ||
41 | ADTS_STREAM_TYPE = videojs.mp2t.ADTS_STREAM_TYPE, | ||
42 | packetize, | ||
43 | |||
44 | PAT, | ||
45 | PMT, | ||
46 | standalonePes, | ||
47 | validateTrack, | ||
48 | validateTrackFragment, | ||
49 | |||
50 | transportPacket, | ||
51 | videoPes, | ||
52 | audioPes; | ||
53 | |||
54 | module('MP2T Packet Stream', { | ||
55 | setup: function() { | ||
56 | transportPacketStream = new TransportPacketStream(); | ||
57 | } | ||
58 | }); | ||
59 | |||
60 | test('empty input does not error', function() { | ||
61 | transportPacketStream.push(new Uint8Array([])); | ||
62 | ok(true, 'did not throw'); | ||
63 | }); | ||
64 | test('parses a generic packet', function() { | ||
65 | var datas = []; | ||
66 | transportPacketStream.on('data', function(event) { | ||
67 | datas.push(event); | ||
68 | }); | ||
69 | transportPacketStream.push(new Uint8Array(188)); | ||
70 | |||
71 | equal(1, datas.length, 'fired one event'); | ||
72 | equal(datas[0].byteLength, 188, 'delivered the packet'); | ||
73 | }); | ||
74 | |||
75 | test('buffers partial packets', function() { | ||
76 | var datas = []; | ||
77 | transportPacketStream.on('data', function(event) { | ||
78 | datas.push(event); | ||
79 | }); | ||
80 | transportPacketStream.push(new Uint8Array(187)); | ||
81 | |||
82 | equal(0, datas.length, 'did not fire an event'); | ||
83 | |||
84 | transportPacketStream.push(new Uint8Array(189)); | ||
85 | equal(2, datas.length, 'fired events'); | ||
86 | equal(188, datas[0].byteLength, 'parsed the first packet'); | ||
87 | equal(188, datas[1].byteLength, 'parsed the second packet'); | ||
88 | }); | ||
89 | |||
90 | test('parses multiple packets delivered at once', function() { | ||
91 | var datas = []; | ||
92 | transportPacketStream.on('data', function(event) { | ||
93 | datas.push(event); | ||
94 | }); | ||
95 | |||
96 | transportPacketStream.push(new Uint8Array(188 * 3)); | ||
97 | equal(3, datas.length, 'fired three events'); | ||
98 | equal(188, datas[0].byteLength, 'parsed the first packet'); | ||
99 | equal(188, datas[1].byteLength, 'parsed the second packet'); | ||
100 | equal(188, datas[2].byteLength, 'parsed the third packet'); | ||
101 | }); | ||
102 | |||
103 | test('buffers extra after multiple packets', function() { | ||
104 | var datas = []; | ||
105 | transportPacketStream.on('data', function(event) { | ||
106 | datas.push(event); | ||
107 | }); | ||
108 | |||
109 | transportPacketStream.push(new Uint8Array(188 * 2 + 10)); | ||
110 | equal(2, datas.length, 'fired two events'); | ||
111 | equal(188, datas[0].byteLength, 'parsed the first packet'); | ||
112 | equal(188, datas[1].byteLength, 'parsed the second packet'); | ||
113 | |||
114 | transportPacketStream.push(new Uint8Array(178)); | ||
115 | equal(3, datas.length, 'fired a final event'); | ||
116 | equal(188, datas[2].length, 'parsed the finel packet'); | ||
117 | }); | ||
118 | |||
119 | module('MP2T TransportParseStream', { | ||
120 | setup: function() { | ||
121 | transportPacketStream = new TransportPacketStream(); | ||
122 | transportParseStream = new TransportParseStream(); | ||
123 | |||
124 | transportPacketStream.pipe(transportParseStream); | ||
125 | } | ||
126 | }); | ||
127 | |||
128 | test('emits an error on an invalid packet', function() { | ||
129 | var errors = []; | ||
130 | transportParseStream.on('error', function(error) { | ||
131 | errors.push(error); | ||
132 | }); | ||
133 | transportParseStream.push(new Uint8Array(188)); | ||
134 | |||
135 | equal(1, errors.length, 'emitted an error'); | ||
136 | }); | ||
137 | |||
138 | test('parses generic packet properties', function() { | ||
139 | var packet; | ||
140 | transportParseStream.on('data', function(data) { | ||
141 | packet = data; | ||
142 | }); | ||
143 | |||
144 | transportParseStream.push(new Uint8Array([ | ||
145 | 0x47, // sync byte | ||
146 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00 | ||
147 | 0x40, 0x01, 0x6c | ||
148 | ])); | ||
149 | ok(packet.payloadUnitStartIndicator, 'parsed payload_unit_start_indicator'); | ||
150 | ok(packet.pid, 'parsed PID'); | ||
151 | }); | ||
152 | |||
153 | test('parses piped data events', function() { | ||
154 | var packet; | ||
155 | transportParseStream.on('data', function(data) { | ||
156 | packet = data; | ||
157 | }); | ||
158 | |||
159 | transportParseStream.push(new Uint8Array([ | ||
160 | 0x47, // sync byte | ||
161 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00 | ||
162 | 0x40, 0x01, 0x6c | ||
163 | ])); | ||
164 | |||
165 | ok(packet, 'parsed a packet'); | ||
166 | }); | ||
167 | |||
168 | test('parses a data packet with adaptation fields', function() { | ||
169 | var packet; | ||
170 | transportParseStream.on('data', function(data) { | ||
171 | packet = data; | ||
172 | }); | ||
173 | |||
174 | transportParseStream.push(new Uint8Array([ | ||
175 | 0x47, // sync byte | ||
176 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000 tsc:01 afc:10 cc:11 afl:00 0000 00 stuffing:00 0000 00 pscp:00 0001 padding:0000 | ||
177 | 0x40, 0x00, 0x6c, 0x00, 0x00, 0x10 | ||
178 | ])); | ||
179 | strictEqual(packet.type, 'pat', 'parsed the packet type'); | ||
180 | }); | ||
181 | |||
182 | test('parses a PES packet', function() { | ||
183 | var packet; | ||
184 | transportParseStream.on('data', function(data) { | ||
185 | packet = data; | ||
186 | }); | ||
187 | |||
188 | // setup a program map table | ||
189 | transportParseStream.programMapTable = { | ||
190 | 0x0010: videojs.mp2t.H264_STREAM_TYPE | ||
191 | }; | ||
192 | |||
193 | transportParseStream.push(new Uint8Array([ | ||
194 | 0x47, // sync byte | ||
195 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:01 cc:11 padding:00 | ||
196 | 0x40, 0x02, 0x5c | ||
197 | ])); | ||
198 | strictEqual(packet.type, 'pes', 'parsed a PES packet'); | ||
199 | }); | ||
200 | |||
201 | test('parses packets with variable length adaptation fields and a payload', function() { | ||
202 | var packet; | ||
203 | transportParseStream.on('data', function(data) { | ||
204 | packet = data; | ||
205 | }); | ||
206 | |||
207 | // setup a program map table | ||
208 | transportParseStream.programMapTable = { | ||
209 | 0x0010: videojs.mp2t.H264_STREAM_TYPE | ||
210 | }; | ||
211 | |||
212 | transportParseStream.push(new Uint8Array([ | ||
213 | 0x47, // sync byte | ||
214 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:11 cc:11 afl:00 0000 11 stuffing:00 0000 0000 00 pscp:00 0001 | ||
215 | 0x40, 0x02, 0x7c, 0x0c, 0x00, 0x01 | ||
216 | ])); | ||
217 | strictEqual(packet.type, 'pes', 'parsed a PES packet'); | ||
218 | }); | ||
219 | |||
220 | /* | ||
221 | Packet Header: | ||
222 | | sb | tei pusi tp pid:5 | pid | tsc afc cc | | ||
223 | with af: | ||
224 | | afl | ... | <data> | | ||
225 | without af: | ||
226 | | <data> | | ||
227 | |||
228 | PAT: | ||
229 | | pf? | ... | | ||
230 | | tid | ssi '0' r sl:4 | sl | tsi:8 | | ||
231 | | tsi | r vn cni | sn | lsn | | ||
232 | |||
233 | with program_number == '0': | ||
234 | | pn | pn | r np:5 | np | | ||
235 | otherwise: | ||
236 | | pn | pn | r pmp:5 | pmp | | ||
237 | */ | ||
238 | |||
239 | PAT = [ | ||
240 | 0x47, // sync byte | ||
241 | // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000 | ||
242 | 0x40, 0x00, | ||
243 | // tsc:01 afc:01 cc:0000 pointer_field:0000 0000 | ||
244 | 0x50, 0x00, | ||
245 | // tid:0000 0000 ssi:0 0:0 r:00 sl:0000 0000 0000 | ||
246 | 0x00, 0x00, 0x00, | ||
247 | // tsi:0000 0000 0000 0000 | ||
248 | 0x00, 0x00, | ||
249 | // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000 | ||
250 | 0x01, 0x00, 0x00, | ||
251 | // pn:0000 0000 0000 0001 | ||
252 | 0x00, 0x01, | ||
253 | // r:000 pmp:0 0000 0010 0000 | ||
254 | 0x00, 0x10, | ||
255 | // crc32:0000 0000 0000 0000 0000 0000 0000 0000 | ||
256 | 0x00, 0x00, 0x00, 0x00 | ||
257 | ]; | ||
258 | |||
259 | test('parses the program map table pid from the program association table (PAT)', function() { | ||
260 | var packet; | ||
261 | transportParseStream.on('data', function(data) { | ||
262 | packet = data; | ||
263 | }); | ||
264 | |||
265 | transportParseStream.push(new Uint8Array(PAT)); | ||
266 | ok(packet, 'parsed a packet'); | ||
267 | strictEqual(0x0010, transportParseStream.pmtPid, 'parsed PMT pid'); | ||
268 | }); | ||
269 | |||
270 | PMT = [ | ||
271 | 0x47, // sync byte | ||
272 | // tei:0 pusi:1 tp:0 pid:0 0000 0010 0000 | ||
273 | 0x40, 0x10, | ||
274 | // tsc:01 afc:01 cc:0000 pointer_field:0000 0000 | ||
275 | 0x50, 0x00, | ||
276 | // tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 0111 | ||
277 | 0x02, 0x00, 0x17, | ||
278 | // pn:0000 0000 0000 0001 | ||
279 | 0x00, 0x01, | ||
280 | // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000 | ||
281 | 0x01, 0x00, 0x00, | ||
282 | // r:000 ppid:0 0011 1111 1111 | ||
283 | 0x03, 0xff, | ||
284 | // r:0000 pil:0000 0000 0000 | ||
285 | 0x00, 0x00, | ||
286 | // h264 | ||
287 | // st:0001 1010 r:000 epid:0 0000 0001 0001 | ||
288 | 0x1b, 0x00, 0x11, | ||
289 | // r:0000 esil:0000 0000 0000 | ||
290 | 0x00, 0x00, | ||
291 | // adts | ||
292 | // st:0000 1111 r:000 epid:0 0000 0001 0010 | ||
293 | 0x0f, 0x00, 0x12, | ||
294 | // r:0000 esil:0000 0000 0000 | ||
295 | 0x00, 0x00, | ||
296 | // crc | ||
297 | 0x00, 0x00, 0x00, 0x00 | ||
298 | ]; | ||
299 | |||
300 | test('parse the elementary streams from a program map table', function() { | ||
301 | var packet; | ||
302 | transportParseStream.on('data', function(data) { | ||
303 | packet = data; | ||
304 | }); | ||
305 | transportParseStream.pmtPid = 0x0010; | ||
306 | |||
307 | transportParseStream.push(new Uint8Array(PMT.concat(0, 0, 0, 0, 0))); | ||
308 | |||
309 | ok(packet, 'parsed a packet'); | ||
310 | ok(transportParseStream.programMapTable, 'parsed a program map'); | ||
311 | strictEqual(0x1b, transportParseStream.programMapTable[0x11], 'associated h264 with pid 0x11'); | ||
312 | strictEqual(0x0f, transportParseStream.programMapTable[0x12], 'associated adts with pid 0x12'); | ||
313 | strictEqual(transportParseStream.programMapTable[0], undefined, 'ignored trailing stuffing bytes'); | ||
314 | deepEqual(transportParseStream.programMapTable, packet.programMapTable, 'recorded the PMT'); | ||
315 | }); | ||
316 | |||
317 | test('parses an elementary stream packet with just a pts', function() { | ||
318 | var packet; | ||
319 | transportParseStream.on('data', function(data) { | ||
320 | packet = data; | ||
321 | }); | ||
322 | |||
323 | transportParseStream.programMapTable = { | ||
324 | 0x11: 0x1b // pid 0x11 is h264 data | ||
325 | }; | ||
326 | |||
327 | transportParseStream.push(new Uint8Array([ | ||
328 | 0x47, // sync byte | ||
329 | // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001 | ||
330 | 0x40, 0x11, | ||
331 | // tsc:01 afc:01 cc:0000 | ||
332 | 0x50, | ||
333 | // pscp:0000 0000 0000 0000 0000 0001 | ||
334 | 0x00, 0x00, 0x01, | ||
335 | // sid:0000 0000 ppl:0000 0000 0000 1001 | ||
336 | 0x00, 0x00, 0x09, | ||
337 | // 10 psc:00 pp:0 dai:1 c:0 ooc:0 | ||
338 | 0x84, | ||
339 | // pdf:10 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0 | ||
340 | 0xc0, | ||
341 | // phdl:0000 0101 '0010' pts:000 mb:1 pts:0000 0000 | ||
342 | 0x05, 0x21, 0x00, | ||
343 | // pts:0000 000 mb:1 pts:0000 0000 pts:0000 000 mb:1 | ||
344 | 0x01, 0x00, 0x01, | ||
345 | // "data":0101 | ||
346 | 0x11 | ||
347 | ])); | ||
348 | |||
349 | ok(packet, 'parsed a packet'); | ||
350 | equal('pes', packet.type, 'recognized a PES packet'); | ||
351 | equal(0x1b, packet.streamType, 'tracked the stream_type'); | ||
352 | equal(1, packet.data.byteLength, 'parsed a single data byte'); | ||
353 | equal(0x11, packet.data[0], 'parsed the data'); | ||
354 | equal(0, packet.pts, 'parsed the pts'); | ||
355 | }); | ||
356 | |||
357 | test('parses an elementary stream packet with a pts and dts', function() { | ||
358 | var packet; | ||
359 | transportParseStream.on('data', function(data) { | ||
360 | packet = data; | ||
361 | }); | ||
362 | |||
363 | transportParseStream.programMapTable = { | ||
364 | 0x11: 0x1b // pid 0x11 is h264 data | ||
365 | }; | ||
366 | |||
367 | transportParseStream.push(new Uint8Array([ | ||
368 | 0x47, // sync byte | ||
369 | // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001 | ||
370 | 0x40, 0x11, | ||
371 | // tsc:01 afc:01 cc:0000 | ||
372 | 0x50, | ||
373 | // pscp:0000 0000 0000 0000 0000 0001 | ||
374 | 0x00, 0x00, 0x01, | ||
375 | // sid:0000 0000 ppl:0000 0000 0000 1110 | ||
376 | 0x00, 0x00, 0x0e, | ||
377 | // 10 psc:00 pp:0 dai:1 c:0 ooc:0 | ||
378 | 0x84, | ||
379 | // pdf:11 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0 | ||
380 | 0xe0, | ||
381 | // phdl:0000 1010 '0011' pts:000 mb:1 pts:0000 0000 | ||
382 | 0x0a, 0x21, 0x00, | ||
383 | // pts:0000 000 mb:1 pts:0000 0000 pts:0000 100 mb:1 | ||
384 | 0x01, 0x00, 0x09, | ||
385 | // '0001' dts:000 mb:1 dts:0000 0000 dts:0000 000 mb:1 | ||
386 | 0x11, 0x00, 0x01, | ||
387 | // dts:0000 0000 dts:0000 010 mb:1 | ||
388 | 0x00, 0x05, | ||
389 | // "data":0101 | ||
390 | 0x11 | ||
391 | ])); | ||
392 | |||
393 | ok(packet, 'parsed a packet'); | ||
394 | equal('pes', packet.type, 'recognized a PES packet'); | ||
395 | equal(0x1b, packet.streamType, 'tracked the stream_type'); | ||
396 | equal(1, packet.data.byteLength, 'parsed a single data byte'); | ||
397 | equal(0x11, packet.data[0], 'parsed the data'); | ||
398 | equal(4 / 90, packet.pts, 'parsed the pts'); | ||
399 | equal(2 / 90, packet.dts, 'parsed the dts'); | ||
400 | }); | ||
401 | |||
402 | /** | ||
403 | * Helper function to create transport stream PES packets | ||
404 | * @param pid {uint8} - the program identifier (PID) | ||
405 | * @param data {arraylike} - the payload bytes | ||
406 | * @payload first {boolean} - true if this PES should be a payload | ||
407 | * unit start | ||
408 | */ | ||
409 | transportPacket = function(pid, data, first) { | ||
410 | var | ||
411 | adaptationFieldLength = 188 - data.length - (first ? 15 : 14), | ||
412 | // transport_packet(), Rec. ITU-T H.222.0, Table 2-2 | ||
413 | result = [ | ||
414 | // sync byte | ||
415 | 0x47, | ||
416 | // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001 | ||
417 | 0x40, pid, | ||
418 | // tsc:01 afc:11 cc:0000 | ||
419 | 0x70 | ||
420 | ].concat([ | ||
421 | // afl | ||
422 | adaptationFieldLength & 0xff, | ||
423 | // di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0 | ||
424 | 0x00 | ||
425 | ]), | ||
426 | i; | ||
427 | |||
428 | i = adaptationFieldLength - 1; | ||
429 | while (i--) { | ||
430 | // stuffing_bytes | ||
431 | result.push(0xff); | ||
432 | } | ||
433 | |||
434 | // PES_packet(), Rec. ITU-T H.222.0, Table 2-21 | ||
435 | result = result.concat([ | ||
436 | // pscp:0000 0000 0000 0000 0000 0001 | ||
437 | 0x00, 0x00, 0x01, | ||
438 | // sid:0000 0000 ppl:0000 0000 0000 0101 | ||
439 | 0x00, 0x00, 0x05, | ||
440 | // 10 psc:00 pp:0 dai:1 c:0 ooc:0 | ||
441 | 0x84, | ||
442 | // pdf:00 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0 | ||
443 | 0x20, | ||
444 | // phdl:0000 0000 | ||
445 | 0x00 | ||
446 | ]); | ||
447 | if (first) { | ||
448 | result.push(0x00); | ||
449 | } | ||
450 | return result.concat(data); | ||
451 | }; | ||
452 | |||
453 | /** | ||
454 | * Helper function to create video PES packets | ||
455 | * @param data {arraylike} - the payload bytes | ||
456 | * @payload first {boolean} - true if this PES should be a payload | ||
457 | * unit start | ||
458 | */ | ||
459 | videoPes = function(data, first) { | ||
460 | return transportPacket(0x11, [ | ||
461 | // NAL unit start code | ||
462 | 0x00, 0x00, 0x01 | ||
463 | ].concat(data), first); | ||
464 | }; | ||
465 | standalonePes = videoPes([0xaf, 0x01], true); | ||
466 | |||
467 | /** | ||
468 | * Helper function to create audio PES packets | ||
469 | * @param data {arraylike} - the payload bytes | ||
470 | * @payload first {boolean} - true if this PES should be a payload | ||
471 | * unit start | ||
472 | */ | ||
473 | audioPes = function(data, first) { | ||
474 | var frameLength = data.length + 7; | ||
475 | return transportPacket(0x12, [ | ||
476 | 0xff, 0xf1, // no CRC | ||
477 | 0x10, // AAC Main, 44.1KHz | ||
478 | 0xb0 | ((frameLength & 0x1800) >> 11), // 2 channels | ||
479 | (frameLength & 0x7f8) >> 3, | ||
480 | ((frameLength & 0x07) << 5) + 7, // frame length in bytes | ||
481 | 0x00 // one AAC per ADTS frame | ||
482 | ].concat(data), first); | ||
483 | }; | ||
484 | |||
485 | test('parses an elementary stream packet without a pts or dts', function() { | ||
486 | |||
487 | var packet; | ||
488 | transportParseStream.on('data', function(data) { | ||
489 | packet = data; | ||
490 | }); | ||
491 | |||
492 | // pid 0x11 is h264 data | ||
493 | transportParseStream.programMapTable = { | ||
494 | 0x11: H264_STREAM_TYPE | ||
495 | }; | ||
496 | |||
497 | transportParseStream.push(new Uint8Array(standalonePes)); | ||
498 | |||
499 | ok(packet, 'parsed a packet'); | ||
500 | equal('pes', packet.type, 'recognized a PES packet'); | ||
501 | equal(0x1b, packet.streamType, 'tracked the stream_type'); | ||
502 | equal(2 + 4, packet.data.byteLength, 'parsed two data bytes'); | ||
503 | equal(0xaf, packet.data[packet.data.length - 2], 'parsed the first data byte'); | ||
504 | equal(0x01, packet.data[packet.data.length - 1], 'parsed the second data byte'); | ||
505 | ok(!packet.pts, 'did not parse a pts'); | ||
506 | ok(!packet.dts, 'did not parse a dts'); | ||
507 | }); | ||
508 | |||
509 | module('MP2T ElementaryStream', { | ||
510 | setup: function() { | ||
511 | elementaryStream = new ElementaryStream(); | ||
512 | } | ||
513 | }); | ||
514 | |||
515 | packetize = function(data) { | ||
516 | var packet = new Uint8Array(MP2T_PACKET_LENGTH); | ||
517 | packet.set(data); | ||
518 | return packet; | ||
519 | }; | ||
520 | |||
521 | test('parses metadata events from PSI packets', function() { | ||
522 | var | ||
523 | metadatas = [], | ||
524 | datas = 0, | ||
525 | sortById = function(left, right) { | ||
526 | return left.id - right.id; | ||
527 | }; | ||
528 | elementaryStream.on('data', function(data) { | ||
529 | if (data.type === 'metadata') { | ||
530 | metadatas.push(data); | ||
531 | } | ||
532 | datas++; | ||
533 | }); | ||
534 | elementaryStream.push({ | ||
535 | type: 'pat' | ||
536 | }); | ||
537 | elementaryStream.push({ | ||
538 | type: 'pmt', | ||
539 | programMapTable: { | ||
540 | 1: 0x1b, | ||
541 | 2: 0x0f | ||
542 | } | ||
543 | }); | ||
544 | |||
545 | equal(1, datas, 'data fired'); | ||
546 | equal(1, metadatas.length, 'metadata generated'); | ||
547 | metadatas[0].tracks.sort(sortById); | ||
548 | deepEqual(metadatas[0].tracks, [{ | ||
549 | id: 1, | ||
550 | codec: 'avc', | ||
551 | type: 'video' | ||
552 | }, { | ||
553 | id: 2, | ||
554 | codec: 'adts', | ||
555 | type: 'audio' | ||
556 | }], 'identified two tracks'); | ||
557 | }); | ||
558 | |||
559 | test('parses standalone program stream packets', function() { | ||
560 | var packets = []; | ||
561 | elementaryStream.on('data', function(packet) { | ||
562 | packets.push(packet); | ||
563 | }); | ||
564 | elementaryStream.push({ | ||
565 | type: 'pes', | ||
566 | streamType: ADTS_STREAM_TYPE, | ||
567 | payloadUnitStartIndicator: true, | ||
568 | pts: 7, | ||
569 | dts: 8, | ||
570 | data: new Uint8Array(19) | ||
571 | }); | ||
572 | elementaryStream.end(); | ||
573 | |||
574 | equal(1, packets.length, 'built one packet'); | ||
575 | equal('audio', packets[0].type, 'identified audio data'); | ||
576 | equal(19, packets[0].data.byteLength, 'parsed the correct payload size'); | ||
577 | }); | ||
578 | |||
579 | test('aggregates program stream packets from the transport stream', function() { | ||
580 | var events = []; | ||
581 | elementaryStream.on('data', function(event) { | ||
582 | events.push(event); | ||
583 | }); | ||
584 | |||
585 | elementaryStream.push({ | ||
586 | type: 'pes', | ||
587 | streamType: H264_STREAM_TYPE, | ||
588 | payloadUnitStartIndicator: true, | ||
589 | pts: 7, | ||
590 | dts: 8, | ||
591 | data: new Uint8Array(7) | ||
592 | }); | ||
593 | equal(0, events.length, 'buffers partial packets'); | ||
594 | |||
595 | elementaryStream.push({ | ||
596 | type: 'pes', | ||
597 | streamType: H264_STREAM_TYPE, | ||
598 | data: new Uint8Array(13) | ||
599 | }); | ||
600 | elementaryStream.end(); | ||
601 | equal(1, events.length, 'built one packet'); | ||
602 | equal('video', events[0].type, 'identified video data'); | ||
603 | equal(events[0].pts, 7, 'passed along the pts'); | ||
604 | equal(events[0].dts, 8, 'passed along the dts'); | ||
605 | equal(20, events[0].data.byteLength, 'concatenated transport packets'); | ||
606 | }); | ||
607 | |||
608 | test('buffers audio and video program streams individually', function() { | ||
609 | var events = []; | ||
610 | elementaryStream.on('data', function(event) { | ||
611 | events.push(event); | ||
612 | }); | ||
613 | |||
614 | elementaryStream.push({ | ||
615 | type: 'pes', | ||
616 | payloadUnitStartIndicator: true, | ||
617 | streamType: H264_STREAM_TYPE, | ||
618 | data: new Uint8Array(1) | ||
619 | }); | ||
620 | elementaryStream.push({ | ||
621 | type: 'pes', | ||
622 | payloadUnitStartIndicator: true, | ||
623 | streamType: ADTS_STREAM_TYPE, | ||
624 | data: new Uint8Array(1) | ||
625 | }); | ||
626 | equal(0, events.length, 'buffers partial packets'); | ||
627 | |||
628 | elementaryStream.push({ | ||
629 | type: 'pes', | ||
630 | streamType: H264_STREAM_TYPE, | ||
631 | data: new Uint8Array(1) | ||
632 | }); | ||
633 | elementaryStream.push({ | ||
634 | type: 'pes', | ||
635 | streamType: ADTS_STREAM_TYPE, | ||
636 | data: new Uint8Array(1) | ||
637 | }); | ||
638 | elementaryStream.end(); | ||
639 | equal(2, events.length, 'parsed a complete packet'); | ||
640 | equal('video', events[0].type, 'identified video data'); | ||
641 | equal('audio', events[1].type, 'identified audio data'); | ||
642 | }); | ||
643 | |||
644 | test('flushes the buffered packets when a new one of that type is started', function() { | ||
645 | var packets = []; | ||
646 | elementaryStream.on('data', function(packet) { | ||
647 | packets.push(packet); | ||
648 | }); | ||
649 | elementaryStream.push({ | ||
650 | type: 'pes', | ||
651 | payloadUnitStartIndicator: true, | ||
652 | streamType: H264_STREAM_TYPE, | ||
653 | data: new Uint8Array(1) | ||
654 | }); | ||
655 | elementaryStream.push({ | ||
656 | type: 'pes', | ||
657 | payloadUnitStartIndicator: true, | ||
658 | streamType: ADTS_STREAM_TYPE, | ||
659 | data: new Uint8Array(7) | ||
660 | }); | ||
661 | elementaryStream.push({ | ||
662 | type: 'pes', | ||
663 | streamType: H264_STREAM_TYPE, | ||
664 | data: new Uint8Array(1) | ||
665 | }); | ||
666 | equal(0, packets.length, 'buffers packets by type'); | ||
667 | |||
668 | elementaryStream.push({ | ||
669 | type: 'pes', | ||
670 | payloadUnitStartIndicator: true, | ||
671 | streamType: H264_STREAM_TYPE, | ||
672 | data: new Uint8Array(1) | ||
673 | }); | ||
674 | equal(1, packets.length, 'built one packet'); | ||
675 | equal('video', packets[0].type, 'identified video data'); | ||
676 | equal(2, packets[0].data.byteLength, 'concatenated packets'); | ||
677 | |||
678 | elementaryStream.end(); | ||
679 | equal(3, packets.length, 'built tow more packets'); | ||
680 | equal('video', packets[1].type, 'identified video data'); | ||
681 | equal(1, packets[1].data.byteLength, 'parsed the video payload'); | ||
682 | equal('audio', packets[2].type, 'identified audio data'); | ||
683 | equal(7, packets[2].data.byteLength, 'parsed the audio payload'); | ||
684 | }); | ||
685 | |||
686 | test('drops packets with unknown stream types', function() { | ||
687 | var packets = []; | ||
688 | elementaryStream.on('data', function(packet) { | ||
689 | packets.push(packet); | ||
690 | }); | ||
691 | elementaryStream.push({ | ||
692 | type: 'pes', | ||
693 | payloadUnitStartIndicator: true, | ||
694 | data: new Uint8Array(1) | ||
695 | }); | ||
696 | elementaryStream.push({ | ||
697 | type: 'pes', | ||
698 | payloadUnitStartIndicator: true, | ||
699 | data: new Uint8Array(1) | ||
700 | }); | ||
701 | |||
702 | equal(packets.length, 0, 'ignored unknown packets'); | ||
703 | }); | ||
704 | |||
705 | module('H264 Stream', { | ||
706 | setup: function() { | ||
707 | h264Stream = new H264Stream(); | ||
708 | } | ||
709 | }); | ||
710 | |||
711 | test('unpacks nal units from simple byte stream framing', function() { | ||
712 | var data; | ||
713 | h264Stream.on('data', function(event) { | ||
714 | data = event; | ||
715 | }); | ||
716 | |||
717 | // the simplest byte stream framing: | ||
718 | h264Stream.push({ | ||
719 | type: 'video', | ||
720 | data: new Uint8Array([ | ||
721 | 0x00, 0x00, 0x00, 0x01, | ||
722 | 0x09, 0x07, | ||
723 | 0x00, 0x00, 0x01 | ||
724 | ]) | ||
725 | }); | ||
726 | |||
727 | ok(data, 'generated a data event'); | ||
728 | equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter'); | ||
729 | equal(data.data.length, 2, 'calculated nal unit length'); | ||
730 | equal(data.data[1], 7, 'read a payload byte'); | ||
731 | }); | ||
732 | |||
733 | test('unpacks nal units from byte streams split across pushes', function() { | ||
734 | var data; | ||
735 | h264Stream.on('data', function(event) { | ||
736 | data = event; | ||
737 | }); | ||
738 | |||
739 | // handles byte streams split across pushes | ||
740 | h264Stream.push({ | ||
741 | type: 'video', | ||
742 | data: new Uint8Array([ | ||
743 | 0x00, 0x00, 0x00, 0x01, | ||
744 | 0x09, 0x07, 0x06, 0x05, | ||
745 | 0x04 | ||
746 | ]) | ||
747 | }); | ||
748 | ok(!data, 'buffers NAL units across events'); | ||
749 | |||
750 | h264Stream.push({ | ||
751 | type: 'video', | ||
752 | data: new Uint8Array([ | ||
753 | 0x03, 0x02, 0x01, | ||
754 | 0x00, 0x00, 0x01 | ||
755 | ]) | ||
756 | }); | ||
757 | ok(data, 'generated a data event'); | ||
758 | equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter'); | ||
759 | equal(data.data.length, 8, 'calculated nal unit length'); | ||
760 | equal(data.data[1], 7, 'read a payload byte'); | ||
761 | }); | ||
762 | |||
763 | test('buffers nal unit trailing zeros across pushes', function() { | ||
764 | var data = []; | ||
765 | h264Stream.on('data', function(event) { | ||
766 | data.push(event); | ||
767 | }); | ||
768 | |||
769 | // lots of zeros after the nal, stretching into the next push | ||
770 | h264Stream.push({ | ||
771 | type: 'video', | ||
772 | data: new Uint8Array([ | ||
773 | 0x00, 0x00, 0x00, 0x01, | ||
774 | 0x09, 0x07, 0x00, 0x00, | ||
775 | 0x00, 0x00, 0x00, 0x00, | ||
776 | 0x00, 0x00, 0x00, 0x00, | ||
777 | 0x00 | ||
778 | ]) | ||
779 | }); | ||
780 | equal(data.length, 1, 'delivered the first nal'); | ||
781 | |||
782 | h264Stream.push({ | ||
783 | type: 'video', | ||
784 | data: new Uint8Array([ | ||
785 | 0x00, 0x00, | ||
786 | 0x00, 0x00, 0x01, | ||
787 | 0x09, 0x06, | ||
788 | 0x00, 0x00, 0x01 | ||
789 | ]) | ||
790 | }); | ||
791 | equal(data.length, 2, 'generated data events'); | ||
792 | equal(data[0].data.length, 2, 'ignored trailing zeros'); | ||
793 | equal(data[0].data[0], 0x09, 'found the first nal start'); | ||
794 | equal(data[1].data.length, 2, 'found the following nal start'); | ||
795 | equal(data[1].data[0], 0x09, 'found the second nal start'); | ||
796 | }); | ||
797 | |||
798 | test('unpacks nal units from byte streams with split sync points', function() { | ||
799 | var data; | ||
800 | h264Stream.on('data', function(event) { | ||
801 | data = event; | ||
802 | }); | ||
803 | |||
804 | // handles sync points split across pushes | ||
805 | h264Stream.push({ | ||
806 | type: 'video', | ||
807 | data: new Uint8Array([ | ||
808 | 0x00, 0x00, 0x00, 0x01, | ||
809 | 0x09, 0x07, | ||
810 | 0x00]) | ||
811 | }); | ||
812 | ok(!data, 'buffers NAL units across events'); | ||
813 | |||
814 | h264Stream.push({ | ||
815 | type: 'video', | ||
816 | data: new Uint8Array([ | ||
817 | 0x00, 0x01 | ||
818 | ]) | ||
819 | }); | ||
820 | ok(data, 'generated a data event'); | ||
821 | equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter'); | ||
822 | equal(data.data.length, 2, 'calculated nal unit length'); | ||
823 | equal(data.data[1], 7, 'read a payload byte'); | ||
824 | }); | ||
825 | |||
826 | test('parses nal unit types', function() { | ||
827 | var data; | ||
828 | h264Stream.on('data', function(event) { | ||
829 | data = event; | ||
830 | }); | ||
831 | |||
832 | h264Stream.push({ | ||
833 | type: 'video', | ||
834 | data: new Uint8Array([ | ||
835 | 0x00, 0x00, 0x00, 0x01, | ||
836 | 0x09 | ||
837 | ]) | ||
838 | }); | ||
839 | h264Stream.end(); | ||
840 | |||
841 | ok(data, 'generated a data event'); | ||
842 | equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter'); | ||
843 | |||
844 | data = null; | ||
845 | h264Stream.push({ | ||
846 | type: 'video', | ||
847 | data: new Uint8Array([ | ||
848 | 0x00, 0x00, 0x00, 0x01, | ||
849 | 0x07, | ||
850 | 0x27, 0x42, 0xe0, 0x0b, | ||
851 | 0xa9, 0x18, 0x60, 0x9d, | ||
852 | 0x80, 0x35, 0x06, 0x01, | ||
853 | 0x06, 0xb6, 0xc2, 0xb5, | ||
854 | 0xef, 0x7c, 0x04 | ||
855 | ]) | ||
856 | }); | ||
857 | h264Stream.end(); | ||
858 | ok(data, 'generated a data event'); | ||
859 | equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set'); | ||
860 | |||
861 | data = null; | ||
862 | h264Stream.push({ | ||
863 | type: 'video', | ||
864 | data: new Uint8Array([ | ||
865 | 0x00, 0x00, 0x00, 0x01, | ||
866 | 0x08, 0x01 | ||
867 | ]) | ||
868 | }); | ||
869 | h264Stream.end(); | ||
870 | ok(data, 'generated a data event'); | ||
871 | equal(data.nalUnitType, 'pic_parameter_set_rbsp', 'identified a picture parameter set'); | ||
872 | |||
873 | data = null; | ||
874 | h264Stream.push({ | ||
875 | type: 'video', | ||
876 | data: new Uint8Array([ | ||
877 | 0x00, 0x00, 0x00, 0x01, | ||
878 | 0x05, 0x01 | ||
879 | ]) | ||
880 | }); | ||
881 | h264Stream.end(); | ||
882 | ok(data, 'generated a data event'); | ||
883 | equal(data.nalUnitType, 'slice_layer_without_partitioning_rbsp_idr', 'identified a key frame'); | ||
884 | }); | ||
885 | |||
886 | // MP4 expects H264 (aka AVC) data to be in storage format. Storage | ||
887 | // format is optimized for reliable, random-access media in contrast | ||
888 | // to the byte stream format that retransmits metadata regularly to | ||
889 | // allow decoders to quickly begin operation from wherever in the | ||
890 | // broadcast they begin receiving. | ||
891 | // Details on the byte stream format can be found in Annex B of | ||
892 | // Recommendation ITU-T H.264. | ||
893 | // The storage format is described in ISO/IEC 14496-15 | ||
894 | test('strips byte stream framing during parsing', function() { | ||
895 | var data = []; | ||
896 | h264Stream.on('data', function(event) { | ||
897 | data.push(event); | ||
898 | }); | ||
899 | |||
900 | h264Stream.push({ | ||
901 | type: 'video', | ||
902 | data: new Uint8Array([ | ||
903 | // -- NAL unit start | ||
904 | // zero_byte | ||
905 | 0x00, | ||
906 | // start_code_prefix_one_3bytes | ||
907 | 0x00, 0x00, 0x01, | ||
908 | // nal_unit_type (picture parameter set) | ||
909 | 0x08, | ||
910 | // fake data | ||
911 | 0x01, 0x02, 0x03, 0x04, | ||
912 | 0x05, 0x06, 0x07, | ||
913 | // trailing_zero_8bits * 5 | ||
914 | 0x00, 0x00, 0x00, 0x00, | ||
915 | 0x00, | ||
916 | |||
917 | // -- NAL unit start | ||
918 | // zero_byte | ||
919 | 0x00, | ||
920 | // start_code_prefix_one_3bytes | ||
921 | 0x00, 0x00, 0x01, | ||
922 | // nal_unit_type (access_unit_delimiter_rbsp) | ||
923 | 0x09, | ||
924 | // fake data | ||
925 | 0x06, 0x05, 0x04, 0x03, | ||
926 | 0x02, 0x01, 0x00 | ||
927 | ]) | ||
928 | }); | ||
929 | h264Stream.end(); | ||
930 | |||
931 | equal(data.length, 2, 'parsed two NAL units'); | ||
932 | deepEqual(new Uint8Array([ | ||
933 | 0x08, | ||
934 | 0x01, 0x02, 0x03, 0x04, | ||
935 | 0x05, 0x06, 0x07 | ||
936 | ]), new Uint8Array(data[0].data), 'parsed the first NAL unit'); | ||
937 | deepEqual(new Uint8Array([ | ||
938 | 0x09, | ||
939 | 0x06, 0x05, 0x04, 0x03, | ||
940 | 0x02, 0x01, 0x00 | ||
941 | ]), new Uint8Array(data[1].data), 'parsed the second NAL unit'); | ||
942 | }); | ||
943 | |||
944 | module('VideoSegmentStream', { | ||
945 | setup: function() { | ||
946 | videoSegmentStream = new VideoSegmentStream({}); | ||
947 | } | ||
948 | }); | ||
949 | |||
950 | // see ISO/IEC 14496-15, Section 5 "AVC elementary streams and sample definitions" | ||
951 | test('concatenates NAL units into AVC elementary streams', function() { | ||
952 | var segment, boxes; | ||
953 | videoSegmentStream.on('data', function(data) { | ||
954 | segment = data; | ||
955 | }); | ||
956 | videoSegmentStream.push({ | ||
957 | data: new Uint8Array([ | ||
958 | 0x08, | ||
959 | 0x01, 0x02, 0x03 | ||
960 | ]) | ||
961 | }); | ||
962 | videoSegmentStream.push({ | ||
963 | data: new Uint8Array([ | ||
964 | 0x08, | ||
965 | 0x04, 0x03, 0x02, 0x01, 0x00 | ||
966 | ]) | ||
967 | }); | ||
968 | videoSegmentStream.end(); | ||
969 | |||
970 | ok(segment, 'generated a data event'); | ||
971 | boxes = videojs.inspectMp4(segment); | ||
972 | equal(boxes[1].byteLength, | ||
973 | (4 + 4) + (4 + 6), | ||
974 | 'wrote the correct number of bytes'); | ||
975 | deepEqual(new Uint8Array(segment.subarray(boxes[0].size + 8)), new Uint8Array([ | ||
976 | 0, 0, 0, 4, | ||
977 | 0x08, 0x01, 0x02, 0x03, | ||
978 | 0, 0, 0, 6, | ||
979 | 0x08, 0x04, 0x03, 0x02, 0x01, 0x00 | ||
980 | ]), 'wrote an AVC stream into the mdat'); | ||
981 | }); | ||
982 | |||
983 | test('scales DTS values from milliseconds to 90kHz', function() { | ||
984 | var segment, boxes, samples; | ||
985 | videoSegmentStream.on('data', function(data) { | ||
986 | segment = data; | ||
987 | }); | ||
988 | videoSegmentStream.push({ | ||
989 | data: new Uint8Array([0x09, 0x01]), | ||
990 | nalUnitType: 'access_unit_delimiter_rbsp', | ||
991 | dts: 1 | ||
992 | }); | ||
993 | videoSegmentStream.push({ | ||
994 | data: new Uint8Array([0x09, 0x01]), | ||
995 | nalUnitType: 'access_unit_delimiter_rbsp', | ||
996 | dts: 2 | ||
997 | }); | ||
998 | videoSegmentStream.push({ | ||
999 | data: new Uint8Array([0x09, 0x01]), | ||
1000 | nalUnitType: 'access_unit_delimiter_rbsp', | ||
1001 | dts: 4 | ||
1002 | }); | ||
1003 | videoSegmentStream.end(); | ||
1004 | |||
1005 | boxes = videojs.inspectMp4(segment); | ||
1006 | samples = boxes[0].boxes[1].boxes[2].samples; | ||
1007 | equal(samples.length, 3, 'generated two samples'); | ||
1008 | equal(samples[0].duration, 1 * 90, 'multiplied DTS duration by 90'); | ||
1009 | equal(samples[1].duration, 2 * 90, 'multiplied DTS duration by 90'); | ||
1010 | equal(samples[2].duration, 2 * 90, 'inferred the final sample duration'); | ||
1011 | }); | ||
1012 | |||
1013 | module('AAC Stream', { | ||
1014 | setup: function() { | ||
1015 | aacStream = new AacStream(); | ||
1016 | } | ||
1017 | }); | ||
1018 | |||
1019 | test('generates AAC frame events from ADTS bytes', function() { | ||
1020 | var frames = []; | ||
1021 | aacStream.on('data', function(frame) { | ||
1022 | frames.push(frame); | ||
1023 | }); | ||
1024 | aacStream.push({ | ||
1025 | type: 'audio', | ||
1026 | data: new Uint8Array([ | ||
1027 | 0xff, 0xf1, // no CRC | ||
1028 | 0x10, // AAC Main, 44.1KHz | ||
1029 | 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes | ||
1030 | 0x00, // one AAC per ADTS frame | ||
1031 | 0x12, 0x34, // AAC payload | ||
1032 | 0x56, 0x78 // extra junk that should be ignored | ||
1033 | ]) | ||
1034 | }); | ||
1035 | |||
1036 | equal(frames.length, 1, 'generated one frame'); | ||
1037 | deepEqual(new Uint8Array(frames[0].data), | ||
1038 | new Uint8Array([0x12, 0x34]), | ||
1039 | 'extracted AAC frame'); | ||
1040 | equal(frames[0].channelcount, 2, 'parsed channelcount'); | ||
1041 | equal(frames[0].samplerate, 44100, 'parsed samplerate'); | ||
1042 | |||
1043 | // Chrome only supports 8, 16, and 32 bit sample sizes. Assuming the | ||
1044 | // default value of 16 in ISO/IEC 14496-12 AudioSampleEntry is | ||
1045 | // acceptable. | ||
1046 | equal(frames[0].samplesize, 16, 'parsed samplesize'); | ||
1047 | }); | ||
1048 | |||
1049 | test('parses across packets', function() { | ||
1050 | |||
1051 | var frames = []; | ||
1052 | aacStream.on('data', function(frame) { | ||
1053 | frames.push(frame); | ||
1054 | }); | ||
1055 | aacStream.push({ | ||
1056 | type: 'audio', | ||
1057 | data: new Uint8Array([ | ||
1058 | 0xff, 0xf1, // no CRC | ||
1059 | 0x10, // AAC Main, 44.1KHz | ||
1060 | 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes | ||
1061 | 0x00, // one AAC per ADTS frame | ||
1062 | 0x12, 0x34 // AAC payload 1 | ||
1063 | ]) | ||
1064 | }); | ||
1065 | aacStream.push({ | ||
1066 | type: 'audio', | ||
1067 | data: new Uint8Array([ | ||
1068 | 0xff, 0xf1, // no CRC | ||
1069 | 0x10, // AAC Main, 44.1KHz | ||
1070 | 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes | ||
1071 | 0x00, // one AAC per ADTS frame | ||
1072 | 0x9a, 0xbc, // AAC payload 2 | ||
1073 | 0xde, 0xf0 // extra junk that should be ignored | ||
1074 | ]) | ||
1075 | }); | ||
1076 | |||
1077 | equal(frames.length, 2, 'parsed two frames'); | ||
1078 | deepEqual(new Uint8Array(frames[1].data), | ||
1079 | new Uint8Array([0x9a, 0xbc]), | ||
1080 | 'extracted the second AAC frame'); | ||
1081 | }); | ||
1082 | |||
1083 | // not handled: ADTS with CRC | ||
1084 | // ADTS with payload broken across push events | ||
1085 | |||
1086 | module('Transmuxer', { | ||
1087 | setup: function() { | ||
1088 | transmuxer = new Transmuxer(); | ||
1089 | } | ||
1090 | }); | ||
1091 | |||
1092 | test('generates a video init segment', function() { | ||
1093 | var segments = []; | ||
1094 | transmuxer.on('data', function(segment) { | ||
1095 | segments.push(segment); | ||
1096 | }); | ||
1097 | transmuxer.push(packetize(PAT)); | ||
1098 | transmuxer.push(packetize(PMT)); | ||
1099 | transmuxer.push(packetize(videoPes([ | ||
1100 | 0x08, 0x01 // pic_parameter_set_rbsp | ||
1101 | ], true))); | ||
1102 | transmuxer.push(packetize(videoPes([ | ||
1103 | 0x07, // seq_parameter_set_rbsp | ||
1104 | 0x27, 0x42, 0xe0, 0x0b, | ||
1105 | 0xa9, 0x18, 0x60, 0x9d, | ||
1106 | 0x80, 0x53, 0x06, 0x01, | ||
1107 | 0x06, 0xb6, 0xc2, 0xb5, | ||
1108 | 0xef, 0x7c, 0x04 | ||
1109 | ], false))); | ||
1110 | transmuxer.end(); | ||
1111 | |||
1112 | equal(segments.length, 2, 'generated init and media segments'); | ||
1113 | ok(segments[0].data, 'wrote data in the init segment'); | ||
1114 | equal(segments[0].type, 'video', 'video is the segment type'); | ||
1115 | }); | ||
1116 | |||
1117 | test('generates an audio init segment', function() { | ||
1118 | var segments = []; | ||
1119 | transmuxer.on('data', function(segment) { | ||
1120 | segments.push(segment); | ||
1121 | }); | ||
1122 | transmuxer.push(packetize(PAT)); | ||
1123 | transmuxer.push(packetize(PMT)); | ||
1124 | transmuxer.push(packetize(audioPes([ | ||
1125 | 0x00, 0x01 | ||
1126 | ], true))); | ||
1127 | transmuxer.end(); | ||
1128 | |||
1129 | equal(segments.length, 2, 'generated init and media segments'); | ||
1130 | ok(segments[0].data, 'wrote data in the init segment'); | ||
1131 | equal(segments[0].type, 'audio', 'audio is the segment type'); | ||
1132 | }); | ||
1133 | |||
1134 | test('buffers video samples until ended', function() { | ||
1135 | var samples = [], boxes; | ||
1136 | transmuxer.on('data', function(data) { | ||
1137 | samples.push(data); | ||
1138 | }); | ||
1139 | transmuxer.push(packetize(PAT)); | ||
1140 | transmuxer.push(packetize(PMT)); | ||
1141 | |||
1142 | // buffer a NAL | ||
1143 | transmuxer.push(packetize(videoPes([0x09, 0x01], true))); | ||
1144 | transmuxer.push(packetize(videoPes([0x00, 0x02]))); | ||
1145 | |||
1146 | // add an access_unit_delimiter_rbsp | ||
1147 | transmuxer.push(packetize(videoPes([0x09, 0x03]))); | ||
1148 | transmuxer.push(packetize(videoPes([0x00, 0x04]))); | ||
1149 | transmuxer.push(packetize(videoPes([0x00, 0x05]))); | ||
1150 | |||
1151 | // flush everything | ||
1152 | transmuxer.end(); | ||
1153 | equal(samples.length, 1, 'emitted one event'); | ||
1154 | boxes = videojs.inspectMp4(samples[0].data); | ||
1155 | equal(boxes.length, 2, 'generated two boxes'); | ||
1156 | equal(boxes[0].type, 'moof', 'the first box is a moof'); | ||
1157 | equal(boxes[1].type, 'mdat', 'the second box is a mdat'); | ||
1158 | deepEqual(new Uint8Array(samples[0].data.subarray(boxes[0].size + 8)), | ||
1159 | new Uint8Array([ | ||
1160 | 0, 0, 0, 2, | ||
1161 | 0x09, 0x01, | ||
1162 | 0, 0, 0, 2, | ||
1163 | 0x00, 0x02, | ||
1164 | 0, 0, 0, 2, | ||
1165 | 0x09, 0x03, | ||
1166 | 0, 0, 0, 2, | ||
1167 | 0x00, 0x04, | ||
1168 | 0, 0, 0, 2, | ||
1169 | 0x00, 0x05]), | ||
1170 | 'concatenated NALs into an mdat'); | ||
1171 | }); | ||
1172 | |||
1173 | validateTrack = function(track, metadata) { | ||
1174 | var mdia, handlerType; | ||
1175 | equal(track.type, 'trak', 'wrote the track type'); | ||
1176 | equal(track.boxes.length, 2, 'wrote track children'); | ||
1177 | equal(track.boxes[0].type, 'tkhd', 'wrote the track header'); | ||
1178 | if (metadata) { | ||
1179 | if (metadata.trackId) { | ||
1180 | equal(track.boxes[0].trackId, metadata.trackId, 'wrote the track id'); | ||
1181 | } | ||
1182 | if (metadata.width) { | ||
1183 | equal(track.boxes[0].width, metadata.width, 'wrote the width'); | ||
1184 | } | ||
1185 | if (metadata.height) { | ||
1186 | equal(track.boxes[0].height, metadata.height, 'wrote the height'); | ||
1187 | } | ||
1188 | } | ||
1189 | |||
1190 | mdia = track.boxes[1]; | ||
1191 | equal(mdia.type, 'mdia', 'wrote the media'); | ||
1192 | equal(mdia.boxes.length, 3, 'wrote the mdia children'); | ||
1193 | |||
1194 | equal(mdia.boxes[0].type, 'mdhd', 'wrote the media header'); | ||
1195 | equal(mdia.boxes[0].language, 'und', 'the language is undefined'); | ||
1196 | equal(mdia.boxes[0].duration, 0xffffffff, 'the duration is at maximum'); | ||
1197 | |||
1198 | equal(mdia.boxes[1].type, 'hdlr', 'wrote the media handler'); | ||
1199 | handlerType = mdia.boxes[1].handlerType; | ||
1200 | |||
1201 | equal(mdia.boxes[2].type, 'minf', 'wrote the media info'); | ||
1202 | }; | ||
1203 | |||
1204 | validateTrackFragment = function(track, segment, metadata) { | ||
1205 | var tfhd, trun, sdtp, i, j, sample, nalUnitType; | ||
1206 | equal(track.type, 'traf', 'wrote a track fragment'); | ||
1207 | equal(track.boxes.length, 4, 'wrote four track fragment children'); | ||
1208 | tfhd = track.boxes[0]; | ||
1209 | equal(tfhd.type, 'tfhd', 'wrote a track fragment header'); | ||
1210 | equal(tfhd.trackId, metadata.trackId, 'wrote the track id'); | ||
1211 | |||
1212 | equal(track.boxes[1].type, | ||
1213 | 'tfdt', | ||
1214 | 'wrote a track fragment decode time box'); | ||
1215 | ok(track.boxes[1].baseMediaDecodeTime >= 0, 'base decode time is non-negative'); | ||
1216 | |||
1217 | trun = track.boxes[2]; | ||
1218 | ok(trun.dataOffset >= 0, 'set data offset'); | ||
1219 | equal(trun.dataOffset, | ||
1220 | metadata.mdatOffset + 8, | ||
1221 | 'trun data offset is the size of the moof'); | ||
1222 | ok(trun.samples.length > 0, 'generated media samples'); | ||
1223 | for (i = 0, j = trun.dataOffset; i < trun.samples.length; i++) { | ||
1224 | sample = trun.samples[i]; | ||
1225 | ok(sample.duration > 0, 'wrote a positive duration for sample ' + i); | ||
1226 | ok(sample.size > 0, 'wrote a positive size for sample ' + i); | ||
1227 | ok(sample.compositionTimeOffset >= 0, | ||
1228 | 'wrote a positive composition time offset for sample ' + i); | ||
1229 | ok(sample.flags, 'wrote sample flags'); | ||
1230 | equal(sample.flags.isLeading, 0, 'the leading nature is unknown'); | ||
1231 | |||
1232 | notEqual(sample.flags.dependsOn, 0, 'sample dependency is not unknown'); | ||
1233 | notEqual(sample.flags.dependsOn, 4, 'sample dependency is valid'); | ||
1234 | nalUnitType = segment[j + 4] & 0x1F; | ||
1235 | equal(nalUnitType, 9, 'samples begin with an access_unit_delimiter_rbsp'); | ||
1236 | |||
1237 | equal(sample.flags.isDependedOn, 0, 'dependency of other samples is unknown'); | ||
1238 | equal(sample.flags.hasRedundancy, 0, 'sample redundancy is unknown'); | ||
1239 | equal(sample.flags.degradationPriority, 0, 'sample degradation priority is zero'); | ||
1240 | |||
1241 | j += sample.size; // advance to the next sample in the mdat | ||
1242 | } | ||
1243 | |||
1244 | sdtp = track.boxes[3]; | ||
1245 | equal(trun.samples.length, | ||
1246 | sdtp.samples.length, | ||
1247 | 'wrote an equal number of trun and sdtp samples'); | ||
1248 | for (i = 0; i < sdtp.samples.length; i++) { | ||
1249 | sample = sdtp.samples[i]; | ||
1250 | notEqual(sample.dependsOn, 0, 'sample dependency is not unknown'); | ||
1251 | equal(trun.samples[i].flags.dependsOn, | ||
1252 | sample.dependsOn, | ||
1253 | 'wrote a consistent dependsOn'); | ||
1254 | equal(trun.samples[i].flags.isDependedOn, | ||
1255 | sample.isDependedOn, | ||
1256 | 'wrote a consistent isDependedOn'); | ||
1257 | equal(trun.samples[i].flags.hasRedundancy, | ||
1258 | sample.hasRedundancy, | ||
1259 | 'wrote a consistent hasRedundancy'); | ||
1260 | } | ||
1261 | }; | ||
1262 | |||
1263 | test('parses an example mp2t file and generates media segments', function() { | ||
1264 | var | ||
1265 | videoSegments = [], | ||
1266 | audioSegments = [], | ||
1267 | sequenceNumber = window.Infinity, | ||
1268 | i, boxes, mfhd; | ||
1269 | |||
1270 | transmuxer.on('data', function(segment) { | ||
1271 | if (segment.type === 'video') { | ||
1272 | videoSegments.push(segment); | ||
1273 | } else if (segment.type === 'audio') { | ||
1274 | audioSegments.push(segment); | ||
1275 | } | ||
1276 | }); | ||
1277 | transmuxer.push(window.bcSegment); | ||
1278 | transmuxer.end(); | ||
1279 | |||
1280 | equal(videoSegments.length, 2, 'generated two video segments'); | ||
1281 | equal(audioSegments.length, 2, 'generated two audio segments'); | ||
1282 | |||
1283 | boxes = videojs.inspectMp4(videoSegments[0].data); | ||
1284 | equal(boxes.length, 2, 'video init segments are composed of two boxes'); | ||
1285 | equal(boxes[0].type, 'ftyp', 'the first box is an ftyp'); | ||
1286 | equal(boxes[1].type, 'moov', 'the second box is a moov'); | ||
1287 | equal(boxes[1].boxes[0].type, 'mvhd', 'generated an mvhd'); | ||
1288 | validateTrack(boxes[1].boxes[1], { | ||
1289 | trackId: 256, | ||
1290 | width: 388, | ||
1291 | height: 300 | ||
1292 | }); | ||
1293 | // validateTrack(boxes[1].boxes[2], { | ||
1294 | // trackId: 257 | ||
1295 | // }); | ||
1296 | // equal(boxes[1].boxes[3].type, 'mvex', 'generated an mvex'); | ||
1297 | |||
1298 | boxes = videojs.inspectMp4(videoSegments[1].data); | ||
1299 | ok(boxes.length > 0, 'video media segments are not empty'); | ||
1300 | ok(boxes.length % 2 === 0, 'video media segments are composed of pairs of boxes'); | ||
1301 | for (i = 0; i < boxes.length; i += 2) { | ||
1302 | equal(boxes[i].type, 'moof', 'first box is a moof'); | ||
1303 | equal(boxes[i].boxes.length, 2, 'the moof has two children'); | ||
1304 | |||
1305 | mfhd = boxes[i].boxes[0]; | ||
1306 | equal(mfhd.type, 'mfhd', 'mfhd is a child of the moof'); | ||
1307 | ok(mfhd.sequenceNumber < sequenceNumber, 'sequence numbers are increasing'); | ||
1308 | sequenceNumber = mfhd.sequenceNumber; | ||
1309 | |||
1310 | equal(boxes[i + 1].type, 'mdat', 'second box is an mdat'); | ||
1311 | validateTrackFragment(boxes[i].boxes[1], videoSegments[1].data, { | ||
1312 | trackId: 256, | ||
1313 | width: 388, | ||
1314 | height: 300, | ||
1315 | mdatOffset: boxes[0].size | ||
1316 | }); | ||
1317 | } | ||
1318 | }); | ||
1319 | |||
1320 | })(window, window.videojs); |
... | @@ -19,13 +19,6 @@ | ... | @@ -19,13 +19,6 @@ |
19 | <script src="../src/videojs-hls.js"></script> | 19 | <script src="../src/videojs-hls.js"></script> |
20 | <script src="../src/xhr.js"></script> | 20 | <script src="../src/xhr.js"></script> |
21 | <script src="../src/stream.js"></script> | 21 | <script src="../src/stream.js"></script> |
22 | <script src="../src/flv-tag.js"></script> | ||
23 | <script src="../src/exp-golomb.js"></script> | ||
24 | <script src="../src/h264-extradata.js"></script> | ||
25 | <script src="../src/h264-stream.js"></script> | ||
26 | <script src="../src/aac-stream.js"></script> | ||
27 | <script src="../src/metadata-stream.js"></script> | ||
28 | <script src="../src/segment-parser.js"></script> | ||
29 | 22 | ||
30 | <!-- M3U8 --> | 23 | <!-- M3U8 --> |
31 | <script src="../src/m3u8/m3u8-parser.js"></script> | 24 | <script src="../src/m3u8/m3u8-parser.js"></script> |
... | @@ -42,11 +35,6 @@ | ... | @@ -42,11 +35,6 @@ |
42 | <script src="tsSegment-bc.js"></script> | 35 | <script src="tsSegment-bc.js"></script> |
43 | <script src="../src/bin-utils.js"></script> | 36 | <script src="../src/bin-utils.js"></script> |
44 | 37 | ||
45 | <!-- mp4 utilities --> | ||
46 | <script src="../src/mp4-generator.js"></script> | ||
47 | <script src="../src/transmuxer.js"></script> | ||
48 | <script src="muxer/js/mp4-inspector.js"></script> | ||
49 | |||
50 | <!-- Test cases --> | 38 | <!-- Test cases --> |
51 | <script> | 39 | <script> |
52 | module('environment'); | 40 | module('environment'); |
... | @@ -56,18 +44,10 @@ | ... | @@ -56,18 +44,10 @@ |
56 | }); | 44 | }); |
57 | </script> | 45 | </script> |
58 | <script src="videojs-hls_test.js"></script> | 46 | <script src="videojs-hls_test.js"></script> |
59 | <script src="segment-parser.js"></script> | ||
60 | <script src="h264-stream_test.js"></script> | ||
61 | <script src="exp-golomb_test.js"></script> | ||
62 | <script src="flv-tag_test.js"></script> | ||
63 | <script src="metadata-stream_test.js"></script> | ||
64 | <script src="m3u8_test.js"></script> | 47 | <script src="m3u8_test.js"></script> |
65 | <script src="playlist_test.js"></script> | 48 | <script src="playlist_test.js"></script> |
66 | <script src="playlist-loader_test.js"></script> | 49 | <script src="playlist-loader_test.js"></script> |
67 | <script src="decrypter_test.js"></script> | 50 | <script src="decrypter_test.js"></script> |
68 | <script src="transmuxer_test.js"></script> | ||
69 | <script src="mp4-inspector_test.js"></script> | ||
70 | <script src="mp4-generator_test.js"></script> | ||
71 | </head> | 51 | </head> |
72 | <body> | 52 | <body> |
73 | <div id="qunit"></div> | 53 | <div id="qunit"></div> | ... | ... |
-
Please register or sign in to post a comment