diff --git a/packages/media-signaling/src/definition/call/IClientMediaCall.ts b/packages/media-signaling/src/definition/call/IClientMediaCall.ts index 360ab11a50350..72081b4b17402 100644 --- a/packages/media-signaling/src/definition/call/IClientMediaCall.ts +++ b/packages/media-signaling/src/definition/call/IClientMediaCall.ts @@ -78,6 +78,8 @@ export interface IClientMediaCall { held: boolean; /* busy = state >= 'accepted' && state < 'hangup' */ busy: boolean; + /* if the other side has put the call on hold */ + remoteHeld: boolean; contact: CallContact; transferredBy: CallContact | null; diff --git a/packages/media-signaling/src/definition/services/webrtc/IWebRTCProcessor.ts b/packages/media-signaling/src/definition/services/webrtc/IWebRTCProcessor.ts index b1a88a3b1a124..86c036dc23e8b 100644 --- a/packages/media-signaling/src/definition/services/webrtc/IWebRTCProcessor.ts +++ b/packages/media-signaling/src/definition/services/webrtc/IWebRTCProcessor.ts @@ -42,6 +42,7 @@ export interface IWebRTCProcessor extends IServiceProcessor; + isRemoteHeld(): boolean; } export type WebRTCProcessorConfig = { diff --git a/packages/media-signaling/src/lib/Call.ts b/packages/media-signaling/src/lib/Call.ts index d8c75c6bebdf1..d6bd6e2a9fb33 100644 --- a/packages/media-signaling/src/lib/Call.ts +++ b/packages/media-signaling/src/lib/Call.ts @@ -125,6 +125,12 @@ export class ClientMediaCall implements IClientMediaCall { return this.webrtcProcessor.held; } + private _remoteHeld: boolean; + + public get remoteHeld(): boolean { + return this._remoteHeld; + } + /** indicates the call is past the "dialing" stage and not yet over */ public get busy(): boolean { return !this.isPendingAcceptance() && !this.isOver(); @@ -208,6 +214,7 @@ export class ClientMediaCall implements IClientMediaCall { this._contact = null; this._transferredBy = null; this._service = null; + this._remoteHeld = false; this.negotiationManager = new NegotiationManager(this, { logger: config.logger }); } @@ -987,6 +994,20 @@ export class ClientMediaCall implements IClientMediaCall { this.stateTimeoutHandlers.clear(); } + private updateRemoteHeld(): void { + if (!this.webrtcProcessor) { + return; + } + + const isRemoteHeld = this.webrtcProcessor.isRemoteHeld(); + if (isRemoteHeld === this._remoteHeld) { + return; + } + + this._remoteHeld = isRemoteHeld; + this.emitter.emit('trackStateChange'); + } + private onWebRTCInternalStateChange(stateName: keyof WebRTCInternalStateMap): void { this.config.logger?.debug('ClientMediaCall.onWebRTCInternalStateChange'); if (!this.webrtcProcessor) { @@ -1006,6 +1027,8 @@ export class ClientMediaCall implements IClientMediaCall { this.requestStateReport(); } + + this.updateRemoteHeld(); } private onNegotiationNeeded(oldNegotiationId: string): void { diff --git a/packages/media-signaling/src/lib/services/webrtc/Processor.ts b/packages/media-signaling/src/lib/services/webrtc/Processor.ts index fc51f816dcdaa..020b0d8b397a4 100644 --- a/packages/media-signaling/src/lib/services/webrtc/Processor.ts +++ b/packages/media-signaling/src/lib/services/webrtc/Processor.ts @@ -249,6 +249,33 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { return this.peer.getStats(selector); } + public isRemoteHeld(): boolean { + if (this.stopped) { + return false; + } + + if (['closed', 'failed', 'new'].includes(this.peer.connectionState)) { + return false; + } + + let anyTransceiverNotSending = false; + const transceivers = this.getAudioTransceivers(); + + for (const transceiver of transceivers) { + if (!transceiver.currentDirection || transceiver.currentDirection === 'stopped') { + continue; + } + + if (transceiver.currentDirection.includes('send')) { + return false; + } + + anyTransceiverNotSending = true; + } + + return anyTransceiverNotSending; + } + public isStable(): boolean { if (this.stopped) { return false;