2 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
8 * See http://dev.w3.org/2011/webrtc/editor/getusermedia.html for more
9 * information on getUserMedia.
13 * Keeps track of our local stream (e.g. what our local webcam is streaming).
16 var gLocalStream
= null;
19 * The MediaConstraints to use when connecting the local stream with a peer
23 var gAddStreamConstraints
= {};
26 * String which keeps track of what happened when we requested user media.
29 var gRequestWebcamAndMicrophoneResult
= 'not-called-yet';
32 * Used as a shortcut. Moved to the top of the page due to race conditions.
33 * @param {string} id is a case-sensitive string representing the unique ID of
34 * the element being sought.
35 * @return {string} id returns the element object specified as a parameter
38 return document
.getElementById(id
);
42 * This function asks permission to use the webcam and mic from the browser. It
43 * will return ok-requested to the test. This does not mean the request was
44 * approved though. The test will then have to click past the dialog that
45 * appears in Chrome, which will run either the OK or failed callback as a
46 * a result. To see which callback was called, use obtainGetUserMediaResult().
48 * @param {string} constraints Defines what to be requested, with mandatory
49 * and optional constraints defined. The contents of this parameter depends
50 * on the WebRTC version. This should be JavaScript code that we eval().
52 function doGetUserMedia(constraints
) {
54 returnToTest('Browser does not support WebRTC.');
58 var evaluatedConstraints
;
59 eval('evaluatedConstraints = ' + constraints
);
61 throw failTest('Not valid JavaScript expression: ' + constraints
);
63 debug('Requesting doGetUserMedia: constraints: ' + constraints
);
64 getUserMedia(evaluatedConstraints
,
66 ensureGotAllExpectedStreams_(stream
, constraints
);
67 getUserMediaOkCallback_(stream
);
69 getUserMediaFailedCallback_
);
70 returnToTest('ok-requested');
74 * Must be called after calling doGetUserMedia.
75 * @return {string} Returns not-called-yet if we have not yet been called back
76 * by WebRTC. Otherwise it returns either ok-got-stream or
77 * failed-with-error-x (where x is the error code from the error
78 * callback) depending on which callback got called by WebRTC.
80 function obtainGetUserMediaResult() {
81 // Translate from the old error to the new. Remove when rename fully deployed.
82 if (gRequestWebcamAndMicrophoneResult
=== 'PERMISSION_DENIED')
83 gRequestWebcamAndMicrophoneResult
= 'PermissionDeniedError';
85 returnToTest(gRequestWebcamAndMicrophoneResult
);
86 return gRequestWebcamAndMicrophoneResult
;
90 * Stops the local stream.
92 function stopLocalStream() {
93 if (gLocalStream
== null)
94 throw failTest('Tried to stop local stream, ' +
95 'but media access is not granted.');
98 returnToTest('ok-stopped');
101 // Functions callable from other JavaScript modules.
104 * Adds the current local media stream to a peer connection.
105 * @param {RTCPeerConnection} peerConnection
107 function addLocalStreamToPeerConnection(peerConnection
) {
108 if (gLocalStream
== null)
109 throw failTest('Tried to add local stream to peer connection, ' +
110 'but there is no stream yet.');
112 peerConnection
.addStream(gLocalStream
, gAddStreamConstraints
);
113 } catch (exception
) {
114 throw failTest('Failed to add stream with constraints ' +
115 gAddStreamConstraints
+ ': ' + exception
);
117 debug('Added local stream.');
121 * Removes the local stream from the peer connection.
122 * @param {rtcpeerconnection} peerConnection
124 function removeLocalStreamFromPeerConnection(peerConnection
) {
125 if (gLocalStream
== null)
126 throw failTest('Tried to remove local stream from peer connection, ' +
127 'but there is no stream yet.');
129 peerConnection
.removeStream(gLocalStream
);
130 } catch (exception
) {
131 throw failTest('Could not remove stream: ' + exception
);
133 debug('Removed local stream.');
137 * @return {string} Returns the current local stream - |gLocalStream|.
139 function getLocalStream() {
147 * @param {MediaStream} stream Media stream from getUserMedia.
148 * @param {String} constraints The constraints passed
150 function ensureGotAllExpectedStreams_(stream
, constraints
) {
151 var requestedVideo
= /video\s*:\s*true/i;
152 if (requestedVideo
.test(constraints
) && stream
.getVideoTracks().length
== 0) {
153 gRequestWebcamAndMicrophoneResult
= 'failed-to-get-video';
154 throw ('Requested video, but did not receive a video stream from ' +
155 'getUserMedia. Perhaps the machine you are running on ' +
156 'does not have a webcam.');
158 var requestedAudio
= /audio\s*:\s*true/i;
159 if (requestedAudio
.test(constraints
) && stream
.getAudioTracks().length
== 0) {
160 gRequestWebcamAndMicrophoneResult
= 'failed-to-get-audio';
161 throw ('Requested audio, but did not receive an audio stream ' +
162 'from getUserMedia. Perhaps the machine you are running ' +
163 'on does not have audio devices.');
169 * @param {MediaStream} stream Media stream.
171 function getUserMediaOkCallback_(stream
) {
172 gLocalStream
= stream
;
173 gRequestWebcamAndMicrophoneResult
= 'ok-got-stream';
175 if (stream
.getVideoTracks().length
> 0) {
176 // Show the video tag if we did request video in the getUserMedia call.
177 var videoTag
= $('local-view');
178 attachMediaStream(videoTag
, stream
);
180 // Due to crbug.com/110938 the size is 0 when onloadedmetadata fires.
181 // videoTag.onloadedmetadata = displayVideoSize_(videoTag);.
182 // Use setTimeout as a workaround for now.
183 setTimeout(function() {displayVideoSize_(videoTag
);}, 500);
189 * @param {string} videoTagId The ID of the video tag to update.
190 * @param {string} width The width of the video to update the video tag, if
191 * width or height is 0, size will be taken from videoTag.videoWidth.
192 * @param {string} height The height of the video to update the video tag, if
193 * width or height is 0 size will be taken from the videoTag.videoHeight.
195 function updateVideoTagSize_(videoTagId
, width
, height
) {
196 var videoTag
= $(videoTagId
);
197 if (width
> 0 || height
> 0) {
198 videoTag
.width
= width
;
199 videoTag
.height
= height
;
202 if (videoTag
.videoWidth
> 0 || videoTag
.videoHeight
> 0) {
203 videoTag
.width
= videoTag
.videoWidth
;
204 videoTag
.height
= videoTag
.videoHeight
;
207 debug('"' + videoTagId
+ '" video stream size is 0, skipping resize');
210 debug('Set video tag "' + videoTagId
+ '" size to ' + videoTag
.width
+ 'x' +
212 displayVideoSize_(videoTag
);
217 * @param {string} videoTag The ID of the video tag + stream used to
218 * write the size to a HTML tag based on id if the div's exists.
220 function displayVideoSize_(videoTag
) {
221 if ($(videoTag
.id
+ '-stream-size') && $(videoTag
.id
+ '-size')) {
222 if (videoTag
.videoWidth
> 0 || videoTag
.videoHeight
> 0) {
223 $(videoTag
.id
+ '-stream-size').innerHTML
= '(stream size: ' +
224 videoTag
.videoWidth
+ 'x' +
225 videoTag
.videoHeight
+ ')';
226 $(videoTag
.id
+ '-size').innerHTML
= videoTag
.width
+ 'x' +
231 debug('Skipping updating -stream-size and -size tags due to div\'s are ' +
237 * Enumerates the audio and video devices available in Chrome and adds the
238 * devices to the HTML elements with Id 'audiosrc' and 'videosrc'.
239 * Checks if device enumeration is supported and if the 'audiosrc' + 'videosrc'
240 * elements exists, if not a debug printout will be displayed.
241 * If the device label is empty, audio/video + sequence number will be used to
242 * populate the name. Also makes sure the children has been loaded in order
243 * to update the constraints.
245 function getDevices() {
246 if ($('audiosrc') && $('videosrc') && $('get-devices')) {
247 var audio_select
= $('audiosrc');
248 var video_select
= $('videosrc');
249 var get_devices
= $('get-devices');
250 audio_select
.innerHTML
= '';
251 video_select
.innerHTML
= '';
253 eval(MediaStreamTrack
.getSources(function() {}));
254 } catch (exception
) {
255 audio_select
.disabled
= true;
256 video_select
.disabled
= true;
257 get_devices
.disabled
= true;
258 updateGetUserMediaConstraints();
259 debug('Device enumeration not supported. ' + exception
);
262 MediaStreamTrack
.getSources(function(devices
) {
263 for (var i
= 0; i
< devices
.length
; i
++) {
264 var option
= document
.createElement('option');
265 option
.value
= devices
[i
].id
;
266 option
.text
= devices
[i
].label
;
267 if (devices
[i
].kind
== 'audio') {
268 if (option
.text
== '') {
269 option
.text
= devices
[i
].id
;
271 audio_select
.appendChild(option
);
273 else if (devices
[i
].kind
== 'video') {
274 if (option
.text
== '') {
275 option
.text
= devices
[i
].id
;
277 video_select
.appendChild(option
);
280 debug('Device type ' + devices
[i
].kind
+ ' not recognized, cannot ' +
281 'enumerate device. Currently only device types \'audio\' and ' +
282 '\'video\' are supported');
283 updateGetUserMediaConstraints();
288 checkIfDeviceDropdownsArePopulated();
291 debug('Device DOM elements cannot be found, cannot display devices');
292 updateGetUserMediaConstraints();
297 * This provides the selected source id from the objects in the parameters
298 * provided to this function. If the audio_select or video_select objects does
299 * not have any HTMLOptions children it will return null in the source object.
300 * @param {object} audio_select HTML drop down element with audio devices added
301 * as HTMLOptionsCollection children.
302 * @param {object} video_select HTML drop down element with audio devices added
303 * as HTMLPptionsCollection children.
304 * @return {object} audio_id video_id Containing audio and video source ID from
305 * the selected devices in the drop down menus provided as parameters to
308 function getSourcesFromField(audio_select
, video_select
) {
313 if (audio_select
.options
.length
> 0) {
314 source
.audio_id
= audio_select
.options
[audio_select
.selectedIndex
].value
;
316 if (video_select
.options
.length
> 0) {
317 source
.video_id
= video_select
.options
[video_select
.selectedIndex
].value
;
324 * @param {NavigatorUserMediaError} error Error containing details.
326 function getUserMediaFailedCallback_(error
) {
327 // Translate from the old error to the new. Remove when rename fully deployed.
328 var errorName
= error
.name
;
329 if (errorName
=== 'PERMISSION_DENIED')
330 errorName
= 'PermissionDeniedError';
332 debug('GetUserMedia FAILED: Maybe the camera is in use by another process?');
333 gRequestWebcamAndMicrophoneResult
= 'failed-with-error-' + errorName
;
334 debug(gRequestWebcamAndMicrophoneResult
);