openvidu-components: Fixed bug replacing tracks in user-settings

pull/707/head
csantosm 2022-02-22 15:17:15 +01:00
parent f40093746f
commit e63ef15d18
2 changed files with 90 additions and 30 deletions

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Device, OpenVidu } from 'openvidu-browser';
import { Device, OpenVidu, OpenViduError, OpenViduErrorName } from 'openvidu-browser';
import { CameraType, DeviceType, CustomDevice } from '../../models/device.model';
import { ILogger } from '../../models/logger.model';
@ -26,6 +26,7 @@ export class DeviceService {
private _isVideoMuted: boolean;
// Initialized with Storage.AUDIO_MUTED info saved on storage
private _isAudioMuted: boolean;
private deviceAccessDeniedError: boolean = false;
constructor(private loggerSrv: LoggerService, private platformSrv: PlatformService, private storageSrv: StorageService) {
this.log = this.loggerSrv.get('DevicesService');
@ -37,13 +38,19 @@ export class DeviceService {
}
async initializeDevices() {
// Forcing media permissions request.
// Sometimes, browser doens't launch the media permissions modal.
const mediaStream = await this.OV.getUserMedia({ audioSource: undefined, videoSource: undefined });
mediaStream?.getAudioTracks().forEach((track) => track.stop());
mediaStream?.getVideoTracks().forEach((track) => track.stop());
try {
// Forcing media permissions request.
// Sometimes, browser doens't launch the media permissions modal.
const mediaStream = await this.OV.getUserMedia({ audioSource: undefined, videoSource: undefined });
mediaStream?.getAudioTracks().forEach((track) => track.stop());
mediaStream?.getVideoTracks().forEach((track) => track.stop());
} catch (error) {
this.deviceAccessDeniedError = (<OpenViduError>error).name === OpenViduErrorName.DEVICE_ACCESS_DENIED;
}
this.devices = await this.OV.getDevices();
console.log(this.devices);
const customDevices = this.initializeCustomDevices(this.devices);
this.cameras = customDevices.cameras;
this.microphones = customDevices.microphones;
@ -56,14 +63,14 @@ export class DeviceService {
private initializeCustomDevices(defaultVDevices: Device[]) {
const FIRST_POSITION = 0;
const defaultMicrophones = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT);
const defaultCameras = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT);
const defaultMicrophones: Device[] = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT);
const defaultCameras: Device[] = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT);
const customDevices: { cameras: CustomDevice[]; microphones: CustomDevice[] } = {
cameras: [],
microphones: []
};
if (this.hasAudioDeviceAvailable) {
if (defaultMicrophones.length > 0) {
defaultMicrophones.forEach((device: Device) => {
customDevices.microphones.push({ label: device.label, device: device.deviceId });
});
@ -73,11 +80,17 @@ export class DeviceService {
if (!!storageMicrophone) {
this.microphoneSelected = storageMicrophone;
} else if (customDevices.microphones.length > 0) {
this.microphoneSelected = customDevices.microphones[0];
if(this.deviceAccessDeniedError && customDevices.microphones.length > 1){
// We assume that the default device is already in use
// Assign an alternative device with the aim of avoiding the DEVICE_ALREADY_IN_USE error
this.microphoneSelected = customDevices.microphones[1];
} else {
this.microphoneSelected = customDevices.microphones[0];
}
}
}
if (this.hasVideoDeviceAvailable) {
if (defaultCameras.length > 0) {
defaultCameras.forEach((device: Device, index: number) => {
const myDevice: CustomDevice = {
label: device.label,
@ -103,7 +116,13 @@ export class DeviceService {
if (!!storageCamera) {
this.cameraSelected = storageCamera;
} else if (customDevices.cameras.length > 0) {
this.cameraSelected = customDevices.cameras[0];
if(this.deviceAccessDeniedError && customDevices.cameras.length > 1){
// We assume that the default device is already in use
// Assign an alternative device with the aim of avoiding the DEVICE_ALREADY_IN_USE error
this.cameraSelected = customDevices.cameras[1];
} else {
this.cameraSelected = customDevices.cameras[0];
}
}
}
return customDevices;

View File

@ -1,5 +1,14 @@
import { Injectable } from '@angular/core';
import { Connection, OpenVidu, Publisher, PublisherProperties, Session, SignalOptions } from 'openvidu-browser';
import {
Connection,
OpenVidu,
OpenViduError,
OpenViduErrorName,
Publisher,
PublisherProperties,
Session,
SignalOptions
} from 'openvidu-browser';
import { LoggerService } from '../logger/logger.service';
@ -107,7 +116,6 @@ export class OpenViduService {
* Initialize a publisher checking devices saved on storage or if participant have devices available.
*/
async initDefaultPublisher(targetElement: string | HTMLElement): Promise<Publisher> {
const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable();
const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable();
const isVideoActive = !this.deviceService.isVideoMuted();
@ -272,29 +280,35 @@ export class OpenViduService {
async replaceTrack(videoType: VideoType, props: PublisherProperties) {
try {
this.log.d(`Replacing ${videoType} track`, props);
if (videoType === VideoType.CAMERA) {
let mediaStream: MediaStream;
const oldMediaStream = this.participantService.getMyCameraPublisher().stream.getMediaStream();
const isFirefoxPlatform = this.platformService.isFirefox();
const isReplacingAudio = !!props.audioSource;
const isReplacingVideo = !!props.videoSource;
if (this.platformService.isFirefox()) {
// Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
// NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
if (isReplacingVideo) {
this.participantService.getMyCameraPublisher().stream.getMediaStream().getVideoTracks()[0].stop();
} else if (isReplacingAudio) {
this.participantService.getMyCameraPublisher().stream.getMediaStream().getAudioTracks()[0].stop();
if (isReplacingVideo) {
if (isFirefoxPlatform) {
// Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
// NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
oldMediaStream.getVideoTracks()[0].stop();
}
}
const track = await this.OV.getUserMedia(props);
if (isReplacingAudio) {
// Replace audio track
const audioTrack: MediaStreamTrack = track.getAudioTracks()[0];
await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack);
} else if (isReplacingVideo) {
mediaStream = await this.createMediaStream(props);
// Replace video track
const videoTrack: MediaStreamTrack = track.getVideoTracks()[0];
const videoTrack: MediaStreamTrack = mediaStream.getVideoTracks()[0];
await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack);
} else if (isReplacingAudio) {
if (isFirefoxPlatform) {
// Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped
// NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
oldMediaStream.getAudioTracks()[0].stop();
}
mediaStream = await this.createMediaStream(props);
// Replace audio track
const audioTrack: MediaStreamTrack = mediaStream.getAudioTracks()[0];
await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack);
}
} else if (videoType === VideoType.SCREEN) {
const newScreenMediaStream = await this.OVScreen.getUserMedia(props);
@ -307,6 +321,33 @@ export class OpenViduService {
}
}
private async createMediaStream(pp: PublisherProperties): Promise<MediaStream>{
let mediaStream: MediaStream;
const isFirefoxPlatform = this.platformService.isFirefox();
const isReplacingAudio = !!pp.audioSource;
const isReplacingVideo = !!pp.videoSource;
try {
mediaStream = await this.OV.getUserMedia(pp);
} catch (error) {
if ((<OpenViduError>error).name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
if (isFirefoxPlatform) {
this.log.w('The device requested is not available. Restoring the older one');
// The track requested is not available so we are getting the old tracks ids for recovering the track
if (isReplacingVideo) {
pp.videoSource = this.deviceService.getCameraSelected().device;
} else if (isReplacingAudio) {
pp.audioSource = this.deviceService.getMicrophoneSelected().device;
}
mediaStream = await this.OV.getUserMedia(pp);
// TODO show error alert informing that the new device is not available
}
}
} finally {
return mediaStream;
}
}
needSendNicknameSignal(): boolean {
const oldNickname: string = JSON.parse(this.webcamSession.connection.data).clientData;
return oldNickname !== this.participantService.getWebcamNickname();