mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: ask for camera and micro at once (one permission popup)
parent
9d15589126
commit
477d9e282d
|
@ -300,7 +300,7 @@ export class OpenVidu {
|
||||||
let errorName: OpenViduErrorName;
|
let errorName: OpenViduErrorName;
|
||||||
const errorMessage = error.toString();
|
const errorMessage = error.toString();
|
||||||
if (!(options.videoSource === 'screen')) {
|
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 {
|
} else {
|
||||||
errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
|
errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,11 @@ export class Publisher extends StreamManager {
|
||||||
*/
|
*/
|
||||||
accessAllowed = false;
|
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
|
* 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
|
* 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 {
|
subscribeToRemote(value?: boolean): void {
|
||||||
this.stream.subscribeToMyRemote();
|
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);
|
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.isLocalStreamReadyToPublish = true;
|
||||||
this.stream.ee.emitEvent('stream-ready-to-publish', []);
|
this.stream.ee.emitEvent('stream-ready-to-publish', []);
|
||||||
|
|
||||||
|
@ -221,134 +233,119 @@ export class Publisher extends StreamManager {
|
||||||
|
|
||||||
this.stream.setOutboundStreamOptions(outboundStreamOptions);
|
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 constraintsAux: MediaStreamConstraints = {};
|
||||||
const timeForDialogEvent = 1250;
|
const timeForDialogEvent = 1250;
|
||||||
|
|
||||||
if (this.stream.isSendVideo()) {
|
if (this.stream.isSendVideo() || this.stream.isSendAudio()) {
|
||||||
|
const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio);
|
||||||
constraintsAux.audio = false;
|
constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
|
||||||
constraintsAux.video = constraints.video;
|
constraintsAux.video = constraints.video;
|
||||||
|
|
||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
this.setPermissionDialogTimer(timeForDialogEvent);
|
this.setPermissionDialogTimer(timeForDialogEvent);
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia(constraintsAux)
|
navigator.mediaDevices.getUserMedia(constraintsAux)
|
||||||
.then(videoOnlyStream => {
|
.then(mediaStream => {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
||||||
|
|
||||||
if (this.stream.isSendAudio()) {
|
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 = (constraints.audio === undefined) ? true : constraints.audio;
|
constraintsAux.audio = definedAudioConstraint;
|
||||||
constraintsAux.video = false;
|
constraintsAux.video = false;
|
||||||
|
|
||||||
startTime = Date.now();
|
startTime = Date.now();
|
||||||
this.setPermissionDialogTimer(timeForDialogEvent);
|
this.setPermissionDialogTimer(timeForDialogEvent);
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia(constraintsAux)
|
navigator.mediaDevices.getUserMedia(constraintsAux)
|
||||||
.then(audioOnlyStream => {
|
.then(audioOnlyStream => {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
||||||
|
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
|
||||||
videoOnlyStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
|
successCallback(mediaStream);
|
||||||
successCallback(videoOnlyStream);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
||||||
|
let errorName, errorMessage;
|
||||||
videoOnlyStream.getVideoTracks().forEach((track) => {
|
|
||||||
track.stop();
|
|
||||||
});
|
|
||||||
let errorName;
|
|
||||||
let errorMessage;
|
|
||||||
switch (error.name.toLowerCase()) {
|
switch (error.name.toLowerCase()) {
|
||||||
case 'notfounderror':
|
case 'notfounderror':
|
||||||
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = error.toString();
|
errorMessage = error.toString();
|
||||||
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
break;
|
break;
|
||||||
case 'notallowederror':
|
case 'notallowederror':
|
||||||
errorName = OpenViduErrorName.MICROPHONE_ACCESS_DENIED;
|
errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED;
|
||||||
errorMessage = error.toString();
|
errorMessage = error.toString();
|
||||||
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
break;
|
break;
|
||||||
case 'overconstrainederror':
|
case 'overconstrainederror':
|
||||||
if (error.constraint.toLowerCase() === 'deviceid') {
|
if (error.constraint.toLowerCase() === 'deviceid') {
|
||||||
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||||
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact + "' not found";
|
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
|
||||||
} else {
|
} else {
|
||||||
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
||||||
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
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 {
|
} else {
|
||||||
successCallback(videoOnlyStream);
|
successCallback(mediaStream);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
||||||
|
let errorName, errorMessage;
|
||||||
let errorName;
|
|
||||||
let errorMessage;
|
|
||||||
switch (error.name.toLowerCase()) {
|
switch (error.name.toLowerCase()) {
|
||||||
case 'notfounderror':
|
case 'notfounderror':
|
||||||
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
navigator.mediaDevices.getUserMedia({
|
||||||
errorMessage = error.toString();
|
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;
|
break;
|
||||||
case 'notallowederror':
|
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();
|
errorMessage = error.toString();
|
||||||
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
break;
|
break;
|
||||||
case 'overconstrainederror':
|
case 'overconstrainederror':
|
||||||
if (error.constraint.toLowerCase() === 'deviceid') {
|
navigator.mediaDevices.getUserMedia({
|
||||||
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
audio: false,
|
||||||
errorMessage = "Video input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
|
video: constraints.video
|
||||||
} else {
|
})
|
||||||
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
.then(mediaStream => {
|
||||||
errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
mediaStream.getVideoTracks().forEach((track) => {
|
||||||
}
|
track.stop();
|
||||||
}
|
});
|
||||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
if (error.constraint.toLowerCase() === 'deviceid') {
|
||||||
});
|
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
|
||||||
|
errorMessage = "Video input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
|
||||||
} else if (this.stream.isSendAudio()) {
|
} else {
|
||||||
|
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
||||||
constraintsAux.audio = (constraints.audio === undefined) ? true : constraints.audio;
|
errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
||||||
constraintsAux.video = false;
|
}
|
||||||
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
const startTime = Date.now();
|
}).catch(e => {
|
||||||
this.setPermissionDialogTimer(timeForDialogEvent);
|
if (error.constraint.toLowerCase() === 'deviceid') {
|
||||||
|
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||||
navigator.mediaDevices.getUserMedia(constraints)
|
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
|
||||||
.then(audioOnlyStream => {
|
} else {
|
||||||
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
|
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
|
||||||
|
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
|
||||||
successCallback(audioOnlyStream);
|
}
|
||||||
})
|
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||||
.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();
|
|
||||||
break;
|
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 '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>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 {
|
} else {
|
||||||
reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET,
|
reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET,
|
||||||
|
|
|
@ -168,6 +168,12 @@ export class Stream {
|
||||||
*/
|
*/
|
||||||
setMediaStream(mediaStream: MediaStream): void {
|
setMediaStream(mediaStream: MediaStream): void {
|
||||||
this.mediaStream = mediaStream;
|
this.mediaStream = mediaStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
updateMediaStreamInVideos() {
|
||||||
this.ee.emitEvent('mediastream-updated');
|
this.ee.emitEvent('mediastream-updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,8 +194,8 @@ export class Stream {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
subscribeToMyRemote(): void {
|
subscribeToMyRemote(value: boolean): void {
|
||||||
this.isSubscribeToRemote = true;
|
this.isSubscribeToRemote = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -395,6 +401,11 @@ export class Stream {
|
||||||
this.processSdpAnswer(response.sdpAnswer)
|
this.processSdpAnswer(response.sdpAnswer)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isLocalStreamPublished = true;
|
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');
|
this.ee.emitEvent('stream-created-by-publisher');
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,8 +20,7 @@
|
||||||
*/
|
*/
|
||||||
export enum OpenViduErrorName {
|
export enum OpenViduErrorName {
|
||||||
BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',
|
BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',
|
||||||
CAMERA_ACCESS_DENIED = 'CAMERA_ACCESS_DENIED',
|
DEVICE_ACCESS_DENIED = 'DEVICE_ACCESS_DENIED',
|
||||||
MICROPHONE_ACCESS_DENIED = 'MICROPHONE_ACCESS_DENIED',
|
|
||||||
SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED',
|
SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED',
|
||||||
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
|
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
|
||||||
SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED',
|
SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED',
|
||||||
|
|
Loading…
Reference in New Issue