From 477d9e282df5013e8a13602b6d4364118f0dbea0 Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Wed, 30 May 2018 12:24:18 +0200 Subject: [PATCH] openvidu-browser: ask for camera and micro at once (one permission popup) --- openvidu-browser/src/OpenVidu/OpenVidu.ts | 2 +- openvidu-browser/src/OpenVidu/Publisher.ts | 155 +++++++++--------- openvidu-browser/src/OpenVidu/Stream.ts | 15 +- .../OpenViduInternal/Enums/OpenViduError.ts | 3 +- 4 files changed, 91 insertions(+), 84 deletions(-) diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index dc2fe6ab..6385c0ce 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -300,7 +300,7 @@ export class OpenVidu { let errorName: OpenViduErrorName; const errorMessage = error.toString(); if (!(options.videoSource === 'screen')) { - errorName = (options.videoSource === false || options.videoSource === null) ? OpenViduErrorName.MICROPHONE_ACCESS_DENIED : OpenViduErrorName.CAMERA_ACCESS_DENIED; + errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED; } else { errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED; } diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index 6195b990..e8d38c0b 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -40,6 +40,11 @@ export class Publisher extends StreamManager { */ accessAllowed = false; + /** + * Whether you have called [[Publisher.subscribeToRemote]] with value `true` or `false` (false by default) + */ + isSubscribedToRemote = false; + /** * The [[Session]] to which the Publisher belongs */ @@ -84,8 +89,10 @@ export class Publisher extends StreamManager { /** * Call this method before [[Session.publish]] to subscribe to your Publisher's stream as any other user would do. The local video will be automatically replaced by the remote video */ - subscribeToRemote(): void { - this.stream.subscribeToMyRemote(); + subscribeToRemote(value?: boolean): void { + value = (value !== undefined) ? value : true; + this.isSubscribedToRemote = value; + this.stream.subscribeToMyRemote(value); } @@ -200,6 +207,11 @@ export class Publisher extends StreamManager { } this.stream.setMediaStream(mediaStream); + if (!this.stream.displayMyRemote()) { + // When we are subscribed to our remote we don't still set the MediaStream object in the video elements to + // avoid early 'streamPlaying' event + this.stream.updateMediaStreamInVideos(); + } this.stream.isLocalStreamReadyToPublish = true; this.stream.ee.emitEvent('stream-ready-to-publish', []); @@ -221,134 +233,119 @@ export class Publisher extends StreamManager { this.stream.setOutboundStreamOptions(outboundStreamOptions); - // Ask independently for audio stream and video stream. If the user asks for both of them and one is blocked, the method still - // success only with the allowed input. This is not the desierd behaviour: if any of them is blocked, access should be denied const constraintsAux: MediaStreamConstraints = {}; const timeForDialogEvent = 1250; - if (this.stream.isSendVideo()) { - - constraintsAux.audio = false; + if (this.stream.isSendVideo() || this.stream.isSendAudio()) { + const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio); + constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint; constraintsAux.video = constraints.video; - let startTime = Date.now(); this.setPermissionDialogTimer(timeForDialogEvent); navigator.mediaDevices.getUserMedia(constraintsAux) - .then(videoOnlyStream => { + .then(mediaStream => { this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - if (this.stream.isSendAudio()) { - - constraintsAux.audio = (constraints.audio === undefined) ? true : constraints.audio; + if (this.stream.isSendScreen() && this.stream.isSendAudio()) { + // When getting desktop as user media audio constraint must be false. Now we can ask for it if required + constraintsAux.audio = definedAudioConstraint; constraintsAux.video = false; - startTime = Date.now(); this.setPermissionDialogTimer(timeForDialogEvent); navigator.mediaDevices.getUserMedia(constraintsAux) .then(audioOnlyStream => { this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - - videoOnlyStream.addTrack(audioOnlyStream.getAudioTracks()[0]); - successCallback(videoOnlyStream); + mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]); + successCallback(mediaStream); }) .catch(error => { this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - - videoOnlyStream.getVideoTracks().forEach((track) => { - track.stop(); - }); - let errorName; - let errorMessage; + let errorName, errorMessage; switch (error.name.toLowerCase()) { case 'notfounderror': errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); break; case 'notallowederror': - errorName = OpenViduErrorName.MICROPHONE_ACCESS_DENIED; + errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED; errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); break; case 'overconstrainederror': if (error.constraint.toLowerCase() === 'deviceid') { errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; - errorMessage = "Audio input device with deviceId '" + ((constraints.audio).deviceId!!).exact + "' not found"; + errorMessage = "Audio input device with deviceId '" + ((constraints.video).deviceId!!).exact + "' not found"; } else { errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; } + errorCallback(new OpenViduError(errorName, errorMessage)); + break; } - errorCallback(new OpenViduError(errorName, errorMessage)); }); } else { - successCallback(videoOnlyStream); + successCallback(mediaStream); } }) .catch(error => { this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - - let errorName; - let errorMessage; + let errorName, errorMessage; switch (error.name.toLowerCase()) { case 'notfounderror': - errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; - errorMessage = error.toString(); + navigator.mediaDevices.getUserMedia({ + audio: false, + video: constraints.video + }) + .then(mediaStream => { + mediaStream.getVideoTracks().forEach((track) => { + track.stop(); + }); + errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + }).catch(e => { + errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + }); break; case 'notallowederror': - errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.CAMERA_ACCESS_DENIED; + errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED; errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); break; case 'overconstrainederror': - if (error.constraint.toLowerCase() === 'deviceid') { - errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; - errorMessage = "Video input device with deviceId '" + ((constraints.video).deviceId!!).exact + "' not found"; - } else { - errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; - errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'"; - } - } - errorCallback(new OpenViduError(errorName, errorMessage)); - }); - - } else if (this.stream.isSendAudio()) { - - constraintsAux.audio = (constraints.audio === undefined) ? true : constraints.audio; - constraintsAux.video = false; - - const startTime = Date.now(); - this.setPermissionDialogTimer(timeForDialogEvent); - - navigator.mediaDevices.getUserMedia(constraints) - .then(audioOnlyStream => { - this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - - successCallback(audioOnlyStream); - }) - .catch(error => { - this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - - let errorName; - let errorMessage; - switch (error.name.toLowerCase()) { - case 'notfounderror': - errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; - errorMessage = error.toString(); + navigator.mediaDevices.getUserMedia({ + audio: false, + video: constraints.video + }) + .then(mediaStream => { + mediaStream.getVideoTracks().forEach((track) => { + track.stop(); + }); + if (error.constraint.toLowerCase() === 'deviceid') { + errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; + errorMessage = "Video input device with deviceId '" + ((constraints.video).deviceId!!).exact + "' not found"; + } else { + errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; + errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'"; + } + errorCallback(new OpenViduError(errorName, errorMessage)); + }).catch(e => { + if (error.constraint.toLowerCase() === 'deviceid') { + errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; + errorMessage = "Audio input device with deviceId '" + ((constraints.video).deviceId!!).exact + "' not found"; + } else { + errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; + errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; + } + errorCallback(new OpenViduError(errorName, errorMessage)); + }); break; - case 'notallowederror': - errorName = OpenViduErrorName.MICROPHONE_ACCESS_DENIED; - errorMessage = error.toString(); - break; - case 'overconstrainederror': - if (error.constraint.toLowerCase() === 'deviceid') { - errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; - errorMessage = "Audio input device with deviceId '" + ((constraints.audio).deviceId!!).exact + "' not found"; - } else { - errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; - errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; - } } - errorCallback(new OpenViduError(errorName, errorMessage)); }); } else { reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET, diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index cdfba934..32ede4bd 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -168,6 +168,12 @@ export class Stream { */ setMediaStream(mediaStream: MediaStream): void { this.mediaStream = mediaStream; + } + + /** + * @hidden + */ + updateMediaStreamInVideos() { this.ee.emitEvent('mediastream-updated'); } @@ -188,8 +194,8 @@ export class Stream { /** * @hidden */ - subscribeToMyRemote(): void { - this.isSubscribeToRemote = true; + subscribeToMyRemote(value: boolean): void { + this.isSubscribeToRemote = value; } /** @@ -395,6 +401,11 @@ export class Stream { this.processSdpAnswer(response.sdpAnswer) .then(() => { this.isLocalStreamPublished = true; + if (this.displayMyRemote()) { + // If remote now we can set the srcObject value of video elements + // 'streamPlaying' event will be triggered + this.updateMediaStreamInVideos(); + } this.ee.emitEvent('stream-created-by-publisher'); resolve(); }) diff --git a/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts b/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts index 8439bcf0..125b2912 100644 --- a/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts +++ b/openvidu-browser/src/OpenViduInternal/Enums/OpenViduError.ts @@ -20,8 +20,7 @@ */ export enum OpenViduErrorName { BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED', - CAMERA_ACCESS_DENIED = 'CAMERA_ACCESS_DENIED', - MICROPHONE_ACCESS_DENIED = 'MICROPHONE_ACCESS_DENIED', + DEVICE_ACCESS_DENIED = 'DEVICE_ACCESS_DENIED', SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED', SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED', SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED',