|
|
@@ -26,9 +26,11 @@ var requestAnimFrame = (function(){
|
|
26
|
26
|
|
|
27
|
27
|
var jsmpeg = window.jsmpeg = function( url, opts ) {
|
|
28
|
28
|
opts = opts || {};
|
|
|
29
|
+ this.progressive = (opts.progressive !== false);
|
|
29
|
30
|
this.benchmark = !!opts.benchmark;
|
|
30
|
31
|
this.canvas = opts.canvas || document.createElement('canvas');
|
|
31
|
32
|
this.autoplay = !!opts.autoplay;
|
|
|
33
|
+ this.wantsToPlay = this.autoplay;
|
|
32
|
34
|
this.loop = !!opts.loop;
|
|
33
|
35
|
this.seekable = !!opts.seekable;
|
|
34
|
36
|
this.externalLoadCallback = opts.onload || null;
|
|
|
@@ -271,25 +273,125 @@ jsmpeg.prototype.currentFrame = -1;
|
|
271
|
273
|
jsmpeg.prototype.currentTime = 0;
|
|
272
|
274
|
jsmpeg.prototype.frameCount = 0;
|
|
273
|
275
|
jsmpeg.prototype.duration = 0;
|
|
|
276
|
+jsmpeg.prototype.progressiveMinSize = 128 * 1024;
|
|
|
277
|
+
|
|
|
278
|
+
|
|
|
279
|
+jsmpeg.prototype.fetchReaderPump = function(reader) {
|
|
|
280
|
+ var that = this;
|
|
|
281
|
+ reader.read().then(function (result) {
|
|
|
282
|
+ that.fetchReaderReceive(reader, result);
|
|
|
283
|
+ });
|
|
|
284
|
+};
|
|
|
285
|
+
|
|
|
286
|
+jsmpeg.prototype.fetchReaderReceive = function(reader, result) {
|
|
|
287
|
+ if( result.done ) {
|
|
|
288
|
+ if( this.seekable ) {
|
|
|
289
|
+ var currentBufferPos = this.buffer.index;
|
|
|
290
|
+ this.collectIntraFrames();
|
|
|
291
|
+ this.buffer.index = currentBufferPos;
|
|
|
292
|
+ }
|
|
|
293
|
+
|
|
|
294
|
+ this.duration = this.frameCount / this.pictureRate;
|
|
|
295
|
+ this.lastFrameIndex = this.buffer.writePos << 3;
|
|
|
296
|
+ return;
|
|
|
297
|
+ }
|
|
|
298
|
+
|
|
|
299
|
+ this.buffer.bytes.set(result.value, this.buffer.writePos);
|
|
|
300
|
+ this.buffer.writePos += result.value.byteLength;
|
|
|
301
|
+
|
|
|
302
|
+ // Find the last picture start code - we have to be careful not trying
|
|
|
303
|
+ // to decode any frames that aren't fully loaded yet.
|
|
|
304
|
+ this.lastFrameIndex = this.findLastPictureStartCode();
|
|
|
305
|
+
|
|
|
306
|
+ // Initialize the sequence headers and start playback if we have enough data
|
|
|
307
|
+ // (at least 128kb)
|
|
|
308
|
+ if( !this.sequenceStarted && this.buffer.writePos >= this.progressiveMinSize ) {
|
|
|
309
|
+ this.findStartCode(START_SEQUENCE);
|
|
|
310
|
+ this.firstSequenceHeader = this.buffer.index;
|
|
|
311
|
+ this.decodeSequenceHeader();
|
|
|
312
|
+
|
|
|
313
|
+ // Load the first frame
|
|
|
314
|
+ this.nextFrame();
|
|
|
315
|
+
|
|
|
316
|
+ if( this.autoplay ) {
|
|
|
317
|
+ this.play();
|
|
|
318
|
+ }
|
|
|
319
|
+
|
|
|
320
|
+ if( this.externalLoadCallback ) {
|
|
|
321
|
+ this.externalLoadCallback(this);
|
|
|
322
|
+ }
|
|
|
323
|
+ }
|
|
|
324
|
+
|
|
|
325
|
+ // If the player starved previously, restart playback now
|
|
|
326
|
+ else if( this.sequenceStarted && this.wantsToPlay && !this.playing ) {
|
|
|
327
|
+ this.play();
|
|
|
328
|
+ }
|
|
|
329
|
+
|
|
|
330
|
+ // Not enough data to start playback yet - show loading progress
|
|
|
331
|
+ else if( !this.sequenceStarted ) {
|
|
|
332
|
+ var status = {loaded: this.buffer.writePos, total: this.progressiveMinSize};
|
|
|
333
|
+ if( this.gl ) {
|
|
|
334
|
+ this.updateLoaderGL(status);
|
|
|
335
|
+ }
|
|
|
336
|
+ else {
|
|
|
337
|
+ this.updateLoader2D(status);
|
|
|
338
|
+ }
|
|
|
339
|
+ }
|
|
|
340
|
+
|
|
|
341
|
+ this.fetchReaderPump(reader);
|
|
|
342
|
+};
|
|
|
343
|
+
|
|
|
344
|
+jsmpeg.prototype.findLastPictureStartCode = function() {
|
|
|
345
|
+ var bufferBytes = this.buffer.bytes;
|
|
|
346
|
+ for( var i = this.buffer.writePos; i > 3; i-- ) {
|
|
|
347
|
+ if(
|
|
|
348
|
+ bufferBytes[i] == START_PICTURE &&
|
|
|
349
|
+ bufferBytes[i-1] == 0x01 &&
|
|
|
350
|
+ bufferBytes[i-2] == 0x00 &&
|
|
|
351
|
+ bufferBytes[i-3] == 0x00
|
|
|
352
|
+ ) {
|
|
|
353
|
+ return (i-3) << 3;
|
|
|
354
|
+ }
|
|
|
355
|
+ }
|
|
|
356
|
+ return 0;
|
|
|
357
|
+};
|
|
274
|
358
|
|
|
275
|
359
|
jsmpeg.prototype.load = function( url ) {
|
|
276
|
360
|
this.url = url;
|
|
277
|
361
|
|
|
278
|
|
- var request = new XMLHttpRequest();
|
|
279
|
362
|
var that = this;
|
|
280
|
|
- request.onreadystatechange = function() {
|
|
281
|
|
- if( request.readyState === request.DONE && request.status === 200 ) {
|
|
282
|
|
- that.loadCallback(request.response);
|
|
283
|
|
- }
|
|
284
|
|
- };
|
|
|
363
|
+ if(
|
|
|
364
|
+ this.progressive &&
|
|
|
365
|
+ window.fetch &&
|
|
|
366
|
+ window.ReadableByteStream
|
|
|
367
|
+ ) {
|
|
|
368
|
+ var reqHeaders = new Headers();
|
|
|
369
|
+ reqHeaders.append('Content-Type', 'video/mpeg');
|
|
|
370
|
+ fetch(url, {headers: reqHeaders}).then(function (res) {
|
|
|
371
|
+ var contentLength = res.headers.get('Content-Length');
|
|
|
372
|
+ var reader = res.body.getReader();
|
|
285
|
373
|
|
|
286
|
|
- request.onprogress = this.gl
|
|
287
|
|
- ? this.updateLoaderGL.bind(this)
|
|
288
|
|
- : this.updateLoader2D.bind(this);
|
|
|
374
|
+ that.buffer = new BitReader(new ArrayBuffer(contentLength));
|
|
|
375
|
+ that.buffer.writePos = 0;
|
|
|
376
|
+ that.fetchReaderPump(reader);
|
|
|
377
|
+ });
|
|
|
378
|
+ }
|
|
|
379
|
+ else {
|
|
|
380
|
+ var request = new XMLHttpRequest();
|
|
|
381
|
+ request.onreadystatechange = function() {
|
|
|
382
|
+ if( request.readyState === request.DONE && request.status === 200 ) {
|
|
|
383
|
+ that.loadCallback(request.response);
|
|
|
384
|
+ }
|
|
|
385
|
+ };
|
|
|
386
|
+
|
|
|
387
|
+ request.onprogress = this.gl
|
|
|
388
|
+ ? this.updateLoaderGL.bind(this)
|
|
|
389
|
+ : this.updateLoader2D.bind(this);
|
|
289
|
390
|
|
|
290
|
|
- request.open('GET', url);
|
|
291
|
|
- request.responseType = 'arraybuffer';
|
|
292
|
|
- request.send();
|
|
|
391
|
+ request.open('GET', url);
|
|
|
392
|
+ request.responseType = 'arraybuffer';
|
|
|
393
|
+ request.send();
|
|
|
394
|
+ }
|
|
293
|
395
|
};
|
|
294
|
396
|
|
|
295
|
397
|
jsmpeg.prototype.updateLoader2D = function( ev ) {
|
|
|
@@ -311,6 +413,7 @@ jsmpeg.prototype.updateLoaderGL = function( ev ) {
|
|
311
|
413
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
312
|
414
|
};
|
|
313
|
415
|
|
|
|
416
|
+
|
|
314
|
417
|
jsmpeg.prototype.loadCallback = function(file) {
|
|
315
|
418
|
this.buffer = new BitReader(file);
|
|
316
|
419
|
|
|
|
@@ -392,11 +495,13 @@ jsmpeg.prototype.play = function() {
|
|
392
|
495
|
if( this.playing ) { return; }
|
|
393
|
496
|
this.targetTime = this.now();
|
|
394
|
497
|
this.playing = true;
|
|
|
498
|
+ this.wantsToPlay = true;
|
|
395
|
499
|
this.scheduleNextFrame();
|
|
396
|
500
|
};
|
|
397
|
501
|
|
|
398
|
502
|
jsmpeg.prototype.pause = function() {
|
|
399
|
503
|
this.playing = false;
|
|
|
504
|
+ this.wantsToPlay = false;
|
|
400
|
505
|
};
|
|
401
|
506
|
|
|
402
|
507
|
jsmpeg.prototype.stop = function() {
|
|
|
@@ -409,6 +514,7 @@ jsmpeg.prototype.stop = function() {
|
|
409
|
514
|
this.client.close();
|
|
410
|
515
|
this.client = null;
|
|
411
|
516
|
}
|
|
|
517
|
+ this.wantsToPlay = false;
|
|
412
|
518
|
};
|
|
413
|
519
|
|
|
414
|
520
|
|
|
|
@@ -473,6 +579,11 @@ jsmpeg.prototype.nextFrame = function() {
|
|
473
|
579
|
this.decodeSequenceHeader();
|
|
474
|
580
|
}
|
|
475
|
581
|
else if( code === START_PICTURE ) {
|
|
|
582
|
+ if( this.progressive && this.buffer.index >= this.lastFrameIndex ) {
|
|
|
583
|
+ // Starved
|
|
|
584
|
+ this.playing = false;
|
|
|
585
|
+ return;
|
|
|
586
|
+ }
|
|
476
|
587
|
if( this.playing ) {
|
|
477
|
588
|
this.scheduleNextFrame();
|
|
478
|
589
|
}
|