Skip to content

Commit 37304c7

Browse files
committed
Added Video.createVideoStream support.
1 parent 399cf3f commit 37304c7

2 files changed

Lines changed: 152 additions & 42 deletions

File tree

src/gameobjects/GameObjectFactory.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -418,18 +418,18 @@ Phaser.GameObjectFactory.prototype = {
418418
/**
419419
* Create a Video object.
420420
*
421-
* The video file must have already been loaded into the Phaser.Cache.
422-
* The key given is the key you used when the video was preloaded.
423421
* This will return a Phaser.Video object which you can pass to a Sprite to be used as a texture.
424-
* If you wish to create a Sprite with a Video texture automatically you can use `GameObjectFactory.videoSprite`.
425422
*
426423
* @method Phaser.GameObjectFactory#video
427-
* @param {string} key - The key of the file in the Phaser.Cache that this video will use.
424+
* @param {string|null} key - The key of the video file in the Phaser.Cache that the Video object will use. If null a `getUserMedia` video stream will be established instead.
425+
* @param {boolean} [captureAudio=false] - If the key is null this controls if audio should be captured along with video in the video stream.
426+
* @param {integer} [width] - If the key is null this width is used to create the video stream. If not provided the video width will be set to the width of the webcam input source.
427+
* @param {integer} [height] - If the key is null this height is used to create the video stream. If not provided the video height will be set to the height of the webcam input source.
428428
* @return {Phaser.Video} The newly created Video object.
429429
*/
430-
video: function (key) {
430+
video: function (key, captureAudio, width, height) {
431431

432-
return new Phaser.Video(this.game, key);
432+
return new Phaser.Video(this.game, key, captureAudio, width, height);
433433

434434
},
435435

src/gameobjects/Video.js

Lines changed: 146 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
/**
88
* A Video object that takes a previously loaded Video from the Phaser Cache and handles playback of it.
99
*
10+
* Alternatively it takes a getUserMedia feed from an active webcam and streams the contents of that to
11+
* the Video instead.
12+
*
1013
* This can be applied to a Sprite as a texture. If multiple Sprites share the same Video texture and playback
1114
* changes (i.e. you pause the video, or seek to a new time) then this change will be seen across all Sprites simultaneously.
1215
*
@@ -24,45 +27,61 @@
2427
* @class Phaser.Video
2528
* @constructor
2629
* @param {Phaser.Game} game - A reference to the currently running game.
27-
* @param {string} key - The key of the video file stored in the Phaser.Cache that this Video object should use.
30+
* @param {string|null} key - The key of the video file in the Phaser.Cache that this Video object should use. If null a `getUserMedia` video stream will be established instead.
31+
* @param {boolean} [captureAudio=false] - If the key is null this controls if audio should be captured along with video in the video stream.
32+
* @param {integer} [width] - If the key is null this width is used to create the video stream. If not provided the video width will be set to the width of the webcam input source.
33+
* @param {integer} [height] - If the key is null this height is used to create the video stream. If not provided the video height will be set to the height of the webcam input source.
2834
*/
29-
Phaser.Video = function (game, key) {
35+
Phaser.Video = function (game, key, captureAudio, width, height) {
36+
37+
if (typeof key === 'undefined') { key = null; }
38+
if (typeof captureAudio === 'undefined') { captureAudio = false; }
3039

3140
/**
3241
* @property {Phaser.Game} game - A reference to the currently running game.
3342
*/
3443
this.game = game;
3544

3645
/**
37-
* @property {string} key - The key of the BitmapData in the Cache, if stored there.
46+
* @property {string} key - The key of the Video in the Cache, if stored there.
3847
*/
3948
this.key = key;
4049

50+
/**
51+
* @property {number} width - The width of the video in pixels.
52+
*/
53+
this.width = (width) ? width : 0;
54+
55+
/**
56+
* @property {number} height - The height of the video in pixels.
57+
*/
58+
this.height = (height) ? height : 0;
59+
4160
/**
4261
* @property {HTMLVideoElement} video - The HTML Video Element that is added to the document.
4362
*/
4463
this.video = null;
4564

46-
var _video = this.game.cache.getVideo(key);
47-
48-
if (_video.isBlob)
65+
if (key === null)
4966
{
50-
this.createVideoFromBlob(_video.data);
67+
this.createVideoStream(captureAudio, width, height);
5168
}
5269
else
5370
{
54-
this.video = _video.data;
55-
}
71+
var _video = this.game.cache.getVideo(key);
5672

57-
/**
58-
* @property {number} width - The width of the video in pixels.
59-
*/
60-
this.width = this.video.videoWidth;
73+
if (_video.isBlob)
74+
{
75+
this.createVideoFromBlob(_video.data);
76+
}
77+
else
78+
{
79+
this.video = _video.data;
80+
}
6181

62-
/**
63-
* @property {number} height - The height of the video in pixels.
64-
*/
65-
this.height = this.video.videoHeight;
82+
this.width = this.video.videoWidth;
83+
this.height = this.video.videoHeight;
84+
}
6685

6786
/**
6887
* @property {PIXI.BaseTexture} baseTexture - The PIXI.BaseTexture.
@@ -118,6 +137,11 @@ Phaser.Video = function (game, key) {
118137
*/
119138
this.touchLocked = false;
120139

140+
/**
141+
* @property {object} videoStream - The Video Stream data. Only set if this Video is streaming from the webcam via `createVideoStream`.
142+
*/
143+
this.videoStream = null;
144+
121145
/**
122146
* @property {boolean} _codeMuted - Internal mute tracking var.
123147
* @private
@@ -166,13 +190,86 @@ Phaser.Video = function (game, key) {
166190
}
167191
else
168192
{
169-
_video.locked = false;
193+
if (_video)
194+
{
195+
_video.locked = false;
196+
}
170197
}
171198

172199
};
173200

174201
Phaser.Video.prototype = {
175202

203+
/**
204+
* Instead of playing a video file this method allows you to stream video data from an attached webcam.
205+
*
206+
* As soon as this method is called the user will be prompted by their browser to "Allow" access to the webcam.
207+
* If they allow it the webcam feed is directed to this Video. Call `Video.play` to start the stream.
208+
*
209+
* If they block the webcam a console warning will be displayed containing the NavigatorUserMediaError event.
210+
*
211+
* You can optionally set a width and height for the stream. If set the input will be cropped to these dimensions.
212+
* If not given as soon as the stream has enough data the video dimensions will be changed to match the webcam device.
213+
* You can listen for this with the onChangeSource signal.
214+
*
215+
* @method Phaser.Video#createVideoStream
216+
* @param {boolean} [captureAudio=false] - Controls if audio should be captured along with video in the video stream.
217+
* @param {integer} [width] - The width is used to create the video stream. If not provided the video width will be set to the width of the webcam input source.
218+
* @param {integer} [height] - The height is used to create the video stream. If not provided the video height will be set to the height of the webcam input source.
219+
* @return {Phaser.Video} This Video object for method chaining.
220+
*/
221+
createVideoStream: function (captureAudio, width, height) {
222+
223+
if (typeof captureAudio === 'undefined') { captureAudio = false; }
224+
if (typeof width === 'undefined') { width = null; }
225+
if (typeof height === 'undefined') { height = null; }
226+
227+
if (!this.game.device.getUserMedia)
228+
{
229+
return false;
230+
}
231+
232+
this.video = document.createElement("video");
233+
this.video.controls = false;
234+
this.video.autoplay = false;
235+
236+
if (width !== null)
237+
{
238+
this.video.width = width;
239+
}
240+
241+
if (height !== null)
242+
{
243+
this.video.height = height;
244+
}
245+
246+
if (!navigator['getUserMedia'])
247+
{
248+
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
249+
}
250+
251+
var _this = this;
252+
253+
var _streamStart = function(stream) {
254+
255+
_this.videoStream = stream;
256+
257+
_this.video.src = window.URL.createObjectURL(stream);
258+
259+
_this.video.addEventListener('loadeddata', function (event) { _this.updateTexture(width, height); }, true);
260+
261+
};
262+
263+
var _streamError = function(e) {
264+
console.warn('Stream Error', e);
265+
}
266+
267+
navigator.getUserMedia({ audio: captureAudio, video: true }, _streamStart, _streamError);
268+
269+
return this;
270+
271+
},
272+
176273
/**
177274
* Creates a new Video element from the given Blob. The Blob must contain the video data in the correct encoded format.
178275
* This method is typically called by the Phaser.Loader and Phaser.Cache for you, but is exposed publicly for convenience.
@@ -183,18 +280,12 @@ Phaser.Video.prototype = {
183280
*/
184281
createVideoFromBlob: function (blob) {
185282

186-
if (this.video !== null)
187-
{
188-
this.destroy();
189-
}
190-
191283
this.video = document.createElement("video");
192284
this.video.controls = false;
193285
this.video.autoplay = false;
194286
this.video.src = window.URL.createObjectURL(blob);
195287

196-
this.width = this.video.videoWidth;
197-
this.height = this.video.videoHeight;
288+
this.updateTexture();
198289

199290
return this;
200291

@@ -205,25 +296,44 @@ Phaser.Video.prototype = {
205296
* Then dispatches the onChangeSource signal.
206297
*
207298
* @method Phaser.Video#updateTexture
299+
* @param {integer} [width] - The new width of the video. If undefined `video.videoWidth` is used.
300+
* @param {integer} [height] - The new height of the video. If undefined `video.videoHeight` is used.
208301
*/
209-
updateTexture: function () {
302+
updateTexture: function (width, height) {
210303

211-
this.width = this.video.videoWidth;
212-
this.height = this.video.videoHeight;
304+
var change = false;
213305

214-
this.baseTexture.forceLoaded(this.width, this.height);
306+
if (typeof width === 'undefined' || width === null) { width = this.video.videoWidth; change = true }
307+
if (typeof height === 'undefined' || height === null) { height = this.video.videoHeight; }
215308

216-
this.texture.frame.width = this.width;
217-
this.texture.frame.height = this.height;
309+
if (change)
310+
{
311+
console.log('updateTexture resizing to webcam', width, height);
312+
}
313+
else
314+
{
315+
console.log('updateTexture resizing to fixed dimensions', width, height);
316+
}
317+
318+
this.width = width;
319+
this.height = height;
218320

219-
this.video.removeEventListener('canplaythrough', this.updateTexture.bind(this));
321+
this.baseTexture.forceLoaded(width, height);
220322

221-
this.onChangeSource.dispatch(this, this.width, this.height);
323+
this.texture.frame.width = width;
324+
this.texture.frame.height = height;
222325

223-
if (this._autoplay)
326+
if (change)
224327
{
225-
this.video.play();
226-
this.onPlay.dispatch(this, this.loop, this.playbackRate);
328+
this.video.removeEventListener('canplaythrough', this.updateTexture.bind(this));
329+
330+
this.onChangeSource.dispatch(this, width, height);
331+
332+
if (this._autoplay)
333+
{
334+
this.video.play();
335+
this.onPlay.dispatch(this, this.loop, this.playbackRate);
336+
}
227337
}
228338

229339
},

0 commit comments

Comments
 (0)