Ver código fonte

Implement progressive loading via fetch/ReadableByteStream; see #70

Dominic Szablewski 10 anos atrás
pai
commit
f1e0a3d518
2 arquivos alterados com 124 adições e 12 exclusões
  1. 1 0
      README.md
  2. 123 12
      jsmpg.js

+ 1 - 0
README.md Ver arquivo

@@ -20,6 +20,7 @@ The `file` argument accepts a URL to a .mpg file or a (yet unconnected) WebSocke
20 20
 The `options` argument to the `jsmpeg()` supports the following properties:
21 21
 
22 22
 - `benchmark` whether to log benchmark results to the browser's console
23
+- `progressive` whether to start playback as soon as the first frames have been loaded. Uses the new `fetch` API if available. Default `true`.
23 24
 - `canvas` the HTML Canvas element to use; jsmpeg will create its own Canvas element if none is provided
24 25
 - `autoplay` whether playback should start automatically after loading
25 26
 - `loop` whether playback is looped

+ 123 - 12
jsmpg.js Ver arquivo

@@ -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
 			}