Expanded mp4 test page
Added the ability to view both the transmuxed video and audio playback automatically Supports segments with only one of the two tracks
Showing
1 changed file
with
168 additions
and
33 deletions
... | @@ -45,6 +45,8 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -45,6 +45,8 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
45 | </pre> | 45 | </pre> |
46 | <small>Looking for the <a href="index.html">FLV tool</a>?</small> | 46 | <small>Looking for the <a href="index.html">FLV tool</a>?</small> |
47 | </header> | 47 | </header> |
48 | <section id="video-place"> | ||
49 | </section> | ||
48 | <section> | 50 | <section> |
49 | <h2>Inputs</h2> | 51 | <h2>Inputs</h2> |
50 | <form id="inputs"> | 52 | <form id="inputs"> |
... | @@ -116,18 +118,132 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -116,18 +118,132 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
116 | Hls: {} | 118 | Hls: {} |
117 | }; | 119 | }; |
118 | </script> | 120 | </script> |
119 | <script src="../../src/stream.js"></script> | 121 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/stream.js"></script> |
120 | <script src="../../src/mp4-generator.js"></script> | 122 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-generator.js"></script> |
121 | <script src="../../src/transmuxer.js"></script> | 123 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/transmuxer.js"></script> |
122 | <script src="../../src/flv-tag.js"></script> | 124 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-inspector.js"></script> |
123 | <script src="../../src/exp-golomb.js"></script> | 125 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/flv-tag.js"></script> |
124 | <script src="js/mp4-inspector.js"></script> | 126 | <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/exp-golomb.js"></script> |
125 | 127 | ||
126 | <script src="../../src/bin-utils.js"></script> | 128 | <script src="../../src/bin-utils.js"></script> |
127 | 129 | ||
128 | <!-- Include QUnit for object diffs --> | 130 | <!-- Include QUnit for object diffs --> |
129 | <script src="../../node_modules/qunitjs/qunit/qunit.js"></script> | 131 | <script src="../../node_modules/qunitjs/qunit/qunit.js"></script> |
130 | <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> | ||
131 | var inputs = document.getElementById('inputs'), | 247 | var inputs = document.getElementById('inputs'), |
132 | original = document.getElementById('original'), | 248 | original = document.getElementById('original'), |
133 | working = document.getElementById('working'), | 249 | working = document.getElementById('working'), |
... | @@ -145,8 +261,9 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -145,8 +261,9 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
145 | workingOutput = document.querySelector('.working-output'), | 261 | workingOutput = document.querySelector('.working-output'), |
146 | 262 | ||
147 | video = document.createElement('video'), | 263 | video = document.createElement('video'), |
148 | mediaSource = new MediaSource(), | 264 | mediaSource = new MediaSource(); |
149 | FlvTag = videojs.Hls.FlvTag; | 265 | |
266 | document.querySelector('#video-place').appendChild(video); | ||
150 | 267 | ||
151 | logevent = function(event) { | 268 | logevent = function(event) { |
152 | console.log(event.type); | 269 | console.log(event.type); |
... | @@ -176,8 +293,8 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -176,8 +293,8 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
176 | 'properties present in the working version but missing in the ' + | 293 | 'properties present in the working version but missing in the ' + |
177 | 'transmuxed output.</p>'; | 294 | 'transmuxed output.</p>'; |
178 | diff += '<pre class="mp4-diff">' + | 295 | diff += '<pre class="mp4-diff">' + |
179 | QUnit.diff(videojs.textifyMp4(transmuxed, null, ' '), | 296 | QUnit.diff(muxjs.textifyMp4(transmuxed, null, ' '), |
180 | videojs.textifyMp4(workingParsed, null, ' ')) + | 297 | muxjs.textifyMp4(workingParsed, null, ' ')) + |
181 | '</pre>'; | 298 | '</pre>'; |
182 | 299 | ||
183 | comparison.innerHTML = diff; | 300 | comparison.innerHTML = diff; |
... | @@ -185,8 +302,8 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -185,8 +302,8 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
185 | 302 | ||
186 | mediaSource.addEventListener('sourceopen', function() { | 303 | mediaSource.addEventListener('sourceopen', function() { |
187 | var | 304 | var |
188 | // buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'); | 305 | buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'); |
189 | buffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'); | 306 | //buffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'); |
190 | buffer.addEventListener('updatestart', logevent); | 307 | buffer.addEventListener('updatestart', logevent); |
191 | buffer.addEventListener('updateend', logevent); | 308 | buffer.addEventListener('updateend', logevent); |
192 | buffer.addEventListener('error', logevent); | 309 | buffer.addEventListener('error', logevent); |
... | @@ -202,46 +319,61 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -202,46 +319,61 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
202 | video.addEventListener('error', console.log.bind(console)); | 319 | video.addEventListener('error', console.log.bind(console)); |
203 | 320 | ||
204 | 321 | ||
205 | videojs.log = console.log.bind(console); | 322 | // muxjs.log = console.log.bind(console); |
206 | 323 | ||
207 | original.addEventListener('change', function() { | 324 | original.addEventListener('change', function() { |
208 | var reader = new FileReader(), | 325 | var reader = new FileReader(), |
209 | videoSegments= [], | ||
210 | audioSegments = [], | ||
211 | videoBuffer = [], | 326 | videoBuffer = [], |
212 | audioBuffer = []; | 327 | audioBuffer = []; |
213 | 328 | ||
214 | reader.addEventListener('loadend', function() { | 329 | reader.addEventListener('loadend', function() { |
215 | var segment = new Uint8Array(reader.result), | 330 | var segment = new Uint8Array(reader.result), |
216 | transmuxer = new videojs.mp2t.Transmuxer(), | 331 | transmuxer = new muxjs.mp2t.Transmuxer(), |
217 | events = [], | 332 | videoSegments = [], |
218 | bytesLength = 0, | 333 | audioSegments = [], |
334 | videoBytesLength = 0, | ||
335 | audioBytesLength = 0, | ||
336 | decodeMe, | ||
219 | bytes, | 337 | bytes, |
220 | i, j, | 338 | i, j, |
221 | hex = ''; | 339 | hex = ''; |
222 | 340 | ||
223 | transmuxer.on('data', function(data) { | 341 | // transmux the MPEG-TS data to BMFF segments |
224 | if (data && data.type === 'audio') { | 342 | transmuxer.on('data', function(segment) { |
225 | events.push(data.data); | 343 | if (segment.type === 'video') { |
226 | bytesLength += data.data.byteLength; | 344 | videoSegments.push(segment); |
345 | videoBytesLength += segment.data.byteLength; | ||
346 | } else { | ||
347 | audioSegments.push(segment); | ||
348 | audioBytesLength += segment.data.byteLength; | ||
227 | } | 349 | } |
228 | }); | 350 | }); |
351 | |||
229 | transmuxer.push(segment); | 352 | transmuxer.push(segment); |
230 | transmuxer.end(); | 353 | transmuxer.end(); |
354 | // XXX - switch to select video/audio to show | ||
355 | decodeMe = videoSegments; | ||
356 | bytes = new Uint8Array(videoBytesLength); | ||
231 | 357 | ||
232 | bytes = new Uint8Array(bytesLength); | 358 | for (j = 0, i = 0; j < decodeMe.length; j++) { |
233 | for (j = 0, i = 0; j < events.length; j++) { | 359 | bytes.set(decodeMe[j].data, i); |
234 | bytes.set(events[j], i); | 360 | i += decodeMe[j].byteLength; |
235 | i += events[j].byteLength; | ||
236 | } | 361 | } |
237 | 362 | ||
238 | vjsBytes = bytes; | 363 | vjsBytes = bytes; |
239 | vjsParsed = videojs.inspectMp4(bytes); | 364 | vjsParsed = muxjs.inspectMp4(bytes); |
240 | console.log('transmuxed', vjsParsed); | 365 | console.log('transmuxed', vjsParsed); |
241 | diffParsed(); | 366 | diffParsed(); |
242 | 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 | |||
243 | // clear old box info | 375 | // clear old box info |
244 | vjsBoxes.innerHTML = videojs.textifyMp4(vjsParsed, null, ' '); | 376 | vjsBoxes.innerHTML = muxjs.textifyMp4(vjsParsed, null, ' '); |
245 | 377 | ||
246 | // write out the result | 378 | // write out the result |
247 | hex += '<pre>'; | 379 | hex += '<pre>'; |
... | @@ -249,9 +381,9 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -249,9 +381,9 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
249 | hex += '</pre>'; | 381 | hex += '</pre>'; |
250 | vjsOutput.innerHTML = hex; | 382 | vjsOutput.innerHTML = hex; |
251 | 383 | ||
252 | // XXX Media Sources Testing | 384 | video.play(); |
253 | //window.vjsSourceBuffer.appendBuffer(bytes); | ||
254 | }); | 385 | }); |
386 | |||
255 | reader.readAsArrayBuffer(this.files[0]); | 387 | reader.readAsArrayBuffer(this.files[0]); |
256 | }, false); | 388 | }, false); |
257 | 389 | ||
... | @@ -263,12 +395,12 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -263,12 +395,12 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
263 | 395 | ||
264 | 396 | ||
265 | workingBytes = bytes; | 397 | workingBytes = bytes; |
266 | workingParsed = videojs.inspectMp4(bytes); | 398 | workingParsed = muxjs.inspectMp4(bytes); |
267 | console.log('working', workingParsed); | 399 | console.log('working', workingParsed); |
268 | diffParsed(); | 400 | diffParsed(); |
269 | 401 | ||
270 | // clear old box info | 402 | // clear old box info |
271 | workingBoxes.innerHTML = videojs.textifyMp4(workingParsed, null, ' '); | 403 | workingBoxes.innerHTML = muxjs.textifyMp4(workingParsed, null, ' '); |
272 | 404 | ||
273 | // output the hex dump | 405 | // output the hex dump |
274 | hex += '<pre>'; | 406 | hex += '<pre>'; |
... | @@ -277,7 +409,10 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. | ... | @@ -277,7 +409,10 @@ mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio. |
277 | workingOutput.innerHTML = hex; | 409 | workingOutput.innerHTML = hex; |
278 | 410 | ||
279 | // XXX Media Sources Testing | 411 | // XXX Media Sources Testing |
280 | window.vjsSourceBuffer.appendBuffer(bytes); | 412 | /* setupMSE(video, |
413 | getSegment.bind(null, []), | ||
414 | getSegment.bind(null, [{data: bytes}]));*/ | ||
415 | //window.vjsSourceBuffer.appendBuffer(bytes); | ||
281 | }); | 416 | }); |
282 | reader.readAsArrayBuffer(this.files[0]); | 417 | reader.readAsArrayBuffer(this.files[0]); |
283 | }, false); | 418 | }, false); | ... | ... |
-
Please register or sign in to post a comment