diff --git a/package.json b/package.json index 9f754279a9886..37524630b87bf 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "scripts": { "build": "turbo run build", "build:services": "turbo run build --filter=rocketchat-services...", - "dev": "turbo run dev --env-mode=loose --parallel --filter=@rocket.chat/meteor...", + "dev": "yarn workspace @rocket.chat/core-typings run build && yarn workspace @rocket.chat/livechat run build && turbo run dev --env-mode=loose --parallel --filter=@rocket.chat/meteor", "dsv": "turbo run dsv --env-mode=loose --filter=@rocket.chat/meteor...", "ms": "turbo run ms --env-mode=loose", "fossify": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' ts-node scripts/fossify.ts", diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 5c19f39d25857..486cac206d60c 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -7448,4 +7448,5 @@ "Select_message_from_user": "Select message from {{username}}", "Select_message_from_user_with_preview": "Select message from {{username}}: {{message}}", "current": "current" -} \ No newline at end of file +} + diff --git a/packages/media-signaling/src/definition/call/IClientMediaCall.ts b/packages/media-signaling/src/definition/call/IClientMediaCall.ts index f56136139725d..f31503e931e09 100644 --- a/packages/media-signaling/src/definition/call/IClientMediaCall.ts +++ b/packages/media-signaling/src/definition/call/IClientMediaCall.ts @@ -107,6 +107,8 @@ export interface IClientMediaCall { accept(): void; reject(): void; hangup(): void; + setMuted(muted: boolean): void; + setHeld(onHold: boolean): void; requestScreenShare(requested: boolean): void; setScreenVideoTrack(videoTrack: MediaStreamTrack | null): Promise; hasScreenVideoTrack(): boolean; diff --git a/packages/media-signaling/src/lib/Session.ts b/packages/media-signaling/src/lib/Session.ts index 7f6e429153604..58245bf4f208f 100644 --- a/packages/media-signaling/src/lib/Session.ts +++ b/packages/media-signaling/src/lib/Session.ts @@ -561,12 +561,12 @@ export class MediaSignalingSession extends Emitter { this.config.logger?.debug('MediaSignalingSession.startInputTrack.done', this.callsToGetUserMedia); if (!userMedia) { - return this.hangupCallsThatNeedInput(); + return; } const tracks = userMedia.getAudioTracks(); if (!tracks.length) { - return this.hangupCallsThatNeedInput(); + return; } const inputTrack = tracks[0]; @@ -585,22 +585,6 @@ export class MediaSignalingSession extends Emitter { return this.setInputTrack(inputTrack); } - private hangupCallsThatNeedInput(): void { - this.config.logger?.debug('MediaSignalingSession.hangupCallsThatNeedInput'); - - for (const call of this.knownCalls.values()) { - if (!call.needsInputTrack()) { - continue; - } - - try { - call.hangup('input-error'); - } catch { - // - } - } - } - private mayNeedInputTrack(): boolean { for (const call of this.knownCalls.values()) { if (call.mayNeedInputTrack()) { diff --git a/packages/media-signaling/src/lib/services/webrtc/Processor.ts b/packages/media-signaling/src/lib/services/webrtc/Processor.ts index fda3466345952..bdd97fd4f3df8 100644 --- a/packages/media-signaling/src/lib/services/webrtc/Processor.ts +++ b/packages/media-signaling/src/lib/services/webrtc/Processor.ts @@ -155,9 +155,6 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { if (this.stopped) { throw new Error('WebRTC Processor has already been stopped.'); } - if (!this.inputTrack) { - throw new Error('no-input-track'); - } await this.initialization; @@ -339,8 +336,11 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { private updateAudioDirectionBeforeNegotiation(): void { // Before the negotiation, we set the direction based on our own state only // We'll tell the SDK that we want to send audio and, depending on the "on hold" state, also receive it - const desiredDirection = this.held ? 'sendonly' : 'sendrecv'; + const desiredDirection = this.getDesiredAudioDirection(); + if (!this.getTransceivers('audio').length) { + this.peer.addTransceiver('audio', { direction: desiredDirection }); + } this.updateDirectionBeforeNegotiation('audio', desiredDirection); } @@ -351,8 +351,8 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { // If we didn't do this, everything would still work, but the browser would trigger redundant renegotiations whenever the directions mismatch - const desiredDirection = this.held ? 'sendonly' : 'sendrecv'; - const acceptableDirection = this.held ? 'inactive' : 'recvonly'; + const desiredDirection = this.getDesiredAudioDirection(); + const acceptableDirection = this.held || !this.inputTrack ? 'inactive' : 'recvonly'; this.updateDirectionAfterNegotiation('audio', desiredDirection, acceptableDirection); } @@ -470,8 +470,8 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { return; } - const desiredDirection = this.held ? 'sendonly' : 'sendrecv'; - const acceptableDirection = this.held ? 'inactive' : 'recvonly'; + const desiredDirection = this.getDesiredAudioDirection(); + const acceptableDirection = this.held || !this.inputTrack ? 'inactive' : 'recvonly'; const transceivers = this.getTransceivers('audio'); for (const transceiver of transceivers) { @@ -486,6 +486,14 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { } } + private getDesiredAudioDirection(): RTCRtpTransceiverDirection { + if (!this.inputTrack) { + return this.held ? 'inactive' : 'recvonly'; + } + + return this.held ? 'sendonly' : 'sendrecv'; + } + private createDataChannel(): void { if (this._dataChannel || this._dataChannelEnded || !this.config.call.hasFlag('create-data-channel')) { return; @@ -565,8 +573,8 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { private getCommandFromDataChannelMessage(message: string): P2PCommand | null { try { - const obj = JSON.parse(message); - if (obj.command && this.isValidCommand(obj.command)) { + const obj = JSON.parse(message) as { command?: unknown }; + if (typeof obj.command === 'string' && this.isValidCommand(obj.command)) { return obj.command; } } catch { @@ -632,6 +640,10 @@ export class MediaCallWebRTCProcessor implements IWebRTCProcessor { return; } + if (!this.inputTrack) { + return; + } + let anyTransceiverNotSending = false; const transceivers = this.getTransceivers('audio'); diff --git a/packages/ui-voip/src/context/MediaCallViewContext.ts b/packages/ui-voip/src/context/MediaCallViewContext.ts index 29ebe544c1324..f50513758241f 100644 --- a/packages/ui-voip/src/context/MediaCallViewContext.ts +++ b/packages/ui-voip/src/context/MediaCallViewContext.ts @@ -19,6 +19,7 @@ type MediaCallViewContextValue = { onForward: () => void; onTone: (tone: string) => void; onEndCall: () => void; + onToggleScreenShare: () => Promise; onCall: () => Promise; onAccept: () => Promise; onSelectPeer: (peerInfo: PeerInfo) => void; @@ -42,6 +43,8 @@ const defaultSessionState: SessionState = { held: false, remoteMuted: false, remoteHeld: false, + screenSharing: false, + remoteScreenSharing: false, callId: undefined, supportedFeatures: ['audio', 'transfer', 'hold'], }; @@ -54,6 +57,7 @@ export const defaultMediaCallContextValue: MediaCallViewContextValue = { onForward: () => undefined, onTone: () => undefined, onEndCall: () => undefined, + onToggleScreenShare: () => Promise.resolve(undefined), onCall: () => Promise.resolve(undefined), onAccept: () => Promise.resolve(undefined), onSelectPeer: () => undefined, diff --git a/packages/ui-voip/src/context/definitions.d.ts b/packages/ui-voip/src/context/definitions.d.ts index 495bf5cf253fb..f49351cba00d0 100644 --- a/packages/ui-voip/src/context/definitions.d.ts +++ b/packages/ui-voip/src/context/definitions.d.ts @@ -29,6 +29,8 @@ interface IBaseSession { held: boolean; remoteMuted: boolean; remoteHeld: boolean; + screenSharing: boolean; + remoteScreenSharing: boolean; startedAt?: Date; hidden: boolean; supportedFeatures: readonly CallFeature[]; diff --git a/packages/ui-voip/src/providers/MediaCallViewProvider.tsx b/packages/ui-voip/src/providers/MediaCallViewProvider.tsx index 44e47c28585d3..62f0efed37831 100644 --- a/packages/ui-voip/src/providers/MediaCallViewProvider.tsx +++ b/packages/ui-voip/src/providers/MediaCallViewProvider.tsx @@ -113,6 +113,7 @@ const MediaCallViewProvider = ({ children }: MediaCallViewProviderProps) => { startCall(false); } catch (error) { console.error('Media Call - Error requesting device', error); + startCall(true); } }; @@ -128,7 +129,8 @@ const MediaCallViewProvider = ({ children }: MediaCallViewProviderProps) => { stopTracks(stream); controls.acceptCall(false); } catch (error) { - console.error('MediaCall - onAccept - Failed to get device, procceeding without microphone', error); + console.error('Media Call - Error requesting device', error); + controls.acceptCall(true); } }; @@ -200,6 +202,10 @@ const MediaCallViewProvider = ({ children }: MediaCallViewProviderProps) => { controls.endCall(); }; + const onToggleScreenShare = useCallback(async () => { + controls.toggleScreenSharing(); + }, [controls]); + const onSelectPeer = (peerInfo: PeerInfo) => { selectPeer(peerInfo); }; @@ -245,6 +251,7 @@ const MediaCallViewProvider = ({ children }: MediaCallViewProviderProps) => { onForward, onTone, onEndCall, + onToggleScreenShare, onCall, onAccept, onSelectPeer, diff --git a/packages/ui-voip/src/providers/useMediaSession.ts b/packages/ui-voip/src/providers/useMediaSession.ts index fb9f59a109507..bac956ee59fcf 100644 --- a/packages/ui-voip/src/providers/useMediaSession.ts +++ b/packages/ui-voip/src/providers/useMediaSession.ts @@ -17,6 +17,8 @@ const defaultSessionInfo: SessionState = { held: false, remoteMuted: false, remoteHeld: false, + screenSharing: false, + remoteScreenSharing: false, startedAt: undefined, hidden: false, supportedFeatures: ['audio', 'transfer', 'hold'], @@ -155,6 +157,8 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS hidden: false, remoteHeld: false, remoteMuted: false, + screenSharing: false, + remoteScreenSharing: false, callId: instanceState.tempCallId, startedAt: undefined, supportedFeatures: [], @@ -169,8 +173,11 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS activeTimestamp: startedAt, features: supportedFeatures, transferredBy: callTransferredBy, + localParticipant, remoteParticipant: { muted: remoteMuted, held: remoteHeld, contact }, } = instanceState; + const localMediaStream = localParticipant.getMediaStream('screen-share'); + const remoteMediaStream = instanceState.remoteParticipant.getMediaStream('screen-share'); const transferredBy = callTransferredBy?.displayName || callTransferredBy?.username || undefined; @@ -187,6 +194,8 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS hidden, remoteHeld, remoteMuted, + screenSharing: Boolean(localMediaStream?.hasVideo()), + remoteScreenSharing: Boolean(remoteMediaStream?.hasVideo()), callId, startedAt, supportedFeatures, @@ -221,6 +230,8 @@ export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionS hidden, remoteHeld, remoteMuted, + screenSharing: Boolean(localMediaStream?.hasVideo()), + remoteScreenSharing: Boolean(remoteMediaStream?.hasVideo()), callId, startedAt, supportedFeatures, diff --git a/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap b/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap index 249bc2f7d6562..c2fbcd440f2b1 100644 --- a/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap +++ b/packages/ui-voip/src/views/MediaCallWidget/__snapshots__/MediaCallWidget.spec.tsx.snap @@ -14,7 +14,7 @@ exports[`renders IncomingCall without crashing 1`] = ` >