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 { 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 { CameraType, DeviceType, CustomDevice } from '../../models/device.model';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
@ -26,6 +26,7 @@ export class DeviceService {
private _isVideoMuted: boolean; private _isVideoMuted: boolean;
// Initialized with Storage.AUDIO_MUTED info saved on storage // Initialized with Storage.AUDIO_MUTED info saved on storage
private _isAudioMuted: boolean; private _isAudioMuted: boolean;
private deviceAccessDeniedError: boolean = false;
constructor(private loggerSrv: LoggerService, private platformSrv: PlatformService, private storageSrv: StorageService) { constructor(private loggerSrv: LoggerService, private platformSrv: PlatformService, private storageSrv: StorageService) {
this.log = this.loggerSrv.get('DevicesService'); this.log = this.loggerSrv.get('DevicesService');
@ -37,13 +38,19 @@ export class DeviceService {
} }
async initializeDevices() { async initializeDevices() {
try {
// Forcing media permissions request. // Forcing media permissions request.
// Sometimes, browser doens't launch the media permissions modal. // Sometimes, browser doens't launch the media permissions modal.
const mediaStream = await this.OV.getUserMedia({ audioSource: undefined, videoSource: undefined }); const mediaStream = await this.OV.getUserMedia({ audioSource: undefined, videoSource: undefined });
mediaStream?.getAudioTracks().forEach((track) => track.stop()); mediaStream?.getAudioTracks().forEach((track) => track.stop());
mediaStream?.getVideoTracks().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(); this.devices = await this.OV.getDevices();
console.log(this.devices);
const customDevices = this.initializeCustomDevices(this.devices); const customDevices = this.initializeCustomDevices(this.devices);
this.cameras = customDevices.cameras; this.cameras = customDevices.cameras;
this.microphones = customDevices.microphones; this.microphones = customDevices.microphones;
@ -56,14 +63,14 @@ export class DeviceService {
private initializeCustomDevices(defaultVDevices: Device[]) { private initializeCustomDevices(defaultVDevices: Device[]) {
const FIRST_POSITION = 0; const FIRST_POSITION = 0;
const defaultMicrophones = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT); const defaultMicrophones: Device[] = defaultVDevices.filter((device) => device.kind === DeviceType.AUDIO_INPUT);
const defaultCameras = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT); const defaultCameras: Device[] = defaultVDevices.filter((device) => device.kind === DeviceType.VIDEO_INPUT);
const customDevices: { cameras: CustomDevice[]; microphones: CustomDevice[] } = { const customDevices: { cameras: CustomDevice[]; microphones: CustomDevice[] } = {
cameras: [], cameras: [],
microphones: [] microphones: []
}; };
if (this.hasAudioDeviceAvailable) { if (defaultMicrophones.length > 0) {
defaultMicrophones.forEach((device: Device) => { defaultMicrophones.forEach((device: Device) => {
customDevices.microphones.push({ label: device.label, device: device.deviceId }); customDevices.microphones.push({ label: device.label, device: device.deviceId });
}); });
@ -73,11 +80,17 @@ export class DeviceService {
if (!!storageMicrophone) { if (!!storageMicrophone) {
this.microphoneSelected = storageMicrophone; this.microphoneSelected = storageMicrophone;
} else if (customDevices.microphones.length > 0) { } else if (customDevices.microphones.length > 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]; this.microphoneSelected = customDevices.microphones[0];
} }
} }
}
if (this.hasVideoDeviceAvailable) { if (defaultCameras.length > 0) {
defaultCameras.forEach((device: Device, index: number) => { defaultCameras.forEach((device: Device, index: number) => {
const myDevice: CustomDevice = { const myDevice: CustomDevice = {
label: device.label, label: device.label,
@ -103,9 +116,15 @@ export class DeviceService {
if (!!storageCamera) { if (!!storageCamera) {
this.cameraSelected = storageCamera; this.cameraSelected = storageCamera;
} else if (customDevices.cameras.length > 0) { } else if (customDevices.cameras.length > 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]; this.cameraSelected = customDevices.cameras[0];
} }
} }
}
return customDevices; return customDevices;
} }

View File

@ -1,5 +1,14 @@
import { Injectable } from '@angular/core'; 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'; 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. * Initialize a publisher checking devices saved on storage or if participant have devices available.
*/ */
async initDefaultPublisher(targetElement: string | HTMLElement): Promise<Publisher> { async initDefaultPublisher(targetElement: string | HTMLElement): Promise<Publisher> {
const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable(); const hasVideoDevices = this.deviceService.hasVideoDeviceAvailable();
const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable(); const hasAudioDevices = this.deviceService.hasAudioDeviceAvailable();
const isVideoActive = !this.deviceService.isVideoMuted(); const isVideoActive = !this.deviceService.isVideoMuted();
@ -272,29 +280,35 @@ export class OpenViduService {
async replaceTrack(videoType: VideoType, props: PublisherProperties) { async replaceTrack(videoType: VideoType, props: PublisherProperties) {
try { try {
this.log.d(`Replacing ${videoType} track`, props); this.log.d(`Replacing ${videoType} track`, props);
if (videoType === VideoType.CAMERA) { if (videoType === VideoType.CAMERA) {
let mediaStream: MediaStream;
const oldMediaStream = this.participantService.getMyCameraPublisher().stream.getMediaStream();
const isFirefoxPlatform = this.platformService.isFirefox();
const isReplacingAudio = !!props.audioSource; const isReplacingAudio = !!props.audioSource;
const isReplacingVideo = !!props.videoSource; const isReplacingVideo = !!props.videoSource;
if (this.platformService.isFirefox()) { if (isReplacingVideo) {
if (isFirefoxPlatform) {
// Firefox throw an exception trying to get a new MediaStreamTrack if the older one is not stopped // 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 // NotReadableError: Concurrent mic process limit. Stopping tracks before call to getUserMedia
if (isReplacingVideo) { oldMediaStream.getVideoTracks()[0].stop();
this.participantService.getMyCameraPublisher().stream.getMediaStream().getVideoTracks()[0].stop();
} else if (isReplacingAudio) {
this.participantService.getMyCameraPublisher().stream.getMediaStream().getAudioTracks()[0].stop();
} }
} mediaStream = await this.createMediaStream(props);
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) {
// Replace video track // Replace video track
const videoTrack: MediaStreamTrack = track.getVideoTracks()[0]; const videoTrack: MediaStreamTrack = mediaStream.getVideoTracks()[0];
await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack); 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) { } else if (videoType === VideoType.SCREEN) {
const newScreenMediaStream = await this.OVScreen.getUserMedia(props); 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 { needSendNicknameSignal(): boolean {
const oldNickname: string = JSON.parse(this.webcamSession.connection.data).clientData; const oldNickname: string = JSON.parse(this.webcamSession.connection.data).clientData;
return oldNickname !== this.participantService.getWebcamNickname(); return oldNickname !== this.participantService.getWebcamNickname();