diff --git a/packages/core/src/types/videoRoomDevice.ts b/packages/core/src/types/videoRoomDevice.ts index d9d50d42a..02b0e240f 100644 --- a/packages/core/src/types/videoRoomDevice.ts +++ b/packages/core/src/types/videoRoomDevice.ts @@ -1,6 +1,8 @@ export type CameraUpdated = 'camera.updated' +export type CameraConstraintsUpdated = 'camera.constraints.updated' export type CameraDisconnected = 'camera.disconnected' export type MicrophoneUpdated = 'microphone.updated' +export type MicrophoneConstraintsUpdated = 'microphone.constraints.updated' export type MicrophoneDisconnected = 'microphone.disconnected' export type SpeakerUpdated = 'speaker.updated' export type SpeakerDisconnected = 'speaker.disconnected' @@ -14,6 +16,10 @@ export type VideoRoomDeviceUpdatedEventNames = | MicrophoneUpdated | SpeakerUpdated +export type VideoRoomDeviceConstraintsUpdatedEventNames = + | CameraConstraintsUpdated + | MicrophoneConstraintsUpdated + export type VideoRoomDeviceDisconnectedEventNames = | CameraDisconnected | MicrophoneDisconnected @@ -21,10 +27,11 @@ export type VideoRoomDeviceDisconnectedEventNames = export type VideoRoomDeviceEventNames = | VideoRoomDeviceUpdatedEventNames + | VideoRoomDeviceConstraintsUpdatedEventNames | VideoRoomDeviceDisconnectedEventNames export interface VideoRoomMediaDeviceInfo { - deviceId: MediaDeviceInfo['deviceId'] | undefined + deviceId: MediaTrackConstraintSet['deviceId'] label: MediaDeviceInfo['label'] | undefined } @@ -33,8 +40,21 @@ export interface DeviceUpdatedEventParams { current: VideoRoomMediaDeviceInfo } +export interface MediaDeviceIdentifiers { + deviceId: MediaTrackConstraintSet['deviceId'] + kind: string + label: string +} +export interface DeviceConstraintsUpdatedEventParams { + kind: string + previous: MediaDeviceIdentifiers | undefined + current: MediaDeviceIdentifiers + constraints: MediaTrackConstraints +} + export type DeviceDisconnectedEventParams = VideoRoomMediaDeviceInfo export type VideoRoomDeviceEventParams = | DeviceUpdatedEventParams | DeviceDisconnectedEventParams + | DeviceConstraintsUpdatedEventParams diff --git a/packages/webrtc/src/BaseConnection.ts b/packages/webrtc/src/BaseConnection.ts index e7f9bfb85..a0693b6ef 100644 --- a/packages/webrtc/src/BaseConnection.ts +++ b/packages/webrtc/src/BaseConnection.ts @@ -21,7 +21,11 @@ import { UpdateMediaParams, UpdateMediaDirection, } from '@signalwire/core' -import type { ReduxComponent, VertoModifyResponse } from '@signalwire/core' +import type { + ReduxComponent, + VertoModifyResponse, + VideoRoomDeviceConstraintsUpdatedEventNames, +} from '@signalwire/core' import RTCPeer from './RTCPeer' import { ConnectionOptions, @@ -29,6 +33,7 @@ import { UpdateMediaOptionsParams, BaseConnectionEvents, OnVertoByeParams, + EmitDeviceConstraintsUpdatedEventsParams, } from './utils/interfaces' import { stopTrack, getUserMedia, streamIsValid } from './utils' import { @@ -563,11 +568,30 @@ export class BaseConnection< // Add or update the transceiver (may trigger renegotiation) await this.handleTransceiverForTrack(newTrack) - // Emit the device.updated events - this.emitDeviceUpdatedEvents({ - newTrack, - prevAudioTrack, - prevVideoTrack, + const prevTrack = newTrack.kind === 'audio' ? + prevAudioTrack: prevVideoTrack + + if (newTrack.getConstraints().deviceId !== prevTrack?.getConstraints().deviceId) { + // Emit the device.updated events only when the device was updated + this.emitDeviceUpdatedEvents({ + newTrack, + prevAudioTrack, + prevVideoTrack, + }) + } + + this.emitDeviceConstraintsUpdatedEvents({ + currentConstraints: newTrack.getConstraints(), + prevTrackIdentifiers: prevTrack ? { + kind: prevTrack?.kind, + label: prevTrack?.label, + deviceId: prevTrack?.getConstraints().deviceId + } : undefined, + currentTrackIdentifiers: { + kind: newTrack.kind, + label: newTrack.label, + deviceId: newTrack.getConstraints().deviceId + } }) } @@ -696,28 +720,48 @@ export class BaseConnection< if (newTrack.kind === 'audio') { this.emit('microphone.updated', { previous: { - deviceId: prevAudioTrack?.id, + // https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/id + deviceId: prevAudioTrack?.getConstraints().deviceId, label: prevAudioTrack?.label, }, current: { - deviceId: newTrack.id, + deviceId: newTrack.getConstraints().deviceId, label: newTrack.label, }, }) } else if (newTrack.kind === 'video') { this.emit('camera.updated', { previous: { - deviceId: prevVideoTrack?.id, + deviceId: prevVideoTrack?.getConstraints().deviceId, label: prevVideoTrack?.label, }, current: { - deviceId: newTrack.id, + deviceId: newTrack.getConstraints().deviceId, label: newTrack.label, }, }) } } + //** internal */ + public emitDeviceConstraintsUpdatedEvents({ + currentConstraints, + prevTrackIdentifiers, + currentTrackIdentifiers, + }: EmitDeviceConstraintsUpdatedEventsParams) { + + const eventPrefix = + currentTrackIdentifiers.kind === 'audioinput' ? 'microphone' : 'camera' + const event: VideoRoomDeviceConstraintsUpdatedEventNames = `${eventPrefix}.constraints.updated` + + this.emit(event, { + kind: currentTrackIdentifiers.kind, + previous: prevTrackIdentifiers, + current: currentTrackIdentifiers, + constraints: currentConstraints, + }) + } + /** * Applies the given constraints by retrieving a new stream and then uses * {@link updateStream} to synchronize local tracks with that new stream. diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index c2faab702..6153e231d 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -368,30 +368,39 @@ export default class RTCPeer { try { const sender = this._getSenderByKind(kind) if (!sender || !sender.track) { - return this.logger.info( - 'No sender to apply constraints', + // TODO this is breaking chance we need to eval if we want to have it on VideoSDK + this.logger.error( + 'No sender track to apply constraints', kind, constraints ) + throw new Error('No sender track to apply constraints') } - if (sender.track.readyState === 'live') { - const newConstraints: MediaTrackConstraints = { - ...sender.track.getConstraints(), - ...constraints, - } - const deviceId = this.getDeviceId(kind) - if (deviceId && !this.options.screenShare) { - newConstraints.deviceId = { exact: deviceId } - } - this.logger.info( - `Apply ${kind} constraints`, - this.call.id, - newConstraints + if (sender.track.readyState !== 'live') { + this.logger.error( + 'Sender track is not live to apply constraints', + kind, + constraints ) - await sender.track.applyConstraints(newConstraints) + throw new Error('Sender track is not live to apply constraints') } + const newConstraints: MediaTrackConstraints = { + ...sender.track.getConstraints(), + ...constraints, + } + const deviceId = this.getDeviceId(kind) + if (deviceId && !this.options.screenShare) { + newConstraints.deviceId = { exact: deviceId } + } + this.logger.info( + `Apply ${kind} constraints`, + this.call.id, + newConstraints + ) + await sender.track.applyConstraints(newConstraints) } catch (error) { this.logger.error('Error applying constraints', kind, constraints) + throw error } } diff --git a/packages/webrtc/src/utils/interfaces.ts b/packages/webrtc/src/utils/interfaces.ts index bda64f22d..e94516593 100644 --- a/packages/webrtc/src/utils/interfaces.ts +++ b/packages/webrtc/src/utils/interfaces.ts @@ -1,4 +1,8 @@ -import type { VideoPositions } from '@signalwire/core' +import type { + EventEmitter, + MediaDeviceIdentifiers, + VideoPositions, +} from '@signalwire/core' import { BaseConnectionState, VideoRoomDeviceEventParams, @@ -107,6 +111,19 @@ export interface EmitDeviceUpdatedEventsParams { prevAudioTrack?: MediaStreamTrack | null prevVideoTrack?: MediaStreamTrack | null } +export interface EmitDeviceConstraintsUpdatedEventsParams { + currentConstraints: MediaTrackConstraints + prevTrackIdentifiers: MediaDeviceIdentifiers | undefined + currentTrackIdentifiers: MediaDeviceIdentifiers +} + +export interface EmitDeviceUpdatedEventHelperParams + extends EmitDeviceUpdatedEventsParams { + emitFn: >( + event: E, + ...args: EventEmitter.EventArgs + ) => boolean +} export type UpdateMediaOptionsParams = Pick< ConnectionOptions, diff --git a/packages/webrtc/src/workers/vertoEventWorker.ts b/packages/webrtc/src/workers/vertoEventWorker.ts index 05eef564f..f419d1747 100644 --- a/packages/webrtc/src/workers/vertoEventWorker.ts +++ b/packages/webrtc/src/workers/vertoEventWorker.ts @@ -15,6 +15,7 @@ import { } from '@signalwire/core' import { BaseConnection } from '../BaseConnection' +import { emitDeviceUpdatedEventHelper } from '../utils/helpers' type VertoEventWorkerOnDone = (args: BaseConnection) => void type VertoEventWorkerOnFail = (args: { error: Error }) => void @@ -129,11 +130,55 @@ export const vertoEventWorker: SDKWorker< break } const { audio, video } = params.mediaParams + + const prevAudioTrack = peer?.localAudioTrack?.clone() if (peer && video) { - peer.applyMediaConstraints('video', video) + const prevTrackIdentifiers = peer?.localVideoTrack + ? { + kind: peer.localVideoTrack.kind, + label: peer.localVideoTrack.label, + deviceId: peer.localVideoTrack.getConstraints().deviceId, + } + : undefined + + peer + .applyMediaConstraints('video', video) + .then(() => { + instance.emitDeviceConstraintsUpdatedEvents({ + currentConstraints: peer.localVideoTrack!.getConstraints(), + prevTrackIdentifiers, + currentTrackIdentifiers: { + kind: peer.localVideoTrack!.kind, + label: peer.localVideoTrack!.label, + deviceId: peer.localVideoTrack!.getConstraints().deviceId, + }, + }) + }) + .catch(console.error) } if (peer && audio) { - peer.applyMediaConstraints('audio', audio) + const prevTrackIdentifiers = peer?.localAudioTrack + ? { + kind: peer.localAudioTrack.kind, + label: peer.localAudioTrack.label, + deviceId: peer.localAudioTrack.getConstraints().deviceId, + } + : undefined + + peer + .applyMediaConstraints('audio', audio) + .then(() => { + instance.emitDeviceConstraintsUpdatedEvents({ + currentConstraints: peer.localAudioTrack!.getConstraints(), + prevTrackIdentifiers, + currentTrackIdentifiers: { + kind: peer.localAudioTrack!.kind, + label: peer.localAudioTrack!.label, + deviceId: peer.localAudioTrack!.getConstraints().deviceId, + }, + }) + }) + .catch(console.error) } break } @@ -164,6 +209,4 @@ export const vertoEventWorker: SDKWorker< }) yield sagaEffects.fork(catchableWorker, action) } - - getLogger().trace('vertoEventWorker ended') }