Skip to content

Commit 5458097

Browse files
committed
Renamed createVideoStream to startMediaStream.
Refactored Video constructor significantly - you can no longer create a webcam stream from the constructor, as it doesn't give you time to respond to onAccess and onError signals in Firefox. Instead call startMediaStream directly having set-up your signal listeners first. startMediaStream now has a chance to dispatch the onError signal if the webcam has been blocked entirely by the browser (auto-block or remembered block). autoPlay attribute removed to stop Firefox throwing a "Invalid URI. Load of media resource failed" error. Tidied up Video.destroy to properly remove video element from the DOM.
1 parent 1c1a2ac commit 5458097

3 files changed

Lines changed: 110 additions & 104 deletions

File tree

src/gameobjects/GameObjectFactory.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -450,15 +450,12 @@ Phaser.GameObjectFactory.prototype = {
450450
* This will return a Phaser.Video object which you can pass to a Sprite to be used as a texture.
451451
*
452452
* @method Phaser.GameObjectFactory#video
453-
* @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.
454-
* @param {boolean} [captureAudio=false] - If the key is null this controls if audio should be captured along with video in the video stream.
455-
* @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.
456-
* @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.
453+
* @param {string|null} key - The key of the video file in the Phaser.Cache that this Video object will play. Set to `null` or leave undefined if you wish to use a webcam as the source. See `startMediaStream` to start webcam capture.
457454
* @return {Phaser.Video} The newly created Video object.
458455
*/
459-
video: function (key, captureAudio, width, height) {
456+
video: function (key) {
460457

461-
return new Phaser.Video(this.game, key, captureAudio, width, height);
458+
return new Phaser.Video(this.game, key);
462459

463460
},
464461

src/gameobjects/Video.js

Lines changed: 104 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
* A Video object that takes a previously loaded Video from the Phaser Cache and handles playback of it.
99
*
1010
* Alternatively it takes a getUserMedia feed from an active webcam and streams the contents of that to
11-
* the Video instead.
11+
* the Video instead (see `startMediaStream` method)
1212
*
13-
* This can be applied to a Sprite as a texture. If multiple Sprites share the same Video texture and playback
13+
* The video can then be applied to a Sprite as a texture. If multiple Sprites share the same Video texture and playback
1414
* changes (i.e. you pause the video, or seek to a new time) then this change will be seen across all Sprites simultaneously.
1515
*
1616
* Due to a bug in IE11 you cannot play a video texture to a Sprite in WebGL. For IE11 force Canvas mode.
@@ -29,43 +29,84 @@
2929
* @class Phaser.Video
3030
* @constructor
3131
* @param {Phaser.Game} game - A reference to the currently running game.
32-
* @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.
33-
* @param {boolean} [captureAudio=false] - If the key is null this controls if audio should be captured along with video in the video stream.
34-
* @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.
35-
* @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.
32+
* @param {string|null} [key=null] - The key of the video file in the Phaser.Cache that this Video object will play. Set to `null` or leave undefined if you wish to use a webcam as the source. See `startMediaStream` to start webcam capture.
3633
*/
37-
Phaser.Video = function (game, key, captureAudio, width, height) {
34+
Phaser.Video = function (game, key) {
3835

3936
if (typeof key === 'undefined') { key = null; }
40-
if (typeof captureAudio === 'undefined') { captureAudio = false; }
4137

4238
/**
4339
* @property {Phaser.Game} game - A reference to the currently running game.
4440
*/
4541
this.game = game;
4642

4743
/**
48-
* @property {string} key - The key of the Video in the Cache, if stored there.
44+
* @property {string} key - The key of the Video in the Cache, if stored there. Will be `null` if this Video is using the webcam instead.
45+
* @default null
4946
*/
5047
this.key = key;
5148

5249
/**
5350
* @property {number} width - The width of the video in pixels.
51+
* @default
5452
*/
55-
this.width = (width) ? width : 0;
53+
this.width = 320;
5654

5755
/**
5856
* @property {number} height - The height of the video in pixels.
57+
* @default
58+
*/
59+
this.height = 240;
60+
61+
/**
62+
* @property {number} type - The const type of this object.
63+
* @default
64+
*/
65+
this.type = Phaser.VIDEO;
66+
67+
/**
68+
* @property {boolean} disableTextureUpload - If true this video will never send its image data to the GPU when its dirty flag is true. This only applies in WebGL.
69+
*/
70+
this.disableTextureUpload = false;
71+
72+
/**
73+
* @property {boolean} touchLocked - true if this video is currently locked awaiting a touch event. This happens on some mobile devices, such as iOS.
74+
* @default
75+
*/
76+
this.touchLocked = false;
77+
78+
/**
79+
* @property {Phaser.Signal} onPlay - This signal is dispatched when the Video starts to play. It sends 3 parameters: a reference to the Video object, if the video is set to loop or not and the playback rate.
80+
*/
81+
this.onPlay = new Phaser.Signal();
82+
83+
/**
84+
* @property {Phaser.Signal} onChangeSource - This signal is dispatched if the Video source is changed. It sends 3 parameters: a reference to the Video object and the new width and height of the new video source.
85+
*/
86+
this.onChangeSource = new Phaser.Signal();
87+
88+
/**
89+
* @property {Phaser.Signal} onComplete - This signal is dispatched when the Video completes playback, i.e. enters an 'ended' state. Videos set to loop will never dispatch this signal.
90+
*/
91+
this.onComplete = new Phaser.Signal();
92+
93+
/**
94+
* @property {Phaser.Signal} onAccess - This signal is dispatched if the user allows access to their webcam.
95+
*/
96+
this.onAccess = new Phaser.Signal();
97+
98+
/**
99+
* @property {Phaser.Signal} onError - This signal is dispatched if an error occurs either getting permission to use the webcam (for a Video Stream) or when trying to play back a video file.
59100
*/
60-
this.height = (height) ? height : 0;
101+
this.onError = new Phaser.Signal();
61102

62103
/**
63104
* @property {HTMLVideoElement} video - The HTML Video Element that is added to the document.
64105
*/
65106
this.video = null;
66107

67108
/**
68-
* @property {MediaStream} videoStream - The Video Stream data. Only set if this Video is streaming from the webcam via `createVideoStream`.
109+
* @property {MediaStream} videoStream - The Video Stream data. Only set if this Video is streaming from the webcam via `startMediaStream`.
69110
*/
70111
this.videoStream = null;
71112

@@ -74,11 +115,7 @@ Phaser.Video = function (game, key, captureAudio, width, height) {
74115
*/
75116
this.isStreaming = false;
76117

77-
if (key === null)
78-
{
79-
this.createVideoStream(captureAudio, width, height);
80-
}
81-
else
118+
if (this.game.cache.checkVideoKey(key))
82119
{
83120
var _video = this.game.cache.getVideo(key);
84121

@@ -123,48 +160,6 @@ Phaser.Video = function (game, key, captureAudio, width, height) {
123160
this.texture.valid = this.video.canplay;
124161
}
125162

126-
/**
127-
* @property {number} type - The const type of this object.
128-
* @default
129-
*/
130-
this.type = Phaser.VIDEO;
131-
132-
/**
133-
* @property {boolean} disableTextureUpload - If true this video will never send its image data to the GPU when its dirty flag is true. This only applies in WebGL.
134-
*/
135-
this.disableTextureUpload = false;
136-
137-
/**
138-
* @property {Phaser.Signal} onPlay - This signal is dispatched when the Video starts to play. It sends 3 parameters: a reference to the Video object, if the video is set to loop or not and the playback rate.
139-
*/
140-
this.onPlay = new Phaser.Signal();
141-
142-
/**
143-
* @property {Phaser.Signal} onChangeSource - This signal is dispatched if the Video source is changed. It sends 3 parameters: a reference to the Video object and the new width and height of the new video source.
144-
*/
145-
this.onChangeSource = new Phaser.Signal();
146-
147-
/**
148-
* @property {Phaser.Signal} onComplete - This signal is dispatched when the Video completes playback, i.e. enters an 'ended' state. Videos set to loop will never dispatch this signal.
149-
*/
150-
this.onComplete = new Phaser.Signal();
151-
152-
/**
153-
* @property {Phaser.Signal} onAccess - This signal is dispatched if the user allows access to their webcam.
154-
*/
155-
this.onAccess = new Phaser.Signal();
156-
157-
/**
158-
* @property {Phaser.Signal} onError - This signal is dispatched if an error occurs either getting permission to use the webcam (for a Video Stream) or when trying to play back a video file.
159-
*/
160-
this.onError = new Phaser.Signal();
161-
162-
/**
163-
* @property {boolean} touchLocked - true if this video is currently locked awaiting a touch event. This happens on some mobile devices, such as iOS.
164-
* @default
165-
*/
166-
this.touchLocked = false;
167-
168163
/**
169164
* A snapshot grabbed from the video. This is initially black. Populate it by calling Video.grab().
170165
* When called the BitmapData is updated with a grab taken from the current video playing or active video stream.
@@ -244,33 +239,33 @@ Phaser.Video.prototype = {
244239
* As soon as this method is called the user will be prompted by their browser to "Allow" access to the webcam.
245240
* If they allow it the webcam feed is directed to this Video. Call `Video.play` to start the stream.
246241
*
247-
* If they block the webcam the onError signal will be dispatched containing the NavigatorUserMediaError event.
242+
* If they block the webcam the onError signal will be dispatched containing the NavigatorUserMediaError
243+
* or MediaStreamError event.
248244
*
249245
* You can optionally set a width and height for the stream. If set the input will be cropped to these dimensions.
250246
* If not given then as soon as the stream has enough data the video dimensions will be changed to match the webcam device.
251247
* You can listen for this with the onChangeSource signal.
252248
*
253-
* @method Phaser.Video#createVideoStream
249+
* @method Phaser.Video#startMediaStream
254250
* @param {boolean} [captureAudio=false] - Controls if audio should be captured along with video in the video stream.
255251
* @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.
256252
* @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.
257-
* @return {Phaser.Video} This Video object for method chaining.
253+
* @return {Phaser.Video} This Video object for method chaining or false if the device doesn't support getUserMedia.
258254
*/
259-
createVideoStream: function (captureAudio, width, height) {
255+
startMediaStream: function (captureAudio, width, height) {
260256

261257
if (typeof captureAudio === 'undefined') { captureAudio = false; }
262258
if (typeof width === 'undefined') { width = null; }
263259
if (typeof height === 'undefined') { height = null; }
264260

265261
if (!this.game.device.getUserMedia)
266262
{
263+
this.onError.dispatch(this, 'No getUserMedia');
267264
return false;
268265
}
269266

270267
this.video = document.createElement("video");
271268

272-
this.video.setAttribute('autoplay', 'autoplay');
273-
274269
if (width !== null)
275270
{
276271
this.video.width = width;
@@ -281,34 +276,6 @@ Phaser.Video.prototype = {
281276
this.video.height = height;
282277
}
283278

284-
var self = this;
285-
286-
navigator.getUserMedia({
287-
"audio": captureAudio,
288-
"video": true
289-
},
290-
function(stream) {
291-
292-
self.videoStream = stream;
293-
294-
// attach the stream to the video
295-
296-
// Set the source of the video element with the stream from the camera
297-
if (self.video.mozSrcObject !== undefined)
298-
{
299-
self.video.mozSrcObject = stream;
300-
}
301-
else
302-
{
303-
self.video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
304-
}
305-
306-
self.video.play();
307-
},
308-
function(err) {
309-
self.onError.dispatch(self, err);
310-
});
311-
312279
this.video.addEventListener('loadeddata', function () {
313280

314281
var retry = 10;
@@ -348,6 +315,37 @@ Phaser.Video.prototype = {
348315

349316
}, false);
350317

318+
// Request access to the webcam
319+
320+
var self = this;
321+
322+
navigator.getUserMedia({ "audio": captureAudio, "video": true },
323+
function(stream) {
324+
325+
// Attach the stream to the video
326+
self.videoStream = stream;
327+
328+
self.video.setAttribute('autoplay', 'autoplay');
329+
330+
// Set the source of the video element with the stream from the camera
331+
if (self.video.mozSrcObject !== undefined)
332+
{
333+
self.video.mozSrcObject = stream;
334+
}
335+
else
336+
{
337+
self.video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
338+
}
339+
340+
self.video.play();
341+
},
342+
function(event) {
343+
344+
self.onError.dispatch(self, event);
345+
346+
}
347+
);
348+
351349
return this;
352350

353351
},
@@ -857,7 +855,19 @@ Phaser.Video.prototype = {
857855

858856
this.stop();
859857

860-
this.video.src = '';
858+
if (this.video.parentNode)
859+
{
860+
this.video.parentNode.removeChild(this.video);
861+
}
862+
863+
while (this.video.hasChildNodes())
864+
{
865+
this.video.removeChild(this.video.firstChild);
866+
}
867+
868+
this.video.removeAttribute('autoplay');
869+
this.video.removeAttribute('src');
870+
861871
this.video = null;
862872

863873
if (this.touchLocked)

typescript/phaser.d.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,8 +1293,7 @@ declare module Phaser {
12931293
tilemap(key?: string, tileWidth?: number, tileHeight?: number, width?: number, height?: number): Phaser.Tilemap;
12941294
tileSprite(x: number, y: number, width: number, height: number, key?: any, frame?: any, group?: Phaser.Group): Phaser.TileSprite;
12951295
tween(obj: any): Phaser.Tween;
1296-
video(key: string, captureAudio?: boolean, width?: number, height?: number): Phaser.Video;
1297-
videoSprite(): void; //todo not sure?
1296+
video(key?: string): Phaser.Video;
12981297

12991298
}
13001299

@@ -2473,12 +2472,12 @@ declare module Phaser {
24732472
touchLocked: boolean;
24742473
complete: () => void;
24752474

2476-
constructor(game: Phaser.Game, key?: string, captureAudio?: boolean, width?: number, height?: number);
2475+
constructor(game: Phaser.Game, key?: string);
24772476

24782477
add(object: Phaser.Sprite | Phaser.Sprite[]| Phaser.Image | Phaser.Image[]): Phaser.Video;
24792478
addToWorld(x?: number, y?: number, anchorX?: number, anchorY?: Number, scaleX?: number, scaleY?: number): Phaser.Image;
24802479
createVideoFromBlob(blob: Blob): Phaser.Video;
2481-
createVideoStream(captureAudio?: boolean, width?: number, height?: number): Phaser.Video;
2480+
startMediaStream(captureAudio?: boolean, width?: number, height?: number): Phaser.Video;
24822481
changeSource(src: string, autoplay?: boolean): Phaser.Video;
24832482
destroy(): void;
24842483
play(loop?: boolean, playbackRate?: number): Phaser.Video;

0 commit comments

Comments
 (0)