mirror of https://github.com/OpenVidu/openvidu.git
ov-components: Refactored and fixed device service
parent
7b560b447c
commit
5072804e67
|
@ -73,17 +73,7 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
|
||||
async ngOnInit() {
|
||||
this.subscribeToPrejoinDirectives();
|
||||
try {
|
||||
const cameraEnabled = this.storageService.isCameraEnabled();
|
||||
const microphoneEnabled = this.storageService.isMicrophoneEnabled();
|
||||
this.tracks = await this.openviduService.createLocalTracks(cameraEnabled, microphoneEnabled);
|
||||
this.openviduService.setLocalTracks(this.tracks);
|
||||
this.videoTrack = this.tracks.find((track) => track.kind === 'video');
|
||||
this.audioTrack = this.tracks.find((track) => track.kind === 'audio');
|
||||
} catch (error) {
|
||||
this.log.e('Error creating local tracks:', error);
|
||||
}
|
||||
|
||||
await this.initializeDevices();
|
||||
this.windowSize = window.innerWidth;
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
@ -105,6 +95,17 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
private async initializeDevices() {
|
||||
try {
|
||||
this.tracks = await this.openviduService.createLocalTracks();
|
||||
this.openviduService.setLocalTracks(this.tracks);
|
||||
this.videoTrack = this.tracks.find((track) => track.kind === 'video');
|
||||
this.audioTrack = this.tracks.find((track) => track.kind === 'audio');
|
||||
} catch (error) {
|
||||
this.log.e('Error creating local tracks:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onDeviceSelectorClicked() {
|
||||
// Some devices as iPhone do not show the menu panels correctly
|
||||
// Updating the container where the panel is added fix the problem.
|
||||
|
|
|
@ -481,7 +481,7 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
|
|||
}
|
||||
this.openviduAngularLayoutTemplate = this.defaultLayoutTemplate;
|
||||
}
|
||||
this.deviceSrv.forceInitDevices().then(() => (this.loading = false));
|
||||
this.deviceSrv.initializeDevices().then(() => (this.loading = false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,7 @@ import { CaptionsLangOption } from '../../models/caption.model';
|
|||
import { OpenViduComponentsConfigService } from '../../services/config/openvidu-components-angular.config.service';
|
||||
import { TranslateService } from '../../services/translate/translate.service';
|
||||
import { LangOption } from '../../models/lang.model';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
|
||||
/**
|
||||
* The **livekitUrl** directive sets the livekitUrl to grant a participant access to a Room.
|
||||
|
@ -614,7 +615,8 @@ export class VideoEnabledDirective implements OnDestroy {
|
|||
*/
|
||||
constructor(
|
||||
public elementRef: ElementRef,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
private libService: OpenViduComponentsConfigService,
|
||||
private storageService: StorageService
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -634,9 +636,25 @@ export class VideoEnabledDirective implements OnDestroy {
|
|||
/**
|
||||
* @ignore
|
||||
*/
|
||||
update(value: boolean) {
|
||||
if (this.libService.isVideoEnabled() !== value) {
|
||||
this.libService.setVideoEnabled(value);
|
||||
update(enabled: boolean) {
|
||||
const storageIsEnabled = this.storageService.isCameraEnabled();
|
||||
|
||||
// Determine the final enabled state of the camera
|
||||
let finalEnabledState: boolean;
|
||||
if (enabled) {
|
||||
// If enabled is true, respect the storage value if it's false
|
||||
finalEnabledState = storageIsEnabled !== false;
|
||||
} else {
|
||||
// If enabled is false, disable the camera
|
||||
finalEnabledState = false;
|
||||
}
|
||||
|
||||
// Update the storage with the final state
|
||||
this.storageService.setCameraEnabled(finalEnabledState);
|
||||
|
||||
// Ensure libService state is consistent with the final enabled state
|
||||
if (this.libService.isVideoEnabled() !== finalEnabledState) {
|
||||
this.libService.setVideoEnabled(finalEnabledState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -668,7 +686,8 @@ export class AudioEnabledDirective implements OnDestroy {
|
|||
*/
|
||||
constructor(
|
||||
public elementRef: ElementRef,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
private libService: OpenViduComponentsConfigService,
|
||||
private storageService: StorageService
|
||||
) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -685,9 +704,24 @@ export class AudioEnabledDirective implements OnDestroy {
|
|||
/**
|
||||
* @ignore
|
||||
*/
|
||||
update(value: boolean) {
|
||||
if (this.libService.isAudioEnabled() !== value) {
|
||||
this.libService.setAudioEnabled(value);
|
||||
update(enabled: boolean) {
|
||||
const storageIsEnabled = this.storageService.isMicrophoneEnabled();
|
||||
|
||||
// Determine the final enabled state of the microphone
|
||||
let finalEnabledState: boolean;
|
||||
if (enabled) {
|
||||
// If enabled is true, respect the storage value if it's false
|
||||
finalEnabledState = storageIsEnabled !== false;
|
||||
} else {
|
||||
// If enabled is false, disable the camera
|
||||
finalEnabledState = false;
|
||||
}
|
||||
|
||||
// Update the storage with the final state
|
||||
this.storageService.setMicrophoneEnabled(finalEnabledState);
|
||||
|
||||
if (this.libService.isAudioEnabled() !== enabled) {
|
||||
this.libService.setAudioEnabled(enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CameraType, CustomDevice, DeviceType } from '../../models/device.model';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { OpenViduComponentsConfigService } from '../config/openvidu-components-angular.config.service';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
import { PlatformService } from '../platform/platform.service';
|
||||
import { StorageService } from '../storage/storage.service';
|
||||
|
@ -17,24 +16,17 @@ export class DeviceService {
|
|||
private devices: MediaDeviceInfo[];
|
||||
private cameras: CustomDevice[] = [];
|
||||
private microphones: CustomDevice[] = [];
|
||||
private cameraSelected: CustomDevice | undefined;
|
||||
private microphoneSelected: CustomDevice | undefined;
|
||||
private cameraSelected?: CustomDevice;
|
||||
private microphoneSelected?: CustomDevice;
|
||||
private log: ILogger;
|
||||
private videoDevicesEnabled: boolean = true;
|
||||
private audioDevicesEnabled: boolean = true;
|
||||
|
||||
// Initialized with Storage.CAMERA_ENABLED info saved on storage
|
||||
private _isCameraEnabled: boolean;
|
||||
// Initialized with Storage.MICROPHONE_ENABLED info saved on storage
|
||||
private _isMicrophoneEnabled: boolean;
|
||||
// Whether the media devices permission have been rejected or not
|
||||
private deviceAccessDeniedError: boolean = false;
|
||||
|
||||
constructor(
|
||||
private loggerSrv: LoggerService,
|
||||
private platformSrv: PlatformService,
|
||||
private storageSrv: StorageService,
|
||||
private libSrv: OpenViduComponentsConfigService
|
||||
private storageSrv: StorageService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('DevicesService');
|
||||
}
|
||||
|
@ -43,26 +35,21 @@ export class DeviceService {
|
|||
* Initialize media devices and select a devices checking in local storage (if exists) or
|
||||
* first devices found by default
|
||||
*/
|
||||
async forceInitDevices() {
|
||||
async initializeDevices() {
|
||||
this.clear();
|
||||
|
||||
try {
|
||||
this.devices = await this.getLocalDevices();
|
||||
} catch (error) {
|
||||
this.log.e('Error getting media devices', error);
|
||||
} finally {
|
||||
if (this.deviceAccessDeniedError) {
|
||||
this.log.w('Media devices permissions were not granted.');
|
||||
} else {
|
||||
this.initializeCustomDevices();
|
||||
this.updateAudioDeviceSelected();
|
||||
this.updateVideoDeviceSelected();
|
||||
|
||||
this._isCameraEnabled = this.storageSrv.isCameraEnabled() || this.libSrv.isVideoEnabled();
|
||||
this._isMicrophoneEnabled = this.storageSrv.isMicrophoneEnabled() || this.libSrv.isAudioEnabled();
|
||||
|
||||
this.log.d('Media devices', this.cameras, this.microphones);
|
||||
return;
|
||||
}
|
||||
|
||||
this.initializeCustomDevices();
|
||||
this.updateSelectedDevices();
|
||||
this.log.d('Media devices', this.cameras, this.microphones);
|
||||
} catch (error) {
|
||||
this.log.e('Error getting media devices', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,87 +63,52 @@ export class DeviceService {
|
|||
}
|
||||
}
|
||||
|
||||
private initializeCustomDevices(updateSelected: boolean = true): void {
|
||||
const FIRST_POSITION = 0;
|
||||
const defaultMicrophones: MediaDeviceInfo[] = this.devices.filter((device) => device.kind === DeviceType.AUDIO_INPUT);
|
||||
const defaultCameras: MediaDeviceInfo[] = this.devices.filter((device) => device.kind === DeviceType.VIDEO_INPUT);
|
||||
private initializeCustomDevices(): void {
|
||||
this.cameras = this.devices
|
||||
.filter((d) => d.kind === DeviceType.VIDEO_INPUT)
|
||||
.map((d) => this.createCustomDevice(d, CameraType.BACK));
|
||||
this.microphones = this.devices
|
||||
.filter((d) => d.kind === DeviceType.AUDIO_INPUT)
|
||||
.map((d) => ({ label: d.label, device: d.deviceId }));
|
||||
|
||||
if (defaultMicrophones.length > 0) {
|
||||
this.microphones = [];
|
||||
defaultMicrophones.forEach((device: MediaDeviceInfo) => {
|
||||
this.microphones.push({ label: device.label, device: device.deviceId });
|
||||
});
|
||||
}
|
||||
|
||||
if (defaultCameras.length > 0) {
|
||||
this.cameras = [];
|
||||
defaultCameras.forEach((device: MediaDeviceInfo, index: number) => {
|
||||
const myDevice: CustomDevice = {
|
||||
label: device.label,
|
||||
device: device.deviceId,
|
||||
type: CameraType.BACK
|
||||
};
|
||||
if (this.platformSrv.isMobile()) {
|
||||
// We assume front video device has 'front' in its label in Mobile devices
|
||||
if (myDevice.label.toLowerCase().includes(CameraType.FRONT.toLowerCase())) {
|
||||
myDevice.type = CameraType.FRONT;
|
||||
}
|
||||
} else {
|
||||
// We assume first device is web camera in Browser Desktop
|
||||
if (index === FIRST_POSITION) {
|
||||
myDevice.type = CameraType.FRONT;
|
||||
}
|
||||
if (this.platformSrv.isMobile()) {
|
||||
this.cameras.forEach((c) => {
|
||||
if (c.label.toLowerCase().includes(CameraType.FRONT.toLowerCase())) {
|
||||
c.type = CameraType.FRONT;
|
||||
}
|
||||
this.cameras.push(myDevice);
|
||||
});
|
||||
} else if (this.cameras.length > 0) {
|
||||
this.cameras[0].type = CameraType.FRONT;
|
||||
}
|
||||
}
|
||||
|
||||
private updateAudioDeviceSelected() {
|
||||
// Setting microphone selected
|
||||
if (this.microphones.length > 0) {
|
||||
const storageMicrophone = this.getMicrophoneFromStogare();
|
||||
if (!!storageMicrophone) {
|
||||
this.microphoneSelected = storageMicrophone;
|
||||
} else if (this.microphones.length > 0) {
|
||||
if (this.deviceAccessDeniedError && this.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 = this.microphones[1];
|
||||
} else {
|
||||
this.microphoneSelected = this.microphones[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
private createCustomDevice(device: MediaDeviceInfo, defaultType: CameraType): CustomDevice {
|
||||
return {
|
||||
label: device.label,
|
||||
device: device.deviceId,
|
||||
type: defaultType
|
||||
};
|
||||
}
|
||||
|
||||
private updateVideoDeviceSelected() {
|
||||
// Setting camera selected
|
||||
if (this.cameras.length > 0) {
|
||||
const storageCamera = this.getCameraFromStorage();
|
||||
if (!!storageCamera) {
|
||||
this.cameraSelected = storageCamera;
|
||||
} else if (this.cameras.length > 0) {
|
||||
if (this.deviceAccessDeniedError && this.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 = this.cameras[1];
|
||||
} else {
|
||||
this.cameraSelected = this.cameras[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
private updateSelectedDevices() {
|
||||
this.cameraSelected = this.getDeviceFromStorage(this.cameras, this.storageSrv.getVideoDevice()) || this.cameras[0];
|
||||
this.microphoneSelected = this.getDeviceFromStorage(this.microphones, this.storageSrv.getAudioDevice()) || this.microphones[0];
|
||||
}
|
||||
|
||||
private getDeviceFromStorage(devices: CustomDevice[], storageDevice: CustomDevice | null): CustomDevice | undefined {
|
||||
if (!storageDevice) return;
|
||||
return devices.find((d) => d.device === storageDevice.device);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isCameraEnabled(): boolean {
|
||||
return this.hasVideoDeviceAvailable() && this._isCameraEnabled;
|
||||
return this.hasVideoDeviceAvailable() && this.storageSrv.isCameraEnabled();
|
||||
}
|
||||
|
||||
isMicrophoneEnabled(): boolean {
|
||||
return this.hasAudioDeviceAvailable() && this._isMicrophoneEnabled;
|
||||
return this.hasAudioDeviceAvailable() && this.storageSrv.isMicrophoneEnabled();
|
||||
}
|
||||
|
||||
getCameraSelected(): CustomDevice | undefined {
|
||||
|
@ -168,13 +120,15 @@ export class DeviceService {
|
|||
}
|
||||
|
||||
setCameraSelected(deviceId: any) {
|
||||
this.cameraSelected = this.getCameraByDeviceField(deviceId);
|
||||
this.saveCameraToStorage(this.cameraSelected);
|
||||
this.cameraSelected = this.getDeviceById(this.cameras, deviceId);
|
||||
const saveFunction = (device) => this.storageSrv.setVideoDevice(device);
|
||||
this.saveDeviceToStorage(this.cameraSelected, saveFunction);
|
||||
}
|
||||
|
||||
setMicSelected(deviceField: any) {
|
||||
this.microphoneSelected = this.getMicrophoneByDeviceField(deviceField);
|
||||
this.saveMicrophoneToStorage(this.microphoneSelected);
|
||||
setMicSelected(deviceId: string) {
|
||||
this.microphoneSelected = this.getDeviceById(this.microphones, deviceId);
|
||||
const saveFunction = (device) => this.storageSrv.setAudioDevice(device);
|
||||
this.saveDeviceToStorage(this.microphoneSelected, saveFunction);
|
||||
}
|
||||
|
||||
needUpdateVideoTrack(newDevice: CustomDevice): boolean {
|
||||
|
@ -201,18 +155,6 @@ export class DeviceService {
|
|||
return this.audioDevicesEnabled && this.microphones.length > 0;
|
||||
}
|
||||
|
||||
cameraNeedsMirror(deviceField: string): boolean {
|
||||
return this.getCameraByDeviceField(deviceField)?.type === CameraType.FRONT;
|
||||
}
|
||||
|
||||
disableVideoDevices() {
|
||||
this.videoDevicesEnabled = false;
|
||||
}
|
||||
|
||||
disableAudioDevices() {
|
||||
this.audioDevicesEnabled = false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.devices = [];
|
||||
this.cameras = [];
|
||||
|
@ -223,34 +165,12 @@ export class DeviceService {
|
|||
this.audioDevicesEnabled = true;
|
||||
}
|
||||
|
||||
private getCameraByDeviceField(deviceField: any): CustomDevice {
|
||||
return <CustomDevice>this.cameras.find((opt: CustomDevice) => opt.device === deviceField || opt.label === deviceField);
|
||||
private getDeviceById(devices: CustomDevice[], deviceId: string): CustomDevice | undefined {
|
||||
return devices.find((d) => d.device === deviceId);
|
||||
}
|
||||
|
||||
private getMicrophoneByDeviceField(deviceField: any): CustomDevice {
|
||||
return <CustomDevice>this.microphones.find((opt: CustomDevice) => opt.device === deviceField || opt.label === deviceField);
|
||||
}
|
||||
|
||||
private getMicrophoneFromStogare(): CustomDevice | undefined {
|
||||
const storageDevice: CustomDevice | null = this.storageSrv.getAudioDevice();
|
||||
if (!!storageDevice && this.microphones.some((device) => device.device === storageDevice.device)) {
|
||||
return storageDevice;
|
||||
}
|
||||
}
|
||||
|
||||
private getCameraFromStorage() {
|
||||
const storageDevice: CustomDevice | null = this.storageSrv.getVideoDevice();
|
||||
if (!!storageDevice && this.cameras.some((device) => device.device === storageDevice.device)) {
|
||||
return storageDevice;
|
||||
}
|
||||
}
|
||||
|
||||
private saveCameraToStorage(cam: CustomDevice) {
|
||||
this.storageSrv.setVideoDevice(cam);
|
||||
}
|
||||
|
||||
private saveMicrophoneToStorage(mic: CustomDevice) {
|
||||
this.storageSrv.setAudioDevice(mic);
|
||||
private saveDeviceToStorage(device: CustomDevice | undefined, saveFunction: (device: CustomDevice) => void) {
|
||||
if (device) saveFunction(device);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -262,17 +182,14 @@ export class DeviceService {
|
|||
// Forcing media permissions request.
|
||||
let localTracks: LocalTrack[] = [];
|
||||
try {
|
||||
try {
|
||||
localTracks = await createLocalTracks({ audio: true, video: true });
|
||||
localTracks.forEach((track) => track.stop());
|
||||
} catch (error) {
|
||||
this.log.e('Error getting local audio tracks', error);
|
||||
}
|
||||
localTracks = await createLocalTracks({ audio: true, video: true });
|
||||
localTracks.forEach((track) => track.stop());
|
||||
|
||||
const devices = this.platformSrv.isFirefox() ? await this.getMediaDevicesFirefox() : await Room.getLocalDevices();
|
||||
return devices.filter((d: MediaDeviceInfo) => d.label && d.deviceId && d.deviceId !== 'default');
|
||||
} catch (error) {
|
||||
this.log.e('Error getting local devices', error);
|
||||
this.deviceAccessDeniedError = true;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export class OpenViduService {
|
|||
constructor(
|
||||
private loggerSrv: LoggerService,
|
||||
private deviceService: DeviceService,
|
||||
private storageSrv: StorageService
|
||||
private storageService: StorageService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('OpenViduService');
|
||||
// this.isSttReadyObs = this._isSttReady.asObservable();
|
||||
|
@ -91,7 +91,7 @@ export class OpenViduService {
|
|||
try {
|
||||
await this.room.connect(this.livekitUrl, this.livekitToken);
|
||||
this.log.d(`Successfully connected to room ${this.room.name}`);
|
||||
const participantName = this.storageSrv.getParticipantName();
|
||||
const participantName = this.storageService.getParticipantName();
|
||||
if (participantName) {
|
||||
this.room.localParticipant.setName(participantName);
|
||||
}
|
||||
|
@ -194,13 +194,21 @@ export class OpenViduService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Allows create local tracks without connecting to a room.
|
||||
* @param videoDeviceId string to specific device, false to disable video, true to default device
|
||||
* @param audioDeviceId string to specific device, false to disable audio, true to default device
|
||||
* @returns Promise<LocalTrack[]> with the created tracks
|
||||
* Creates local tracks for video and audio devices.
|
||||
*
|
||||
* @param videoDeviceId - The ID of the video device to use. If not provided, the default video device will be used.
|
||||
* @param audioDeviceId - The ID of the audio device to use. If not provided, the default audio device will be used.
|
||||
* @returns A promise that resolves to an array of LocalTrack objects representing the created tracks.
|
||||
* @internal
|
||||
*/
|
||||
async createLocalTracks(videoDeviceId: string | boolean, audioDeviceId: string | boolean): Promise<LocalTrack[]> {
|
||||
async createLocalTracks(
|
||||
videoDeviceId: string | boolean | undefined = undefined,
|
||||
audioDeviceId: string | boolean | undefined = undefined
|
||||
): Promise<LocalTrack[]> {
|
||||
// If video and audio device IDs are not provided, check if they are enabled and use the default devices
|
||||
if (videoDeviceId === undefined) videoDeviceId = this.deviceService.isCameraEnabled();
|
||||
if (audioDeviceId === undefined) audioDeviceId = this.deviceService.isMicrophoneEnabled();
|
||||
|
||||
let options: CreateLocalTracksOptions = {
|
||||
audio: { echoCancellation: true, noiseSuppression: true },
|
||||
video: {}
|
||||
|
|
|
@ -79,11 +79,11 @@ export class OpenviduWebComponentComponent {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
_videoEnabled: boolean = false;
|
||||
_videoEnabled: boolean = true;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_audioEnabled: boolean = false;
|
||||
_audioEnabled: boolean = true;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue