diff options
author | RaindropsSys <contact@minteck.org> | 2023-04-24 14:03:36 +0200 |
---|---|---|
committer | RaindropsSys <contact@minteck.org> | 2023-04-24 14:03:36 +0200 |
commit | 633c92eae865e957121e08de634aeee11a8b3992 (patch) | |
tree | 09d881bee1dae0b6eee49db1dfaf0f500240606c /includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc | |
parent | c4657e4509733699c0f26a3c900bab47e915d5a0 (diff) | |
download | pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.gz pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.bz2 pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.zip |
Updated 18 files, added 1692 files and deleted includes/system/compare.inc (automated)
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc')
92 files changed, 7245 insertions, 0 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.d.ts new file mode 100644 index 0000000..3c1d344 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.d.ts @@ -0,0 +1,15 @@ +/** + * Acquires a reference to the shared AudioContext. + * It's highly recommended to reuse this AudioContext rather than creating your + * own, because multiple AudioContexts can be problematic in some browsers. + * Make sure to call releaseContext when you're done using it. + * @returns The shared AudioContext + */ +export declare const acquireContext: () => AudioContext; +/** + * Signals that one of the references to the shared AudioContext has been + * released, allowing the context and associated hardware resources to be + * cleaned up if nothing else is using it. + */ +export declare const releaseContext: () => void; +//# sourceMappingURL=audioContext.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.d.ts.map new file mode 100644 index 0000000..dd32fcd --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"audioContext.d.ts","sourceRoot":"","sources":["../../src/webrtc/audioContext.ts"],"names":[],"mappings":"AAmBA;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,QAAO,YAIjC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAO,IAMjC,CAAC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.js new file mode 100644 index 0000000..a950ad5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.js @@ -0,0 +1,54 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.releaseContext = exports.acquireContext = void 0; +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +let audioContext = null; +let refCount = 0; + +/** + * Acquires a reference to the shared AudioContext. + * It's highly recommended to reuse this AudioContext rather than creating your + * own, because multiple AudioContexts can be problematic in some browsers. + * Make sure to call releaseContext when you're done using it. + * @returns The shared AudioContext + */ +const acquireContext = () => { + if (audioContext === null) audioContext = new AudioContext(); + refCount++; + return audioContext; +}; + +/** + * Signals that one of the references to the shared AudioContext has been + * released, allowing the context and associated hardware resources to be + * cleaned up if nothing else is using it. + */ +exports.acquireContext = acquireContext; +const releaseContext = () => { + refCount--; + if (refCount === 0) { + var _audioContext; + (_audioContext = audioContext) === null || _audioContext === void 0 ? void 0 : _audioContext.close(); + audioContext = null; + } +}; +exports.releaseContext = releaseContext; +//# sourceMappingURL=audioContext.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.js.map new file mode 100644 index 0000000..f146db3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/audioContext.js.map @@ -0,0 +1 @@ +{"version":3,"file":"audioContext.js","names":["audioContext","refCount","acquireContext","AudioContext","exports","releaseContext","_audioContext","close"],"sources":["../../src/webrtc/audioContext.ts"],"sourcesContent":["/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nlet audioContext: AudioContext | null = null;\nlet refCount = 0;\n\n/**\n * Acquires a reference to the shared AudioContext.\n * It's highly recommended to reuse this AudioContext rather than creating your\n * own, because multiple AudioContexts can be problematic in some browsers.\n * Make sure to call releaseContext when you're done using it.\n * @returns The shared AudioContext\n */\nexport const acquireContext = (): AudioContext => {\n if (audioContext === null) audioContext = new AudioContext();\n refCount++;\n return audioContext;\n};\n\n/**\n * Signals that one of the references to the shared AudioContext has been\n * released, allowing the context and associated hardware resources to be\n * cleaned up if nothing else is using it.\n */\nexport const releaseContext = (): void => {\n refCount--;\n if (refCount === 0) {\n audioContext?.close();\n audioContext = null;\n }\n};\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,IAAIA,YAAiC,GAAG,IAAI;AAC5C,IAAIC,QAAQ,GAAG,CAAC;;AAEhB;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMC,cAAc,GAAGA,CAAA,KAAoB;EAC9C,IAAIF,YAAY,KAAK,IAAI,EAAEA,YAAY,GAAG,IAAIG,YAAY,EAAE;EAC5DF,QAAQ,EAAE;EACV,OAAOD,YAAY;AACvB,CAAC;;AAED;AACA;AACA;AACA;AACA;AAJAI,OAAA,CAAAF,cAAA,GAAAA,cAAA;AAKO,MAAMG,cAAc,GAAGA,CAAA,KAAY;EACtCJ,QAAQ,EAAE;EACV,IAAIA,QAAQ,KAAK,CAAC,EAAE;IAAA,IAAAK,aAAA;IAChB,CAAAA,aAAA,GAAAN,YAAY,cAAAM,aAAA,uBAAZA,aAAA,CAAcC,KAAK,EAAE;IACrBP,YAAY,GAAG,IAAI;EACvB;AACJ,CAAC;AAACI,OAAA,CAAAC,cAAA,GAAAA,cAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.d.ts new file mode 100644 index 0000000..de1dc28 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.d.ts @@ -0,0 +1,551 @@ +import { MatrixEvent } from "../models/event"; +import { RoomMember } from "../models/room-member"; +import { MCallAnswer, MCallHangupReject } from "./callEventTypes"; +import { CallFeed } from "./callFeed"; +import { MatrixClient } from "../client"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { IScreensharingOpts } from "./mediaHandler"; +import { GroupCallStats } from "./stats/groupCallStats"; +interface CallOpts { + roomId: string; + invitee?: string; + client: MatrixClient; + /** + * Whether relay through TURN should be forced. + * @deprecated use opts.forceTURN when creating the matrix client + * since it's only possible to set this option on outbound calls. + */ + forceTURN?: boolean; + turnServers?: Array<TurnServer>; + opponentDeviceId?: string; + opponentSessionId?: string; + groupCallId?: string; +} +interface TurnServer { + urls: Array<string>; + username?: string; + password?: string; + ttl?: number; +} +interface AssertedIdentity { + id: string; + displayName: string; +} +export declare enum CallState { + Fledgling = "fledgling", + InviteSent = "invite_sent", + WaitLocalMedia = "wait_local_media", + CreateOffer = "create_offer", + CreateAnswer = "create_answer", + Connecting = "connecting", + Connected = "connected", + Ringing = "ringing", + Ended = "ended" +} +export declare enum CallType { + Voice = "voice", + Video = "video" +} +export declare enum CallDirection { + Inbound = "inbound", + Outbound = "outbound" +} +export declare enum CallParty { + Local = "local", + Remote = "remote" +} +export declare enum CallEvent { + Hangup = "hangup", + State = "state", + Error = "error", + Replaced = "replaced", + LocalHoldUnhold = "local_hold_unhold", + RemoteHoldUnhold = "remote_hold_unhold", + HoldUnhold = "hold_unhold", + FeedsChanged = "feeds_changed", + AssertedIdentityChanged = "asserted_identity_changed", + LengthChanged = "length_changed", + DataChannel = "datachannel", + SendVoipEvent = "send_voip_event" +} +export declare enum CallErrorCode { + /** The user chose to end the call */ + UserHangup = "user_hangup", + /** An error code when the local client failed to create an offer. */ + LocalOfferFailed = "local_offer_failed", + /** + * An error code when there is no local mic/camera to use. This may be because + * the hardware isn't plugged in, or the user has explicitly denied access. + */ + NoUserMedia = "no_user_media", + /** + * Error code used when a call event failed to send + * because unknown devices were present in the room + */ + UnknownDevices = "unknown_devices", + /** + * Error code used when we fail to send the invite + * for some reason other than there being unknown devices + */ + SendInvite = "send_invite", + /** + * An answer could not be created + */ + CreateAnswer = "create_answer", + /** + * An offer could not be created + */ + CreateOffer = "create_offer", + /** + * Error code used when we fail to send the answer + * for some reason other than there being unknown devices + */ + SendAnswer = "send_answer", + /** + * The session description from the other side could not be set + */ + SetRemoteDescription = "set_remote_description", + /** + * The session description from this side could not be set + */ + SetLocalDescription = "set_local_description", + /** + * A different device answered the call + */ + AnsweredElsewhere = "answered_elsewhere", + /** + * No media connection could be established to the other party + */ + IceFailed = "ice_failed", + /** + * The invite timed out whilst waiting for an answer + */ + InviteTimeout = "invite_timeout", + /** + * The call was replaced by another call + */ + Replaced = "replaced", + /** + * Signalling for the call could not be sent (other than the initial invite) + */ + SignallingFailed = "signalling_timeout", + /** + * The remote party is busy + */ + UserBusy = "user_busy", + /** + * We transferred the call off to somewhere else + */ + Transferred = "transferred", + /** + * A call from the same user was found with a new session id + */ + NewSession = "new_session" +} +export declare class CallError extends Error { + readonly code: string; + constructor(code: CallErrorCode, msg: string, err: Error); +} +export declare function genCallID(): string; +export interface VoipEvent { + type: "toDevice" | "sendEvent"; + eventType: string; + userId?: string; + opponentDeviceId?: string; + roomId?: string; + content: Record<string, unknown>; +} +/** + * These now all have the call object as an argument. Why? Well, to know which call a given event is + * about you have three options: + * 1. Use a closure as the callback that remembers what call it's listening to. This can be + * a pain because you need to pass the listener function again when you remove the listener, + * which might be somewhere else. + * 2. Use not-very-well-known fact that EventEmitter sets 'this' to the emitter object in the + * callback. This doesn't really play well with modern Typescript and eslint and doesn't work + * with our pattern of re-emitting events. + * 3. Pass the object in question as an argument to the callback. + * + * Now that we have group calls which have to deal with multiple call objects, this will + * become more important, and I think methods 1 and 2 are just going to cause issues. + */ +export type CallEventHandlerMap = { + [CallEvent.DataChannel]: (channel: RTCDataChannel, call: MatrixCall) => void; + [CallEvent.FeedsChanged]: (feeds: CallFeed[], call: MatrixCall) => void; + [CallEvent.Replaced]: (newCall: MatrixCall, oldCall: MatrixCall) => void; + [CallEvent.Error]: (error: CallError, call: MatrixCall) => void; + [CallEvent.RemoteHoldUnhold]: (onHold: boolean, call: MatrixCall) => void; + [CallEvent.LocalHoldUnhold]: (onHold: boolean, call: MatrixCall) => void; + [CallEvent.LengthChanged]: (length: number, call: MatrixCall) => void; + [CallEvent.State]: (state: CallState, oldState: CallState, call: MatrixCall) => void; + [CallEvent.Hangup]: (call: MatrixCall) => void; + [CallEvent.AssertedIdentityChanged]: (call: MatrixCall) => void; + [CallEvent.HoldUnhold]: (onHold: boolean) => void; + [CallEvent.SendVoipEvent]: (event: VoipEvent, call: MatrixCall) => void; +}; +export declare class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap> { + roomId: string; + callId: string; + invitee?: string; + hangupParty?: CallParty; + hangupReason?: string; + direction?: CallDirection; + ourPartyId: string; + peerConn?: RTCPeerConnection; + toDeviceSeq: number; + isPtt: boolean; + private _state; + private readonly client; + private readonly forceTURN?; + private readonly turnServers; + private candidateSendQueue; + private candidateSendTries; + private candidatesEnded; + private feeds; + private transceivers; + private inviteOrAnswerSent; + private waitForLocalAVStream; + private successor?; + private opponentMember?; + private opponentVersion?; + private opponentPartyId; + private opponentCaps?; + private iceDisconnectedTimeout?; + private inviteTimeout?; + private readonly removeTrackListeners; + private remoteOnHold; + private callStatsAtEnd?; + private makingOffer; + private ignoreOffer; + private responsePromiseChain?; + private remoteCandidateBuffer; + private remoteAssertedIdentity?; + private remoteSDPStreamMetadata?; + private callLengthInterval?; + private callStartTime?; + private opponentDeviceId?; + private opponentDeviceInfo?; + private opponentSessionId?; + groupCallId?: string; + private stopVideoTrackTimer?; + private readonly isOnlyDataChannelAllowed; + private stats; + /** + * Construct a new Matrix Call. + * @param opts - Config options. + */ + constructor(opts: CallOpts); + /** + * Place a voice call to this room. + * @throws If you have not specified a listener for 'error' events. + */ + placeVoiceCall(): Promise<void>; + /** + * Place a video call to this room. + * @throws If you have not specified a listener for 'error' events. + */ + placeVideoCall(): Promise<void>; + /** + * Create a datachannel using this call's peer connection. + * @param label - A human readable label for this datachannel + * @param options - An object providing configuration options for the data channel. + */ + createDataChannel(label: string, options: RTCDataChannelInit | undefined): RTCDataChannel; + getOpponentMember(): RoomMember | undefined; + getOpponentDeviceId(): string | undefined; + getOpponentSessionId(): string | undefined; + opponentCanBeTransferred(): boolean; + opponentSupportsDTMF(): boolean; + getRemoteAssertedIdentity(): AssertedIdentity | undefined; + get state(): CallState; + private set state(value); + get type(): CallType; + get hasLocalUserMediaVideoTrack(): boolean; + get hasRemoteUserMediaVideoTrack(): boolean; + get hasLocalUserMediaAudioTrack(): boolean; + get hasRemoteUserMediaAudioTrack(): boolean; + private get hasUserMediaAudioSender(); + private get hasUserMediaVideoSender(); + get localUsermediaFeed(): CallFeed | undefined; + get localScreensharingFeed(): CallFeed | undefined; + get localUsermediaStream(): MediaStream | undefined; + get localScreensharingStream(): MediaStream | undefined; + get remoteUsermediaFeed(): CallFeed | undefined; + get remoteScreensharingFeed(): CallFeed | undefined; + get remoteUsermediaStream(): MediaStream | undefined; + get remoteScreensharingStream(): MediaStream | undefined; + private getFeedByStreamId; + /** + * Returns an array of all CallFeeds + * @returns CallFeeds + */ + getFeeds(): Array<CallFeed>; + /** + * Returns an array of all local CallFeeds + * @returns local CallFeeds + */ + getLocalFeeds(): Array<CallFeed>; + /** + * Returns an array of all remote CallFeeds + * @returns remote CallFeeds + */ + getRemoteFeeds(): Array<CallFeed>; + private initOpponentCrypto; + /** + * Generates and returns localSDPStreamMetadata + * @returns localSDPStreamMetadata + */ + private getLocalSDPStreamMetadata; + /** + * Returns true if there are no incoming feeds, + * otherwise returns false + * @returns no incoming feeds + */ + noIncomingFeeds(): boolean; + private pushRemoteFeed; + /** + * This method is used ONLY if the other client doesn't support sending SDPStreamMetadata + */ + private pushRemoteFeedWithoutMetadata; + private pushNewLocalFeed; + /** + * Pushes supplied feed to the call + * @param callFeed - to push + * @param addToPeerConnection - whether to add the tracks to the peer connection + */ + pushLocalFeed(callFeed: CallFeed, addToPeerConnection?: boolean): void; + /** + * Removes local call feed from the call and its tracks from the peer + * connection + * @param callFeed - to remove + */ + removeLocalFeed(callFeed: CallFeed): void; + private deleteAllFeeds; + private deleteFeedByStream; + private deleteFeed; + getCurrentCallStats(): Promise<any[] | undefined>; + private collectCallStats; + /** + * Configure this call from an invite event. Used by MatrixClient. + * @param event - The m.call.invite event + */ + initWithInvite(event: MatrixEvent): Promise<void>; + /** + * Configure this call from a hangup or reject event. Used by MatrixClient. + * @param event - The m.call.hangup event + */ + initWithHangup(event: MatrixEvent): void; + private shouldAnswerWithMediaType; + /** + * Answer a call. + */ + answer(audio?: boolean, video?: boolean): Promise<void>; + answerWithCallFeeds(callFeeds: CallFeed[]): void; + /** + * Replace this call with a new call, e.g. for glare resolution. Used by + * MatrixClient. + * @param newCall - The new call. + */ + replacedBy(newCall: MatrixCall): void; + /** + * Hangup a call. + * @param reason - The reason why the call is being hung up. + * @param suppressEvent - True to suppress emitting an event. + */ + hangup(reason: CallErrorCode, suppressEvent: boolean): void; + /** + * Reject a call + * This used to be done by calling hangup, but is a separate method and protocol + * event as of MSC2746. + */ + reject(): void; + /** + * Adds an audio and/or video track - upgrades the call + * @param audio - should add an audio track + * @param video - should add an video track + */ + private upgradeCall; + /** + * Returns true if this.remoteSDPStreamMetadata is defined, otherwise returns false + * @returns can screenshare + */ + opponentSupportsSDPStreamMetadata(): boolean; + /** + * If there is a screensharing stream returns true, otherwise returns false + * @returns is screensharing + */ + isScreensharing(): boolean; + /** + * Starts/stops screensharing + * @param enabled - the desired screensharing state + * @param desktopCapturerSourceId - optional id of the desktop capturer source to use + * @returns new screensharing state + */ + setScreensharingEnabled(enabled: boolean, opts?: IScreensharingOpts): Promise<boolean>; + /** + * Starts/stops screensharing + * Should be used ONLY if the opponent doesn't support SDPStreamMetadata + * @param enabled - the desired screensharing state + * @param desktopCapturerSourceId - optional id of the desktop capturer source to use + * @returns new screensharing state + */ + private setScreensharingEnabledWithoutMetadataSupport; + /** + * Replaces/adds the tracks from the passed stream to the localUsermediaStream + * @param stream - to use a replacement for the local usermedia stream + */ + updateLocalUsermediaStream(stream: MediaStream, forceAudio?: boolean, forceVideo?: boolean): Promise<void>; + /** + * Set whether our outbound video should be muted or not. + * @param muted - True to mute the outbound video. + * @returns the new mute state + */ + setLocalVideoMuted(muted: boolean): Promise<boolean>; + /** + * Check if local video is muted. + * + * If there are multiple video tracks, <i>all</i> of the tracks need to be muted + * for this to return true. This means if there are no video tracks, this will + * return true. + * @returns True if the local preview video is muted, else false + * (including if the call is not set up yet). + */ + isLocalVideoMuted(): boolean; + /** + * Set whether the microphone should be muted or not. + * @param muted - True to mute the mic. + * @returns the new mute state + */ + setMicrophoneMuted(muted: boolean): Promise<boolean>; + /** + * Check if the microphone is muted. + * + * If there are multiple audio tracks, <i>all</i> of the tracks need to be muted + * for this to return true. This means if there are no audio tracks, this will + * return true. + * @returns True if the mic is muted, else false (including if the call + * is not set up yet). + */ + isMicrophoneMuted(): boolean; + /** + * @returns true if we have put the party on the other side of the call on hold + * (that is, we are signalling to them that we are not listening) + */ + isRemoteOnHold(): boolean; + setRemoteOnHold(onHold: boolean): void; + /** + * Indicates whether we are 'on hold' to the remote party (ie. if true, + * they cannot hear us). + * @returns true if the other party has put us on hold + */ + isLocalOnHold(): boolean; + /** + * Sends a DTMF digit to the other party + * @param digit - The digit (nb. string - '#' and '*' are dtmf too) + */ + sendDtmfDigit(digit: string): void; + private updateMuteStatus; + sendMetadataUpdate(): Promise<void>; + private gotCallFeedsForInvite; + private sendAnswer; + private queueGotCallFeedsForAnswer; + private mungeSdp; + private createOffer; + private createAnswer; + private gotCallFeedsForAnswer; + /** + * Internal + */ + private gotLocalIceCandidate; + private onIceGatheringStateChange; + onRemoteIceCandidatesReceived(ev: MatrixEvent): Promise<void>; + /** + * Used by MatrixClient. + */ + onAnswerReceived(event: MatrixEvent): Promise<void>; + onSelectAnswerReceived(event: MatrixEvent): Promise<void>; + onNegotiateReceived(event: MatrixEvent): Promise<void>; + private updateRemoteSDPStreamMetadata; + onSDPStreamMetadataChangedReceived(event: MatrixEvent): void; + onAssertedIdentityReceived(event: MatrixEvent): Promise<void>; + callHasEnded(): boolean; + private queueGotLocalOffer; + private wrappedGotLocalOffer; + private gotLocalOffer; + private getLocalOfferFailed; + private getUserMediaFailed; + private onIceConnectionStateChanged; + private onSignallingStateChanged; + private onTrack; + private onDataChannel; + /** + * This method removes all video/rtx codecs from screensharing video + * transceivers. This is necessary since they can cause problems. Without + * this the following steps should produce an error: + * Chromium calls Firefox + * Firefox answers + * Firefox starts screen-sharing + * Chromium starts screen-sharing + * Call crashes for Chromium with: + * [96685:23:0518/162603.933321:ERROR:webrtc_video_engine.cc(3296)] RTX codec (PT=97) mapped to PT=96 which is not in the codec list. + * [96685:23:0518/162603.933377:ERROR:webrtc_video_engine.cc(1171)] GetChangedRecvParameters called without any video codecs. + * [96685:23:0518/162603.933430:ERROR:sdp_offer_answer.cc(4302)] Failed to set local video description recv parameters for m-section with mid='2'. (INVALID_PARAMETER) + */ + private getRidOfRTXCodecs; + private onNegotiationNeeded; + onHangupReceived: (msg: MCallHangupReject) => void; + onRejectReceived: (msg: MCallHangupReject) => void; + onAnsweredElsewhere: (msg: MCallAnswer) => void; + /** + * @internal + */ + private sendVoipEvent; + /** + * Queue a candidate to be sent + * @param content - The candidate to queue up, or null if candidates have finished being generated + * and end-of-candidates should be signalled + */ + private queueCandidate; + private discardDuplicateCandidates; + transfer(targetUserId: string): Promise<void>; + transferToCall(transferTargetCall: MatrixCall): Promise<void>; + private terminate; + private stopAllMedia; + private checkForErrorListener; + private sendCandidateQueue; + /** + * Place a call to this room. + * @throws if you have not specified a listener for 'error' events. + * @throws if have passed audio=false. + */ + placeCall(audio: boolean, video: boolean): Promise<void>; + /** + * Place a call to this room with call feed. + * @param callFeeds - to use + * @throws if you have not specified a listener for 'error' events. + * @throws if have passed audio=false. + */ + placeCallWithCallFeeds(callFeeds: CallFeed[], requestScreenshareFeed?: boolean): Promise<void>; + private createPeerConnection; + private partyIdMatches; + private chooseOpponent; + private addBufferedIceCandidates; + private addIceCandidates; + get hasPeerConnection(): boolean; + initStats(stats: GroupCallStats, peerId?: string): void; +} +export declare function setTracksEnabled(tracks: Array<MediaStreamTrack>, enabled: boolean): void; +export declare function supportsMatrixCall(): boolean; +/** + * DEPRECATED + * Use client.createCall() + * + * Create a new Matrix call for the browser. + * @param client - The client instance to use. + * @param roomId - The room the call is in. + * @param options - DEPRECATED optional options map. + * @returns the call or null if the browser doesn't support calling. + */ +export declare function createNewMatrixCall(client: MatrixClient, roomId: string, options?: Pick<CallOpts, "forceTURN" | "invitee" | "opponentDeviceId" | "opponentSessionId" | "groupCallId">): MatrixCall | null; +export {}; +//# sourceMappingURL=call.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.d.ts.map new file mode 100644 index 0000000..a1d00d6 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/webrtc/call.ts"],"names":[],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.js new file mode 100644 index 0000000..45e23ad --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.js @@ -0,0 +1,2341 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MatrixCall = exports.CallType = exports.CallState = exports.CallParty = exports.CallEvent = exports.CallErrorCode = exports.CallError = exports.CallDirection = void 0; +exports.createNewMatrixCall = createNewMatrixCall; +exports.genCallID = genCallID; +exports.setTracksEnabled = setTracksEnabled; +exports.supportsMatrixCall = supportsMatrixCall; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _uuid = require("uuid"); +var _sdpTransform = require("sdp-transform"); +var _logger = require("../logger"); +var utils = _interopRequireWildcard(require("../utils")); +var _event = require("../@types/event"); +var _randomstring = require("../randomstring"); +var _callEventTypes = require("./callEventTypes"); +var _callFeed = require("./callFeed"); +var _typedEventEmitter = require("../models/typed-event-emitter"); +var _deviceinfo = require("../crypto/deviceinfo"); +var _groupCall = require("./groupCall"); +var _httpApi = require("../http-api"); +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } +var MediaType; +(function (MediaType) { + MediaType["AUDIO"] = "audio"; + MediaType["VIDEO"] = "video"; +})(MediaType || (MediaType = {})); +var CodecName; // add more as needed +// Used internally to specify modifications to codec parameters in SDP +(function (CodecName) { + CodecName["OPUS"] = "opus"; +})(CodecName || (CodecName = {})); +let CallState; +exports.CallState = CallState; +(function (CallState) { + CallState["Fledgling"] = "fledgling"; + CallState["InviteSent"] = "invite_sent"; + CallState["WaitLocalMedia"] = "wait_local_media"; + CallState["CreateOffer"] = "create_offer"; + CallState["CreateAnswer"] = "create_answer"; + CallState["Connecting"] = "connecting"; + CallState["Connected"] = "connected"; + CallState["Ringing"] = "ringing"; + CallState["Ended"] = "ended"; +})(CallState || (exports.CallState = CallState = {})); +let CallType; +exports.CallType = CallType; +(function (CallType) { + CallType["Voice"] = "voice"; + CallType["Video"] = "video"; +})(CallType || (exports.CallType = CallType = {})); +let CallDirection; +exports.CallDirection = CallDirection; +(function (CallDirection) { + CallDirection["Inbound"] = "inbound"; + CallDirection["Outbound"] = "outbound"; +})(CallDirection || (exports.CallDirection = CallDirection = {})); +let CallParty; +exports.CallParty = CallParty; +(function (CallParty) { + CallParty["Local"] = "local"; + CallParty["Remote"] = "remote"; +})(CallParty || (exports.CallParty = CallParty = {})); +let CallEvent; +exports.CallEvent = CallEvent; +(function (CallEvent) { + CallEvent["Hangup"] = "hangup"; + CallEvent["State"] = "state"; + CallEvent["Error"] = "error"; + CallEvent["Replaced"] = "replaced"; + CallEvent["LocalHoldUnhold"] = "local_hold_unhold"; + CallEvent["RemoteHoldUnhold"] = "remote_hold_unhold"; + CallEvent["HoldUnhold"] = "hold_unhold"; + CallEvent["FeedsChanged"] = "feeds_changed"; + CallEvent["AssertedIdentityChanged"] = "asserted_identity_changed"; + CallEvent["LengthChanged"] = "length_changed"; + CallEvent["DataChannel"] = "datachannel"; + CallEvent["SendVoipEvent"] = "send_voip_event"; +})(CallEvent || (exports.CallEvent = CallEvent = {})); +let CallErrorCode; +/** + * The version field that we set in m.call.* events + */ +exports.CallErrorCode = CallErrorCode; +(function (CallErrorCode) { + CallErrorCode["UserHangup"] = "user_hangup"; + CallErrorCode["LocalOfferFailed"] = "local_offer_failed"; + CallErrorCode["NoUserMedia"] = "no_user_media"; + CallErrorCode["UnknownDevices"] = "unknown_devices"; + CallErrorCode["SendInvite"] = "send_invite"; + CallErrorCode["CreateAnswer"] = "create_answer"; + CallErrorCode["CreateOffer"] = "create_offer"; + CallErrorCode["SendAnswer"] = "send_answer"; + CallErrorCode["SetRemoteDescription"] = "set_remote_description"; + CallErrorCode["SetLocalDescription"] = "set_local_description"; + CallErrorCode["AnsweredElsewhere"] = "answered_elsewhere"; + CallErrorCode["IceFailed"] = "ice_failed"; + CallErrorCode["InviteTimeout"] = "invite_timeout"; + CallErrorCode["Replaced"] = "replaced"; + CallErrorCode["SignallingFailed"] = "signalling_timeout"; + CallErrorCode["UserBusy"] = "user_busy"; + CallErrorCode["Transferred"] = "transferred"; + CallErrorCode["NewSession"] = "new_session"; +})(CallErrorCode || (exports.CallErrorCode = CallErrorCode = {})); +const VOIP_PROTO_VERSION = "1"; + +/** The fallback ICE server to use for STUN or TURN protocols. */ +const FALLBACK_ICE_SERVER = "stun:turn.matrix.org"; + +/** The length of time a call can be ringing for. */ +const CALL_TIMEOUT_MS = 60 * 1000; // ms +/** The time after which we increment callLength */ +const CALL_LENGTH_INTERVAL = 1000; // ms +/** The time after which we end the call, if ICE got disconnected */ +const ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms + +class CallError extends Error { + constructor(code, msg, err) { + // Still don't think there's any way to have proper nested errors + super(msg + ": " + err); + (0, _defineProperty2.default)(this, "code", void 0); + this.code = code; + } +} +exports.CallError = CallError; +function genCallID() { + return Date.now().toString() + (0, _randomstring.randomString)(16); +} +function getCodecParamMods(isPtt) { + const mods = [{ + mediaType: "audio", + codec: "opus", + enableDtx: true, + maxAverageBitrate: isPtt ? 12000 : undefined + }]; + return mods; +} +// generates keys for the map of transceivers +// kind is unfortunately a string rather than MediaType as this is the type of +// track.kind +function getTransceiverKey(purpose, kind) { + return purpose + ":" + kind; +} +class MatrixCall extends _typedEventEmitter.TypedEventEmitter { + // whether this call should have push-to-talk semantics + // This should be set by the consumer on incoming & outgoing calls. + + // A queue for candidates waiting to go out. + // We try to amalgamate candidates into a single candidate message where + // possible + + // our transceivers for each purpose and type of media + + // The party ID of the other side: undefined if we haven't chosen a partner + // yet, null if we have but they didn't send a party ID. + + // The logic of when & if a call is on hold is nontrivial and explained in is*OnHold + // This flag represents whether we want the other party to be on hold + + // the stats for the call at the point it ended. We can't get these after we + // tear the call down, so we just grab a snapshot before we stop the call. + // The typescript definitions have this type as 'any' :( + + // Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example + + // If candidates arrive before we've picked an opponent (which, in particular, + // will happen if the opponent sends candidates eagerly before the user answers + // the call) we buffer them up here so we can then add the ones from the party we pick + + // Used to keep the timer for the delay before actually stopping our + // video track after muting (see setLocalVideoMuted) + + // Used to allow connection without Video and Audio. To establish a webrtc connection without media a Data channel is + // needed At the moment this property is true if we allow MatrixClient with isVoipWithNoMediaAllowed = true + + /** + * Construct a new Matrix Call. + * @param opts - Config options. + */ + constructor(opts) { + var _opts$forceTURN; + super(); + (0, _defineProperty2.default)(this, "roomId", void 0); + (0, _defineProperty2.default)(this, "callId", void 0); + (0, _defineProperty2.default)(this, "invitee", void 0); + (0, _defineProperty2.default)(this, "hangupParty", void 0); + (0, _defineProperty2.default)(this, "hangupReason", void 0); + (0, _defineProperty2.default)(this, "direction", void 0); + (0, _defineProperty2.default)(this, "ourPartyId", void 0); + (0, _defineProperty2.default)(this, "peerConn", void 0); + (0, _defineProperty2.default)(this, "toDeviceSeq", 0); + (0, _defineProperty2.default)(this, "isPtt", false); + (0, _defineProperty2.default)(this, "_state", CallState.Fledgling); + (0, _defineProperty2.default)(this, "client", void 0); + (0, _defineProperty2.default)(this, "forceTURN", void 0); + (0, _defineProperty2.default)(this, "turnServers", void 0); + (0, _defineProperty2.default)(this, "candidateSendQueue", []); + (0, _defineProperty2.default)(this, "candidateSendTries", 0); + (0, _defineProperty2.default)(this, "candidatesEnded", false); + (0, _defineProperty2.default)(this, "feeds", []); + (0, _defineProperty2.default)(this, "transceivers", new Map()); + (0, _defineProperty2.default)(this, "inviteOrAnswerSent", false); + (0, _defineProperty2.default)(this, "waitForLocalAVStream", false); + (0, _defineProperty2.default)(this, "successor", void 0); + (0, _defineProperty2.default)(this, "opponentMember", void 0); + (0, _defineProperty2.default)(this, "opponentVersion", void 0); + (0, _defineProperty2.default)(this, "opponentPartyId", void 0); + (0, _defineProperty2.default)(this, "opponentCaps", void 0); + (0, _defineProperty2.default)(this, "iceDisconnectedTimeout", void 0); + (0, _defineProperty2.default)(this, "inviteTimeout", void 0); + (0, _defineProperty2.default)(this, "removeTrackListeners", new Map()); + (0, _defineProperty2.default)(this, "remoteOnHold", false); + (0, _defineProperty2.default)(this, "callStatsAtEnd", void 0); + (0, _defineProperty2.default)(this, "makingOffer", false); + (0, _defineProperty2.default)(this, "ignoreOffer", false); + (0, _defineProperty2.default)(this, "responsePromiseChain", void 0); + (0, _defineProperty2.default)(this, "remoteCandidateBuffer", new Map()); + (0, _defineProperty2.default)(this, "remoteAssertedIdentity", void 0); + (0, _defineProperty2.default)(this, "remoteSDPStreamMetadata", void 0); + (0, _defineProperty2.default)(this, "callLengthInterval", void 0); + (0, _defineProperty2.default)(this, "callStartTime", void 0); + (0, _defineProperty2.default)(this, "opponentDeviceId", void 0); + (0, _defineProperty2.default)(this, "opponentDeviceInfo", void 0); + (0, _defineProperty2.default)(this, "opponentSessionId", void 0); + (0, _defineProperty2.default)(this, "groupCallId", void 0); + (0, _defineProperty2.default)(this, "stopVideoTrackTimer", void 0); + (0, _defineProperty2.default)(this, "isOnlyDataChannelAllowed", void 0); + (0, _defineProperty2.default)(this, "stats", void 0); + (0, _defineProperty2.default)(this, "gotLocalIceCandidate", event => { + if (event.candidate) { + if (this.candidatesEnded) { + _logger.logger.warn(`Call ${this.callId} gotLocalIceCandidate() got candidate after candidates have ended - ignoring!`); + return; + } + _logger.logger.debug(`Call ${this.callId} got local ICE ${event.candidate.sdpMid} ${event.candidate.candidate}`); + if (this.callHasEnded()) return; + + // As with the offer, note we need to make a copy of this object, not + // pass the original: that broke in Chrome ~m43. + if (event.candidate.candidate === "") { + this.queueCandidate(null); + } else { + this.queueCandidate(event.candidate); + } + } + }); + (0, _defineProperty2.default)(this, "onIceGatheringStateChange", event => { + var _this$peerConn; + _logger.logger.debug(`Call ${this.callId} onIceGatheringStateChange() ice gathering state changed to ${this.peerConn.iceGatheringState}`); + if (((_this$peerConn = this.peerConn) === null || _this$peerConn === void 0 ? void 0 : _this$peerConn.iceGatheringState) === "complete") { + this.queueCandidate(null); + } + }); + (0, _defineProperty2.default)(this, "getLocalOfferFailed", err => { + _logger.logger.error(`Call ${this.callId} getLocalOfferFailed() running`, err); + this.emit(CallEvent.Error, new CallError(CallErrorCode.LocalOfferFailed, "Failed to get local offer!", err), this); + this.terminate(CallParty.Local, CallErrorCode.LocalOfferFailed, false); + }); + (0, _defineProperty2.default)(this, "getUserMediaFailed", err => { + if (this.successor) { + this.successor.getUserMediaFailed(err); + return; + } + _logger.logger.warn(`Call ${this.callId} getUserMediaFailed() failed to get user media - ending call`, err); + this.emit(CallEvent.Error, new CallError(CallErrorCode.NoUserMedia, "Couldn't start capturing media! Is your microphone set up and " + "does this app have permission?", err), this); + this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false); + }); + (0, _defineProperty2.default)(this, "onIceConnectionStateChanged", () => { + var _this$peerConn2, _this$peerConn$iceCon, _this$peerConn3, _this$peerConn4, _this$peerConn6; + if (this.callHasEnded()) { + return; // because ICE can still complete as we're ending the call + } + + _logger.logger.debug(`Call ${this.callId} onIceConnectionStateChanged() running (state=${(_this$peerConn2 = this.peerConn) === null || _this$peerConn2 === void 0 ? void 0 : _this$peerConn2.iceConnectionState})`); + + // ideally we'd consider the call to be connected when we get media but + // chrome doesn't implement any of the 'onstarted' events yet + if (["connected", "completed"].includes((_this$peerConn$iceCon = (_this$peerConn3 = this.peerConn) === null || _this$peerConn3 === void 0 ? void 0 : _this$peerConn3.iceConnectionState) !== null && _this$peerConn$iceCon !== void 0 ? _this$peerConn$iceCon : "")) { + clearTimeout(this.iceDisconnectedTimeout); + this.iceDisconnectedTimeout = undefined; + this.state = CallState.Connected; + if (!this.callLengthInterval && !this.callStartTime) { + this.callStartTime = Date.now(); + this.callLengthInterval = setInterval(() => { + this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime) / 1000), this); + }, CALL_LENGTH_INTERVAL); + } + } else if (((_this$peerConn4 = this.peerConn) === null || _this$peerConn4 === void 0 ? void 0 : _this$peerConn4.iceConnectionState) == "failed") { + var _this$peerConn5; + // Firefox for Android does not yet have support for restartIce() + // (the types say it's always defined though, so we have to cast + // to prevent typescript from warning). + if ((_this$peerConn5 = this.peerConn) !== null && _this$peerConn5 !== void 0 && _this$peerConn5.restartIce) { + this.candidatesEnded = false; + this.peerConn.restartIce(); + } else { + _logger.logger.info(`Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE failed and no ICE restart method)`); + this.hangup(CallErrorCode.IceFailed, false); + } + } else if (((_this$peerConn6 = this.peerConn) === null || _this$peerConn6 === void 0 ? void 0 : _this$peerConn6.iceConnectionState) == "disconnected") { + this.iceDisconnectedTimeout = setTimeout(() => { + _logger.logger.info(`Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE disconnected for too long)`); + this.hangup(CallErrorCode.IceFailed, false); + }, ICE_DISCONNECTED_TIMEOUT); + this.state = CallState.Connecting; + } + + // In PTT mode, override feed status to muted when we lose connection to + // the peer, since we don't want to block the line if they're not saying anything. + // Experimenting in Chrome, this happens after 5 or 6 seconds, which is probably + // fast enough. + if (this.isPtt && ["failed", "disconnected"].includes(this.peerConn.iceConnectionState)) { + for (const feed of this.getRemoteFeeds()) { + feed.setAudioVideoMuted(true, true); + } + } + }); + (0, _defineProperty2.default)(this, "onSignallingStateChanged", () => { + var _this$peerConn7; + _logger.logger.debug(`Call ${this.callId} onSignallingStateChanged() running (state=${(_this$peerConn7 = this.peerConn) === null || _this$peerConn7 === void 0 ? void 0 : _this$peerConn7.signalingState})`); + }); + (0, _defineProperty2.default)(this, "onTrack", ev => { + if (ev.streams.length === 0) { + _logger.logger.warn(`Call ${this.callId} onTrack() called with streamless track streamless (kind=${ev.track.kind})`); + return; + } + const stream = ev.streams[0]; + this.pushRemoteFeed(stream); + if (!this.removeTrackListeners.has(stream)) { + const onRemoveTrack = () => { + if (stream.getTracks().length === 0) { + _logger.logger.info(`Call ${this.callId} onTrack() removing track (streamId=${stream.id})`); + this.deleteFeedByStream(stream); + stream.removeEventListener("removetrack", onRemoveTrack); + this.removeTrackListeners.delete(stream); + } + }; + stream.addEventListener("removetrack", onRemoveTrack); + this.removeTrackListeners.set(stream, onRemoveTrack); + } + }); + (0, _defineProperty2.default)(this, "onDataChannel", ev => { + this.emit(CallEvent.DataChannel, ev.channel, this); + }); + (0, _defineProperty2.default)(this, "onNegotiationNeeded", async () => { + _logger.logger.info(`Call ${this.callId} onNegotiationNeeded() negotiation is needed!`); + if (this.state !== CallState.CreateOffer && this.opponentVersion === 0) { + _logger.logger.info(`Call ${this.callId} onNegotiationNeeded() opponent does not support renegotiation: ignoring negotiationneeded event`); + return; + } + this.queueGotLocalOffer(); + }); + (0, _defineProperty2.default)(this, "onHangupReceived", msg => { + _logger.logger.debug(`Call ${this.callId} onHangupReceived() running`); + + // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen + // a partner yet but we're treating the hangup as a reject as per VoIP v0) + if (this.partyIdMatches(msg) || this.state === CallState.Ringing) { + // default reason is user_hangup + this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true); + } else { + _logger.logger.info(`Call ${this.callId} onHangupReceived() ignoring message from party ID ${msg.party_id}: our partner is ${this.opponentPartyId}`); + } + }); + (0, _defineProperty2.default)(this, "onRejectReceived", msg => { + _logger.logger.debug(`Call ${this.callId} onRejectReceived() running`); + + // No need to check party_id for reject because if we'd received either + // an answer or reject, we wouldn't be in state InviteSent + + const shouldTerminate = + // reject events also end the call if it's ringing: it's another of + // our devices rejecting the call. + [CallState.InviteSent, CallState.Ringing].includes(this.state) || + // also if we're in the init state and it's an inbound call, since + // this means we just haven't entered the ringing state yet + this.state === CallState.Fledgling && this.direction === CallDirection.Inbound; + if (shouldTerminate) { + this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true); + } else { + _logger.logger.debug(`Call ${this.callId} onRejectReceived() called in wrong state (state=${this.state})`); + } + }); + (0, _defineProperty2.default)(this, "onAnsweredElsewhere", msg => { + _logger.logger.debug(`Call ${this.callId} onAnsweredElsewhere() running`); + this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true); + }); + this.roomId = opts.roomId; + this.invitee = opts.invitee; + this.client = opts.client; + if (!this.client.deviceId) throw new Error("Client must have a device ID to start calls"); + this.forceTURN = (_opts$forceTURN = opts.forceTURN) !== null && _opts$forceTURN !== void 0 ? _opts$forceTURN : false; + this.ourPartyId = this.client.deviceId; + this.opponentDeviceId = opts.opponentDeviceId; + this.opponentSessionId = opts.opponentSessionId; + this.groupCallId = opts.groupCallId; + // Array of Objects with urls, username, credential keys + this.turnServers = opts.turnServers || []; + if (this.turnServers.length === 0 && this.client.isFallbackICEServerAllowed()) { + this.turnServers.push({ + urls: [FALLBACK_ICE_SERVER] + }); + } + for (const server of this.turnServers) { + utils.checkObjectHasKeys(server, ["urls"]); + } + this.callId = genCallID(); + // If the Client provides calls without audio and video we need a datachannel for a webrtc connection + this.isOnlyDataChannelAllowed = this.client.isVoipWithNoMediaAllowed; + } + + /** + * Place a voice call to this room. + * @throws If you have not specified a listener for 'error' events. + */ + async placeVoiceCall() { + await this.placeCall(true, false); + } + + /** + * Place a video call to this room. + * @throws If you have not specified a listener for 'error' events. + */ + async placeVideoCall() { + await this.placeCall(true, true); + } + + /** + * Create a datachannel using this call's peer connection. + * @param label - A human readable label for this datachannel + * @param options - An object providing configuration options for the data channel. + */ + createDataChannel(label, options) { + const dataChannel = this.peerConn.createDataChannel(label, options); + this.emit(CallEvent.DataChannel, dataChannel, this); + return dataChannel; + } + getOpponentMember() { + return this.opponentMember; + } + getOpponentDeviceId() { + return this.opponentDeviceId; + } + getOpponentSessionId() { + return this.opponentSessionId; + } + opponentCanBeTransferred() { + return Boolean(this.opponentCaps && this.opponentCaps["m.call.transferee"]); + } + opponentSupportsDTMF() { + return Boolean(this.opponentCaps && this.opponentCaps["m.call.dtmf"]); + } + getRemoteAssertedIdentity() { + return this.remoteAssertedIdentity; + } + get state() { + return this._state; + } + set state(state) { + const oldState = this._state; + this._state = state; + this.emit(CallEvent.State, state, oldState, this); + } + get type() { + // we may want to look for a video receiver here rather than a track to match the + // sender behaviour, although in practice they should be the same thing + return this.hasUserMediaVideoSender || this.hasRemoteUserMediaVideoTrack ? CallType.Video : CallType.Voice; + } + get hasLocalUserMediaVideoTrack() { + var _this$localUsermediaS; + return !!((_this$localUsermediaS = this.localUsermediaStream) !== null && _this$localUsermediaS !== void 0 && _this$localUsermediaS.getVideoTracks().length); + } + get hasRemoteUserMediaVideoTrack() { + return this.getRemoteFeeds().some(feed => { + var _feed$stream; + return feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Usermedia && ((_feed$stream = feed.stream) === null || _feed$stream === void 0 ? void 0 : _feed$stream.getVideoTracks().length); + }); + } + get hasLocalUserMediaAudioTrack() { + var _this$localUsermediaS2; + return !!((_this$localUsermediaS2 = this.localUsermediaStream) !== null && _this$localUsermediaS2 !== void 0 && _this$localUsermediaS2.getAudioTracks().length); + } + get hasRemoteUserMediaAudioTrack() { + return this.getRemoteFeeds().some(feed => { + var _feed$stream2; + return feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Usermedia && !!((_feed$stream2 = feed.stream) !== null && _feed$stream2 !== void 0 && _feed$stream2.getAudioTracks().length); + }); + } + get hasUserMediaAudioSender() { + var _this$transceivers$ge; + return Boolean((_this$transceivers$ge = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Usermedia, "audio"))) === null || _this$transceivers$ge === void 0 ? void 0 : _this$transceivers$ge.sender); + } + get hasUserMediaVideoSender() { + var _this$transceivers$ge2; + return Boolean((_this$transceivers$ge2 = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Usermedia, "video"))) === null || _this$transceivers$ge2 === void 0 ? void 0 : _this$transceivers$ge2.sender); + } + get localUsermediaFeed() { + return this.getLocalFeeds().find(feed => feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Usermedia); + } + get localScreensharingFeed() { + return this.getLocalFeeds().find(feed => feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare); + } + get localUsermediaStream() { + var _this$localUsermediaF; + return (_this$localUsermediaF = this.localUsermediaFeed) === null || _this$localUsermediaF === void 0 ? void 0 : _this$localUsermediaF.stream; + } + get localScreensharingStream() { + var _this$localScreenshar; + return (_this$localScreenshar = this.localScreensharingFeed) === null || _this$localScreenshar === void 0 ? void 0 : _this$localScreenshar.stream; + } + get remoteUsermediaFeed() { + return this.getRemoteFeeds().find(feed => feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Usermedia); + } + get remoteScreensharingFeed() { + return this.getRemoteFeeds().find(feed => feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare); + } + get remoteUsermediaStream() { + var _this$remoteUsermedia; + return (_this$remoteUsermedia = this.remoteUsermediaFeed) === null || _this$remoteUsermedia === void 0 ? void 0 : _this$remoteUsermedia.stream; + } + get remoteScreensharingStream() { + var _this$remoteScreensha; + return (_this$remoteScreensha = this.remoteScreensharingFeed) === null || _this$remoteScreensha === void 0 ? void 0 : _this$remoteScreensha.stream; + } + getFeedByStreamId(streamId) { + return this.getFeeds().find(feed => feed.stream.id === streamId); + } + + /** + * Returns an array of all CallFeeds + * @returns CallFeeds + */ + getFeeds() { + return this.feeds; + } + + /** + * Returns an array of all local CallFeeds + * @returns local CallFeeds + */ + getLocalFeeds() { + return this.feeds.filter(feed => feed.isLocal()); + } + + /** + * Returns an array of all remote CallFeeds + * @returns remote CallFeeds + */ + getRemoteFeeds() { + return this.feeds.filter(feed => !feed.isLocal()); + } + async initOpponentCrypto() { + var _this$getOpponentMemb, _deviceInfoMap$get; + if (!this.opponentDeviceId) return; + if (!this.client.getUseE2eForGroupCall()) return; + // It's possible to want E2EE and yet not have the means to manage E2EE + // ourselves (for example if the client is a RoomWidgetClient) + if (!this.client.isCryptoEnabled()) { + // All we know is the device ID + this.opponentDeviceInfo = new _deviceinfo.DeviceInfo(this.opponentDeviceId); + return; + } + // if we've got to this point, we do want to init crypto, so throw if we can't + if (!this.client.crypto) throw new Error("Crypto is not initialised."); + const userId = this.invitee || ((_this$getOpponentMemb = this.getOpponentMember()) === null || _this$getOpponentMemb === void 0 ? void 0 : _this$getOpponentMemb.userId); + if (!userId) throw new Error("Couldn't find opponent user ID to init crypto"); + const deviceInfoMap = await this.client.crypto.deviceList.downloadKeys([userId], false); + this.opponentDeviceInfo = (_deviceInfoMap$get = deviceInfoMap.get(userId)) === null || _deviceInfoMap$get === void 0 ? void 0 : _deviceInfoMap$get.get(this.opponentDeviceId); + if (this.opponentDeviceInfo === undefined) { + throw new _groupCall.GroupCallUnknownDeviceError(userId); + } + } + + /** + * Generates and returns localSDPStreamMetadata + * @returns localSDPStreamMetadata + */ + getLocalSDPStreamMetadata(updateStreamIds = false) { + const metadata = {}; + for (const localFeed of this.getLocalFeeds()) { + if (updateStreamIds) { + localFeed.sdpMetadataStreamId = localFeed.stream.id; + } + metadata[localFeed.sdpMetadataStreamId] = { + purpose: localFeed.purpose, + audio_muted: localFeed.isAudioMuted(), + video_muted: localFeed.isVideoMuted() + }; + } + return metadata; + } + + /** + * Returns true if there are no incoming feeds, + * otherwise returns false + * @returns no incoming feeds + */ + noIncomingFeeds() { + return !this.feeds.some(feed => !feed.isLocal()); + } + pushRemoteFeed(stream) { + // Fallback to old behavior if the other side doesn't support SDPStreamMetadata + if (!this.opponentSupportsSDPStreamMetadata()) { + this.pushRemoteFeedWithoutMetadata(stream); + return; + } + const userId = this.getOpponentMember().userId; + const purpose = this.remoteSDPStreamMetadata[stream.id].purpose; + const audioMuted = this.remoteSDPStreamMetadata[stream.id].audio_muted; + const videoMuted = this.remoteSDPStreamMetadata[stream.id].video_muted; + if (!purpose) { + _logger.logger.warn(`Call ${this.callId} pushRemoteFeed() ignoring stream because we didn't get any metadata about it (streamId=${stream.id})`); + return; + } + if (this.getFeedByStreamId(stream.id)) { + _logger.logger.warn(`Call ${this.callId} pushRemoteFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`); + return; + } + this.feeds.push(new _callFeed.CallFeed({ + client: this.client, + call: this, + roomId: this.roomId, + userId, + deviceId: this.getOpponentDeviceId(), + stream, + purpose, + audioMuted, + videoMuted + })); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + _logger.logger.info(`Call ${this.callId} pushRemoteFeed() pushed stream (streamId=${stream.id}, active=${stream.active}, purpose=${purpose})`); + } + + /** + * This method is used ONLY if the other client doesn't support sending SDPStreamMetadata + */ + pushRemoteFeedWithoutMetadata(stream) { + var _this$feeds$find; + const userId = this.getOpponentMember().userId; + // We can guess the purpose here since the other client can only send one stream + const purpose = _callEventTypes.SDPStreamMetadataPurpose.Usermedia; + const oldRemoteStream = (_this$feeds$find = this.feeds.find(feed => !feed.isLocal())) === null || _this$feeds$find === void 0 ? void 0 : _this$feeds$find.stream; + + // Note that we check by ID and always set the remote stream: Chrome appears + // to make new stream objects when transceiver directionality is changed and the 'active' + // status of streams change - Dave + // If we already have a stream, check this stream has the same id + if (oldRemoteStream && stream.id !== oldRemoteStream.id) { + _logger.logger.warn(`Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring new stream because we already have stream (streamId=${stream.id})`); + return; + } + if (this.getFeedByStreamId(stream.id)) { + _logger.logger.warn(`Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring stream because we already have a feed for it (streamId=${stream.id})`); + return; + } + this.feeds.push(new _callFeed.CallFeed({ + client: this.client, + call: this, + roomId: this.roomId, + audioMuted: false, + videoMuted: false, + userId, + deviceId: this.getOpponentDeviceId(), + stream, + purpose + })); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + _logger.logger.info(`Call ${this.callId} pushRemoteFeedWithoutMetadata() pushed stream (streamId=${stream.id}, active=${stream.active})`); + } + pushNewLocalFeed(stream, purpose, addToPeerConnection = true) { + const userId = this.client.getUserId(); + + // Tracks don't always start off enabled, eg. chrome will give a disabled + // audio track if you ask for user media audio and already had one that + // you'd set to disabled (presumably because it clones them internally). + setTracksEnabled(stream.getAudioTracks(), true); + setTracksEnabled(stream.getVideoTracks(), true); + if (this.getFeedByStreamId(stream.id)) { + _logger.logger.warn(`Call ${this.callId} pushNewLocalFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`); + return; + } + this.pushLocalFeed(new _callFeed.CallFeed({ + client: this.client, + roomId: this.roomId, + audioMuted: false, + videoMuted: false, + userId, + deviceId: this.getOpponentDeviceId(), + stream, + purpose + }), addToPeerConnection); + } + + /** + * Pushes supplied feed to the call + * @param callFeed - to push + * @param addToPeerConnection - whether to add the tracks to the peer connection + */ + pushLocalFeed(callFeed, addToPeerConnection = true) { + if (this.feeds.some(feed => callFeed.stream.id === feed.stream.id)) { + _logger.logger.info(`Call ${this.callId} pushLocalFeed() ignoring duplicate local stream (streamId=${callFeed.stream.id})`); + return; + } + this.feeds.push(callFeed); + if (addToPeerConnection) { + for (const track of callFeed.stream.getTracks()) { + _logger.logger.info(`Call ${this.callId} pushLocalFeed() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${callFeed.stream.id}, streamPurpose=${callFeed.purpose}, enabled=${track.enabled})`); + const tKey = getTransceiverKey(callFeed.purpose, track.kind); + if (this.transceivers.has(tKey)) { + // we already have a sender, so we re-use it. We try to re-use transceivers as much + // as possible because they can't be removed once added, so otherwise they just + // accumulate which makes the SDP very large very quickly: in fact it only takes + // about 6 video tracks to exceed the maximum size of an Olm-encrypted + // Matrix event. + const transceiver = this.transceivers.get(tKey); + transceiver.sender.replaceTrack(track); + // set the direction to indicate we're going to start sending again + // (this will trigger the re-negotiation) + transceiver.direction = transceiver.direction === "inactive" ? "sendonly" : "sendrecv"; + } else { + // create a new one. We need to use addTrack rather addTransceiver for this because firefox + // doesn't yet implement RTCRTPSender.setStreams() + // (https://bugzilla.mozilla.org/show_bug.cgi?id=1510802) so we'd have no way to group the + // two tracks together into a stream. + const newSender = this.peerConn.addTrack(track, callFeed.stream); + + // now go & fish for the new transceiver + const newTransceiver = this.peerConn.getTransceivers().find(t => t.sender === newSender); + if (newTransceiver) { + this.transceivers.set(tKey, newTransceiver); + } else { + _logger.logger.warn(`Call ${this.callId} pushLocalFeed() didn't find a matching transceiver after adding track!`); + } + } + } + } + _logger.logger.info(`Call ${this.callId} pushLocalFeed() pushed stream (id=${callFeed.stream.id}, active=${callFeed.stream.active}, purpose=${callFeed.purpose})`); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + } + + /** + * Removes local call feed from the call and its tracks from the peer + * connection + * @param callFeed - to remove + */ + removeLocalFeed(callFeed) { + const audioTransceiverKey = getTransceiverKey(callFeed.purpose, "audio"); + const videoTransceiverKey = getTransceiverKey(callFeed.purpose, "video"); + for (const transceiverKey of [audioTransceiverKey, videoTransceiverKey]) { + // this is slightly mixing the track and transceiver API but is basically just shorthand. + // There is no way to actually remove a transceiver, so this just sets it to inactive + // (or recvonly) and replaces the source with nothing. + if (this.transceivers.has(transceiverKey)) { + const transceiver = this.transceivers.get(transceiverKey); + if (transceiver.sender) this.peerConn.removeTrack(transceiver.sender); + } + } + if (callFeed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare) { + this.client.getMediaHandler().stopScreensharingStream(callFeed.stream); + } + this.deleteFeed(callFeed); + } + deleteAllFeeds() { + for (const feed of this.feeds) { + if (!feed.isLocal() || !this.groupCallId) { + feed.dispose(); + } + } + this.feeds = []; + this.emit(CallEvent.FeedsChanged, this.feeds, this); + } + deleteFeedByStream(stream) { + const feed = this.getFeedByStreamId(stream.id); + if (!feed) { + _logger.logger.warn(`Call ${this.callId} deleteFeedByStream() didn't find the feed to delete (streamId=${stream.id})`); + return; + } + this.deleteFeed(feed); + } + deleteFeed(feed) { + feed.dispose(); + this.feeds.splice(this.feeds.indexOf(feed), 1); + this.emit(CallEvent.FeedsChanged, this.feeds, this); + } + + // The typescript definitions have this type as 'any' :( + async getCurrentCallStats() { + if (this.callHasEnded()) { + return this.callStatsAtEnd; + } + return this.collectCallStats(); + } + async collectCallStats() { + // This happens when the call fails before it starts. + // For example when we fail to get capture sources + if (!this.peerConn) return; + const statsReport = await this.peerConn.getStats(); + const stats = []; + statsReport.forEach(item => { + stats.push(item); + }); + return stats; + } + + /** + * Configure this call from an invite event. Used by MatrixClient. + * @param event - The m.call.invite event + */ + async initWithInvite(event) { + var _this$feeds$find2; + const invite = event.getContent(); + this.direction = CallDirection.Inbound; + + // make sure we have valid turn creds. Unless something's gone wrong, it should + // poll and keep the credentials valid so this should be instant. + const haveTurnCreds = await this.client.checkTurnServers(); + if (!haveTurnCreds) { + _logger.logger.warn(`Call ${this.callId} initWithInvite() failed to get TURN credentials! Proceeding with call anyway...`); + } + const sdpStreamMetadata = invite[_callEventTypes.SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); + } else { + _logger.logger.debug(`Call ${this.callId} initWithInvite() did not get any SDPStreamMetadata! Can not send/receive multiple streams`); + } + this.peerConn = this.createPeerConnection(); + // we must set the party ID before await-ing on anything: the call event + // handler will start giving us more call events (eg. candidates) so if + // we haven't set the party ID, we'll ignore them. + this.chooseOpponent(event); + await this.initOpponentCrypto(); + try { + await this.peerConn.setRemoteDescription(invite.offer); + await this.addBufferedIceCandidates(); + } catch (e) { + _logger.logger.debug(`Call ${this.callId} initWithInvite() failed to set remote description`, e); + this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); + return; + } + const remoteStream = (_this$feeds$find2 = this.feeds.find(feed => !feed.isLocal())) === null || _this$feeds$find2 === void 0 ? void 0 : _this$feeds$find2.stream; + + // According to previous comments in this file, firefox at some point did not + // add streams until media started arriving on them. Testing latest firefox + // (81 at time of writing), this is no longer a problem, so let's do it the correct way. + // + // For example in case of no media webrtc connections like screen share only call we have to allow webrtc + // connections without remote media. In this case we always use a data channel. At the moment we allow as well + // only data channel as media in the WebRTC connection with this setup here. + if (!this.isOnlyDataChannelAllowed && (!remoteStream || remoteStream.getTracks().length === 0)) { + _logger.logger.error(`Call ${this.callId} initWithInvite() no remote stream or no tracks after setting remote description!`); + this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); + return; + } + this.state = CallState.Ringing; + if (event.getLocalAge()) { + // Time out the call if it's ringing for too long + const ringingTimer = setTimeout(() => { + if (this.state == CallState.Ringing) { + var _this$stats; + _logger.logger.debug(`Call ${this.callId} initWithInvite() invite has expired. Hanging up.`); + this.hangupParty = CallParty.Remote; // effectively + this.state = CallState.Ended; + this.stopAllMedia(); + if (this.peerConn.signalingState != "closed") { + this.peerConn.close(); + } + (_this$stats = this.stats) === null || _this$stats === void 0 ? void 0 : _this$stats.removeStatsReportGatherer(this.callId); + this.emit(CallEvent.Hangup, this); + } + }, invite.lifetime - event.getLocalAge()); + const onState = state => { + if (state !== CallState.Ringing) { + clearTimeout(ringingTimer); + this.off(CallEvent.State, onState); + } + }; + this.on(CallEvent.State, onState); + } + } + + /** + * Configure this call from a hangup or reject event. Used by MatrixClient. + * @param event - The m.call.hangup event + */ + initWithHangup(event) { + // perverse as it may seem, sometimes we want to instantiate a call with a + // hangup message (because when getting the state of the room on load, events + // come in reverse order and we want to remember that a call has been hung up) + this.state = CallState.Ended; + } + shouldAnswerWithMediaType(wantedValue, valueOfTheOtherSide, type) { + if (wantedValue && !valueOfTheOtherSide) { + // TODO: Figure out how to do this + _logger.logger.warn(`Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type} because the other side isn't sending it either.`); + return false; + } else if (!utils.isNullOrUndefined(wantedValue) && wantedValue !== valueOfTheOtherSide && !this.opponentSupportsSDPStreamMetadata()) { + _logger.logger.warn(`Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type}=${wantedValue} because the other side doesn't support it. Answering with ${type}=${valueOfTheOtherSide}.`); + return valueOfTheOtherSide; + } + return wantedValue !== null && wantedValue !== void 0 ? wantedValue : valueOfTheOtherSide; + } + + /** + * Answer a call. + */ + async answer(audio, video) { + if (this.inviteOrAnswerSent) return; + // TODO: Figure out how to do this + if (audio === false && video === false) throw new Error("You CANNOT answer a call without media"); + if (!this.localUsermediaStream && !this.waitForLocalAVStream) { + const prevState = this.state; + const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, "audio"); + const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, "video"); + this.state = CallState.WaitLocalMedia; + this.waitForLocalAVStream = true; + try { + var _this$client$getDevic; + const stream = await this.client.getMediaHandler().getUserMediaStream(answerWithAudio, answerWithVideo); + this.waitForLocalAVStream = false; + const usermediaFeed = new _callFeed.CallFeed({ + client: this.client, + roomId: this.roomId, + userId: this.client.getUserId(), + deviceId: (_this$client$getDevic = this.client.getDeviceId()) !== null && _this$client$getDevic !== void 0 ? _this$client$getDevic : undefined, + stream, + purpose: _callEventTypes.SDPStreamMetadataPurpose.Usermedia, + audioMuted: false, + videoMuted: false + }); + const feeds = [usermediaFeed]; + if (this.localScreensharingFeed) { + feeds.push(this.localScreensharingFeed); + } + this.answerWithCallFeeds(feeds); + } catch (e) { + if (answerWithVideo) { + // Try to answer without video + _logger.logger.warn(`Call ${this.callId} answer() failed to getUserMedia(), trying to getUserMedia() without video`); + this.state = prevState; + this.waitForLocalAVStream = false; + await this.answer(answerWithAudio, false); + } else { + this.getUserMediaFailed(e); + return; + } + } + } else if (this.waitForLocalAVStream) { + this.state = CallState.WaitLocalMedia; + } + } + answerWithCallFeeds(callFeeds) { + if (this.inviteOrAnswerSent) return; + this.queueGotCallFeedsForAnswer(callFeeds); + } + + /** + * Replace this call with a new call, e.g. for glare resolution. Used by + * MatrixClient. + * @param newCall - The new call. + */ + replacedBy(newCall) { + _logger.logger.debug(`Call ${this.callId} replacedBy() running (newCallId=${newCall.callId})`); + if (this.state === CallState.WaitLocalMedia) { + _logger.logger.debug(`Call ${this.callId} replacedBy() telling new call to wait for local media (newCallId=${newCall.callId})`); + newCall.waitForLocalAVStream = true; + } else if ([CallState.CreateOffer, CallState.InviteSent].includes(this.state)) { + if (newCall.direction === CallDirection.Outbound) { + newCall.queueGotCallFeedsForAnswer([]); + } else { + _logger.logger.debug(`Call ${this.callId} replacedBy() handing local stream to new call(newCallId=${newCall.callId})`); + newCall.queueGotCallFeedsForAnswer(this.getLocalFeeds().map(feed => feed.clone())); + } + } + this.successor = newCall; + this.emit(CallEvent.Replaced, newCall, this); + this.hangup(CallErrorCode.Replaced, true); + } + + /** + * Hangup a call. + * @param reason - The reason why the call is being hung up. + * @param suppressEvent - True to suppress emitting an event. + */ + hangup(reason, suppressEvent) { + if (this.callHasEnded()) return; + _logger.logger.debug(`Call ${this.callId} hangup() ending call (reason=${reason})`); + this.terminate(CallParty.Local, reason, !suppressEvent); + // We don't want to send hangup here if we didn't even get to sending an invite + if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return; + const content = {}; + // Don't send UserHangup reason to older clients + if (this.opponentVersion && this.opponentVersion !== 0 || reason !== CallErrorCode.UserHangup) { + content["reason"] = reason; + } + this.sendVoipEvent(_event.EventType.CallHangup, content); + } + + /** + * Reject a call + * This used to be done by calling hangup, but is a separate method and protocol + * event as of MSC2746. + */ + reject() { + if (this.state !== CallState.Ringing) { + throw Error("Call must be in 'ringing' state to reject!"); + } + if (this.opponentVersion === 0) { + _logger.logger.info(`Call ${this.callId} reject() opponent version is less than 1: sending hangup instead of reject (opponentVersion=${this.opponentVersion})`); + this.hangup(CallErrorCode.UserHangup, true); + return; + } + _logger.logger.debug("Rejecting call: " + this.callId); + this.terminate(CallParty.Local, CallErrorCode.UserHangup, true); + this.sendVoipEvent(_event.EventType.CallReject, {}); + } + + /** + * Adds an audio and/or video track - upgrades the call + * @param audio - should add an audio track + * @param video - should add an video track + */ + async upgradeCall(audio, video) { + // We don't do call downgrades + if (!audio && !video) return; + if (!this.opponentSupportsSDPStreamMetadata()) return; + try { + _logger.logger.debug(`Call ${this.callId} upgradeCall() upgrading call (audio=${audio}, video=${video})`); + const getAudio = audio || this.hasLocalUserMediaAudioTrack; + const getVideo = video || this.hasLocalUserMediaVideoTrack; + + // updateLocalUsermediaStream() will take the tracks, use them as + // replacement and throw the stream away, so it isn't reusable + const stream = await this.client.getMediaHandler().getUserMediaStream(getAudio, getVideo, false); + await this.updateLocalUsermediaStream(stream, audio, video); + } catch (error) { + _logger.logger.error(`Call ${this.callId} upgradeCall() failed to upgrade the call`, error); + this.emit(CallEvent.Error, new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", error), this); + } + } + + /** + * Returns true if this.remoteSDPStreamMetadata is defined, otherwise returns false + * @returns can screenshare + */ + opponentSupportsSDPStreamMetadata() { + return Boolean(this.remoteSDPStreamMetadata); + } + + /** + * If there is a screensharing stream returns true, otherwise returns false + * @returns is screensharing + */ + isScreensharing() { + return Boolean(this.localScreensharingStream); + } + + /** + * Starts/stops screensharing + * @param enabled - the desired screensharing state + * @param desktopCapturerSourceId - optional id of the desktop capturer source to use + * @returns new screensharing state + */ + async setScreensharingEnabled(enabled, opts) { + // Skip if there is nothing to do + if (enabled && this.isScreensharing()) { + _logger.logger.warn(`Call ${this.callId} setScreensharingEnabled() there is already a screensharing stream - there is nothing to do!`); + return true; + } else if (!enabled && !this.isScreensharing()) { + _logger.logger.warn(`Call ${this.callId} setScreensharingEnabled() there already isn't a screensharing stream - there is nothing to do!`); + return false; + } + + // Fallback to replaceTrack() + if (!this.opponentSupportsSDPStreamMetadata()) { + return this.setScreensharingEnabledWithoutMetadataSupport(enabled, opts); + } + _logger.logger.debug(`Call ${this.callId} setScreensharingEnabled() running (enabled=${enabled})`); + if (enabled) { + try { + const stream = await this.client.getMediaHandler().getScreensharingStream(opts); + if (!stream) return false; + this.pushNewLocalFeed(stream, _callEventTypes.SDPStreamMetadataPurpose.Screenshare); + return true; + } catch (err) { + _logger.logger.error(`Call ${this.callId} setScreensharingEnabled() failed to get screen-sharing stream:`, err); + return false; + } + } else { + const audioTransceiver = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Screenshare, "audio")); + const videoTransceiver = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Screenshare, "video")); + for (const transceiver of [audioTransceiver, videoTransceiver]) { + // this is slightly mixing the track and transceiver API but is basically just shorthand + // for removing the sender. + if (transceiver && transceiver.sender) this.peerConn.removeTrack(transceiver.sender); + } + this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream); + this.deleteFeedByStream(this.localScreensharingStream); + return false; + } + } + + /** + * Starts/stops screensharing + * Should be used ONLY if the opponent doesn't support SDPStreamMetadata + * @param enabled - the desired screensharing state + * @param desktopCapturerSourceId - optional id of the desktop capturer source to use + * @returns new screensharing state + */ + async setScreensharingEnabledWithoutMetadataSupport(enabled, opts) { + _logger.logger.debug(`Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() running (enabled=${enabled})`); + if (enabled) { + try { + var _this$transceivers$ge3; + const stream = await this.client.getMediaHandler().getScreensharingStream(opts); + if (!stream) return false; + const track = stream.getTracks().find(track => track.kind === "video"); + const sender = (_this$transceivers$ge3 = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Usermedia, "video"))) === null || _this$transceivers$ge3 === void 0 ? void 0 : _this$transceivers$ge3.sender; + sender === null || sender === void 0 ? void 0 : sender.replaceTrack(track !== null && track !== void 0 ? track : null); + this.pushNewLocalFeed(stream, _callEventTypes.SDPStreamMetadataPurpose.Screenshare, false); + return true; + } catch (err) { + _logger.logger.error(`Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() failed to get screen-sharing stream:`, err); + return false; + } + } else { + var _this$localUsermediaS3, _this$transceivers$ge4; + const track = (_this$localUsermediaS3 = this.localUsermediaStream) === null || _this$localUsermediaS3 === void 0 ? void 0 : _this$localUsermediaS3.getTracks().find(track => track.kind === "video"); + const sender = (_this$transceivers$ge4 = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Usermedia, "video"))) === null || _this$transceivers$ge4 === void 0 ? void 0 : _this$transceivers$ge4.sender; + sender === null || sender === void 0 ? void 0 : sender.replaceTrack(track !== null && track !== void 0 ? track : null); + this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream); + this.deleteFeedByStream(this.localScreensharingStream); + return false; + } + } + + /** + * Replaces/adds the tracks from the passed stream to the localUsermediaStream + * @param stream - to use a replacement for the local usermedia stream + */ + async updateLocalUsermediaStream(stream, forceAudio = false, forceVideo = false) { + const callFeed = this.localUsermediaFeed; + const audioEnabled = forceAudio || !callFeed.isAudioMuted() && !this.remoteOnHold; + const videoEnabled = forceVideo || !callFeed.isVideoMuted() && !this.remoteOnHold; + _logger.logger.log(`Call ${this.callId} updateLocalUsermediaStream() running (streamId=${stream.id}, audio=${audioEnabled}, video=${videoEnabled})`); + setTracksEnabled(stream.getAudioTracks(), audioEnabled); + setTracksEnabled(stream.getVideoTracks(), videoEnabled); + + // We want to keep the same stream id, so we replace the tracks rather + // than the whole stream. + + // Firstly, we replace the tracks in our localUsermediaStream. + for (const track of this.localUsermediaStream.getTracks()) { + this.localUsermediaStream.removeTrack(track); + track.stop(); + } + for (const track of stream.getTracks()) { + this.localUsermediaStream.addTrack(track); + } + + // Then replace the old tracks, if possible. + for (const track of stream.getTracks()) { + const tKey = getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Usermedia, track.kind); + const transceiver = this.transceivers.get(tKey); + const oldSender = transceiver === null || transceiver === void 0 ? void 0 : transceiver.sender; + let added = false; + if (oldSender) { + try { + _logger.logger.info(`Call ${this.callId} updateLocalUsermediaStream() replacing track (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`); + await oldSender.replaceTrack(track); + // Set the direction to indicate we're going to be sending. + // This is only necessary in the cases where we're upgrading + // the call to video after downgrading it. + transceiver.direction = transceiver.direction === "inactive" ? "sendonly" : "sendrecv"; + added = true; + } catch (error) { + _logger.logger.warn(`Call ${this.callId} updateLocalUsermediaStream() replaceTrack failed: adding new transceiver instead`, error); + } + } + if (!added) { + _logger.logger.info(`Call ${this.callId} updateLocalUsermediaStream() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`); + const newSender = this.peerConn.addTrack(track, this.localUsermediaStream); + const newTransceiver = this.peerConn.getTransceivers().find(t => t.sender === newSender); + if (newTransceiver) { + this.transceivers.set(tKey, newTransceiver); + } else { + _logger.logger.warn(`Call ${this.callId} updateLocalUsermediaStream() couldn't find matching transceiver for newly added track!`); + } + } + } + } + + /** + * Set whether our outbound video should be muted or not. + * @param muted - True to mute the outbound video. + * @returns the new mute state + */ + async setLocalVideoMuted(muted) { + var _this$localUsermediaF3; + _logger.logger.log(`Call ${this.callId} setLocalVideoMuted() running ${muted}`); + + // if we were still thinking about stopping and removing the video + // track: don't, because we want it back. + if (!muted && this.stopVideoTrackTimer !== undefined) { + clearTimeout(this.stopVideoTrackTimer); + this.stopVideoTrackTimer = undefined; + } + if (!(await this.client.getMediaHandler().hasVideoDevice())) { + return this.isLocalVideoMuted(); + } + if (!this.hasUserMediaVideoSender && !muted) { + var _this$localUsermediaF2; + (_this$localUsermediaF2 = this.localUsermediaFeed) === null || _this$localUsermediaF2 === void 0 ? void 0 : _this$localUsermediaF2.setAudioVideoMuted(null, muted); + await this.upgradeCall(false, true); + return this.isLocalVideoMuted(); + } + + // we may not have a video track - if not, re-request usermedia + if (!muted && this.localUsermediaStream.getVideoTracks().length === 0) { + const stream = await this.client.getMediaHandler().getUserMediaStream(true, true); + await this.updateLocalUsermediaStream(stream); + } + (_this$localUsermediaF3 = this.localUsermediaFeed) === null || _this$localUsermediaF3 === void 0 ? void 0 : _this$localUsermediaF3.setAudioVideoMuted(null, muted); + this.updateMuteStatus(); + await this.sendMetadataUpdate(); + + // if we're muting video, set a timeout to stop & remove the video track so we release + // the camera. We wait a short time to do this because when we disable a track, WebRTC + // will send black video for it. If we just stop and remove it straight away, the video + // will just freeze which means that when we unmute video, the other side will briefly + // get a static frame of us from before we muted. This way, the still frame is just black. + // A very small delay is not always enough so the theory here is that it needs to be long + // enough for WebRTC to encode a frame: 120ms should be long enough even if we're only + // doing 10fps. + if (muted) { + this.stopVideoTrackTimer = setTimeout(() => { + for (const t of this.localUsermediaStream.getVideoTracks()) { + t.stop(); + this.localUsermediaStream.removeTrack(t); + } + }, 120); + } + return this.isLocalVideoMuted(); + } + + /** + * Check if local video is muted. + * + * If there are multiple video tracks, <i>all</i> of the tracks need to be muted + * for this to return true. This means if there are no video tracks, this will + * return true. + * @returns True if the local preview video is muted, else false + * (including if the call is not set up yet). + */ + isLocalVideoMuted() { + var _this$localUsermediaF4, _this$localUsermediaF5; + return (_this$localUsermediaF4 = (_this$localUsermediaF5 = this.localUsermediaFeed) === null || _this$localUsermediaF5 === void 0 ? void 0 : _this$localUsermediaF5.isVideoMuted()) !== null && _this$localUsermediaF4 !== void 0 ? _this$localUsermediaF4 : false; + } + + /** + * Set whether the microphone should be muted or not. + * @param muted - True to mute the mic. + * @returns the new mute state + */ + async setMicrophoneMuted(muted) { + var _this$localUsermediaF6; + _logger.logger.log(`Call ${this.callId} setMicrophoneMuted() running ${muted}`); + if (!(await this.client.getMediaHandler().hasAudioDevice())) { + return this.isMicrophoneMuted(); + } + if (!muted && (!this.hasUserMediaAudioSender || !this.hasLocalUserMediaAudioTrack)) { + await this.upgradeCall(true, false); + return this.isMicrophoneMuted(); + } + (_this$localUsermediaF6 = this.localUsermediaFeed) === null || _this$localUsermediaF6 === void 0 ? void 0 : _this$localUsermediaF6.setAudioVideoMuted(muted, null); + this.updateMuteStatus(); + await this.sendMetadataUpdate(); + return this.isMicrophoneMuted(); + } + + /** + * Check if the microphone is muted. + * + * If there are multiple audio tracks, <i>all</i> of the tracks need to be muted + * for this to return true. This means if there are no audio tracks, this will + * return true. + * @returns True if the mic is muted, else false (including if the call + * is not set up yet). + */ + isMicrophoneMuted() { + var _this$localUsermediaF7, _this$localUsermediaF8; + return (_this$localUsermediaF7 = (_this$localUsermediaF8 = this.localUsermediaFeed) === null || _this$localUsermediaF8 === void 0 ? void 0 : _this$localUsermediaF8.isAudioMuted()) !== null && _this$localUsermediaF7 !== void 0 ? _this$localUsermediaF7 : false; + } + + /** + * @returns true if we have put the party on the other side of the call on hold + * (that is, we are signalling to them that we are not listening) + */ + isRemoteOnHold() { + return this.remoteOnHold; + } + setRemoteOnHold(onHold) { + if (this.isRemoteOnHold() === onHold) return; + this.remoteOnHold = onHold; + for (const transceiver of this.peerConn.getTransceivers()) { + // We don't send hold music or anything so we're not actually + // sending anything, but sendrecv is fairly standard for hold and + // it makes it a lot easier to figure out who's put who on hold. + transceiver.direction = onHold ? "sendonly" : "sendrecv"; + } + this.updateMuteStatus(); + this.sendMetadataUpdate(); + this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold, this); + } + + /** + * Indicates whether we are 'on hold' to the remote party (ie. if true, + * they cannot hear us). + * @returns true if the other party has put us on hold + */ + isLocalOnHold() { + if (this.state !== CallState.Connected) return false; + let callOnHold = true; + + // We consider a call to be on hold only if *all* the tracks are on hold + // (is this the right thing to do?) + for (const transceiver of this.peerConn.getTransceivers()) { + const trackOnHold = ["inactive", "recvonly"].includes(transceiver.currentDirection); + if (!trackOnHold) callOnHold = false; + } + return callOnHold; + } + + /** + * Sends a DTMF digit to the other party + * @param digit - The digit (nb. string - '#' and '*' are dtmf too) + */ + sendDtmfDigit(digit) { + for (const sender of this.peerConn.getSenders()) { + var _sender$track; + if (((_sender$track = sender.track) === null || _sender$track === void 0 ? void 0 : _sender$track.kind) === "audio" && sender.dtmf) { + sender.dtmf.insertDTMF(digit); + return; + } + } + throw new Error("Unable to find a track to send DTMF on"); + } + updateMuteStatus() { + const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold; + const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold; + _logger.logger.log(`Call ${this.callId} updateMuteStatus stream ${this.localUsermediaStream.id} micShouldBeMuted ${micShouldBeMuted} vidShouldBeMuted ${vidShouldBeMuted}`); + setTracksEnabled(this.localUsermediaStream.getAudioTracks(), !micShouldBeMuted); + setTracksEnabled(this.localUsermediaStream.getVideoTracks(), !vidShouldBeMuted); + } + async sendMetadataUpdate() { + await this.sendVoipEvent(_event.EventType.CallSDPStreamMetadataChangedPrefix, { + [_callEventTypes.SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata() + }); + } + gotCallFeedsForInvite(callFeeds, requestScreenshareFeed = false) { + if (this.successor) { + this.successor.queueGotCallFeedsForAnswer(callFeeds); + return; + } + if (this.callHasEnded()) { + this.stopAllMedia(); + return; + } + for (const feed of callFeeds) { + this.pushLocalFeed(feed); + } + if (requestScreenshareFeed) { + this.peerConn.addTransceiver("video", { + direction: "recvonly" + }); + } + this.state = CallState.CreateOffer; + _logger.logger.debug(`Call ${this.callId} gotUserMediaForInvite() run`); + // Now we wait for the negotiationneeded event + } + + async sendAnswer() { + const answerContent = { + answer: { + sdp: this.peerConn.localDescription.sdp, + // type is now deprecated as of Matrix VoIP v1, but + // required to still be sent for backwards compat + type: this.peerConn.localDescription.type + }, + [_callEventTypes.SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true) + }; + answerContent.capabilities = { + "m.call.transferee": this.client.supportsCallTransfer, + "m.call.dtmf": false + }; + + // We have just taken the local description from the peerConn which will + // contain all the local candidates added so far, so we can discard any candidates + // we had queued up because they'll be in the answer. + const discardCount = this.discardDuplicateCandidates(); + _logger.logger.info(`Call ${this.callId} sendAnswer() discarding ${discardCount} candidates that will be sent in answer`); + try { + await this.sendVoipEvent(_event.EventType.CallAnswer, answerContent); + // If this isn't the first time we've tried to send the answer, + // we may have candidates queued up, so send them now. + this.inviteOrAnswerSent = true; + } catch (error) { + // We've failed to answer: back to the ringing state + this.state = CallState.Ringing; + if (error instanceof _httpApi.MatrixError && error.event) this.client.cancelPendingEvent(error.event); + let code = CallErrorCode.SendAnswer; + let message = "Failed to send answer"; + if (error.name == "UnknownDeviceError") { + code = CallErrorCode.UnknownDevices; + message = "Unknown devices present in the room"; + } + this.emit(CallEvent.Error, new CallError(code, message, error), this); + throw error; + } + + // error handler re-throws so this won't happen on error, but + // we don't want the same error handling on the candidate queue + this.sendCandidateQueue(); + } + queueGotCallFeedsForAnswer(callFeeds) { + // Ensure only one negotiate/answer event is being processed at a time. + if (this.responsePromiseChain) { + this.responsePromiseChain = this.responsePromiseChain.then(() => this.gotCallFeedsForAnswer(callFeeds)); + } else { + this.responsePromiseChain = this.gotCallFeedsForAnswer(callFeeds); + } + } + + // Enables DTX (discontinuous transmission) on the given session to reduce + // bandwidth when transmitting silence + mungeSdp(description, mods) { + // The only way to enable DTX at this time is through SDP munging + const sdp = (0, _sdpTransform.parse)(description.sdp); + sdp.media.forEach(media => { + const payloadTypeToCodecMap = new Map(); + const codecToPayloadTypeMap = new Map(); + for (const rtp of media.rtp) { + payloadTypeToCodecMap.set(rtp.payload, rtp.codec); + codecToPayloadTypeMap.set(rtp.codec, rtp.payload); + } + for (const mod of mods) { + if (mod.mediaType !== media.type) continue; + if (!codecToPayloadTypeMap.has(mod.codec)) { + _logger.logger.info(`Call ${this.callId} mungeSdp() ignoring SDP modifications for ${mod.codec} as it's not present.`); + continue; + } + const extraConfig = []; + if (mod.enableDtx !== undefined) { + extraConfig.push(`usedtx=${mod.enableDtx ? "1" : "0"}`); + } + if (mod.maxAverageBitrate !== undefined) { + extraConfig.push(`maxaveragebitrate=${mod.maxAverageBitrate}`); + } + let found = false; + for (const fmtp of media.fmtp) { + if (payloadTypeToCodecMap.get(fmtp.payload) === mod.codec) { + found = true; + fmtp.config += ";" + extraConfig.join(";"); + } + } + if (!found) { + media.fmtp.push({ + payload: codecToPayloadTypeMap.get(mod.codec), + config: extraConfig.join(";") + }); + } + } + }); + description.sdp = (0, _sdpTransform.write)(sdp); + } + async createOffer() { + const offer = await this.peerConn.createOffer(); + this.mungeSdp(offer, getCodecParamMods(this.isPtt)); + return offer; + } + async createAnswer() { + const answer = await this.peerConn.createAnswer(); + this.mungeSdp(answer, getCodecParamMods(this.isPtt)); + return answer; + } + async gotCallFeedsForAnswer(callFeeds) { + if (this.callHasEnded()) return; + this.waitForLocalAVStream = false; + for (const feed of callFeeds) { + this.pushLocalFeed(feed); + } + this.state = CallState.CreateAnswer; + let answer; + try { + this.getRidOfRTXCodecs(); + answer = await this.createAnswer(); + } catch (err) { + _logger.logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() failed to create answer: `, err); + this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true); + return; + } + try { + await this.peerConn.setLocalDescription(answer); + + // make sure we're still going + if (this.callHasEnded()) return; + this.state = CallState.Connecting; + + // Allow a short time for initial candidates to be gathered + await new Promise(resolve => { + setTimeout(resolve, 200); + }); + + // make sure the call hasn't ended before we continue + if (this.callHasEnded()) return; + this.sendAnswer(); + } catch (err) { + _logger.logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() error setting local description!`, err); + this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true); + return; + } + } + + /** + * Internal + */ + + async onRemoteIceCandidatesReceived(ev) { + if (this.callHasEnded()) { + //debuglog("Ignoring remote ICE candidate because call has ended"); + return; + } + const content = ev.getContent(); + const candidates = content.candidates; + if (!candidates) { + _logger.logger.info(`Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates event with no candidates!`); + return; + } + const fromPartyId = content.version === 0 ? null : content.party_id || null; + if (this.opponentPartyId === undefined) { + // we haven't picked an opponent yet so save the candidates + if (fromPartyId) { + _logger.logger.info(`Call ${this.callId} onRemoteIceCandidatesReceived() buffering ${candidates.length} candidates until we pick an opponent`); + const bufferedCandidates = this.remoteCandidateBuffer.get(fromPartyId) || []; + bufferedCandidates.push(...candidates); + this.remoteCandidateBuffer.set(fromPartyId, bufferedCandidates); + } + return; + } + if (!this.partyIdMatches(content)) { + _logger.logger.info(`Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates from party ID ${content.party_id}: we have chosen party ID ${this.opponentPartyId}`); + return; + } + await this.addIceCandidates(candidates); + } + + /** + * Used by MatrixClient. + */ + async onAnswerReceived(event) { + const content = event.getContent(); + _logger.logger.debug(`Call ${this.callId} onAnswerReceived() running (hangupParty=${content.party_id})`); + if (this.callHasEnded()) { + _logger.logger.debug(`Call ${this.callId} onAnswerReceived() ignoring answer because call has ended`); + return; + } + if (this.opponentPartyId !== undefined) { + _logger.logger.info(`Call ${this.callId} onAnswerReceived() ignoring answer from party ID ${content.party_id}: we already have an answer/reject from ${this.opponentPartyId}`); + return; + } + this.chooseOpponent(event); + await this.addBufferedIceCandidates(); + this.state = CallState.Connecting; + const sdpStreamMetadata = content[_callEventTypes.SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); + } else { + _logger.logger.warn(`Call ${this.callId} onAnswerReceived() did not get any SDPStreamMetadata! Can not send/receive multiple streams`); + } + try { + await this.peerConn.setRemoteDescription(content.answer); + } catch (e) { + _logger.logger.debug(`Call ${this.callId} onAnswerReceived() failed to set remote description`, e); + this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); + return; + } + + // If the answer we selected has a party_id, send a select_answer event + // We do this after setting the remote description since otherwise we'd block + // call setup on it + if (this.opponentPartyId !== null) { + try { + await this.sendVoipEvent(_event.EventType.CallSelectAnswer, { + selected_party_id: this.opponentPartyId + }); + } catch (err) { + // This isn't fatal, and will just mean that if another party has raced to answer + // the call, they won't know they got rejected, so we carry on & don't retry. + _logger.logger.warn(`Call ${this.callId} onAnswerReceived() failed to send select_answer event`, err); + } + } + } + async onSelectAnswerReceived(event) { + if (this.direction !== CallDirection.Inbound) { + _logger.logger.warn(`Call ${this.callId} onSelectAnswerReceived() got select_answer for an outbound call: ignoring`); + return; + } + const selectedPartyId = event.getContent().selected_party_id; + if (selectedPartyId === undefined || selectedPartyId === null) { + _logger.logger.warn(`Call ${this.callId} onSelectAnswerReceived() got nonsensical select_answer with null/undefined selected_party_id: ignoring`); + return; + } + if (selectedPartyId !== this.ourPartyId) { + _logger.logger.info(`Call ${this.callId} onSelectAnswerReceived() got select_answer for party ID ${selectedPartyId}: we are party ID ${this.ourPartyId}.`); + // The other party has picked somebody else's answer + await this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true); + } + } + async onNegotiateReceived(event) { + const content = event.getContent(); + const description = content.description; + if (!description || !description.sdp || !description.type) { + _logger.logger.info(`Call ${this.callId} onNegotiateReceived() ignoring invalid m.call.negotiate event`); + return; + } + // Politeness always follows the direction of the call: in a glare situation, + // we pick either the inbound or outbound call, so one side will always be + // inbound and one outbound + const polite = this.direction === CallDirection.Inbound; + + // Here we follow the perfect negotiation logic from + // https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation + const offerCollision = description.type === "offer" && (this.makingOffer || this.peerConn.signalingState !== "stable"); + this.ignoreOffer = !polite && offerCollision; + if (this.ignoreOffer) { + _logger.logger.info(`Call ${this.callId} onNegotiateReceived() ignoring colliding negotiate event because we're impolite`); + return; + } + const prevLocalOnHold = this.isLocalOnHold(); + const sdpStreamMetadata = content[_callEventTypes.SDPStreamMetadataKey]; + if (sdpStreamMetadata) { + this.updateRemoteSDPStreamMetadata(sdpStreamMetadata); + } else { + _logger.logger.warn(`Call ${this.callId} onNegotiateReceived() received negotiation event without SDPStreamMetadata!`); + } + try { + await this.peerConn.setRemoteDescription(description); + if (description.type === "offer") { + var _localDescription; + let answer; + try { + this.getRidOfRTXCodecs(); + answer = await this.createAnswer(); + } catch (err) { + _logger.logger.debug(`Call ${this.callId} onNegotiateReceived() failed to create answer: `, err); + this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true); + return; + } + await this.peerConn.setLocalDescription(answer); + this.sendVoipEvent(_event.EventType.CallNegotiate, { + description: (_localDescription = this.peerConn.localDescription) === null || _localDescription === void 0 ? void 0 : _localDescription.toJSON(), + [_callEventTypes.SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true) + }); + } + } catch (err) { + _logger.logger.warn(`Call ${this.callId} onNegotiateReceived() failed to complete negotiation`, err); + } + const newLocalOnHold = this.isLocalOnHold(); + if (prevLocalOnHold !== newLocalOnHold) { + this.emit(CallEvent.LocalHoldUnhold, newLocalOnHold, this); + // also this one for backwards compat + this.emit(CallEvent.HoldUnhold, newLocalOnHold); + } + } + updateRemoteSDPStreamMetadata(metadata) { + this.remoteSDPStreamMetadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true); + for (const feed of this.getRemoteFeeds()) { + var _streamId; + const streamId = feed.stream.id; + const metadata = this.remoteSDPStreamMetadata[streamId]; + feed.setAudioVideoMuted(metadata === null || metadata === void 0 ? void 0 : metadata.audio_muted, metadata === null || metadata === void 0 ? void 0 : metadata.video_muted); + feed.purpose = (_streamId = this.remoteSDPStreamMetadata[streamId]) === null || _streamId === void 0 ? void 0 : _streamId.purpose; + } + } + onSDPStreamMetadataChangedReceived(event) { + const content = event.getContent(); + const metadata = content[_callEventTypes.SDPStreamMetadataKey]; + this.updateRemoteSDPStreamMetadata(metadata); + } + async onAssertedIdentityReceived(event) { + const content = event.getContent(); + if (!content.asserted_identity) return; + this.remoteAssertedIdentity = { + id: content.asserted_identity.id, + displayName: content.asserted_identity.display_name + }; + this.emit(CallEvent.AssertedIdentityChanged, this); + } + callHasEnded() { + // This exists as workaround to typescript trying to be clever and erroring + // when putting if (this.state === CallState.Ended) return; twice in the same + // function, even though that function is async. + return this.state === CallState.Ended; + } + queueGotLocalOffer() { + // Ensure only one negotiate/answer event is being processed at a time. + if (this.responsePromiseChain) { + this.responsePromiseChain = this.responsePromiseChain.then(() => this.wrappedGotLocalOffer()); + } else { + this.responsePromiseChain = this.wrappedGotLocalOffer(); + } + } + async wrappedGotLocalOffer() { + this.makingOffer = true; + try { + // XXX: in what situations do we believe gotLocalOffer actually throws? It appears + // to handle most of its exceptions itself and terminate the call. I'm not entirely + // sure it would ever throw, so I can't add a test for these lines. + // Also the tense is different between "gotLocalOffer" and "getLocalOfferFailed" so + // it's not entirely clear whether getLocalOfferFailed is just misnamed or whether + // they've been cross-polinated somehow at some point. + await this.gotLocalOffer(); + } catch (e) { + this.getLocalOfferFailed(e); + return; + } finally { + this.makingOffer = false; + } + } + async gotLocalOffer() { + _logger.logger.debug(`Call ${this.callId} gotLocalOffer() running`); + if (this.callHasEnded()) { + _logger.logger.debug(`Call ${this.callId} gotLocalOffer() ignoring newly created offer because the call has ended"`); + return; + } + let offer; + try { + this.getRidOfRTXCodecs(); + offer = await this.createOffer(); + } catch (err) { + _logger.logger.debug(`Call ${this.callId} gotLocalOffer() failed to create offer: `, err); + this.terminate(CallParty.Local, CallErrorCode.CreateOffer, true); + return; + } + try { + await this.peerConn.setLocalDescription(offer); + } catch (err) { + _logger.logger.debug(`Call ${this.callId} gotLocalOffer() error setting local description!`, err); + this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true); + return; + } + if (this.peerConn.iceGatheringState === "gathering") { + // Allow a short time for initial candidates to be gathered + await new Promise(resolve => { + setTimeout(resolve, 200); + }); + } + if (this.callHasEnded()) return; + const eventType = this.state === CallState.CreateOffer ? _event.EventType.CallInvite : _event.EventType.CallNegotiate; + const content = { + lifetime: CALL_TIMEOUT_MS + }; + if (eventType === _event.EventType.CallInvite && this.invitee) { + content.invitee = this.invitee; + } + + // clunky because TypeScript can't follow the types through if we use an expression as the key + if (this.state === CallState.CreateOffer) { + var _localDescription2; + content.offer = (_localDescription2 = this.peerConn.localDescription) === null || _localDescription2 === void 0 ? void 0 : _localDescription2.toJSON(); + } else { + var _localDescription3; + content.description = (_localDescription3 = this.peerConn.localDescription) === null || _localDescription3 === void 0 ? void 0 : _localDescription3.toJSON(); + } + content.capabilities = { + "m.call.transferee": this.client.supportsCallTransfer, + "m.call.dtmf": false + }; + content[_callEventTypes.SDPStreamMetadataKey] = this.getLocalSDPStreamMetadata(true); + + // Get rid of any candidates waiting to be sent: they'll be included in the local + // description we just got and will send in the offer. + const discardCount = this.discardDuplicateCandidates(); + _logger.logger.info(`Call ${this.callId} gotLocalOffer() discarding ${discardCount} candidates that will be sent in offer`); + try { + await this.sendVoipEvent(eventType, content); + } catch (error) { + _logger.logger.error(`Call ${this.callId} gotLocalOffer() failed to send invite`, error); + if (error instanceof _httpApi.MatrixError && error.event) this.client.cancelPendingEvent(error.event); + let code = CallErrorCode.SignallingFailed; + let message = "Signalling failed"; + if (this.state === CallState.CreateOffer) { + code = CallErrorCode.SendInvite; + message = "Failed to send invite"; + } + if (error.name == "UnknownDeviceError") { + code = CallErrorCode.UnknownDevices; + message = "Unknown devices present in the room"; + } + this.emit(CallEvent.Error, new CallError(code, message, error), this); + this.terminate(CallParty.Local, code, false); + + // no need to carry on & send the candidate queue, but we also + // don't want to rethrow the error + return; + } + this.sendCandidateQueue(); + if (this.state === CallState.CreateOffer) { + this.inviteOrAnswerSent = true; + this.state = CallState.InviteSent; + this.inviteTimeout = setTimeout(() => { + this.inviteTimeout = undefined; + if (this.state === CallState.InviteSent) { + this.hangup(CallErrorCode.InviteTimeout, false); + } + }, CALL_TIMEOUT_MS); + } + } + /** + * This method removes all video/rtx codecs from screensharing video + * transceivers. This is necessary since they can cause problems. Without + * this the following steps should produce an error: + * Chromium calls Firefox + * Firefox answers + * Firefox starts screen-sharing + * Chromium starts screen-sharing + * Call crashes for Chromium with: + * [96685:23:0518/162603.933321:ERROR:webrtc_video_engine.cc(3296)] RTX codec (PT=97) mapped to PT=96 which is not in the codec list. + * [96685:23:0518/162603.933377:ERROR:webrtc_video_engine.cc(1171)] GetChangedRecvParameters called without any video codecs. + * [96685:23:0518/162603.933430:ERROR:sdp_offer_answer.cc(4302)] Failed to set local video description recv parameters for m-section with mid='2'. (INVALID_PARAMETER) + */ + getRidOfRTXCodecs() { + // RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF + if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) return; + const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs; + const sendCodecs = RTCRtpSender.getCapabilities("video").codecs; + const codecs = [...sendCodecs, ...recvCodecs]; + for (const codec of codecs) { + if (codec.mimeType === "video/rtx") { + const rtxCodecIndex = codecs.indexOf(codec); + codecs.splice(rtxCodecIndex, 1); + } + } + const screenshareVideoTransceiver = this.transceivers.get(getTransceiverKey(_callEventTypes.SDPStreamMetadataPurpose.Screenshare, "video")); + if (screenshareVideoTransceiver) screenshareVideoTransceiver.setCodecPreferences(codecs); + } + /** + * @internal + */ + async sendVoipEvent(eventType, content) { + const realContent = Object.assign({}, content, { + version: VOIP_PROTO_VERSION, + call_id: this.callId, + party_id: this.ourPartyId, + conf_id: this.groupCallId + }); + if (this.opponentDeviceId) { + var _this$getOpponentMemb2; + const toDeviceSeq = this.toDeviceSeq++; + const content = _objectSpread(_objectSpread({}, realContent), {}, { + device_id: this.client.deviceId, + sender_session_id: this.client.getSessionId(), + dest_session_id: this.opponentSessionId, + seq: toDeviceSeq, + [_event.ToDeviceMessageId]: (0, _uuid.v4)() + }); + this.emit(CallEvent.SendVoipEvent, { + type: "toDevice", + eventType, + userId: this.invitee || ((_this$getOpponentMemb2 = this.getOpponentMember()) === null || _this$getOpponentMemb2 === void 0 ? void 0 : _this$getOpponentMemb2.userId), + opponentDeviceId: this.opponentDeviceId, + content + }, this); + const userId = this.invitee || this.getOpponentMember().userId; + if (this.client.getUseE2eForGroupCall()) { + if (!this.opponentDeviceInfo) { + _logger.logger.warn(`Call ${this.callId} sendVoipEvent() failed: we do not have opponentDeviceInfo`); + return; + } + await this.client.encryptAndSendToDevices([{ + userId, + deviceInfo: this.opponentDeviceInfo + }], { + type: eventType, + content + }); + } else { + await this.client.sendToDevice(eventType, new Map([[userId, new Map([[this.opponentDeviceId, content]])]])); + } + } else { + var _this$getOpponentMemb3; + this.emit(CallEvent.SendVoipEvent, { + type: "sendEvent", + eventType, + roomId: this.roomId, + content: realContent, + userId: this.invitee || ((_this$getOpponentMemb3 = this.getOpponentMember()) === null || _this$getOpponentMemb3 === void 0 ? void 0 : _this$getOpponentMemb3.userId) + }, this); + await this.client.sendEvent(this.roomId, eventType, realContent); + } + } + + /** + * Queue a candidate to be sent + * @param content - The candidate to queue up, or null if candidates have finished being generated + * and end-of-candidates should be signalled + */ + queueCandidate(content) { + // We partially de-trickle candidates by waiting for `delay` before sending them + // amalgamated, in order to avoid sending too many m.call.candidates events and hitting + // rate limits in Matrix. + // In practice, it'd be better to remove rate limits for m.call.* + + // N.B. this deliberately lets you queue and send blank candidates, which MSC2746 + // currently proposes as the way to indicate that candidate gathering is complete. + // This will hopefully be changed to an explicit rather than implicit notification + // shortly. + if (content) { + this.candidateSendQueue.push(content); + } else { + this.candidatesEnded = true; + } + + // Don't send the ICE candidates yet if the call is in the ringing state: this + // means we tried to pick (ie. started generating candidates) and then failed to + // send the answer and went back to the ringing state. Queue up the candidates + // to send if we successfully send the answer. + // Equally don't send if we haven't yet sent the answer because we can send the + // first batch of candidates along with the answer + if (this.state === CallState.Ringing || !this.inviteOrAnswerSent) return; + + // MSC2746 recommends these values (can be quite long when calling because the + // callee will need a while to answer the call) + const delay = this.direction === CallDirection.Inbound ? 500 : 2000; + if (this.candidateSendTries === 0) { + setTimeout(() => { + this.sendCandidateQueue(); + }, delay); + } + } + + // Discard all non-end-of-candidates messages + // Return the number of candidate messages that were discarded. + // Call this method before sending an invite or answer message + discardDuplicateCandidates() { + let discardCount = 0; + const newQueue = []; + for (let i = 0; i < this.candidateSendQueue.length; i++) { + const candidate = this.candidateSendQueue[i]; + if (candidate.candidate === "") { + newQueue.push(candidate); + } else { + discardCount++; + } + } + this.candidateSendQueue = newQueue; + return discardCount; + } + + /* + * Transfers this call to another user + */ + async transfer(targetUserId) { + // Fetch the target user's global profile info: their room avatar / displayname + // could be different in whatever room we share with them. + const profileInfo = await this.client.getProfileInfo(targetUserId); + const replacementId = genCallID(); + const body = { + replacement_id: genCallID(), + target_user: { + id: targetUserId, + display_name: profileInfo.displayname, + avatar_url: profileInfo.avatar_url + }, + create_call: replacementId + }; + await this.sendVoipEvent(_event.EventType.CallReplaces, body); + await this.terminate(CallParty.Local, CallErrorCode.Transferred, true); + } + + /* + * Transfers this call to the target call, effectively 'joining' the + * two calls (so the remote parties on each call are connected together). + */ + async transferToCall(transferTargetCall) { + var _transferTargetCall$g, _this$getOpponentMemb4; + const targetUserId = (_transferTargetCall$g = transferTargetCall.getOpponentMember()) === null || _transferTargetCall$g === void 0 ? void 0 : _transferTargetCall$g.userId; + const targetProfileInfo = targetUserId ? await this.client.getProfileInfo(targetUserId) : undefined; + const opponentUserId = (_this$getOpponentMemb4 = this.getOpponentMember()) === null || _this$getOpponentMemb4 === void 0 ? void 0 : _this$getOpponentMemb4.userId; + const transfereeProfileInfo = opponentUserId ? await this.client.getProfileInfo(opponentUserId) : undefined; + const newCallId = genCallID(); + const bodyToTransferTarget = { + // the replacements on each side have their own ID, and it's distinct from the + // ID of the new call (but we can use the same function to generate it) + replacement_id: genCallID(), + target_user: { + id: opponentUserId, + display_name: transfereeProfileInfo === null || transfereeProfileInfo === void 0 ? void 0 : transfereeProfileInfo.displayname, + avatar_url: transfereeProfileInfo === null || transfereeProfileInfo === void 0 ? void 0 : transfereeProfileInfo.avatar_url + }, + await_call: newCallId + }; + await transferTargetCall.sendVoipEvent(_event.EventType.CallReplaces, bodyToTransferTarget); + const bodyToTransferee = { + replacement_id: genCallID(), + target_user: { + id: targetUserId, + display_name: targetProfileInfo === null || targetProfileInfo === void 0 ? void 0 : targetProfileInfo.displayname, + avatar_url: targetProfileInfo === null || targetProfileInfo === void 0 ? void 0 : targetProfileInfo.avatar_url + }, + create_call: newCallId + }; + await this.sendVoipEvent(_event.EventType.CallReplaces, bodyToTransferee); + await this.terminate(CallParty.Local, CallErrorCode.Transferred, true); + await transferTargetCall.terminate(CallParty.Local, CallErrorCode.Transferred, true); + } + async terminate(hangupParty, hangupReason, shouldEmit) { + var _this$stats2; + if (this.callHasEnded()) return; + this.hangupParty = hangupParty; + this.hangupReason = hangupReason; + this.state = CallState.Ended; + if (this.inviteTimeout) { + clearTimeout(this.inviteTimeout); + this.inviteTimeout = undefined; + } + if (this.iceDisconnectedTimeout !== undefined) { + clearTimeout(this.iceDisconnectedTimeout); + this.iceDisconnectedTimeout = undefined; + } + if (this.callLengthInterval) { + clearInterval(this.callLengthInterval); + this.callLengthInterval = undefined; + } + if (this.stopVideoTrackTimer !== undefined) { + clearTimeout(this.stopVideoTrackTimer); + this.stopVideoTrackTimer = undefined; + } + for (const [stream, listener] of this.removeTrackListeners) { + stream.removeEventListener("removetrack", listener); + } + this.removeTrackListeners.clear(); + this.callStatsAtEnd = await this.collectCallStats(); + + // Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds() + this.stopAllMedia(); + this.deleteAllFeeds(); + if (this.peerConn && this.peerConn.signalingState !== "closed") { + this.peerConn.close(); + } + (_this$stats2 = this.stats) === null || _this$stats2 === void 0 ? void 0 : _this$stats2.removeStatsReportGatherer(this.callId); + if (shouldEmit) { + this.emit(CallEvent.Hangup, this); + } + this.client.callEventHandler.calls.delete(this.callId); + } + stopAllMedia() { + _logger.logger.debug(`Call ${this.callId} stopAllMedia() running`); + for (const feed of this.feeds) { + // Slightly awkward as local feed need to go via the correct method on + // the MediaHandler so they get removed from MediaHandler (remote tracks + // don't) + // NB. We clone local streams when passing them to individual calls in a group + // call, so we can (and should) stop the clones once we no longer need them: + // the other clones will continue fine. + if (feed.isLocal() && feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Usermedia) { + this.client.getMediaHandler().stopUserMediaStream(feed.stream); + } else if (feed.isLocal() && feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare) { + this.client.getMediaHandler().stopScreensharingStream(feed.stream); + } else if (!feed.isLocal()) { + _logger.logger.debug(`Call ${this.callId} stopAllMedia() stopping stream (streamId=${feed.stream.id})`); + for (const track of feed.stream.getTracks()) { + track.stop(); + } + } + } + } + checkForErrorListener() { + if (this.listeners(_typedEventEmitter.EventEmitterEvents.Error).length === 0) { + throw new Error("You MUST attach an error listener using call.on('error', function() {})"); + } + } + async sendCandidateQueue() { + if (this.candidateSendQueue.length === 0 || this.callHasEnded()) { + return; + } + const candidates = this.candidateSendQueue; + this.candidateSendQueue = []; + ++this.candidateSendTries; + const content = { + candidates: candidates.map(candidate => candidate.toJSON()) + }; + if (this.candidatesEnded) { + // If there are no more candidates, signal this by adding an empty string candidate + content.candidates.push({ + candidate: "" + }); + } + _logger.logger.debug(`Call ${this.callId} sendCandidateQueue() attempting to send ${candidates.length} candidates`); + try { + await this.sendVoipEvent(_event.EventType.CallCandidates, content); + // reset our retry count if we have successfully sent our candidates + // otherwise queueCandidate() will refuse to try to flush the queue + this.candidateSendTries = 0; + + // Try to send candidates again just in case we received more candidates while sending. + this.sendCandidateQueue(); + } catch (error) { + // don't retry this event: we'll send another one later as we might + // have more candidates by then. + if (error instanceof _httpApi.MatrixError && error.event) this.client.cancelPendingEvent(error.event); + + // put all the candidates we failed to send back in the queue + this.candidateSendQueue.push(...candidates); + if (this.candidateSendTries > 5) { + _logger.logger.debug(`Call ${this.callId} sendCandidateQueue() failed to send candidates on attempt ${this.candidateSendTries}. Giving up on this call.`, error); + const code = CallErrorCode.SignallingFailed; + const message = "Signalling failed"; + this.emit(CallEvent.Error, new CallError(code, message, error), this); + this.hangup(code, false); + return; + } + const delayMs = 500 * Math.pow(2, this.candidateSendTries); + ++this.candidateSendTries; + _logger.logger.debug(`Call ${this.callId} sendCandidateQueue() failed to send candidates. Retrying in ${delayMs}ms`, error); + setTimeout(() => { + this.sendCandidateQueue(); + }, delayMs); + } + } + + /** + * Place a call to this room. + * @throws if you have not specified a listener for 'error' events. + * @throws if have passed audio=false. + */ + async placeCall(audio, video) { + if (!audio) { + throw new Error("You CANNOT start a call without audio"); + } + this.state = CallState.WaitLocalMedia; + try { + var _this$client$getDevic2; + const stream = await this.client.getMediaHandler().getUserMediaStream(audio, video); + + // make sure all the tracks are enabled (same as pushNewLocalFeed - + // we probably ought to just have one code path for adding streams) + setTracksEnabled(stream.getAudioTracks(), true); + setTracksEnabled(stream.getVideoTracks(), true); + const callFeed = new _callFeed.CallFeed({ + client: this.client, + roomId: this.roomId, + userId: this.client.getUserId(), + deviceId: (_this$client$getDevic2 = this.client.getDeviceId()) !== null && _this$client$getDevic2 !== void 0 ? _this$client$getDevic2 : undefined, + stream, + purpose: _callEventTypes.SDPStreamMetadataPurpose.Usermedia, + audioMuted: false, + videoMuted: false + }); + await this.placeCallWithCallFeeds([callFeed]); + } catch (e) { + this.getUserMediaFailed(e); + return; + } + } + + /** + * Place a call to this room with call feed. + * @param callFeeds - to use + * @throws if you have not specified a listener for 'error' events. + * @throws if have passed audio=false. + */ + async placeCallWithCallFeeds(callFeeds, requestScreenshareFeed = false) { + this.checkForErrorListener(); + this.direction = CallDirection.Outbound; + await this.initOpponentCrypto(); + + // XXX Find a better way to do this + this.client.callEventHandler.calls.set(this.callId, this); + + // make sure we have valid turn creds. Unless something's gone wrong, it should + // poll and keep the credentials valid so this should be instant. + const haveTurnCreds = await this.client.checkTurnServers(); + if (!haveTurnCreds) { + _logger.logger.warn(`Call ${this.callId} placeCallWithCallFeeds() failed to get TURN credentials! Proceeding with call anyway...`); + } + + // create the peer connection now so it can be gathering candidates while we get user + // media (assuming a candidate pool size is configured) + this.peerConn = this.createPeerConnection(); + this.gotCallFeedsForInvite(callFeeds, requestScreenshareFeed); + } + createPeerConnection() { + var _this$stats3; + const pc = new window.RTCPeerConnection({ + iceTransportPolicy: this.forceTURN ? "relay" : undefined, + iceServers: this.turnServers, + iceCandidatePoolSize: this.client.iceCandidatePoolSize, + bundlePolicy: "max-bundle" + }); + + // 'connectionstatechange' would be better, but firefox doesn't implement that. + pc.addEventListener("iceconnectionstatechange", this.onIceConnectionStateChanged); + pc.addEventListener("signalingstatechange", this.onSignallingStateChanged); + pc.addEventListener("icecandidate", this.gotLocalIceCandidate); + pc.addEventListener("icegatheringstatechange", this.onIceGatheringStateChange); + pc.addEventListener("track", this.onTrack); + pc.addEventListener("negotiationneeded", this.onNegotiationNeeded); + pc.addEventListener("datachannel", this.onDataChannel); + (_this$stats3 = this.stats) === null || _this$stats3 === void 0 ? void 0 : _this$stats3.addStatsReportGatherer(this.callId, "unknown", pc); + return pc; + } + partyIdMatches(msg) { + // They must either match or both be absent (in which case opponentPartyId will be null) + // Also we ignore party IDs on the invite/offer if the version is 0, so we must do the same + // here and use null if the version is 0 (woe betide any opponent sending messages in the + // same call with different versions) + const msgPartyId = msg.version === 0 ? null : msg.party_id || null; + return msgPartyId === this.opponentPartyId; + } + + // Commits to an opponent for the call + // ev: An invite or answer event + chooseOpponent(ev) { + var _getMember; + // I choo-choo-choose you + const msg = ev.getContent(); + _logger.logger.debug(`Call ${this.callId} chooseOpponent() running (partyId=${msg.party_id})`); + this.opponentVersion = msg.version; + if (this.opponentVersion === 0) { + // set to null to indicate that we've chosen an opponent, but because + // they're v0 they have no party ID (even if they sent one, we're ignoring it) + this.opponentPartyId = null; + } else { + // set to their party ID, or if they're naughty and didn't send one despite + // not being v0, set it to null to indicate we picked an opponent with no + // party ID + this.opponentPartyId = msg.party_id || null; + } + this.opponentCaps = msg.capabilities || {}; + this.opponentMember = (_getMember = this.client.getRoom(this.roomId).getMember(ev.getSender())) !== null && _getMember !== void 0 ? _getMember : undefined; + } + async addBufferedIceCandidates() { + const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId); + if (bufferedCandidates) { + _logger.logger.info(`Call ${this.callId} addBufferedIceCandidates() adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`); + await this.addIceCandidates(bufferedCandidates); + } + this.remoteCandidateBuffer.clear(); + } + async addIceCandidates(candidates) { + for (const candidate of candidates) { + if ((candidate.sdpMid === null || candidate.sdpMid === undefined) && (candidate.sdpMLineIndex === null || candidate.sdpMLineIndex === undefined)) { + _logger.logger.debug(`Call ${this.callId} addIceCandidates() got remote ICE end-of-candidates`); + } else { + _logger.logger.debug(`Call ${this.callId} addIceCandidates() got remote ICE candidate (sdpMid=${candidate.sdpMid}, candidate=${candidate.candidate})`); + } + try { + await this.peerConn.addIceCandidate(candidate); + } catch (err) { + if (!this.ignoreOffer) { + _logger.logger.info(`Call ${this.callId} addIceCandidates() failed to add remote ICE candidate`, err); + } + } + } + } + get hasPeerConnection() { + return Boolean(this.peerConn); + } + initStats(stats, peerId = "unknown") { + this.stats = stats; + this.stats.start(); + } +} +exports.MatrixCall = MatrixCall; +function setTracksEnabled(tracks, enabled) { + for (const track of tracks) { + track.enabled = enabled; + } +} +function supportsMatrixCall() { + // typeof prevents Node from erroring on an undefined reference + if (typeof window === "undefined" || typeof document === "undefined") { + // NB. We don't log here as apps try to create a call object as a test for + // whether calls are supported, so we shouldn't fill the logs up. + return false; + } + + // Firefox throws on so little as accessing the RTCPeerConnection when operating in a secure mode. + // There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616 though the concern + // is that the browser throwing a SecurityError will brick the client creation process. + try { + const supported = Boolean(window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate || navigator.mediaDevices); + if (!supported) { + /* istanbul ignore if */ // Adds a lot of noise to test runs, so disable logging there. + if (process.env.NODE_ENV !== "test") { + _logger.logger.error("WebRTC is not supported in this browser / environment"); + } + return false; + } + } catch (e) { + _logger.logger.error("Exception thrown when trying to access WebRTC", e); + return false; + } + return true; +} + +/** + * DEPRECATED + * Use client.createCall() + * + * Create a new Matrix call for the browser. + * @param client - The client instance to use. + * @param roomId - The room the call is in. + * @param options - DEPRECATED optional options map. + * @returns the call or null if the browser doesn't support calling. + */ +function createNewMatrixCall(client, roomId, options) { + if (!supportsMatrixCall()) return null; + const optionsForceTURN = options ? options.forceTURN : false; + const opts = { + client: client, + roomId: roomId, + invitee: options === null || options === void 0 ? void 0 : options.invitee, + turnServers: client.getTurnServers(), + // call level options + forceTURN: client.forceTURN || optionsForceTURN, + opponentDeviceId: options === null || options === void 0 ? void 0 : options.opponentDeviceId, + opponentSessionId: options === null || options === void 0 ? void 0 : options.opponentSessionId, + groupCallId: options === null || options === void 0 ? void 0 : options.groupCallId + }; + const call = new MatrixCall(opts); + client.reEmitter.reEmit(call, Object.values(CallEvent)); + return call; +} +//# sourceMappingURL=call.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.js.map new file mode 100644 index 0000000..955170b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/call.js.map @@ -0,0 +1 @@ +{"version":3,"file":"call.js","names":["_uuid","require","_sdpTransform","_logger","utils","_interopRequireWildcard","_event","_randomstring","_callEventTypes","_callFeed","_typedEventEmitter","_deviceinfo","_groupCall","_httpApi","_getRequireWildcardCache","nodeInterop","WeakMap","cacheBabelInterop","cacheNodeInterop","obj","__esModule","default","cache","has","get","newObj","hasPropertyDescriptor","Object","defineProperty","getOwnPropertyDescriptor","key","prototype","hasOwnProperty","call","desc","set","ownKeys","object","enumerableOnly","keys","getOwnPropertySymbols","symbols","filter","sym","enumerable","push","apply","_objectSpread","target","i","arguments","length","source","forEach","_defineProperty2","getOwnPropertyDescriptors","defineProperties","MediaType","CodecName","CallState","exports","CallType","CallDirection","CallParty","CallEvent","CallErrorCode","VOIP_PROTO_VERSION","FALLBACK_ICE_SERVER","CALL_TIMEOUT_MS","CALL_LENGTH_INTERVAL","ICE_DISCONNECTED_TIMEOUT","CallError","Error","constructor","code","msg","err","genCallID","Date","now","toString","randomString","getCodecParamMods","isPtt","mods","mediaType","codec","enableDtx","maxAverageBitrate","undefined","getTransceiverKey","purpose","kind","MatrixCall","TypedEventEmitter","opts","_opts$forceTURN","Fledgling","Map","event","candidate","candidatesEnded","logger","warn","callId","debug","sdpMid","callHasEnded","queueCandidate","_this$peerConn","peerConn","iceGatheringState","error","emit","LocalOfferFailed","terminate","Local","successor","getUserMediaFailed","NoUserMedia","_this$peerConn2","_this$peerConn$iceCon","_this$peerConn3","_this$peerConn4","_this$peerConn6","iceConnectionState","includes","clearTimeout","iceDisconnectedTimeout","state","Connected","callLengthInterval","callStartTime","setInterval","LengthChanged","Math","round","_this$peerConn5","restartIce","info","hangup","IceFailed","setTimeout","Connecting","feed","getRemoteFeeds","setAudioVideoMuted","_this$peerConn7","signalingState","ev","streams","track","stream","pushRemoteFeed","removeTrackListeners","onRemoveTrack","getTracks","id","deleteFeedByStream","removeEventListener","delete","addEventListener","DataChannel","channel","CreateOffer","opponentVersion","queueGotLocalOffer","partyIdMatches","Ringing","Remote","reason","UserHangup","party_id","opponentPartyId","shouldTerminate","InviteSent","direction","Inbound","AnsweredElsewhere","roomId","invitee","client","deviceId","forceTURN","ourPartyId","opponentDeviceId","opponentSessionId","groupCallId","turnServers","isFallbackICEServerAllowed","urls","server","checkObjectHasKeys","isOnlyDataChannelAllowed","isVoipWithNoMediaAllowed","placeVoiceCall","placeCall","placeVideoCall","createDataChannel","label","options","dataChannel","getOpponentMember","opponentMember","getOpponentDeviceId","getOpponentSessionId","opponentCanBeTransferred","Boolean","opponentCaps","opponentSupportsDTMF","getRemoteAssertedIdentity","remoteAssertedIdentity","_state","oldState","State","type","hasUserMediaVideoSender","hasRemoteUserMediaVideoTrack","Video","Voice","hasLocalUserMediaVideoTrack","_this$localUsermediaS","localUsermediaStream","getVideoTracks","some","_feed$stream","SDPStreamMetadataPurpose","Usermedia","hasLocalUserMediaAudioTrack","_this$localUsermediaS2","getAudioTracks","hasRemoteUserMediaAudioTrack","_feed$stream2","hasUserMediaAudioSender","_this$transceivers$ge","transceivers","sender","_this$transceivers$ge2","localUsermediaFeed","getLocalFeeds","find","localScreensharingFeed","Screenshare","_this$localUsermediaF","localScreensharingStream","_this$localScreenshar","remoteUsermediaFeed","remoteScreensharingFeed","remoteUsermediaStream","_this$remoteUsermedia","remoteScreensharingStream","_this$remoteScreensha","getFeedByStreamId","streamId","getFeeds","feeds","isLocal","initOpponentCrypto","_this$getOpponentMemb","_deviceInfoMap$get","getUseE2eForGroupCall","isCryptoEnabled","opponentDeviceInfo","DeviceInfo","crypto","userId","deviceInfoMap","deviceList","downloadKeys","GroupCallUnknownDeviceError","getLocalSDPStreamMetadata","updateStreamIds","metadata","localFeed","sdpMetadataStreamId","audio_muted","isAudioMuted","video_muted","isVideoMuted","noIncomingFeeds","opponentSupportsSDPStreamMetadata","pushRemoteFeedWithoutMetadata","remoteSDPStreamMetadata","audioMuted","videoMuted","CallFeed","FeedsChanged","active","_this$feeds$find","oldRemoteStream","pushNewLocalFeed","addToPeerConnection","getUserId","setTracksEnabled","pushLocalFeed","callFeed","enabled","tKey","transceiver","replaceTrack","newSender","addTrack","newTransceiver","getTransceivers","t","removeLocalFeed","audioTransceiverKey","videoTransceiverKey","transceiverKey","removeTrack","getMediaHandler","stopScreensharingStream","deleteFeed","deleteAllFeeds","dispose","splice","indexOf","getCurrentCallStats","callStatsAtEnd","collectCallStats","statsReport","getStats","stats","item","initWithInvite","_this$feeds$find2","invite","getContent","haveTurnCreds","checkTurnServers","sdpStreamMetadata","SDPStreamMetadataKey","updateRemoteSDPStreamMetadata","createPeerConnection","chooseOpponent","setRemoteDescription","offer","addBufferedIceCandidates","e","SetRemoteDescription","remoteStream","getLocalAge","ringingTimer","_this$stats","hangupParty","Ended","stopAllMedia","close","removeStatsReportGatherer","Hangup","lifetime","onState","off","on","initWithHangup","shouldAnswerWithMediaType","wantedValue","valueOfTheOtherSide","isNullOrUndefined","answer","audio","video","inviteOrAnswerSent","waitForLocalAVStream","prevState","answerWithAudio","answerWithVideo","WaitLocalMedia","_this$client$getDevic","getUserMediaStream","usermediaFeed","getDeviceId","answerWithCallFeeds","callFeeds","queueGotCallFeedsForAnswer","replacedBy","newCall","Outbound","map","clone","Replaced","suppressEvent","content","sendVoipEvent","EventType","CallHangup","reject","CallReject","upgradeCall","getAudio","getVideo","updateLocalUsermediaStream","isScreensharing","setScreensharingEnabled","setScreensharingEnabledWithoutMetadataSupport","getScreensharingStream","audioTransceiver","videoTransceiver","_this$transceivers$ge3","_this$localUsermediaS3","_this$transceivers$ge4","forceAudio","forceVideo","audioEnabled","remoteOnHold","videoEnabled","log","stop","oldSender","added","setLocalVideoMuted","muted","_this$localUsermediaF3","stopVideoTrackTimer","hasVideoDevice","isLocalVideoMuted","_this$localUsermediaF2","updateMuteStatus","sendMetadataUpdate","_this$localUsermediaF4","_this$localUsermediaF5","setMicrophoneMuted","_this$localUsermediaF6","hasAudioDevice","isMicrophoneMuted","_this$localUsermediaF7","_this$localUsermediaF8","isRemoteOnHold","setRemoteOnHold","onHold","RemoteHoldUnhold","isLocalOnHold","callOnHold","trackOnHold","currentDirection","sendDtmfDigit","digit","getSenders","_sender$track","dtmf","insertDTMF","micShouldBeMuted","vidShouldBeMuted","CallSDPStreamMetadataChangedPrefix","gotCallFeedsForInvite","requestScreenshareFeed","addTransceiver","sendAnswer","answerContent","sdp","localDescription","capabilities","supportsCallTransfer","discardCount","discardDuplicateCandidates","CallAnswer","MatrixError","cancelPendingEvent","SendAnswer","message","name","UnknownDevices","sendCandidateQueue","responsePromiseChain","then","gotCallFeedsForAnswer","mungeSdp","description","parseSdp","media","payloadTypeToCodecMap","codecToPayloadTypeMap","rtp","payload","mod","extraConfig","found","fmtp","config","join","writeSdp","createOffer","createAnswer","CreateAnswer","getRidOfRTXCodecs","setLocalDescription","Promise","resolve","SetLocalDescription","onRemoteIceCandidatesReceived","candidates","fromPartyId","version","bufferedCandidates","remoteCandidateBuffer","addIceCandidates","onAnswerReceived","CallSelectAnswer","selected_party_id","onSelectAnswerReceived","selectedPartyId","onNegotiateReceived","polite","offerCollision","makingOffer","ignoreOffer","prevLocalOnHold","_localDescription","CallNegotiate","toJSON","newLocalOnHold","LocalHoldUnhold","HoldUnhold","recursivelyAssign","_streamId","onSDPStreamMetadataChangedReceived","onAssertedIdentityReceived","asserted_identity","displayName","display_name","AssertedIdentityChanged","wrappedGotLocalOffer","gotLocalOffer","getLocalOfferFailed","eventType","CallInvite","_localDescription2","_localDescription3","SignallingFailed","SendInvite","inviteTimeout","InviteTimeout","RTCRtpReceiver","getCapabilities","RTCRtpSender","recvCodecs","codecs","sendCodecs","mimeType","rtxCodecIndex","screenshareVideoTransceiver","setCodecPreferences","realContent","assign","call_id","conf_id","_this$getOpponentMemb2","toDeviceSeq","device_id","sender_session_id","getSessionId","dest_session_id","seq","ToDeviceMessageId","uuidv4","SendVoipEvent","encryptAndSendToDevices","deviceInfo","sendToDevice","_this$getOpponentMemb3","sendEvent","candidateSendQueue","delay","candidateSendTries","newQueue","transfer","targetUserId","profileInfo","getProfileInfo","replacementId","body","replacement_id","target_user","displayname","avatar_url","create_call","CallReplaces","Transferred","transferToCall","transferTargetCall","_transferTargetCall$g","_this$getOpponentMemb4","targetProfileInfo","opponentUserId","transfereeProfileInfo","newCallId","bodyToTransferTarget","await_call","bodyToTransferee","hangupReason","shouldEmit","_this$stats2","clearInterval","listener","clear","callEventHandler","calls","stopUserMediaStream","checkForErrorListener","listeners","EventEmitterEvents","CallCandidates","delayMs","pow","_this$client$getDevic2","placeCallWithCallFeeds","_this$stats3","pc","window","RTCPeerConnection","iceTransportPolicy","iceServers","iceCandidatePoolSize","bundlePolicy","onIceConnectionStateChanged","onSignallingStateChanged","gotLocalIceCandidate","onIceGatheringStateChange","onTrack","onNegotiationNeeded","onDataChannel","addStatsReportGatherer","msgPartyId","_getMember","getRoom","getMember","getSender","sdpMLineIndex","addIceCandidate","hasPeerConnection","initStats","peerId","start","tracks","supportsMatrixCall","document","supported","RTCSessionDescription","RTCIceCandidate","navigator","mediaDevices","process","env","NODE_ENV","createNewMatrixCall","optionsForceTURN","getTurnServers","reEmitter","reEmit","values"],"sources":["../../src/webrtc/call.ts"],"sourcesContent":["/*\nCopyright 2015, 2016 OpenMarket Ltd\nCopyright 2017 New Vector Ltd\nCopyright 2019, 2020 The Matrix.org Foundation C.I.C.\nCopyright 2021 - 2022 Å imon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module. See {@link createNewMatrixCall} for the public API.\n */\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { parse as parseSdp, write as writeSdp } from \"sdp-transform\";\n\nimport { logger } from \"../logger\";\nimport * as utils from \"../utils\";\nimport { IContent, MatrixEvent } from \"../models/event\";\nimport { EventType, ToDeviceMessageId } from \"../@types/event\";\nimport { RoomMember } from \"../models/room-member\";\nimport { randomString } from \"../randomstring\";\nimport {\n MCallReplacesEvent,\n MCallAnswer,\n MCallInviteNegotiate,\n CallCapabilities,\n SDPStreamMetadataPurpose,\n SDPStreamMetadata,\n SDPStreamMetadataKey,\n MCallSDPStreamMetadataChanged,\n MCallSelectAnswer,\n MCAllAssertedIdentity,\n MCallCandidates,\n MCallBase,\n MCallHangupReject,\n} from \"./callEventTypes\";\nimport { CallFeed } from \"./callFeed\";\nimport { MatrixClient } from \"../client\";\nimport { EventEmitterEvents, TypedEventEmitter } from \"../models/typed-event-emitter\";\nimport { DeviceInfo } from \"../crypto/deviceinfo\";\nimport { GroupCallUnknownDeviceError } from \"./groupCall\";\nimport { IScreensharingOpts } from \"./mediaHandler\";\nimport { MatrixError } from \"../http-api\";\nimport { GroupCallStats } from \"./stats/groupCallStats\";\n\ninterface CallOpts {\n // The room ID for this call.\n roomId: string;\n invitee?: string;\n // The Matrix Client instance to send events to.\n client: MatrixClient;\n /**\n * Whether relay through TURN should be forced.\n * @deprecated use opts.forceTURN when creating the matrix client\n * since it's only possible to set this option on outbound calls.\n */\n forceTURN?: boolean;\n // A list of TURN servers.\n turnServers?: Array<TurnServer>;\n opponentDeviceId?: string;\n opponentSessionId?: string;\n groupCallId?: string;\n}\n\ninterface TurnServer {\n urls: Array<string>;\n username?: string;\n password?: string;\n ttl?: number;\n}\n\ninterface AssertedIdentity {\n id: string;\n displayName: string;\n}\n\nenum MediaType {\n AUDIO = \"audio\",\n VIDEO = \"video\",\n}\n\nenum CodecName {\n OPUS = \"opus\",\n // add more as needed\n}\n\n// Used internally to specify modifications to codec parameters in SDP\ninterface CodecParamsMod {\n mediaType: MediaType;\n codec: CodecName;\n enableDtx?: boolean; // true to enable discontinuous transmission, false to disable, undefined to leave as-is\n maxAverageBitrate?: number; // sets the max average bitrate, or undefined to leave as-is\n}\n\nexport enum CallState {\n Fledgling = \"fledgling\",\n InviteSent = \"invite_sent\",\n WaitLocalMedia = \"wait_local_media\",\n CreateOffer = \"create_offer\",\n CreateAnswer = \"create_answer\",\n Connecting = \"connecting\",\n Connected = \"connected\",\n Ringing = \"ringing\",\n Ended = \"ended\",\n}\n\nexport enum CallType {\n Voice = \"voice\",\n Video = \"video\",\n}\n\nexport enum CallDirection {\n Inbound = \"inbound\",\n Outbound = \"outbound\",\n}\n\nexport enum CallParty {\n Local = \"local\",\n Remote = \"remote\",\n}\n\nexport enum CallEvent {\n Hangup = \"hangup\",\n State = \"state\",\n Error = \"error\",\n Replaced = \"replaced\",\n\n // The value of isLocalOnHold() has changed\n LocalHoldUnhold = \"local_hold_unhold\",\n // The value of isRemoteOnHold() has changed\n RemoteHoldUnhold = \"remote_hold_unhold\",\n // backwards compat alias for LocalHoldUnhold: remove in a major version bump\n HoldUnhold = \"hold_unhold\",\n // Feeds have changed\n FeedsChanged = \"feeds_changed\",\n\n AssertedIdentityChanged = \"asserted_identity_changed\",\n\n LengthChanged = \"length_changed\",\n\n DataChannel = \"datachannel\",\n\n SendVoipEvent = \"send_voip_event\",\n}\n\nexport enum CallErrorCode {\n /** The user chose to end the call */\n UserHangup = \"user_hangup\",\n\n /** An error code when the local client failed to create an offer. */\n LocalOfferFailed = \"local_offer_failed\",\n /**\n * An error code when there is no local mic/camera to use. This may be because\n * the hardware isn't plugged in, or the user has explicitly denied access.\n */\n NoUserMedia = \"no_user_media\",\n\n /**\n * Error code used when a call event failed to send\n * because unknown devices were present in the room\n */\n UnknownDevices = \"unknown_devices\",\n\n /**\n * Error code used when we fail to send the invite\n * for some reason other than there being unknown devices\n */\n SendInvite = \"send_invite\",\n\n /**\n * An answer could not be created\n */\n CreateAnswer = \"create_answer\",\n\n /**\n * An offer could not be created\n */\n CreateOffer = \"create_offer\",\n\n /**\n * Error code used when we fail to send the answer\n * for some reason other than there being unknown devices\n */\n SendAnswer = \"send_answer\",\n\n /**\n * The session description from the other side could not be set\n */\n SetRemoteDescription = \"set_remote_description\",\n\n /**\n * The session description from this side could not be set\n */\n SetLocalDescription = \"set_local_description\",\n\n /**\n * A different device answered the call\n */\n AnsweredElsewhere = \"answered_elsewhere\",\n\n /**\n * No media connection could be established to the other party\n */\n IceFailed = \"ice_failed\",\n\n /**\n * The invite timed out whilst waiting for an answer\n */\n InviteTimeout = \"invite_timeout\",\n\n /**\n * The call was replaced by another call\n */\n Replaced = \"replaced\",\n\n /**\n * Signalling for the call could not be sent (other than the initial invite)\n */\n SignallingFailed = \"signalling_timeout\",\n\n /**\n * The remote party is busy\n */\n UserBusy = \"user_busy\",\n\n /**\n * We transferred the call off to somewhere else\n */\n Transferred = \"transferred\",\n\n /**\n * A call from the same user was found with a new session id\n */\n NewSession = \"new_session\",\n}\n\n/**\n * The version field that we set in m.call.* events\n */\nconst VOIP_PROTO_VERSION = \"1\";\n\n/** The fallback ICE server to use for STUN or TURN protocols. */\nconst FALLBACK_ICE_SERVER = \"stun:turn.matrix.org\";\n\n/** The length of time a call can be ringing for. */\nconst CALL_TIMEOUT_MS = 60 * 1000; // ms\n/** The time after which we increment callLength */\nconst CALL_LENGTH_INTERVAL = 1000; // ms\n/** The time after which we end the call, if ICE got disconnected */\nconst ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms\n\nexport class CallError extends Error {\n public readonly code: string;\n\n public constructor(code: CallErrorCode, msg: string, err: Error) {\n // Still don't think there's any way to have proper nested errors\n super(msg + \": \" + err);\n\n this.code = code;\n }\n}\n\nexport function genCallID(): string {\n return Date.now().toString() + randomString(16);\n}\n\nfunction getCodecParamMods(isPtt: boolean): CodecParamsMod[] {\n const mods = [\n {\n mediaType: \"audio\",\n codec: \"opus\",\n enableDtx: true,\n maxAverageBitrate: isPtt ? 12000 : undefined,\n },\n ] as CodecParamsMod[];\n\n return mods;\n}\n\nexport interface VoipEvent {\n type: \"toDevice\" | \"sendEvent\";\n eventType: string;\n userId?: string;\n opponentDeviceId?: string;\n roomId?: string;\n content: Record<string, unknown>;\n}\n\n/**\n * These now all have the call object as an argument. Why? Well, to know which call a given event is\n * about you have three options:\n * 1. Use a closure as the callback that remembers what call it's listening to. This can be\n * a pain because you need to pass the listener function again when you remove the listener,\n * which might be somewhere else.\n * 2. Use not-very-well-known fact that EventEmitter sets 'this' to the emitter object in the\n * callback. This doesn't really play well with modern Typescript and eslint and doesn't work\n * with our pattern of re-emitting events.\n * 3. Pass the object in question as an argument to the callback.\n *\n * Now that we have group calls which have to deal with multiple call objects, this will\n * become more important, and I think methods 1 and 2 are just going to cause issues.\n */\nexport type CallEventHandlerMap = {\n [CallEvent.DataChannel]: (channel: RTCDataChannel, call: MatrixCall) => void;\n [CallEvent.FeedsChanged]: (feeds: CallFeed[], call: MatrixCall) => void;\n [CallEvent.Replaced]: (newCall: MatrixCall, oldCall: MatrixCall) => void;\n [CallEvent.Error]: (error: CallError, call: MatrixCall) => void;\n [CallEvent.RemoteHoldUnhold]: (onHold: boolean, call: MatrixCall) => void;\n [CallEvent.LocalHoldUnhold]: (onHold: boolean, call: MatrixCall) => void;\n [CallEvent.LengthChanged]: (length: number, call: MatrixCall) => void;\n [CallEvent.State]: (state: CallState, oldState: CallState, call: MatrixCall) => void;\n [CallEvent.Hangup]: (call: MatrixCall) => void;\n [CallEvent.AssertedIdentityChanged]: (call: MatrixCall) => void;\n /* @deprecated */\n [CallEvent.HoldUnhold]: (onHold: boolean) => void;\n [CallEvent.SendVoipEvent]: (event: VoipEvent, call: MatrixCall) => void;\n};\n\n// The key of the transceiver map (purpose + media type, separated by ':')\ntype TransceiverKey = string;\n\n// generates keys for the map of transceivers\n// kind is unfortunately a string rather than MediaType as this is the type of\n// track.kind\nfunction getTransceiverKey(purpose: SDPStreamMetadataPurpose, kind: TransceiverKey): string {\n return purpose + \":\" + kind;\n}\n\nexport class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap> {\n public roomId: string;\n public callId: string;\n public invitee?: string;\n public hangupParty?: CallParty;\n public hangupReason?: string;\n public direction?: CallDirection;\n public ourPartyId: string;\n public peerConn?: RTCPeerConnection;\n public toDeviceSeq = 0;\n\n // whether this call should have push-to-talk semantics\n // This should be set by the consumer on incoming & outgoing calls.\n public isPtt = false;\n\n private _state = CallState.Fledgling;\n private readonly client: MatrixClient;\n private readonly forceTURN?: boolean;\n private readonly turnServers: Array<TurnServer>;\n // A queue for candidates waiting to go out.\n // We try to amalgamate candidates into a single candidate message where\n // possible\n private candidateSendQueue: Array<RTCIceCandidate> = [];\n private candidateSendTries = 0;\n private candidatesEnded = false;\n private feeds: Array<CallFeed> = [];\n\n // our transceivers for each purpose and type of media\n private transceivers = new Map<TransceiverKey, RTCRtpTransceiver>();\n\n private inviteOrAnswerSent = false;\n private waitForLocalAVStream = false;\n private successor?: MatrixCall;\n private opponentMember?: RoomMember;\n private opponentVersion?: number | string;\n // The party ID of the other side: undefined if we haven't chosen a partner\n // yet, null if we have but they didn't send a party ID.\n private opponentPartyId: string | null | undefined;\n private opponentCaps?: CallCapabilities;\n private iceDisconnectedTimeout?: ReturnType<typeof setTimeout>;\n private inviteTimeout?: ReturnType<typeof setTimeout>;\n private readonly removeTrackListeners = new Map<MediaStream, () => void>();\n\n // The logic of when & if a call is on hold is nontrivial and explained in is*OnHold\n // This flag represents whether we want the other party to be on hold\n private remoteOnHold = false;\n\n // the stats for the call at the point it ended. We can't get these after we\n // tear the call down, so we just grab a snapshot before we stop the call.\n // The typescript definitions have this type as 'any' :(\n private callStatsAtEnd?: any[];\n\n // Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example\n private makingOffer = false;\n private ignoreOffer = false;\n\n private responsePromiseChain?: Promise<void>;\n\n // If candidates arrive before we've picked an opponent (which, in particular,\n // will happen if the opponent sends candidates eagerly before the user answers\n // the call) we buffer them up here so we can then add the ones from the party we pick\n private remoteCandidateBuffer = new Map<string, RTCIceCandidate[]>();\n\n private remoteAssertedIdentity?: AssertedIdentity;\n private remoteSDPStreamMetadata?: SDPStreamMetadata;\n\n private callLengthInterval?: ReturnType<typeof setInterval>;\n private callStartTime?: number;\n\n private opponentDeviceId?: string;\n private opponentDeviceInfo?: DeviceInfo;\n private opponentSessionId?: string;\n public groupCallId?: string;\n\n // Used to keep the timer for the delay before actually stopping our\n // video track after muting (see setLocalVideoMuted)\n private stopVideoTrackTimer?: ReturnType<typeof setTimeout>;\n // Used to allow connection without Video and Audio. To establish a webrtc connection without media a Data channel is\n // needed At the moment this property is true if we allow MatrixClient with isVoipWithNoMediaAllowed = true\n private readonly isOnlyDataChannelAllowed: boolean;\n private stats: GroupCallStats | undefined;\n\n /**\n * Construct a new Matrix Call.\n * @param opts - Config options.\n */\n public constructor(opts: CallOpts) {\n super();\n\n this.roomId = opts.roomId;\n this.invitee = opts.invitee;\n this.client = opts.client;\n\n if (!this.client.deviceId) throw new Error(\"Client must have a device ID to start calls\");\n\n this.forceTURN = opts.forceTURN ?? false;\n this.ourPartyId = this.client.deviceId;\n this.opponentDeviceId = opts.opponentDeviceId;\n this.opponentSessionId = opts.opponentSessionId;\n this.groupCallId = opts.groupCallId;\n // Array of Objects with urls, username, credential keys\n this.turnServers = opts.turnServers || [];\n if (this.turnServers.length === 0 && this.client.isFallbackICEServerAllowed()) {\n this.turnServers.push({\n urls: [FALLBACK_ICE_SERVER],\n });\n }\n for (const server of this.turnServers) {\n utils.checkObjectHasKeys(server, [\"urls\"]);\n }\n this.callId = genCallID();\n // If the Client provides calls without audio and video we need a datachannel for a webrtc connection\n this.isOnlyDataChannelAllowed = this.client.isVoipWithNoMediaAllowed;\n }\n\n /**\n * Place a voice call to this room.\n * @throws If you have not specified a listener for 'error' events.\n */\n public async placeVoiceCall(): Promise<void> {\n await this.placeCall(true, false);\n }\n\n /**\n * Place a video call to this room.\n * @throws If you have not specified a listener for 'error' events.\n */\n public async placeVideoCall(): Promise<void> {\n await this.placeCall(true, true);\n }\n\n /**\n * Create a datachannel using this call's peer connection.\n * @param label - A human readable label for this datachannel\n * @param options - An object providing configuration options for the data channel.\n */\n public createDataChannel(label: string, options: RTCDataChannelInit | undefined): RTCDataChannel {\n const dataChannel = this.peerConn!.createDataChannel(label, options);\n this.emit(CallEvent.DataChannel, dataChannel, this);\n return dataChannel;\n }\n\n public getOpponentMember(): RoomMember | undefined {\n return this.opponentMember;\n }\n\n public getOpponentDeviceId(): string | undefined {\n return this.opponentDeviceId;\n }\n\n public getOpponentSessionId(): string | undefined {\n return this.opponentSessionId;\n }\n\n public opponentCanBeTransferred(): boolean {\n return Boolean(this.opponentCaps && this.opponentCaps[\"m.call.transferee\"]);\n }\n\n public opponentSupportsDTMF(): boolean {\n return Boolean(this.opponentCaps && this.opponentCaps[\"m.call.dtmf\"]);\n }\n\n public getRemoteAssertedIdentity(): AssertedIdentity | undefined {\n return this.remoteAssertedIdentity;\n }\n\n public get state(): CallState {\n return this._state;\n }\n\n private set state(state: CallState) {\n const oldState = this._state;\n this._state = state;\n this.emit(CallEvent.State, state, oldState, this);\n }\n\n public get type(): CallType {\n // we may want to look for a video receiver here rather than a track to match the\n // sender behaviour, although in practice they should be the same thing\n return this.hasUserMediaVideoSender || this.hasRemoteUserMediaVideoTrack ? CallType.Video : CallType.Voice;\n }\n\n public get hasLocalUserMediaVideoTrack(): boolean {\n return !!this.localUsermediaStream?.getVideoTracks().length;\n }\n\n public get hasRemoteUserMediaVideoTrack(): boolean {\n return this.getRemoteFeeds().some((feed) => {\n return feed.purpose === SDPStreamMetadataPurpose.Usermedia && feed.stream?.getVideoTracks().length;\n });\n }\n\n public get hasLocalUserMediaAudioTrack(): boolean {\n return !!this.localUsermediaStream?.getAudioTracks().length;\n }\n\n public get hasRemoteUserMediaAudioTrack(): boolean {\n return this.getRemoteFeeds().some((feed) => {\n return feed.purpose === SDPStreamMetadataPurpose.Usermedia && !!feed.stream?.getAudioTracks().length;\n });\n }\n\n private get hasUserMediaAudioSender(): boolean {\n return Boolean(this.transceivers.get(getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"audio\"))?.sender);\n }\n\n private get hasUserMediaVideoSender(): boolean {\n return Boolean(this.transceivers.get(getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"video\"))?.sender);\n }\n\n public get localUsermediaFeed(): CallFeed | undefined {\n return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);\n }\n\n public get localScreensharingFeed(): CallFeed | undefined {\n return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);\n }\n\n public get localUsermediaStream(): MediaStream | undefined {\n return this.localUsermediaFeed?.stream;\n }\n\n public get localScreensharingStream(): MediaStream | undefined {\n return this.localScreensharingFeed?.stream;\n }\n\n public get remoteUsermediaFeed(): CallFeed | undefined {\n return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);\n }\n\n public get remoteScreensharingFeed(): CallFeed | undefined {\n return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);\n }\n\n public get remoteUsermediaStream(): MediaStream | undefined {\n return this.remoteUsermediaFeed?.stream;\n }\n\n public get remoteScreensharingStream(): MediaStream | undefined {\n return this.remoteScreensharingFeed?.stream;\n }\n\n private getFeedByStreamId(streamId: string): CallFeed | undefined {\n return this.getFeeds().find((feed) => feed.stream.id === streamId);\n }\n\n /**\n * Returns an array of all CallFeeds\n * @returns CallFeeds\n */\n public getFeeds(): Array<CallFeed> {\n return this.feeds;\n }\n\n /**\n * Returns an array of all local CallFeeds\n * @returns local CallFeeds\n */\n public getLocalFeeds(): Array<CallFeed> {\n return this.feeds.filter((feed) => feed.isLocal());\n }\n\n /**\n * Returns an array of all remote CallFeeds\n * @returns remote CallFeeds\n */\n public getRemoteFeeds(): Array<CallFeed> {\n return this.feeds.filter((feed) => !feed.isLocal());\n }\n\n private async initOpponentCrypto(): Promise<void> {\n if (!this.opponentDeviceId) return;\n if (!this.client.getUseE2eForGroupCall()) return;\n // It's possible to want E2EE and yet not have the means to manage E2EE\n // ourselves (for example if the client is a RoomWidgetClient)\n if (!this.client.isCryptoEnabled()) {\n // All we know is the device ID\n this.opponentDeviceInfo = new DeviceInfo(this.opponentDeviceId);\n return;\n }\n // if we've got to this point, we do want to init crypto, so throw if we can't\n if (!this.client.crypto) throw new Error(\"Crypto is not initialised.\");\n\n const userId = this.invitee || this.getOpponentMember()?.userId;\n\n if (!userId) throw new Error(\"Couldn't find opponent user ID to init crypto\");\n\n const deviceInfoMap = await this.client.crypto.deviceList.downloadKeys([userId], false);\n this.opponentDeviceInfo = deviceInfoMap.get(userId)?.get(this.opponentDeviceId);\n if (this.opponentDeviceInfo === undefined) {\n throw new GroupCallUnknownDeviceError(userId);\n }\n }\n\n /**\n * Generates and returns localSDPStreamMetadata\n * @returns localSDPStreamMetadata\n */\n private getLocalSDPStreamMetadata(updateStreamIds = false): SDPStreamMetadata {\n const metadata: SDPStreamMetadata = {};\n for (const localFeed of this.getLocalFeeds()) {\n if (updateStreamIds) {\n localFeed.sdpMetadataStreamId = localFeed.stream.id;\n }\n\n metadata[localFeed.sdpMetadataStreamId] = {\n purpose: localFeed.purpose,\n audio_muted: localFeed.isAudioMuted(),\n video_muted: localFeed.isVideoMuted(),\n };\n }\n return metadata;\n }\n\n /**\n * Returns true if there are no incoming feeds,\n * otherwise returns false\n * @returns no incoming feeds\n */\n public noIncomingFeeds(): boolean {\n return !this.feeds.some((feed) => !feed.isLocal());\n }\n\n private pushRemoteFeed(stream: MediaStream): void {\n // Fallback to old behavior if the other side doesn't support SDPStreamMetadata\n if (!this.opponentSupportsSDPStreamMetadata()) {\n this.pushRemoteFeedWithoutMetadata(stream);\n return;\n }\n\n const userId = this.getOpponentMember()!.userId;\n const purpose = this.remoteSDPStreamMetadata![stream.id].purpose;\n const audioMuted = this.remoteSDPStreamMetadata![stream.id].audio_muted;\n const videoMuted = this.remoteSDPStreamMetadata![stream.id].video_muted;\n\n if (!purpose) {\n logger.warn(\n `Call ${this.callId} pushRemoteFeed() ignoring stream because we didn't get any metadata about it (streamId=${stream.id})`,\n );\n return;\n }\n\n if (this.getFeedByStreamId(stream.id)) {\n logger.warn(\n `Call ${this.callId} pushRemoteFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`,\n );\n return;\n }\n\n this.feeds.push(\n new CallFeed({\n client: this.client,\n call: this,\n roomId: this.roomId,\n userId,\n deviceId: this.getOpponentDeviceId(),\n stream,\n purpose,\n audioMuted,\n videoMuted,\n }),\n );\n\n this.emit(CallEvent.FeedsChanged, this.feeds, this);\n\n logger.info(\n `Call ${this.callId} pushRemoteFeed() pushed stream (streamId=${stream.id}, active=${stream.active}, purpose=${purpose})`,\n );\n }\n\n /**\n * This method is used ONLY if the other client doesn't support sending SDPStreamMetadata\n */\n private pushRemoteFeedWithoutMetadata(stream: MediaStream): void {\n const userId = this.getOpponentMember()!.userId;\n // We can guess the purpose here since the other client can only send one stream\n const purpose = SDPStreamMetadataPurpose.Usermedia;\n const oldRemoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream;\n\n // Note that we check by ID and always set the remote stream: Chrome appears\n // to make new stream objects when transceiver directionality is changed and the 'active'\n // status of streams change - Dave\n // If we already have a stream, check this stream has the same id\n if (oldRemoteStream && stream.id !== oldRemoteStream.id) {\n logger.warn(\n `Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring new stream because we already have stream (streamId=${stream.id})`,\n );\n return;\n }\n\n if (this.getFeedByStreamId(stream.id)) {\n logger.warn(\n `Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring stream because we already have a feed for it (streamId=${stream.id})`,\n );\n return;\n }\n\n this.feeds.push(\n new CallFeed({\n client: this.client,\n call: this,\n roomId: this.roomId,\n audioMuted: false,\n videoMuted: false,\n userId,\n deviceId: this.getOpponentDeviceId(),\n stream,\n purpose,\n }),\n );\n\n this.emit(CallEvent.FeedsChanged, this.feeds, this);\n\n logger.info(\n `Call ${this.callId} pushRemoteFeedWithoutMetadata() pushed stream (streamId=${stream.id}, active=${stream.active})`,\n );\n }\n\n private pushNewLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose, addToPeerConnection = true): void {\n const userId = this.client.getUserId()!;\n\n // Tracks don't always start off enabled, eg. chrome will give a disabled\n // audio track if you ask for user media audio and already had one that\n // you'd set to disabled (presumably because it clones them internally).\n setTracksEnabled(stream.getAudioTracks(), true);\n setTracksEnabled(stream.getVideoTracks(), true);\n\n if (this.getFeedByStreamId(stream.id)) {\n logger.warn(\n `Call ${this.callId} pushNewLocalFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`,\n );\n return;\n }\n\n this.pushLocalFeed(\n new CallFeed({\n client: this.client,\n roomId: this.roomId,\n audioMuted: false,\n videoMuted: false,\n userId,\n deviceId: this.getOpponentDeviceId(),\n stream,\n purpose,\n }),\n addToPeerConnection,\n );\n }\n\n /**\n * Pushes supplied feed to the call\n * @param callFeed - to push\n * @param addToPeerConnection - whether to add the tracks to the peer connection\n */\n public pushLocalFeed(callFeed: CallFeed, addToPeerConnection = true): void {\n if (this.feeds.some((feed) => callFeed.stream.id === feed.stream.id)) {\n logger.info(\n `Call ${this.callId} pushLocalFeed() ignoring duplicate local stream (streamId=${callFeed.stream.id})`,\n );\n return;\n }\n\n this.feeds.push(callFeed);\n\n if (addToPeerConnection) {\n for (const track of callFeed.stream.getTracks()) {\n logger.info(\n `Call ${this.callId} pushLocalFeed() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${callFeed.stream.id}, streamPurpose=${callFeed.purpose}, enabled=${track.enabled})`,\n );\n\n const tKey = getTransceiverKey(callFeed.purpose, track.kind);\n if (this.transceivers.has(tKey)) {\n // we already have a sender, so we re-use it. We try to re-use transceivers as much\n // as possible because they can't be removed once added, so otherwise they just\n // accumulate which makes the SDP very large very quickly: in fact it only takes\n // about 6 video tracks to exceed the maximum size of an Olm-encrypted\n // Matrix event.\n const transceiver = this.transceivers.get(tKey)!;\n\n transceiver.sender.replaceTrack(track);\n // set the direction to indicate we're going to start sending again\n // (this will trigger the re-negotiation)\n transceiver.direction = transceiver.direction === \"inactive\" ? \"sendonly\" : \"sendrecv\";\n } else {\n // create a new one. We need to use addTrack rather addTransceiver for this because firefox\n // doesn't yet implement RTCRTPSender.setStreams()\n // (https://bugzilla.mozilla.org/show_bug.cgi?id=1510802) so we'd have no way to group the\n // two tracks together into a stream.\n const newSender = this.peerConn!.addTrack(track, callFeed.stream);\n\n // now go & fish for the new transceiver\n const newTransceiver = this.peerConn!.getTransceivers().find((t) => t.sender === newSender);\n if (newTransceiver) {\n this.transceivers.set(tKey, newTransceiver);\n } else {\n logger.warn(\n `Call ${this.callId} pushLocalFeed() didn't find a matching transceiver after adding track!`,\n );\n }\n }\n }\n }\n\n logger.info(\n `Call ${this.callId} pushLocalFeed() pushed stream (id=${callFeed.stream.id}, active=${callFeed.stream.active}, purpose=${callFeed.purpose})`,\n );\n\n this.emit(CallEvent.FeedsChanged, this.feeds, this);\n }\n\n /**\n * Removes local call feed from the call and its tracks from the peer\n * connection\n * @param callFeed - to remove\n */\n public removeLocalFeed(callFeed: CallFeed): void {\n const audioTransceiverKey = getTransceiverKey(callFeed.purpose, \"audio\");\n const videoTransceiverKey = getTransceiverKey(callFeed.purpose, \"video\");\n\n for (const transceiverKey of [audioTransceiverKey, videoTransceiverKey]) {\n // this is slightly mixing the track and transceiver API but is basically just shorthand.\n // There is no way to actually remove a transceiver, so this just sets it to inactive\n // (or recvonly) and replaces the source with nothing.\n if (this.transceivers.has(transceiverKey)) {\n const transceiver = this.transceivers.get(transceiverKey)!;\n if (transceiver.sender) this.peerConn!.removeTrack(transceiver.sender);\n }\n }\n\n if (callFeed.purpose === SDPStreamMetadataPurpose.Screenshare) {\n this.client.getMediaHandler().stopScreensharingStream(callFeed.stream);\n }\n\n this.deleteFeed(callFeed);\n }\n\n private deleteAllFeeds(): void {\n for (const feed of this.feeds) {\n if (!feed.isLocal() || !this.groupCallId) {\n feed.dispose();\n }\n }\n\n this.feeds = [];\n this.emit(CallEvent.FeedsChanged, this.feeds, this);\n }\n\n private deleteFeedByStream(stream: MediaStream): void {\n const feed = this.getFeedByStreamId(stream.id);\n if (!feed) {\n logger.warn(\n `Call ${this.callId} deleteFeedByStream() didn't find the feed to delete (streamId=${stream.id})`,\n );\n return;\n }\n this.deleteFeed(feed);\n }\n\n private deleteFeed(feed: CallFeed): void {\n feed.dispose();\n this.feeds.splice(this.feeds.indexOf(feed), 1);\n this.emit(CallEvent.FeedsChanged, this.feeds, this);\n }\n\n // The typescript definitions have this type as 'any' :(\n public async getCurrentCallStats(): Promise<any[] | undefined> {\n if (this.callHasEnded()) {\n return this.callStatsAtEnd;\n }\n\n return this.collectCallStats();\n }\n\n private async collectCallStats(): Promise<any[] | undefined> {\n // This happens when the call fails before it starts.\n // For example when we fail to get capture sources\n if (!this.peerConn) return;\n\n const statsReport = await this.peerConn.getStats();\n const stats: any[] = [];\n statsReport.forEach((item) => {\n stats.push(item);\n });\n\n return stats;\n }\n\n /**\n * Configure this call from an invite event. Used by MatrixClient.\n * @param event - The m.call.invite event\n */\n public async initWithInvite(event: MatrixEvent): Promise<void> {\n const invite = event.getContent<MCallInviteNegotiate>();\n this.direction = CallDirection.Inbound;\n\n // make sure we have valid turn creds. Unless something's gone wrong, it should\n // poll and keep the credentials valid so this should be instant.\n const haveTurnCreds = await this.client.checkTurnServers();\n if (!haveTurnCreds) {\n logger.warn(\n `Call ${this.callId} initWithInvite() failed to get TURN credentials! Proceeding with call anyway...`,\n );\n }\n\n const sdpStreamMetadata = invite[SDPStreamMetadataKey];\n if (sdpStreamMetadata) {\n this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);\n } else {\n logger.debug(\n `Call ${this.callId} initWithInvite() did not get any SDPStreamMetadata! Can not send/receive multiple streams`,\n );\n }\n\n this.peerConn = this.createPeerConnection();\n // we must set the party ID before await-ing on anything: the call event\n // handler will start giving us more call events (eg. candidates) so if\n // we haven't set the party ID, we'll ignore them.\n this.chooseOpponent(event);\n await this.initOpponentCrypto();\n try {\n await this.peerConn.setRemoteDescription(invite.offer);\n await this.addBufferedIceCandidates();\n } catch (e) {\n logger.debug(`Call ${this.callId} initWithInvite() failed to set remote description`, e);\n this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);\n return;\n }\n\n const remoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream;\n\n // According to previous comments in this file, firefox at some point did not\n // add streams until media started arriving on them. Testing latest firefox\n // (81 at time of writing), this is no longer a problem, so let's do it the correct way.\n //\n // For example in case of no media webrtc connections like screen share only call we have to allow webrtc\n // connections without remote media. In this case we always use a data channel. At the moment we allow as well\n // only data channel as media in the WebRTC connection with this setup here.\n if (!this.isOnlyDataChannelAllowed && (!remoteStream || remoteStream.getTracks().length === 0)) {\n logger.error(\n `Call ${this.callId} initWithInvite() no remote stream or no tracks after setting remote description!`,\n );\n this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);\n return;\n }\n\n this.state = CallState.Ringing;\n\n if (event.getLocalAge()) {\n // Time out the call if it's ringing for too long\n const ringingTimer = setTimeout(() => {\n if (this.state == CallState.Ringing) {\n logger.debug(`Call ${this.callId} initWithInvite() invite has expired. Hanging up.`);\n this.hangupParty = CallParty.Remote; // effectively\n this.state = CallState.Ended;\n this.stopAllMedia();\n if (this.peerConn!.signalingState != \"closed\") {\n this.peerConn!.close();\n }\n this.stats?.removeStatsReportGatherer(this.callId);\n this.emit(CallEvent.Hangup, this);\n }\n }, invite.lifetime - event.getLocalAge());\n\n const onState = (state: CallState): void => {\n if (state !== CallState.Ringing) {\n clearTimeout(ringingTimer);\n this.off(CallEvent.State, onState);\n }\n };\n this.on(CallEvent.State, onState);\n }\n }\n\n /**\n * Configure this call from a hangup or reject event. Used by MatrixClient.\n * @param event - The m.call.hangup event\n */\n public initWithHangup(event: MatrixEvent): void {\n // perverse as it may seem, sometimes we want to instantiate a call with a\n // hangup message (because when getting the state of the room on load, events\n // come in reverse order and we want to remember that a call has been hung up)\n this.state = CallState.Ended;\n }\n\n private shouldAnswerWithMediaType(\n wantedValue: boolean | undefined,\n valueOfTheOtherSide: boolean,\n type: \"audio\" | \"video\",\n ): boolean {\n if (wantedValue && !valueOfTheOtherSide) {\n // TODO: Figure out how to do this\n logger.warn(\n `Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type} because the other side isn't sending it either.`,\n );\n return false;\n } else if (\n !utils.isNullOrUndefined(wantedValue) &&\n wantedValue !== valueOfTheOtherSide &&\n !this.opponentSupportsSDPStreamMetadata()\n ) {\n logger.warn(\n `Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type}=${wantedValue} because the other side doesn't support it. Answering with ${type}=${valueOfTheOtherSide}.`,\n );\n return valueOfTheOtherSide!;\n }\n return wantedValue ?? valueOfTheOtherSide!;\n }\n\n /**\n * Answer a call.\n */\n public async answer(audio?: boolean, video?: boolean): Promise<void> {\n if (this.inviteOrAnswerSent) return;\n // TODO: Figure out how to do this\n if (audio === false && video === false) throw new Error(\"You CANNOT answer a call without media\");\n\n if (!this.localUsermediaStream && !this.waitForLocalAVStream) {\n const prevState = this.state;\n const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, \"audio\");\n const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, \"video\");\n\n this.state = CallState.WaitLocalMedia;\n this.waitForLocalAVStream = true;\n\n try {\n const stream = await this.client.getMediaHandler().getUserMediaStream(answerWithAudio, answerWithVideo);\n this.waitForLocalAVStream = false;\n const usermediaFeed = new CallFeed({\n client: this.client,\n roomId: this.roomId,\n userId: this.client.getUserId()!,\n deviceId: this.client.getDeviceId() ?? undefined,\n stream,\n purpose: SDPStreamMetadataPurpose.Usermedia,\n audioMuted: false,\n videoMuted: false,\n });\n\n const feeds = [usermediaFeed];\n\n if (this.localScreensharingFeed) {\n feeds.push(this.localScreensharingFeed);\n }\n\n this.answerWithCallFeeds(feeds);\n } catch (e) {\n if (answerWithVideo) {\n // Try to answer without video\n logger.warn(\n `Call ${this.callId} answer() failed to getUserMedia(), trying to getUserMedia() without video`,\n );\n this.state = prevState;\n this.waitForLocalAVStream = false;\n await this.answer(answerWithAudio, false);\n } else {\n this.getUserMediaFailed(<Error>e);\n return;\n }\n }\n } else if (this.waitForLocalAVStream) {\n this.state = CallState.WaitLocalMedia;\n }\n }\n\n public answerWithCallFeeds(callFeeds: CallFeed[]): void {\n if (this.inviteOrAnswerSent) return;\n\n this.queueGotCallFeedsForAnswer(callFeeds);\n }\n\n /**\n * Replace this call with a new call, e.g. for glare resolution. Used by\n * MatrixClient.\n * @param newCall - The new call.\n */\n public replacedBy(newCall: MatrixCall): void {\n logger.debug(`Call ${this.callId} replacedBy() running (newCallId=${newCall.callId})`);\n if (this.state === CallState.WaitLocalMedia) {\n logger.debug(\n `Call ${this.callId} replacedBy() telling new call to wait for local media (newCallId=${newCall.callId})`,\n );\n newCall.waitForLocalAVStream = true;\n } else if ([CallState.CreateOffer, CallState.InviteSent].includes(this.state)) {\n if (newCall.direction === CallDirection.Outbound) {\n newCall.queueGotCallFeedsForAnswer([]);\n } else {\n logger.debug(\n `Call ${this.callId} replacedBy() handing local stream to new call(newCallId=${newCall.callId})`,\n );\n newCall.queueGotCallFeedsForAnswer(this.getLocalFeeds().map((feed) => feed.clone()));\n }\n }\n this.successor = newCall;\n this.emit(CallEvent.Replaced, newCall, this);\n this.hangup(CallErrorCode.Replaced, true);\n }\n\n /**\n * Hangup a call.\n * @param reason - The reason why the call is being hung up.\n * @param suppressEvent - True to suppress emitting an event.\n */\n public hangup(reason: CallErrorCode, suppressEvent: boolean): void {\n if (this.callHasEnded()) return;\n\n logger.debug(`Call ${this.callId} hangup() ending call (reason=${reason})`);\n this.terminate(CallParty.Local, reason, !suppressEvent);\n // We don't want to send hangup here if we didn't even get to sending an invite\n if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;\n const content: IContent = {};\n // Don't send UserHangup reason to older clients\n if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {\n content[\"reason\"] = reason;\n }\n this.sendVoipEvent(EventType.CallHangup, content);\n }\n\n /**\n * Reject a call\n * This used to be done by calling hangup, but is a separate method and protocol\n * event as of MSC2746.\n */\n public reject(): void {\n if (this.state !== CallState.Ringing) {\n throw Error(\"Call must be in 'ringing' state to reject!\");\n }\n\n if (this.opponentVersion === 0) {\n logger.info(\n `Call ${this.callId} reject() opponent version is less than 1: sending hangup instead of reject (opponentVersion=${this.opponentVersion})`,\n );\n this.hangup(CallErrorCode.UserHangup, true);\n return;\n }\n\n logger.debug(\"Rejecting call: \" + this.callId);\n this.terminate(CallParty.Local, CallErrorCode.UserHangup, true);\n this.sendVoipEvent(EventType.CallReject, {});\n }\n\n /**\n * Adds an audio and/or video track - upgrades the call\n * @param audio - should add an audio track\n * @param video - should add an video track\n */\n private async upgradeCall(audio: boolean, video: boolean): Promise<void> {\n // We don't do call downgrades\n if (!audio && !video) return;\n if (!this.opponentSupportsSDPStreamMetadata()) return;\n\n try {\n logger.debug(`Call ${this.callId} upgradeCall() upgrading call (audio=${audio}, video=${video})`);\n const getAudio = audio || this.hasLocalUserMediaAudioTrack;\n const getVideo = video || this.hasLocalUserMediaVideoTrack;\n\n // updateLocalUsermediaStream() will take the tracks, use them as\n // replacement and throw the stream away, so it isn't reusable\n const stream = await this.client.getMediaHandler().getUserMediaStream(getAudio, getVideo, false);\n await this.updateLocalUsermediaStream(stream, audio, video);\n } catch (error) {\n logger.error(`Call ${this.callId} upgradeCall() failed to upgrade the call`, error);\n this.emit(\n CallEvent.Error,\n new CallError(CallErrorCode.NoUserMedia, \"Failed to get camera access: \", <Error>error),\n this,\n );\n }\n }\n\n /**\n * Returns true if this.remoteSDPStreamMetadata is defined, otherwise returns false\n * @returns can screenshare\n */\n public opponentSupportsSDPStreamMetadata(): boolean {\n return Boolean(this.remoteSDPStreamMetadata);\n }\n\n /**\n * If there is a screensharing stream returns true, otherwise returns false\n * @returns is screensharing\n */\n public isScreensharing(): boolean {\n return Boolean(this.localScreensharingStream);\n }\n\n /**\n * Starts/stops screensharing\n * @param enabled - the desired screensharing state\n * @param desktopCapturerSourceId - optional id of the desktop capturer source to use\n * @returns new screensharing state\n */\n public async setScreensharingEnabled(enabled: boolean, opts?: IScreensharingOpts): Promise<boolean> {\n // Skip if there is nothing to do\n if (enabled && this.isScreensharing()) {\n logger.warn(\n `Call ${this.callId} setScreensharingEnabled() there is already a screensharing stream - there is nothing to do!`,\n );\n return true;\n } else if (!enabled && !this.isScreensharing()) {\n logger.warn(\n `Call ${this.callId} setScreensharingEnabled() there already isn't a screensharing stream - there is nothing to do!`,\n );\n return false;\n }\n\n // Fallback to replaceTrack()\n if (!this.opponentSupportsSDPStreamMetadata()) {\n return this.setScreensharingEnabledWithoutMetadataSupport(enabled, opts);\n }\n\n logger.debug(`Call ${this.callId} setScreensharingEnabled() running (enabled=${enabled})`);\n if (enabled) {\n try {\n const stream = await this.client.getMediaHandler().getScreensharingStream(opts);\n if (!stream) return false;\n this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare);\n return true;\n } catch (err) {\n logger.error(`Call ${this.callId} setScreensharingEnabled() failed to get screen-sharing stream:`, err);\n return false;\n }\n } else {\n const audioTransceiver = this.transceivers.get(\n getTransceiverKey(SDPStreamMetadataPurpose.Screenshare, \"audio\"),\n );\n const videoTransceiver = this.transceivers.get(\n getTransceiverKey(SDPStreamMetadataPurpose.Screenshare, \"video\"),\n );\n\n for (const transceiver of [audioTransceiver, videoTransceiver]) {\n // this is slightly mixing the track and transceiver API but is basically just shorthand\n // for removing the sender.\n if (transceiver && transceiver.sender) this.peerConn!.removeTrack(transceiver.sender);\n }\n\n this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);\n this.deleteFeedByStream(this.localScreensharingStream!);\n return false;\n }\n }\n\n /**\n * Starts/stops screensharing\n * Should be used ONLY if the opponent doesn't support SDPStreamMetadata\n * @param enabled - the desired screensharing state\n * @param desktopCapturerSourceId - optional id of the desktop capturer source to use\n * @returns new screensharing state\n */\n private async setScreensharingEnabledWithoutMetadataSupport(\n enabled: boolean,\n opts?: IScreensharingOpts,\n ): Promise<boolean> {\n logger.debug(\n `Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() running (enabled=${enabled})`,\n );\n if (enabled) {\n try {\n const stream = await this.client.getMediaHandler().getScreensharingStream(opts);\n if (!stream) return false;\n\n const track = stream.getTracks().find((track) => track.kind === \"video\");\n\n const sender = this.transceivers.get(\n getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"video\"),\n )?.sender;\n\n sender?.replaceTrack(track ?? null);\n\n this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare, false);\n\n return true;\n } catch (err) {\n logger.error(\n `Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() failed to get screen-sharing stream:`,\n err,\n );\n return false;\n }\n } else {\n const track = this.localUsermediaStream?.getTracks().find((track) => track.kind === \"video\");\n const sender = this.transceivers.get(\n getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"video\"),\n )?.sender;\n sender?.replaceTrack(track ?? null);\n\n this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);\n this.deleteFeedByStream(this.localScreensharingStream!);\n\n return false;\n }\n }\n\n /**\n * Replaces/adds the tracks from the passed stream to the localUsermediaStream\n * @param stream - to use a replacement for the local usermedia stream\n */\n public async updateLocalUsermediaStream(\n stream: MediaStream,\n forceAudio = false,\n forceVideo = false,\n ): Promise<void> {\n const callFeed = this.localUsermediaFeed!;\n const audioEnabled = forceAudio || (!callFeed.isAudioMuted() && !this.remoteOnHold);\n const videoEnabled = forceVideo || (!callFeed.isVideoMuted() && !this.remoteOnHold);\n logger.log(\n `Call ${this.callId} updateLocalUsermediaStream() running (streamId=${stream.id}, audio=${audioEnabled}, video=${videoEnabled})`,\n );\n setTracksEnabled(stream.getAudioTracks(), audioEnabled);\n setTracksEnabled(stream.getVideoTracks(), videoEnabled);\n\n // We want to keep the same stream id, so we replace the tracks rather\n // than the whole stream.\n\n // Firstly, we replace the tracks in our localUsermediaStream.\n for (const track of this.localUsermediaStream!.getTracks()) {\n this.localUsermediaStream!.removeTrack(track);\n track.stop();\n }\n for (const track of stream.getTracks()) {\n this.localUsermediaStream!.addTrack(track);\n }\n\n // Then replace the old tracks, if possible.\n for (const track of stream.getTracks()) {\n const tKey = getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, track.kind);\n\n const transceiver = this.transceivers.get(tKey);\n const oldSender = transceiver?.sender;\n let added = false;\n if (oldSender) {\n try {\n logger.info(\n `Call ${this.callId} updateLocalUsermediaStream() replacing track (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`,\n );\n await oldSender.replaceTrack(track);\n // Set the direction to indicate we're going to be sending.\n // This is only necessary in the cases where we're upgrading\n // the call to video after downgrading it.\n transceiver.direction = transceiver.direction === \"inactive\" ? \"sendonly\" : \"sendrecv\";\n added = true;\n } catch (error) {\n logger.warn(\n `Call ${this.callId} updateLocalUsermediaStream() replaceTrack failed: adding new transceiver instead`,\n error,\n );\n }\n }\n\n if (!added) {\n logger.info(\n `Call ${this.callId} updateLocalUsermediaStream() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`,\n );\n\n const newSender = this.peerConn!.addTrack(track, this.localUsermediaStream!);\n const newTransceiver = this.peerConn!.getTransceivers().find((t) => t.sender === newSender);\n if (newTransceiver) {\n this.transceivers.set(tKey, newTransceiver);\n } else {\n logger.warn(\n `Call ${this.callId} updateLocalUsermediaStream() couldn't find matching transceiver for newly added track!`,\n );\n }\n }\n }\n }\n\n /**\n * Set whether our outbound video should be muted or not.\n * @param muted - True to mute the outbound video.\n * @returns the new mute state\n */\n public async setLocalVideoMuted(muted: boolean): Promise<boolean> {\n logger.log(`Call ${this.callId} setLocalVideoMuted() running ${muted}`);\n\n // if we were still thinking about stopping and removing the video\n // track: don't, because we want it back.\n if (!muted && this.stopVideoTrackTimer !== undefined) {\n clearTimeout(this.stopVideoTrackTimer);\n this.stopVideoTrackTimer = undefined;\n }\n\n if (!(await this.client.getMediaHandler().hasVideoDevice())) {\n return this.isLocalVideoMuted();\n }\n\n if (!this.hasUserMediaVideoSender && !muted) {\n this.localUsermediaFeed?.setAudioVideoMuted(null, muted);\n await this.upgradeCall(false, true);\n return this.isLocalVideoMuted();\n }\n\n // we may not have a video track - if not, re-request usermedia\n if (!muted && this.localUsermediaStream!.getVideoTracks().length === 0) {\n const stream = await this.client.getMediaHandler().getUserMediaStream(true, true);\n await this.updateLocalUsermediaStream(stream);\n }\n\n this.localUsermediaFeed?.setAudioVideoMuted(null, muted);\n\n this.updateMuteStatus();\n await this.sendMetadataUpdate();\n\n // if we're muting video, set a timeout to stop & remove the video track so we release\n // the camera. We wait a short time to do this because when we disable a track, WebRTC\n // will send black video for it. If we just stop and remove it straight away, the video\n // will just freeze which means that when we unmute video, the other side will briefly\n // get a static frame of us from before we muted. This way, the still frame is just black.\n // A very small delay is not always enough so the theory here is that it needs to be long\n // enough for WebRTC to encode a frame: 120ms should be long enough even if we're only\n // doing 10fps.\n if (muted) {\n this.stopVideoTrackTimer = setTimeout(() => {\n for (const t of this.localUsermediaStream!.getVideoTracks()) {\n t.stop();\n this.localUsermediaStream!.removeTrack(t);\n }\n }, 120);\n }\n\n return this.isLocalVideoMuted();\n }\n\n /**\n * Check if local video is muted.\n *\n * If there are multiple video tracks, <i>all</i> of the tracks need to be muted\n * for this to return true. This means if there are no video tracks, this will\n * return true.\n * @returns True if the local preview video is muted, else false\n * (including if the call is not set up yet).\n */\n public isLocalVideoMuted(): boolean {\n return this.localUsermediaFeed?.isVideoMuted() ?? false;\n }\n\n /**\n * Set whether the microphone should be muted or not.\n * @param muted - True to mute the mic.\n * @returns the new mute state\n */\n public async setMicrophoneMuted(muted: boolean): Promise<boolean> {\n logger.log(`Call ${this.callId} setMicrophoneMuted() running ${muted}`);\n if (!(await this.client.getMediaHandler().hasAudioDevice())) {\n return this.isMicrophoneMuted();\n }\n\n if (!muted && (!this.hasUserMediaAudioSender || !this.hasLocalUserMediaAudioTrack)) {\n await this.upgradeCall(true, false);\n return this.isMicrophoneMuted();\n }\n this.localUsermediaFeed?.setAudioVideoMuted(muted, null);\n this.updateMuteStatus();\n await this.sendMetadataUpdate();\n return this.isMicrophoneMuted();\n }\n\n /**\n * Check if the microphone is muted.\n *\n * If there are multiple audio tracks, <i>all</i> of the tracks need to be muted\n * for this to return true. This means if there are no audio tracks, this will\n * return true.\n * @returns True if the mic is muted, else false (including if the call\n * is not set up yet).\n */\n public isMicrophoneMuted(): boolean {\n return this.localUsermediaFeed?.isAudioMuted() ?? false;\n }\n\n /**\n * @returns true if we have put the party on the other side of the call on hold\n * (that is, we are signalling to them that we are not listening)\n */\n public isRemoteOnHold(): boolean {\n return this.remoteOnHold;\n }\n\n public setRemoteOnHold(onHold: boolean): void {\n if (this.isRemoteOnHold() === onHold) return;\n this.remoteOnHold = onHold;\n\n for (const transceiver of this.peerConn!.getTransceivers()) {\n // We don't send hold music or anything so we're not actually\n // sending anything, but sendrecv is fairly standard for hold and\n // it makes it a lot easier to figure out who's put who on hold.\n transceiver.direction = onHold ? \"sendonly\" : \"sendrecv\";\n }\n this.updateMuteStatus();\n this.sendMetadataUpdate();\n\n this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold, this);\n }\n\n /**\n * Indicates whether we are 'on hold' to the remote party (ie. if true,\n * they cannot hear us).\n * @returns true if the other party has put us on hold\n */\n public isLocalOnHold(): boolean {\n if (this.state !== CallState.Connected) return false;\n\n let callOnHold = true;\n\n // We consider a call to be on hold only if *all* the tracks are on hold\n // (is this the right thing to do?)\n for (const transceiver of this.peerConn!.getTransceivers()) {\n const trackOnHold = [\"inactive\", \"recvonly\"].includes(transceiver.currentDirection!);\n\n if (!trackOnHold) callOnHold = false;\n }\n\n return callOnHold;\n }\n\n /**\n * Sends a DTMF digit to the other party\n * @param digit - The digit (nb. string - '#' and '*' are dtmf too)\n */\n public sendDtmfDigit(digit: string): void {\n for (const sender of this.peerConn!.getSenders()) {\n if (sender.track?.kind === \"audio\" && sender.dtmf) {\n sender.dtmf.insertDTMF(digit);\n return;\n }\n }\n\n throw new Error(\"Unable to find a track to send DTMF on\");\n }\n\n private updateMuteStatus(): void {\n const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold;\n const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold;\n\n logger.log(\n `Call ${this.callId} updateMuteStatus stream ${\n this.localUsermediaStream!.id\n } micShouldBeMuted ${micShouldBeMuted} vidShouldBeMuted ${vidShouldBeMuted}`,\n );\n\n setTracksEnabled(this.localUsermediaStream!.getAudioTracks(), !micShouldBeMuted);\n setTracksEnabled(this.localUsermediaStream!.getVideoTracks(), !vidShouldBeMuted);\n }\n\n public async sendMetadataUpdate(): Promise<void> {\n await this.sendVoipEvent(EventType.CallSDPStreamMetadataChangedPrefix, {\n [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(),\n });\n }\n\n private gotCallFeedsForInvite(callFeeds: CallFeed[], requestScreenshareFeed = false): void {\n if (this.successor) {\n this.successor.queueGotCallFeedsForAnswer(callFeeds);\n return;\n }\n if (this.callHasEnded()) {\n this.stopAllMedia();\n return;\n }\n\n for (const feed of callFeeds) {\n this.pushLocalFeed(feed);\n }\n\n if (requestScreenshareFeed) {\n this.peerConn!.addTransceiver(\"video\", {\n direction: \"recvonly\",\n });\n }\n\n this.state = CallState.CreateOffer;\n\n logger.debug(`Call ${this.callId} gotUserMediaForInvite() run`);\n // Now we wait for the negotiationneeded event\n }\n\n private async sendAnswer(): Promise<void> {\n const answerContent = {\n answer: {\n sdp: this.peerConn!.localDescription!.sdp,\n // type is now deprecated as of Matrix VoIP v1, but\n // required to still be sent for backwards compat\n type: this.peerConn!.localDescription!.type,\n },\n [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true),\n } as MCallAnswer;\n\n answerContent.capabilities = {\n \"m.call.transferee\": this.client.supportsCallTransfer,\n \"m.call.dtmf\": false,\n };\n\n // We have just taken the local description from the peerConn which will\n // contain all the local candidates added so far, so we can discard any candidates\n // we had queued up because they'll be in the answer.\n const discardCount = this.discardDuplicateCandidates();\n logger.info(\n `Call ${this.callId} sendAnswer() discarding ${discardCount} candidates that will be sent in answer`,\n );\n\n try {\n await this.sendVoipEvent(EventType.CallAnswer, answerContent);\n // If this isn't the first time we've tried to send the answer,\n // we may have candidates queued up, so send them now.\n this.inviteOrAnswerSent = true;\n } catch (error) {\n // We've failed to answer: back to the ringing state\n this.state = CallState.Ringing;\n if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);\n\n let code = CallErrorCode.SendAnswer;\n let message = \"Failed to send answer\";\n if ((<Error>error).name == \"UnknownDeviceError\") {\n code = CallErrorCode.UnknownDevices;\n message = \"Unknown devices present in the room\";\n }\n this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);\n throw error;\n }\n\n // error handler re-throws so this won't happen on error, but\n // we don't want the same error handling on the candidate queue\n this.sendCandidateQueue();\n }\n\n private queueGotCallFeedsForAnswer(callFeeds: CallFeed[]): void {\n // Ensure only one negotiate/answer event is being processed at a time.\n if (this.responsePromiseChain) {\n this.responsePromiseChain = this.responsePromiseChain.then(() => this.gotCallFeedsForAnswer(callFeeds));\n } else {\n this.responsePromiseChain = this.gotCallFeedsForAnswer(callFeeds);\n }\n }\n\n // Enables DTX (discontinuous transmission) on the given session to reduce\n // bandwidth when transmitting silence\n private mungeSdp(description: RTCSessionDescriptionInit, mods: CodecParamsMod[]): void {\n // The only way to enable DTX at this time is through SDP munging\n const sdp = parseSdp(description.sdp!);\n\n sdp.media.forEach((media) => {\n const payloadTypeToCodecMap = new Map<number, string>();\n const codecToPayloadTypeMap = new Map<string, number>();\n for (const rtp of media.rtp) {\n payloadTypeToCodecMap.set(rtp.payload, rtp.codec);\n codecToPayloadTypeMap.set(rtp.codec, rtp.payload);\n }\n\n for (const mod of mods) {\n if (mod.mediaType !== media.type) continue;\n\n if (!codecToPayloadTypeMap.has(mod.codec)) {\n logger.info(\n `Call ${this.callId} mungeSdp() ignoring SDP modifications for ${mod.codec} as it's not present.`,\n );\n continue;\n }\n\n const extraConfig: string[] = [];\n if (mod.enableDtx !== undefined) {\n extraConfig.push(`usedtx=${mod.enableDtx ? \"1\" : \"0\"}`);\n }\n if (mod.maxAverageBitrate !== undefined) {\n extraConfig.push(`maxaveragebitrate=${mod.maxAverageBitrate}`);\n }\n\n let found = false;\n for (const fmtp of media.fmtp) {\n if (payloadTypeToCodecMap.get(fmtp.payload) === mod.codec) {\n found = true;\n fmtp.config += \";\" + extraConfig.join(\";\");\n }\n }\n if (!found) {\n media.fmtp.push({\n payload: codecToPayloadTypeMap.get(mod.codec)!,\n config: extraConfig.join(\";\"),\n });\n }\n }\n });\n description.sdp = writeSdp(sdp);\n }\n\n private async createOffer(): Promise<RTCSessionDescriptionInit> {\n const offer = await this.peerConn!.createOffer();\n this.mungeSdp(offer, getCodecParamMods(this.isPtt));\n return offer;\n }\n\n private async createAnswer(): Promise<RTCSessionDescriptionInit> {\n const answer = await this.peerConn!.createAnswer();\n this.mungeSdp(answer, getCodecParamMods(this.isPtt));\n return answer;\n }\n\n private async gotCallFeedsForAnswer(callFeeds: CallFeed[]): Promise<void> {\n if (this.callHasEnded()) return;\n\n this.waitForLocalAVStream = false;\n\n for (const feed of callFeeds) {\n this.pushLocalFeed(feed);\n }\n\n this.state = CallState.CreateAnswer;\n\n let answer: RTCSessionDescriptionInit;\n try {\n this.getRidOfRTXCodecs();\n answer = await this.createAnswer();\n } catch (err) {\n logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() failed to create answer: `, err);\n this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true);\n return;\n }\n\n try {\n await this.peerConn!.setLocalDescription(answer);\n\n // make sure we're still going\n if (this.callHasEnded()) return;\n\n this.state = CallState.Connecting;\n\n // Allow a short time for initial candidates to be gathered\n await new Promise((resolve) => {\n setTimeout(resolve, 200);\n });\n\n // make sure the call hasn't ended before we continue\n if (this.callHasEnded()) return;\n\n this.sendAnswer();\n } catch (err) {\n logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() error setting local description!`, err);\n this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);\n return;\n }\n }\n\n /**\n * Internal\n */\n private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent): void => {\n if (event.candidate) {\n if (this.candidatesEnded) {\n logger.warn(\n `Call ${this.callId} gotLocalIceCandidate() got candidate after candidates have ended - ignoring!`,\n );\n return;\n }\n\n logger.debug(`Call ${this.callId} got local ICE ${event.candidate.sdpMid} ${event.candidate.candidate}`);\n\n if (this.callHasEnded()) return;\n\n // As with the offer, note we need to make a copy of this object, not\n // pass the original: that broke in Chrome ~m43.\n if (event.candidate.candidate === \"\") {\n this.queueCandidate(null);\n } else {\n this.queueCandidate(event.candidate);\n }\n }\n };\n\n private onIceGatheringStateChange = (event: Event): void => {\n logger.debug(\n `Call ${this.callId} onIceGatheringStateChange() ice gathering state changed to ${\n this.peerConn!.iceGatheringState\n }`,\n );\n if (this.peerConn?.iceGatheringState === \"complete\") {\n this.queueCandidate(null);\n }\n };\n\n public async onRemoteIceCandidatesReceived(ev: MatrixEvent): Promise<void> {\n if (this.callHasEnded()) {\n //debuglog(\"Ignoring remote ICE candidate because call has ended\");\n return;\n }\n\n const content = ev.getContent<MCallCandidates>();\n const candidates = content.candidates;\n if (!candidates) {\n logger.info(\n `Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates event with no candidates!`,\n );\n return;\n }\n\n const fromPartyId = content.version === 0 ? null : content.party_id || null;\n\n if (this.opponentPartyId === undefined) {\n // we haven't picked an opponent yet so save the candidates\n if (fromPartyId) {\n logger.info(\n `Call ${this.callId} onRemoteIceCandidatesReceived() buffering ${candidates.length} candidates until we pick an opponent`,\n );\n const bufferedCandidates = this.remoteCandidateBuffer.get(fromPartyId) || [];\n bufferedCandidates.push(...candidates);\n this.remoteCandidateBuffer.set(fromPartyId, bufferedCandidates);\n }\n return;\n }\n\n if (!this.partyIdMatches(content)) {\n logger.info(\n `Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates from party ID ${content.party_id}: we have chosen party ID ${this.opponentPartyId}`,\n );\n\n return;\n }\n\n await this.addIceCandidates(candidates);\n }\n\n /**\n * Used by MatrixClient.\n */\n public async onAnswerReceived(event: MatrixEvent): Promise<void> {\n const content = event.getContent<MCallAnswer>();\n logger.debug(`Call ${this.callId} onAnswerReceived() running (hangupParty=${content.party_id})`);\n\n if (this.callHasEnded()) {\n logger.debug(`Call ${this.callId} onAnswerReceived() ignoring answer because call has ended`);\n return;\n }\n\n if (this.opponentPartyId !== undefined) {\n logger.info(\n `Call ${this.callId} onAnswerReceived() ignoring answer from party ID ${content.party_id}: we already have an answer/reject from ${this.opponentPartyId}`,\n );\n return;\n }\n\n this.chooseOpponent(event);\n await this.addBufferedIceCandidates();\n\n this.state = CallState.Connecting;\n\n const sdpStreamMetadata = content[SDPStreamMetadataKey];\n if (sdpStreamMetadata) {\n this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);\n } else {\n logger.warn(\n `Call ${this.callId} onAnswerReceived() did not get any SDPStreamMetadata! Can not send/receive multiple streams`,\n );\n }\n\n try {\n await this.peerConn!.setRemoteDescription(content.answer);\n } catch (e) {\n logger.debug(`Call ${this.callId} onAnswerReceived() failed to set remote description`, e);\n this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);\n return;\n }\n\n // If the answer we selected has a party_id, send a select_answer event\n // We do this after setting the remote description since otherwise we'd block\n // call setup on it\n if (this.opponentPartyId !== null) {\n try {\n await this.sendVoipEvent(EventType.CallSelectAnswer, {\n selected_party_id: this.opponentPartyId,\n });\n } catch (err) {\n // This isn't fatal, and will just mean that if another party has raced to answer\n // the call, they won't know they got rejected, so we carry on & don't retry.\n logger.warn(`Call ${this.callId} onAnswerReceived() failed to send select_answer event`, err);\n }\n }\n }\n\n public async onSelectAnswerReceived(event: MatrixEvent): Promise<void> {\n if (this.direction !== CallDirection.Inbound) {\n logger.warn(\n `Call ${this.callId} onSelectAnswerReceived() got select_answer for an outbound call: ignoring`,\n );\n return;\n }\n\n const selectedPartyId = event.getContent<MCallSelectAnswer>().selected_party_id;\n\n if (selectedPartyId === undefined || selectedPartyId === null) {\n logger.warn(\n `Call ${this.callId} onSelectAnswerReceived() got nonsensical select_answer with null/undefined selected_party_id: ignoring`,\n );\n return;\n }\n\n if (selectedPartyId !== this.ourPartyId) {\n logger.info(\n `Call ${this.callId} onSelectAnswerReceived() got select_answer for party ID ${selectedPartyId}: we are party ID ${this.ourPartyId}.`,\n );\n // The other party has picked somebody else's answer\n await this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true);\n }\n }\n\n public async onNegotiateReceived(event: MatrixEvent): Promise<void> {\n const content = event.getContent<MCallInviteNegotiate>();\n const description = content.description;\n if (!description || !description.sdp || !description.type) {\n logger.info(`Call ${this.callId} onNegotiateReceived() ignoring invalid m.call.negotiate event`);\n return;\n }\n // Politeness always follows the direction of the call: in a glare situation,\n // we pick either the inbound or outbound call, so one side will always be\n // inbound and one outbound\n const polite = this.direction === CallDirection.Inbound;\n\n // Here we follow the perfect negotiation logic from\n // https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation\n const offerCollision =\n description.type === \"offer\" && (this.makingOffer || this.peerConn!.signalingState !== \"stable\");\n\n this.ignoreOffer = !polite && offerCollision;\n if (this.ignoreOffer) {\n logger.info(\n `Call ${this.callId} onNegotiateReceived() ignoring colliding negotiate event because we're impolite`,\n );\n return;\n }\n\n const prevLocalOnHold = this.isLocalOnHold();\n\n const sdpStreamMetadata = content[SDPStreamMetadataKey];\n if (sdpStreamMetadata) {\n this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);\n } else {\n logger.warn(\n `Call ${this.callId} onNegotiateReceived() received negotiation event without SDPStreamMetadata!`,\n );\n }\n\n try {\n await this.peerConn!.setRemoteDescription(description);\n\n if (description.type === \"offer\") {\n let answer: RTCSessionDescriptionInit;\n try {\n this.getRidOfRTXCodecs();\n answer = await this.createAnswer();\n } catch (err) {\n logger.debug(`Call ${this.callId} onNegotiateReceived() failed to create answer: `, err);\n this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true);\n return;\n }\n\n await this.peerConn!.setLocalDescription(answer);\n\n this.sendVoipEvent(EventType.CallNegotiate, {\n description: this.peerConn!.localDescription?.toJSON(),\n [SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(true),\n });\n }\n } catch (err) {\n logger.warn(`Call ${this.callId} onNegotiateReceived() failed to complete negotiation`, err);\n }\n\n const newLocalOnHold = this.isLocalOnHold();\n if (prevLocalOnHold !== newLocalOnHold) {\n this.emit(CallEvent.LocalHoldUnhold, newLocalOnHold, this);\n // also this one for backwards compat\n this.emit(CallEvent.HoldUnhold, newLocalOnHold);\n }\n }\n\n private updateRemoteSDPStreamMetadata(metadata: SDPStreamMetadata): void {\n this.remoteSDPStreamMetadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true);\n for (const feed of this.getRemoteFeeds()) {\n const streamId = feed.stream.id;\n const metadata = this.remoteSDPStreamMetadata![streamId];\n\n feed.setAudioVideoMuted(metadata?.audio_muted, metadata?.video_muted);\n feed.purpose = this.remoteSDPStreamMetadata![streamId]?.purpose;\n }\n }\n\n public onSDPStreamMetadataChangedReceived(event: MatrixEvent): void {\n const content = event.getContent<MCallSDPStreamMetadataChanged>();\n const metadata = content[SDPStreamMetadataKey];\n this.updateRemoteSDPStreamMetadata(metadata);\n }\n\n public async onAssertedIdentityReceived(event: MatrixEvent): Promise<void> {\n const content = event.getContent<MCAllAssertedIdentity>();\n if (!content.asserted_identity) return;\n\n this.remoteAssertedIdentity = {\n id: content.asserted_identity.id,\n displayName: content.asserted_identity.display_name,\n };\n this.emit(CallEvent.AssertedIdentityChanged, this);\n }\n\n public callHasEnded(): boolean {\n // This exists as workaround to typescript trying to be clever and erroring\n // when putting if (this.state === CallState.Ended) return; twice in the same\n // function, even though that function is async.\n return this.state === CallState.Ended;\n }\n\n private queueGotLocalOffer(): void {\n // Ensure only one negotiate/answer event is being processed at a time.\n if (this.responsePromiseChain) {\n this.responsePromiseChain = this.responsePromiseChain.then(() => this.wrappedGotLocalOffer());\n } else {\n this.responsePromiseChain = this.wrappedGotLocalOffer();\n }\n }\n\n private async wrappedGotLocalOffer(): Promise<void> {\n this.makingOffer = true;\n try {\n // XXX: in what situations do we believe gotLocalOffer actually throws? It appears\n // to handle most of its exceptions itself and terminate the call. I'm not entirely\n // sure it would ever throw, so I can't add a test for these lines.\n // Also the tense is different between \"gotLocalOffer\" and \"getLocalOfferFailed\" so\n // it's not entirely clear whether getLocalOfferFailed is just misnamed or whether\n // they've been cross-polinated somehow at some point.\n await this.gotLocalOffer();\n } catch (e) {\n this.getLocalOfferFailed(e as Error);\n return;\n } finally {\n this.makingOffer = false;\n }\n }\n\n private async gotLocalOffer(): Promise<void> {\n logger.debug(`Call ${this.callId} gotLocalOffer() running`);\n\n if (this.callHasEnded()) {\n logger.debug(\n `Call ${this.callId} gotLocalOffer() ignoring newly created offer because the call has ended\"`,\n );\n return;\n }\n\n let offer: RTCSessionDescriptionInit;\n try {\n this.getRidOfRTXCodecs();\n offer = await this.createOffer();\n } catch (err) {\n logger.debug(`Call ${this.callId} gotLocalOffer() failed to create offer: `, err);\n this.terminate(CallParty.Local, CallErrorCode.CreateOffer, true);\n return;\n }\n\n try {\n await this.peerConn!.setLocalDescription(offer);\n } catch (err) {\n logger.debug(`Call ${this.callId} gotLocalOffer() error setting local description!`, err);\n this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);\n return;\n }\n\n if (this.peerConn!.iceGatheringState === \"gathering\") {\n // Allow a short time for initial candidates to be gathered\n await new Promise((resolve) => {\n setTimeout(resolve, 200);\n });\n }\n\n if (this.callHasEnded()) return;\n\n const eventType = this.state === CallState.CreateOffer ? EventType.CallInvite : EventType.CallNegotiate;\n\n const content = {\n lifetime: CALL_TIMEOUT_MS,\n } as MCallInviteNegotiate;\n\n if (eventType === EventType.CallInvite && this.invitee) {\n content.invitee = this.invitee;\n }\n\n // clunky because TypeScript can't follow the types through if we use an expression as the key\n if (this.state === CallState.CreateOffer) {\n content.offer = this.peerConn!.localDescription?.toJSON();\n } else {\n content.description = this.peerConn!.localDescription?.toJSON();\n }\n\n content.capabilities = {\n \"m.call.transferee\": this.client.supportsCallTransfer,\n \"m.call.dtmf\": false,\n };\n\n content[SDPStreamMetadataKey] = this.getLocalSDPStreamMetadata(true);\n\n // Get rid of any candidates waiting to be sent: they'll be included in the local\n // description we just got and will send in the offer.\n const discardCount = this.discardDuplicateCandidates();\n logger.info(\n `Call ${this.callId} gotLocalOffer() discarding ${discardCount} candidates that will be sent in offer`,\n );\n\n try {\n await this.sendVoipEvent(eventType, content);\n } catch (error) {\n logger.error(`Call ${this.callId} gotLocalOffer() failed to send invite`, error);\n if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);\n\n let code = CallErrorCode.SignallingFailed;\n let message = \"Signalling failed\";\n if (this.state === CallState.CreateOffer) {\n code = CallErrorCode.SendInvite;\n message = \"Failed to send invite\";\n }\n if ((<Error>error).name == \"UnknownDeviceError\") {\n code = CallErrorCode.UnknownDevices;\n message = \"Unknown devices present in the room\";\n }\n\n this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);\n this.terminate(CallParty.Local, code, false);\n\n // no need to carry on & send the candidate queue, but we also\n // don't want to rethrow the error\n return;\n }\n\n this.sendCandidateQueue();\n if (this.state === CallState.CreateOffer) {\n this.inviteOrAnswerSent = true;\n this.state = CallState.InviteSent;\n this.inviteTimeout = setTimeout(() => {\n this.inviteTimeout = undefined;\n if (this.state === CallState.InviteSent) {\n this.hangup(CallErrorCode.InviteTimeout, false);\n }\n }, CALL_TIMEOUT_MS);\n }\n }\n\n private getLocalOfferFailed = (err: Error): void => {\n logger.error(`Call ${this.callId} getLocalOfferFailed() running`, err);\n\n this.emit(\n CallEvent.Error,\n new CallError(CallErrorCode.LocalOfferFailed, \"Failed to get local offer!\", err),\n this,\n );\n this.terminate(CallParty.Local, CallErrorCode.LocalOfferFailed, false);\n };\n\n private getUserMediaFailed = (err: Error): void => {\n if (this.successor) {\n this.successor.getUserMediaFailed(err);\n return;\n }\n\n logger.warn(`Call ${this.callId} getUserMediaFailed() failed to get user media - ending call`, err);\n\n this.emit(\n CallEvent.Error,\n new CallError(\n CallErrorCode.NoUserMedia,\n \"Couldn't start capturing media! Is your microphone set up and \" + \"does this app have permission?\",\n err,\n ),\n this,\n );\n this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false);\n };\n\n private onIceConnectionStateChanged = (): void => {\n if (this.callHasEnded()) {\n return; // because ICE can still complete as we're ending the call\n }\n logger.debug(\n `Call ${this.callId} onIceConnectionStateChanged() running (state=${this.peerConn?.iceConnectionState})`,\n );\n\n // ideally we'd consider the call to be connected when we get media but\n // chrome doesn't implement any of the 'onstarted' events yet\n if ([\"connected\", \"completed\"].includes(this.peerConn?.iceConnectionState ?? \"\")) {\n clearTimeout(this.iceDisconnectedTimeout);\n this.iceDisconnectedTimeout = undefined;\n this.state = CallState.Connected;\n\n if (!this.callLengthInterval && !this.callStartTime) {\n this.callStartTime = Date.now();\n\n this.callLengthInterval = setInterval(() => {\n this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime!) / 1000), this);\n }, CALL_LENGTH_INTERVAL);\n }\n } else if (this.peerConn?.iceConnectionState == \"failed\") {\n // Firefox for Android does not yet have support for restartIce()\n // (the types say it's always defined though, so we have to cast\n // to prevent typescript from warning).\n if (this.peerConn?.restartIce as (() => void) | null) {\n this.candidatesEnded = false;\n this.peerConn!.restartIce();\n } else {\n logger.info(\n `Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE failed and no ICE restart method)`,\n );\n this.hangup(CallErrorCode.IceFailed, false);\n }\n } else if (this.peerConn?.iceConnectionState == \"disconnected\") {\n this.iceDisconnectedTimeout = setTimeout(() => {\n logger.info(\n `Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE disconnected for too long)`,\n );\n this.hangup(CallErrorCode.IceFailed, false);\n }, ICE_DISCONNECTED_TIMEOUT);\n this.state = CallState.Connecting;\n }\n\n // In PTT mode, override feed status to muted when we lose connection to\n // the peer, since we don't want to block the line if they're not saying anything.\n // Experimenting in Chrome, this happens after 5 or 6 seconds, which is probably\n // fast enough.\n if (this.isPtt && [\"failed\", \"disconnected\"].includes(this.peerConn!.iceConnectionState)) {\n for (const feed of this.getRemoteFeeds()) {\n feed.setAudioVideoMuted(true, true);\n }\n }\n };\n\n private onSignallingStateChanged = (): void => {\n logger.debug(`Call ${this.callId} onSignallingStateChanged() running (state=${this.peerConn?.signalingState})`);\n };\n\n private onTrack = (ev: RTCTrackEvent): void => {\n if (ev.streams.length === 0) {\n logger.warn(\n `Call ${this.callId} onTrack() called with streamless track streamless (kind=${ev.track.kind})`,\n );\n return;\n }\n\n const stream = ev.streams[0];\n this.pushRemoteFeed(stream);\n\n if (!this.removeTrackListeners.has(stream)) {\n const onRemoveTrack = (): void => {\n if (stream.getTracks().length === 0) {\n logger.info(`Call ${this.callId} onTrack() removing track (streamId=${stream.id})`);\n this.deleteFeedByStream(stream);\n stream.removeEventListener(\"removetrack\", onRemoveTrack);\n this.removeTrackListeners.delete(stream);\n }\n };\n stream.addEventListener(\"removetrack\", onRemoveTrack);\n this.removeTrackListeners.set(stream, onRemoveTrack);\n }\n };\n\n private onDataChannel = (ev: RTCDataChannelEvent): void => {\n this.emit(CallEvent.DataChannel, ev.channel, this);\n };\n\n /**\n * This method removes all video/rtx codecs from screensharing video\n * transceivers. This is necessary since they can cause problems. Without\n * this the following steps should produce an error:\n * Chromium calls Firefox\n * Firefox answers\n * Firefox starts screen-sharing\n * Chromium starts screen-sharing\n * Call crashes for Chromium with:\n * [96685:23:0518/162603.933321:ERROR:webrtc_video_engine.cc(3296)] RTX codec (PT=97) mapped to PT=96 which is not in the codec list.\n * [96685:23:0518/162603.933377:ERROR:webrtc_video_engine.cc(1171)] GetChangedRecvParameters called without any video codecs.\n * [96685:23:0518/162603.933430:ERROR:sdp_offer_answer.cc(4302)] Failed to set local video description recv parameters for m-section with mid='2'. (INVALID_PARAMETER)\n */\n private getRidOfRTXCodecs(): void {\n // RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF\n if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) return;\n\n const recvCodecs = RTCRtpReceiver.getCapabilities(\"video\")!.codecs;\n const sendCodecs = RTCRtpSender.getCapabilities(\"video\")!.codecs;\n const codecs = [...sendCodecs, ...recvCodecs];\n\n for (const codec of codecs) {\n if (codec.mimeType === \"video/rtx\") {\n const rtxCodecIndex = codecs.indexOf(codec);\n codecs.splice(rtxCodecIndex, 1);\n }\n }\n\n const screenshareVideoTransceiver = this.transceivers.get(\n getTransceiverKey(SDPStreamMetadataPurpose.Screenshare, \"video\"),\n );\n if (screenshareVideoTransceiver) screenshareVideoTransceiver.setCodecPreferences(codecs);\n }\n\n private onNegotiationNeeded = async (): Promise<void> => {\n logger.info(`Call ${this.callId} onNegotiationNeeded() negotiation is needed!`);\n\n if (this.state !== CallState.CreateOffer && this.opponentVersion === 0) {\n logger.info(\n `Call ${this.callId} onNegotiationNeeded() opponent does not support renegotiation: ignoring negotiationneeded event`,\n );\n return;\n }\n\n this.queueGotLocalOffer();\n };\n\n public onHangupReceived = (msg: MCallHangupReject): void => {\n logger.debug(`Call ${this.callId} onHangupReceived() running`);\n\n // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen\n // a partner yet but we're treating the hangup as a reject as per VoIP v0)\n if (this.partyIdMatches(msg) || this.state === CallState.Ringing) {\n // default reason is user_hangup\n this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true);\n } else {\n logger.info(\n `Call ${this.callId} onHangupReceived() ignoring message from party ID ${msg.party_id}: our partner is ${this.opponentPartyId}`,\n );\n }\n };\n\n public onRejectReceived = (msg: MCallHangupReject): void => {\n logger.debug(`Call ${this.callId} onRejectReceived() running`);\n\n // No need to check party_id for reject because if we'd received either\n // an answer or reject, we wouldn't be in state InviteSent\n\n const shouldTerminate =\n // reject events also end the call if it's ringing: it's another of\n // our devices rejecting the call.\n [CallState.InviteSent, CallState.Ringing].includes(this.state) ||\n // also if we're in the init state and it's an inbound call, since\n // this means we just haven't entered the ringing state yet\n (this.state === CallState.Fledgling && this.direction === CallDirection.Inbound);\n\n if (shouldTerminate) {\n this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true);\n } else {\n logger.debug(`Call ${this.callId} onRejectReceived() called in wrong state (state=${this.state})`);\n }\n };\n\n public onAnsweredElsewhere = (msg: MCallAnswer): void => {\n logger.debug(`Call ${this.callId} onAnsweredElsewhere() running`);\n this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true);\n };\n\n /**\n * @internal\n */\n private async sendVoipEvent(eventType: string, content: object): Promise<void> {\n const realContent = Object.assign({}, content, {\n version: VOIP_PROTO_VERSION,\n call_id: this.callId,\n party_id: this.ourPartyId,\n conf_id: this.groupCallId,\n });\n\n if (this.opponentDeviceId) {\n const toDeviceSeq = this.toDeviceSeq++;\n const content = {\n ...realContent,\n device_id: this.client.deviceId,\n sender_session_id: this.client.getSessionId(),\n dest_session_id: this.opponentSessionId,\n seq: toDeviceSeq,\n [ToDeviceMessageId]: uuidv4(),\n };\n\n this.emit(\n CallEvent.SendVoipEvent,\n {\n type: \"toDevice\",\n eventType,\n userId: this.invitee || this.getOpponentMember()?.userId,\n opponentDeviceId: this.opponentDeviceId,\n content,\n },\n this,\n );\n\n const userId = this.invitee || this.getOpponentMember()!.userId;\n if (this.client.getUseE2eForGroupCall()) {\n if (!this.opponentDeviceInfo) {\n logger.warn(`Call ${this.callId} sendVoipEvent() failed: we do not have opponentDeviceInfo`);\n return;\n }\n\n await this.client.encryptAndSendToDevices(\n [\n {\n userId,\n deviceInfo: this.opponentDeviceInfo,\n },\n ],\n {\n type: eventType,\n content,\n },\n );\n } else {\n await this.client.sendToDevice(\n eventType,\n new Map<string, any>([[userId, new Map([[this.opponentDeviceId, content]])]]),\n );\n }\n } else {\n this.emit(\n CallEvent.SendVoipEvent,\n {\n type: \"sendEvent\",\n eventType,\n roomId: this.roomId,\n content: realContent,\n userId: this.invitee || this.getOpponentMember()?.userId,\n },\n this,\n );\n\n await this.client.sendEvent(this.roomId!, eventType, realContent);\n }\n }\n\n /**\n * Queue a candidate to be sent\n * @param content - The candidate to queue up, or null if candidates have finished being generated\n * and end-of-candidates should be signalled\n */\n private queueCandidate(content: RTCIceCandidate | null): void {\n // We partially de-trickle candidates by waiting for `delay` before sending them\n // amalgamated, in order to avoid sending too many m.call.candidates events and hitting\n // rate limits in Matrix.\n // In practice, it'd be better to remove rate limits for m.call.*\n\n // N.B. this deliberately lets you queue and send blank candidates, which MSC2746\n // currently proposes as the way to indicate that candidate gathering is complete.\n // This will hopefully be changed to an explicit rather than implicit notification\n // shortly.\n if (content) {\n this.candidateSendQueue.push(content);\n } else {\n this.candidatesEnded = true;\n }\n\n // Don't send the ICE candidates yet if the call is in the ringing state: this\n // means we tried to pick (ie. started generating candidates) and then failed to\n // send the answer and went back to the ringing state. Queue up the candidates\n // to send if we successfully send the answer.\n // Equally don't send if we haven't yet sent the answer because we can send the\n // first batch of candidates along with the answer\n if (this.state === CallState.Ringing || !this.inviteOrAnswerSent) return;\n\n // MSC2746 recommends these values (can be quite long when calling because the\n // callee will need a while to answer the call)\n const delay = this.direction === CallDirection.Inbound ? 500 : 2000;\n\n if (this.candidateSendTries === 0) {\n setTimeout(() => {\n this.sendCandidateQueue();\n }, delay);\n }\n }\n\n // Discard all non-end-of-candidates messages\n // Return the number of candidate messages that were discarded.\n // Call this method before sending an invite or answer message\n private discardDuplicateCandidates(): number {\n let discardCount = 0;\n const newQueue: RTCIceCandidate[] = [];\n\n for (let i = 0; i < this.candidateSendQueue.length; i++) {\n const candidate = this.candidateSendQueue[i];\n if (candidate.candidate === \"\") {\n newQueue.push(candidate);\n } else {\n discardCount++;\n }\n }\n\n this.candidateSendQueue = newQueue;\n\n return discardCount;\n }\n\n /*\n * Transfers this call to another user\n */\n public async transfer(targetUserId: string): Promise<void> {\n // Fetch the target user's global profile info: their room avatar / displayname\n // could be different in whatever room we share with them.\n const profileInfo = await this.client.getProfileInfo(targetUserId);\n\n const replacementId = genCallID();\n\n const body = {\n replacement_id: genCallID(),\n target_user: {\n id: targetUserId,\n display_name: profileInfo.displayname,\n avatar_url: profileInfo.avatar_url,\n },\n create_call: replacementId,\n } as MCallReplacesEvent;\n\n await this.sendVoipEvent(EventType.CallReplaces, body);\n\n await this.terminate(CallParty.Local, CallErrorCode.Transferred, true);\n }\n\n /*\n * Transfers this call to the target call, effectively 'joining' the\n * two calls (so the remote parties on each call are connected together).\n */\n public async transferToCall(transferTargetCall: MatrixCall): Promise<void> {\n const targetUserId = transferTargetCall.getOpponentMember()?.userId;\n const targetProfileInfo = targetUserId ? await this.client.getProfileInfo(targetUserId) : undefined;\n const opponentUserId = this.getOpponentMember()?.userId;\n const transfereeProfileInfo = opponentUserId ? await this.client.getProfileInfo(opponentUserId) : undefined;\n\n const newCallId = genCallID();\n\n const bodyToTransferTarget = {\n // the replacements on each side have their own ID, and it's distinct from the\n // ID of the new call (but we can use the same function to generate it)\n replacement_id: genCallID(),\n target_user: {\n id: opponentUserId,\n display_name: transfereeProfileInfo?.displayname,\n avatar_url: transfereeProfileInfo?.avatar_url,\n },\n await_call: newCallId,\n } as MCallReplacesEvent;\n\n await transferTargetCall.sendVoipEvent(EventType.CallReplaces, bodyToTransferTarget);\n\n const bodyToTransferee = {\n replacement_id: genCallID(),\n target_user: {\n id: targetUserId,\n display_name: targetProfileInfo?.displayname,\n avatar_url: targetProfileInfo?.avatar_url,\n },\n create_call: newCallId,\n } as MCallReplacesEvent;\n\n await this.sendVoipEvent(EventType.CallReplaces, bodyToTransferee);\n\n await this.terminate(CallParty.Local, CallErrorCode.Transferred, true);\n await transferTargetCall.terminate(CallParty.Local, CallErrorCode.Transferred, true);\n }\n\n private async terminate(hangupParty: CallParty, hangupReason: CallErrorCode, shouldEmit: boolean): Promise<void> {\n if (this.callHasEnded()) return;\n\n this.hangupParty = hangupParty;\n this.hangupReason = hangupReason;\n this.state = CallState.Ended;\n\n if (this.inviteTimeout) {\n clearTimeout(this.inviteTimeout);\n this.inviteTimeout = undefined;\n }\n if (this.iceDisconnectedTimeout !== undefined) {\n clearTimeout(this.iceDisconnectedTimeout);\n this.iceDisconnectedTimeout = undefined;\n }\n if (this.callLengthInterval) {\n clearInterval(this.callLengthInterval);\n this.callLengthInterval = undefined;\n }\n if (this.stopVideoTrackTimer !== undefined) {\n clearTimeout(this.stopVideoTrackTimer);\n this.stopVideoTrackTimer = undefined;\n }\n\n for (const [stream, listener] of this.removeTrackListeners) {\n stream.removeEventListener(\"removetrack\", listener);\n }\n this.removeTrackListeners.clear();\n\n this.callStatsAtEnd = await this.collectCallStats();\n\n // Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds()\n this.stopAllMedia();\n this.deleteAllFeeds();\n\n if (this.peerConn && this.peerConn.signalingState !== \"closed\") {\n this.peerConn.close();\n }\n this.stats?.removeStatsReportGatherer(this.callId);\n\n if (shouldEmit) {\n this.emit(CallEvent.Hangup, this);\n }\n\n this.client.callEventHandler!.calls.delete(this.callId);\n }\n\n private stopAllMedia(): void {\n logger.debug(`Call ${this.callId} stopAllMedia() running`);\n\n for (const feed of this.feeds) {\n // Slightly awkward as local feed need to go via the correct method on\n // the MediaHandler so they get removed from MediaHandler (remote tracks\n // don't)\n // NB. We clone local streams when passing them to individual calls in a group\n // call, so we can (and should) stop the clones once we no longer need them:\n // the other clones will continue fine.\n if (feed.isLocal() && feed.purpose === SDPStreamMetadataPurpose.Usermedia) {\n this.client.getMediaHandler().stopUserMediaStream(feed.stream);\n } else if (feed.isLocal() && feed.purpose === SDPStreamMetadataPurpose.Screenshare) {\n this.client.getMediaHandler().stopScreensharingStream(feed.stream);\n } else if (!feed.isLocal()) {\n logger.debug(`Call ${this.callId} stopAllMedia() stopping stream (streamId=${feed.stream.id})`);\n for (const track of feed.stream.getTracks()) {\n track.stop();\n }\n }\n }\n }\n\n private checkForErrorListener(): void {\n if (this.listeners(EventEmitterEvents.Error).length === 0) {\n throw new Error(\"You MUST attach an error listener using call.on('error', function() {})\");\n }\n }\n\n private async sendCandidateQueue(): Promise<void> {\n if (this.candidateSendQueue.length === 0 || this.callHasEnded()) {\n return;\n }\n\n const candidates = this.candidateSendQueue;\n this.candidateSendQueue = [];\n ++this.candidateSendTries;\n const content = { candidates: candidates.map((candidate) => candidate.toJSON()) };\n if (this.candidatesEnded) {\n // If there are no more candidates, signal this by adding an empty string candidate\n content.candidates.push({\n candidate: \"\",\n });\n }\n logger.debug(`Call ${this.callId} sendCandidateQueue() attempting to send ${candidates.length} candidates`);\n try {\n await this.sendVoipEvent(EventType.CallCandidates, content);\n // reset our retry count if we have successfully sent our candidates\n // otherwise queueCandidate() will refuse to try to flush the queue\n this.candidateSendTries = 0;\n\n // Try to send candidates again just in case we received more candidates while sending.\n this.sendCandidateQueue();\n } catch (error) {\n // don't retry this event: we'll send another one later as we might\n // have more candidates by then.\n if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);\n\n // put all the candidates we failed to send back in the queue\n this.candidateSendQueue.push(...candidates);\n\n if (this.candidateSendTries > 5) {\n logger.debug(\n `Call ${this.callId} sendCandidateQueue() failed to send candidates on attempt ${this.candidateSendTries}. Giving up on this call.`,\n error,\n );\n\n const code = CallErrorCode.SignallingFailed;\n const message = \"Signalling failed\";\n\n this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);\n this.hangup(code, false);\n\n return;\n }\n\n const delayMs = 500 * Math.pow(2, this.candidateSendTries);\n ++this.candidateSendTries;\n logger.debug(\n `Call ${this.callId} sendCandidateQueue() failed to send candidates. Retrying in ${delayMs}ms`,\n error,\n );\n setTimeout(() => {\n this.sendCandidateQueue();\n }, delayMs);\n }\n }\n\n /**\n * Place a call to this room.\n * @throws if you have not specified a listener for 'error' events.\n * @throws if have passed audio=false.\n */\n public async placeCall(audio: boolean, video: boolean): Promise<void> {\n if (!audio) {\n throw new Error(\"You CANNOT start a call without audio\");\n }\n this.state = CallState.WaitLocalMedia;\n\n try {\n const stream = await this.client.getMediaHandler().getUserMediaStream(audio, video);\n\n // make sure all the tracks are enabled (same as pushNewLocalFeed -\n // we probably ought to just have one code path for adding streams)\n setTracksEnabled(stream.getAudioTracks(), true);\n setTracksEnabled(stream.getVideoTracks(), true);\n\n const callFeed = new CallFeed({\n client: this.client,\n roomId: this.roomId,\n userId: this.client.getUserId()!,\n deviceId: this.client.getDeviceId() ?? undefined,\n stream,\n purpose: SDPStreamMetadataPurpose.Usermedia,\n audioMuted: false,\n videoMuted: false,\n });\n await this.placeCallWithCallFeeds([callFeed]);\n } catch (e) {\n this.getUserMediaFailed(<Error>e);\n return;\n }\n }\n\n /**\n * Place a call to this room with call feed.\n * @param callFeeds - to use\n * @throws if you have not specified a listener for 'error' events.\n * @throws if have passed audio=false.\n */\n public async placeCallWithCallFeeds(callFeeds: CallFeed[], requestScreenshareFeed = false): Promise<void> {\n this.checkForErrorListener();\n this.direction = CallDirection.Outbound;\n\n await this.initOpponentCrypto();\n\n // XXX Find a better way to do this\n this.client.callEventHandler!.calls.set(this.callId, this);\n\n // make sure we have valid turn creds. Unless something's gone wrong, it should\n // poll and keep the credentials valid so this should be instant.\n const haveTurnCreds = await this.client.checkTurnServers();\n if (!haveTurnCreds) {\n logger.warn(\n `Call ${this.callId} placeCallWithCallFeeds() failed to get TURN credentials! Proceeding with call anyway...`,\n );\n }\n\n // create the peer connection now so it can be gathering candidates while we get user\n // media (assuming a candidate pool size is configured)\n this.peerConn = this.createPeerConnection();\n this.gotCallFeedsForInvite(callFeeds, requestScreenshareFeed);\n }\n\n private createPeerConnection(): RTCPeerConnection {\n const pc = new window.RTCPeerConnection({\n iceTransportPolicy: this.forceTURN ? \"relay\" : undefined,\n iceServers: this.turnServers,\n iceCandidatePoolSize: this.client.iceCandidatePoolSize,\n bundlePolicy: \"max-bundle\",\n });\n\n // 'connectionstatechange' would be better, but firefox doesn't implement that.\n pc.addEventListener(\"iceconnectionstatechange\", this.onIceConnectionStateChanged);\n pc.addEventListener(\"signalingstatechange\", this.onSignallingStateChanged);\n pc.addEventListener(\"icecandidate\", this.gotLocalIceCandidate);\n pc.addEventListener(\"icegatheringstatechange\", this.onIceGatheringStateChange);\n pc.addEventListener(\"track\", this.onTrack);\n pc.addEventListener(\"negotiationneeded\", this.onNegotiationNeeded);\n pc.addEventListener(\"datachannel\", this.onDataChannel);\n\n this.stats?.addStatsReportGatherer(this.callId, \"unknown\", pc);\n return pc;\n }\n\n private partyIdMatches(msg: MCallBase): boolean {\n // They must either match or both be absent (in which case opponentPartyId will be null)\n // Also we ignore party IDs on the invite/offer if the version is 0, so we must do the same\n // here and use null if the version is 0 (woe betide any opponent sending messages in the\n // same call with different versions)\n const msgPartyId = msg.version === 0 ? null : msg.party_id || null;\n return msgPartyId === this.opponentPartyId;\n }\n\n // Commits to an opponent for the call\n // ev: An invite or answer event\n private chooseOpponent(ev: MatrixEvent): void {\n // I choo-choo-choose you\n const msg = ev.getContent<MCallInviteNegotiate | MCallAnswer>();\n\n logger.debug(`Call ${this.callId} chooseOpponent() running (partyId=${msg.party_id})`);\n\n this.opponentVersion = msg.version;\n if (this.opponentVersion === 0) {\n // set to null to indicate that we've chosen an opponent, but because\n // they're v0 they have no party ID (even if they sent one, we're ignoring it)\n this.opponentPartyId = null;\n } else {\n // set to their party ID, or if they're naughty and didn't send one despite\n // not being v0, set it to null to indicate we picked an opponent with no\n // party ID\n this.opponentPartyId = msg.party_id || null;\n }\n this.opponentCaps = msg.capabilities || ({} as CallCapabilities);\n this.opponentMember = this.client.getRoom(this.roomId)!.getMember(ev.getSender()!) ?? undefined;\n }\n\n private async addBufferedIceCandidates(): Promise<void> {\n const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId!);\n if (bufferedCandidates) {\n logger.info(\n `Call ${this.callId} addBufferedIceCandidates() adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`,\n );\n await this.addIceCandidates(bufferedCandidates);\n }\n this.remoteCandidateBuffer.clear();\n }\n\n private async addIceCandidates(candidates: RTCIceCandidate[]): Promise<void> {\n for (const candidate of candidates) {\n if (\n (candidate.sdpMid === null || candidate.sdpMid === undefined) &&\n (candidate.sdpMLineIndex === null || candidate.sdpMLineIndex === undefined)\n ) {\n logger.debug(`Call ${this.callId} addIceCandidates() got remote ICE end-of-candidates`);\n } else {\n logger.debug(\n `Call ${this.callId} addIceCandidates() got remote ICE candidate (sdpMid=${candidate.sdpMid}, candidate=${candidate.candidate})`,\n );\n }\n\n try {\n await this.peerConn!.addIceCandidate(candidate);\n } catch (err) {\n if (!this.ignoreOffer) {\n logger.info(`Call ${this.callId} addIceCandidates() failed to add remote ICE candidate`, err);\n }\n }\n }\n }\n\n public get hasPeerConnection(): boolean {\n return Boolean(this.peerConn);\n }\n\n public initStats(stats: GroupCallStats, peerId = \"unknown\"): void {\n this.stats = stats;\n this.stats.start();\n }\n}\n\nexport function setTracksEnabled(tracks: Array<MediaStreamTrack>, enabled: boolean): void {\n for (const track of tracks) {\n track.enabled = enabled;\n }\n}\n\nexport function supportsMatrixCall(): boolean {\n // typeof prevents Node from erroring on an undefined reference\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n // NB. We don't log here as apps try to create a call object as a test for\n // whether calls are supported, so we shouldn't fill the logs up.\n return false;\n }\n\n // Firefox throws on so little as accessing the RTCPeerConnection when operating in a secure mode.\n // There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616 though the concern\n // is that the browser throwing a SecurityError will brick the client creation process.\n try {\n const supported = Boolean(\n window.RTCPeerConnection ||\n window.RTCSessionDescription ||\n window.RTCIceCandidate ||\n navigator.mediaDevices,\n );\n if (!supported) {\n /* istanbul ignore if */ // Adds a lot of noise to test runs, so disable logging there.\n if (process.env.NODE_ENV !== \"test\") {\n logger.error(\"WebRTC is not supported in this browser / environment\");\n }\n return false;\n }\n } catch (e) {\n logger.error(\"Exception thrown when trying to access WebRTC\", e);\n return false;\n }\n\n return true;\n}\n\n/**\n * DEPRECATED\n * Use client.createCall()\n *\n * Create a new Matrix call for the browser.\n * @param client - The client instance to use.\n * @param roomId - The room the call is in.\n * @param options - DEPRECATED optional options map.\n * @returns the call or null if the browser doesn't support calling.\n */\nexport function createNewMatrixCall(\n client: MatrixClient,\n roomId: string,\n options?: Pick<CallOpts, \"forceTURN\" | \"invitee\" | \"opponentDeviceId\" | \"opponentSessionId\" | \"groupCallId\">,\n): MatrixCall | null {\n if (!supportsMatrixCall()) return null;\n\n const optionsForceTURN = options ? options.forceTURN : false;\n\n const opts: CallOpts = {\n client: client,\n roomId: roomId,\n invitee: options?.invitee,\n turnServers: client.getTurnServers(),\n // call level options\n forceTURN: client.forceTURN || optionsForceTURN,\n opponentDeviceId: options?.opponentDeviceId,\n opponentSessionId: options?.opponentSessionId,\n groupCallId: options?.groupCallId,\n };\n const call = new MatrixCall(opts);\n\n client.reEmitter.reEmit(call, Object.values(CallEvent));\n\n return call;\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.d.ts new file mode 100644 index 0000000..249a518 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.d.ts @@ -0,0 +1,37 @@ +import { MatrixEvent } from "../models/event"; +import { MatrixCall } from "./call"; +import { MatrixClient } from "../client"; +export declare enum CallEventHandlerEvent { + Incoming = "Call.incoming" +} +export type CallEventHandlerEventHandlerMap = { + /** + * Fires whenever an incoming call arrives. + * @param call - The incoming call. + * @example + * ``` + * matrixClient.on("Call.incoming", function(call){ + * call.answer(); // auto-answer + * }); + * ``` + */ + [CallEventHandlerEvent.Incoming]: (call: MatrixCall) => void; +}; +export declare class CallEventHandler { + calls: Map<string, MatrixCall>; + callEventBuffer: MatrixEvent[]; + nextSeqByCall: Map<string, number>; + toDeviceEventBuffers: Map<string, Array<MatrixEvent>>; + private client; + private candidateEventsByCall; + private eventBufferPromiseChain?; + constructor(client: MatrixClient); + start(): void; + stop(): void; + private onSync; + private evaluateEventBuffer; + private onRoomTimeline; + private onToDeviceEvent; + private handleCallEvent; +} +//# sourceMappingURL=callEventHandler.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.d.ts.map new file mode 100644 index 0000000..88bf9a3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"callEventHandler.d.ts","sourceRoot":"","sources":["../../src/webrtc/callEventHandler.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAA2E,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE7G,OAAO,EAAe,YAAY,EAAE,MAAM,WAAW,CAAC;AAStD,oBAAY,qBAAqB;IAC7B,QAAQ,kBAAkB;CAC7B;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC1C;;;;;;;;;OASG;IACH,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF,qBAAa,gBAAgB;IAElB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/B,eAAe,EAAE,WAAW,EAAE,CAAC;IAC/B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAC/C,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAa;IAEzE,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,qBAAqB,CAAkC;IAC/D,OAAO,CAAC,uBAAuB,CAAC,CAAgB;gBAE7B,MAAM,EAAE,YAAY;IAehC,KAAK,IAAI,IAAI;IAMb,IAAI,IAAI,IAAI;IAMnB,OAAO,CAAC,MAAM,CAaZ;YAEY,mBAAmB;IAsCjC,OAAO,CAAC,cAAc,CAEpB;IAEF,OAAO,CAAC,eAAe,CA+CrB;YAEY,eAAe;CAyOhC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.js new file mode 100644 index 0000000..5e3c242 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.js @@ -0,0 +1,347 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.CallEventHandlerEvent = exports.CallEventHandler = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _logger = require("../logger"); +var _call = require("./call"); +var _event = require("../@types/event"); +var _client = require("../client"); +var _groupCall3 = require("./groupCall"); +var _room = require("../models/room"); +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Don't ring unless we'd be ringing for at least 3 seconds: the user needs some +// time to press the 'accept' button +const RING_GRACE_PERIOD = 3000; +let CallEventHandlerEvent; +exports.CallEventHandlerEvent = CallEventHandlerEvent; +(function (CallEventHandlerEvent) { + CallEventHandlerEvent["Incoming"] = "Call.incoming"; +})(CallEventHandlerEvent || (exports.CallEventHandlerEvent = CallEventHandlerEvent = {})); +class CallEventHandler { + // XXX: Most of these are only public because of the tests + + constructor(client) { + (0, _defineProperty2.default)(this, "calls", void 0); + (0, _defineProperty2.default)(this, "callEventBuffer", void 0); + (0, _defineProperty2.default)(this, "nextSeqByCall", new Map()); + (0, _defineProperty2.default)(this, "toDeviceEventBuffers", new Map()); + (0, _defineProperty2.default)(this, "client", void 0); + (0, _defineProperty2.default)(this, "candidateEventsByCall", void 0); + (0, _defineProperty2.default)(this, "eventBufferPromiseChain", void 0); + (0, _defineProperty2.default)(this, "onSync", () => { + // Process the current event buffer and start queuing into a new one. + const currentEventBuffer = this.callEventBuffer; + this.callEventBuffer = []; + + // Ensure correct ordering by only processing this queue after the previous one has finished processing + if (this.eventBufferPromiseChain) { + this.eventBufferPromiseChain = this.eventBufferPromiseChain.then(() => this.evaluateEventBuffer(currentEventBuffer)); + } else { + this.eventBufferPromiseChain = this.evaluateEventBuffer(currentEventBuffer); + } + }); + (0, _defineProperty2.default)(this, "onRoomTimeline", event => { + this.callEventBuffer.push(event); + }); + (0, _defineProperty2.default)(this, "onToDeviceEvent", event => { + const content = event.getContent(); + if (!content.call_id) { + this.callEventBuffer.push(event); + return; + } + if (!this.nextSeqByCall.has(content.call_id)) { + this.nextSeqByCall.set(content.call_id, 0); + } + if (content.seq === undefined) { + this.callEventBuffer.push(event); + return; + } + const nextSeq = this.nextSeqByCall.get(content.call_id) || 0; + if (content.seq !== nextSeq) { + if (!this.toDeviceEventBuffers.has(content.call_id)) { + this.toDeviceEventBuffers.set(content.call_id, []); + } + const buffer = this.toDeviceEventBuffers.get(content.call_id); + const index = buffer.findIndex(e => e.getContent().seq > content.seq); + if (index === -1) { + buffer.push(event); + } else { + buffer.splice(index, 0, event); + } + } else { + const callId = content.call_id; + this.callEventBuffer.push(event); + this.nextSeqByCall.set(callId, content.seq + 1); + const buffer = this.toDeviceEventBuffers.get(callId); + let nextEvent = buffer && buffer.shift(); + while (nextEvent && nextEvent.getContent().seq === this.nextSeqByCall.get(callId)) { + this.callEventBuffer.push(nextEvent); + this.nextSeqByCall.set(callId, nextEvent.getContent().seq + 1); + nextEvent = buffer.shift(); + } + } + }); + this.client = client; + this.calls = new Map(); + // The sync code always emits one event at a time, so it will patiently + // wait for us to finish processing a call invite before delivering the + // next event, even if that next event is a hangup. We therefore accumulate + // all our call events and then process them on the 'sync' event, ie. + // each time a sync has completed. This way, we can avoid emitting incoming + // call events if we get both the invite and answer/hangup in the same sync. + // This happens quite often, eg. replaying sync from storage, catchup sync + // after loading and after we've been offline for a bit. + this.callEventBuffer = []; + this.candidateEventsByCall = new Map(); + } + start() { + this.client.on(_client.ClientEvent.Sync, this.onSync); + this.client.on(_room.RoomEvent.Timeline, this.onRoomTimeline); + this.client.on(_client.ClientEvent.ToDeviceEvent, this.onToDeviceEvent); + } + stop() { + this.client.removeListener(_client.ClientEvent.Sync, this.onSync); + this.client.removeListener(_room.RoomEvent.Timeline, this.onRoomTimeline); + this.client.removeListener(_client.ClientEvent.ToDeviceEvent, this.onToDeviceEvent); + } + async evaluateEventBuffer(eventBuffer) { + await Promise.all(eventBuffer.map(event => this.client.decryptEventIfNeeded(event))); + const callEvents = eventBuffer.filter(event => { + const eventType = event.getType(); + return eventType.startsWith("m.call.") || eventType.startsWith("org.matrix.call."); + }); + const ignoreCallIds = new Set(); + + // inspect the buffer and mark all calls which have been answered + // or hung up before passing them to the call event handler. + for (const event of callEvents) { + const eventType = event.getType(); + if (eventType === _event.EventType.CallAnswer || eventType === _event.EventType.CallHangup) { + ignoreCallIds.add(event.getContent().call_id); + } + } + + // Process call events in the order that they were received + for (const event of callEvents) { + const eventType = event.getType(); + const callId = event.getContent().call_id; + if (eventType === _event.EventType.CallInvite && ignoreCallIds.has(callId)) { + // This call has previously been answered or hung up: ignore it + continue; + } + try { + await this.handleCallEvent(event); + } catch (e) { + _logger.logger.error("CallEventHandler evaluateEventBuffer() caught exception handling call event", e); + } + } + } + async handleCallEvent(event) { + var _getGroupCallById, _getGroupCallById$roo; + this.client.emit(_client.ClientEvent.ReceivedVoipEvent, event); + const content = event.getContent(); + const callRoomId = event.getRoomId() || ((_getGroupCallById = this.client.groupCallEventHandler.getGroupCallById(content.conf_id)) === null || _getGroupCallById === void 0 ? void 0 : (_getGroupCallById$roo = _getGroupCallById.room) === null || _getGroupCallById$roo === void 0 ? void 0 : _getGroupCallById$roo.roomId); + const groupCallId = content.conf_id; + const type = event.getType(); + const senderId = event.getSender(); + let call = content.call_id ? this.calls.get(content.call_id) : undefined; + let opponentDeviceId; + let groupCall; + if (groupCallId) { + groupCall = this.client.groupCallEventHandler.getGroupCallById(groupCallId); + if (!groupCall) { + _logger.logger.warn(`CallEventHandler handleCallEvent() could not find a group call - ignoring event (groupCallId=${groupCallId}, type=${type})`); + return; + } + opponentDeviceId = content.device_id; + if (!opponentDeviceId) { + _logger.logger.warn(`CallEventHandler handleCallEvent() could not find a device id - ignoring event (senderId=${senderId})`); + groupCall.emit(_groupCall3.GroupCallEvent.Error, new _groupCall3.GroupCallUnknownDeviceError(senderId)); + return; + } + if (content.dest_session_id !== this.client.getSessionId()) { + _logger.logger.warn("CallEventHandler handleCallEvent() call event does not match current session id - ignoring"); + return; + } + } + const weSentTheEvent = senderId === this.client.credentials.userId && (opponentDeviceId === undefined || opponentDeviceId === this.client.getDeviceId()); + if (!callRoomId) return; + if (type === _event.EventType.CallInvite) { + var _this$client$getTurnS, _createNewMatrixCall, _groupCall; + // ignore invites you send + if (weSentTheEvent) return; + // expired call + if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) return; + // stale/old invite event + if (call && call.state === _call.CallState.Ended) return; + if (call) { + _logger.logger.warn(`CallEventHandler handleCallEvent() already has a call but got an invite - clobbering (callId=${content.call_id})`); + } + if (content.invitee && content.invitee !== this.client.getUserId()) { + return; // This invite was meant for another user in the room + } + + const timeUntilTurnCresExpire = ((_this$client$getTurnS = this.client.getTurnServersExpiry()) !== null && _this$client$getTurnS !== void 0 ? _this$client$getTurnS : 0) - Date.now(); + _logger.logger.info("CallEventHandler handleCallEvent() current turn creds expire in " + timeUntilTurnCresExpire + " ms"); + call = (_createNewMatrixCall = (0, _call.createNewMatrixCall)(this.client, callRoomId, { + forceTURN: this.client.forceTURN, + opponentDeviceId, + groupCallId, + opponentSessionId: content.sender_session_id + })) !== null && _createNewMatrixCall !== void 0 ? _createNewMatrixCall : undefined; + if (!call) { + _logger.logger.log(`CallEventHandler handleCallEvent() this client does not support WebRTC (callId=${content.call_id})`); + // don't hang up the call: there could be other clients + // connected that do support WebRTC and declining the + // the call on their behalf would be really annoying. + return; + } + call.callId = content.call_id; + const stats = (_groupCall = groupCall) === null || _groupCall === void 0 ? void 0 : _groupCall.getGroupCallStats(); + if (stats) { + call.initStats(stats); + } + try { + await call.initWithInvite(event); + } catch (e) { + if (e instanceof _call.CallError) { + if (e.code === _groupCall3.GroupCallErrorCode.UnknownDevice) { + var _groupCall2; + (_groupCall2 = groupCall) === null || _groupCall2 === void 0 ? void 0 : _groupCall2.emit(_groupCall3.GroupCallEvent.Error, e); + } else { + _logger.logger.error(e); + } + } + } + this.calls.set(call.callId, call); + + // if we stashed candidate events for that call ID, play them back now + if (this.candidateEventsByCall.get(call.callId)) { + for (const ev of this.candidateEventsByCall.get(call.callId)) { + call.onRemoteIceCandidatesReceived(ev); + } + } + + // Were we trying to call that user (room)? + let existingCall; + for (const thisCall of this.calls.values()) { + var _call$getOpponentMemb; + const isCalling = [_call.CallState.WaitLocalMedia, _call.CallState.CreateOffer, _call.CallState.InviteSent].includes(thisCall.state); + if (call.roomId === thisCall.roomId && thisCall.direction === _call.CallDirection.Outbound && ((_call$getOpponentMemb = call.getOpponentMember()) === null || _call$getOpponentMemb === void 0 ? void 0 : _call$getOpponentMemb.userId) === thisCall.invitee && isCalling) { + existingCall = thisCall; + break; + } + } + if (existingCall) { + if (existingCall.callId > call.callId) { + _logger.logger.log(`CallEventHandler handleCallEvent() detected glare - answering incoming call and canceling outgoing call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`); + existingCall.replacedBy(call); + } else { + _logger.logger.log(`CallEventHandler handleCallEvent() detected glare - hanging up incoming call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`); + call.hangup(_call.CallErrorCode.Replaced, true); + } + } else { + this.client.emit(CallEventHandlerEvent.Incoming, call); + } + return; + } else if (type === _event.EventType.CallCandidates) { + if (weSentTheEvent) return; + if (!call) { + // store the candidates; we may get a call eventually. + if (!this.candidateEventsByCall.has(content.call_id)) { + this.candidateEventsByCall.set(content.call_id, []); + } + this.candidateEventsByCall.get(content.call_id).push(event); + } else { + call.onRemoteIceCandidatesReceived(event); + } + return; + } else if ([_event.EventType.CallHangup, _event.EventType.CallReject].includes(type)) { + // Note that we also observe our own hangups here so we can see + // if we've already rejected a call that would otherwise be valid + if (!call) { + var _createNewMatrixCall2; + // if not live, store the fact that the call has ended because + // we're probably getting events backwards so + // the hangup will come before the invite + call = (_createNewMatrixCall2 = (0, _call.createNewMatrixCall)(this.client, callRoomId, { + opponentDeviceId, + opponentSessionId: content.sender_session_id + })) !== null && _createNewMatrixCall2 !== void 0 ? _createNewMatrixCall2 : undefined; + if (call) { + call.callId = content.call_id; + call.initWithHangup(event); + this.calls.set(content.call_id, call); + } + } else { + if (call.state !== _call.CallState.Ended) { + if (type === _event.EventType.CallHangup) { + call.onHangupReceived(content); + } else { + call.onRejectReceived(content); + } + + // @ts-expect-error typescript thinks the state can't be 'ended' because we're + // inside the if block where it wasn't, but it could have changed because + // on[Hangup|Reject]Received are side-effecty. + if (call.state === _call.CallState.Ended) this.calls.delete(content.call_id); + } + } + return; + } + + // The following events need a call and a peer connection + if (!call || !call.hasPeerConnection) { + _logger.logger.info(`CallEventHandler handleCallEvent() discarding possible call event as we don't have a call (type=${type})`); + return; + } + // Ignore remote echo + if (event.getContent().party_id === call.ourPartyId) return; + switch (type) { + case _event.EventType.CallAnswer: + if (weSentTheEvent) { + if (call.state === _call.CallState.Ringing) { + call.onAnsweredElsewhere(content); + } + } else { + call.onAnswerReceived(event); + } + break; + case _event.EventType.CallSelectAnswer: + call.onSelectAnswerReceived(event); + break; + case _event.EventType.CallNegotiate: + call.onNegotiateReceived(event); + break; + case _event.EventType.CallAssertedIdentity: + case _event.EventType.CallAssertedIdentityPrefix: + call.onAssertedIdentityReceived(event); + break; + case _event.EventType.CallSDPStreamMetadataChanged: + case _event.EventType.CallSDPStreamMetadataChangedPrefix: + call.onSDPStreamMetadataChangedReceived(event); + break; + } + } +} +exports.CallEventHandler = CallEventHandler; +//# sourceMappingURL=callEventHandler.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.js.map new file mode 100644 index 0000000..ea92d1a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"callEventHandler.js","names":["_logger","require","_call","_event","_client","_groupCall3","_room","RING_GRACE_PERIOD","CallEventHandlerEvent","exports","CallEventHandler","constructor","client","_defineProperty2","default","Map","currentEventBuffer","callEventBuffer","eventBufferPromiseChain","then","evaluateEventBuffer","event","push","content","getContent","call_id","nextSeqByCall","has","set","seq","undefined","nextSeq","get","toDeviceEventBuffers","buffer","index","findIndex","e","splice","callId","nextEvent","shift","calls","candidateEventsByCall","start","on","ClientEvent","Sync","onSync","RoomEvent","Timeline","onRoomTimeline","ToDeviceEvent","onToDeviceEvent","stop","removeListener","eventBuffer","Promise","all","map","decryptEventIfNeeded","callEvents","filter","eventType","getType","startsWith","ignoreCallIds","Set","EventType","CallAnswer","CallHangup","add","CallInvite","handleCallEvent","logger","error","_getGroupCallById","_getGroupCallById$roo","emit","ReceivedVoipEvent","callRoomId","getRoomId","groupCallEventHandler","getGroupCallById","conf_id","room","roomId","groupCallId","type","senderId","getSender","call","opponentDeviceId","groupCall","warn","device_id","GroupCallEvent","Error","GroupCallUnknownDeviceError","dest_session_id","getSessionId","weSentTheEvent","credentials","userId","getDeviceId","_this$client$getTurnS","_createNewMatrixCall","_groupCall","getLocalAge","lifetime","state","CallState","Ended","invitee","getUserId","timeUntilTurnCresExpire","getTurnServersExpiry","Date","now","info","createNewMatrixCall","forceTURN","opponentSessionId","sender_session_id","log","stats","getGroupCallStats","initStats","initWithInvite","CallError","code","GroupCallErrorCode","UnknownDevice","_groupCall2","ev","onRemoteIceCandidatesReceived","existingCall","thisCall","values","_call$getOpponentMemb","isCalling","WaitLocalMedia","CreateOffer","InviteSent","includes","direction","CallDirection","Outbound","getOpponentMember","replacedBy","hangup","CallErrorCode","Replaced","Incoming","CallCandidates","CallReject","_createNewMatrixCall2","initWithHangup","onHangupReceived","onRejectReceived","delete","hasPeerConnection","party_id","ourPartyId","Ringing","onAnsweredElsewhere","onAnswerReceived","CallSelectAnswer","onSelectAnswerReceived","CallNegotiate","onNegotiateReceived","CallAssertedIdentity","CallAssertedIdentityPrefix","onAssertedIdentityReceived","CallSDPStreamMetadataChanged","CallSDPStreamMetadataChangedPrefix","onSDPStreamMetadataChangedReceived"],"sources":["../../src/webrtc/callEventHandler.ts"],"sourcesContent":["/*\nCopyright 2020 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { MatrixEvent } from \"../models/event\";\nimport { logger } from \"../logger\";\nimport { CallDirection, CallError, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from \"./call\";\nimport { EventType } from \"../@types/event\";\nimport { ClientEvent, MatrixClient } from \"../client\";\nimport { MCallAnswer, MCallHangupReject } from \"./callEventTypes\";\nimport { GroupCall, GroupCallErrorCode, GroupCallEvent, GroupCallUnknownDeviceError } from \"./groupCall\";\nimport { RoomEvent } from \"../models/room\";\n\n// Don't ring unless we'd be ringing for at least 3 seconds: the user needs some\n// time to press the 'accept' button\nconst RING_GRACE_PERIOD = 3000;\n\nexport enum CallEventHandlerEvent {\n Incoming = \"Call.incoming\",\n}\n\nexport type CallEventHandlerEventHandlerMap = {\n /**\n * Fires whenever an incoming call arrives.\n * @param call - The incoming call.\n * @example\n * ```\n * matrixClient.on(\"Call.incoming\", function(call){\n * call.answer(); // auto-answer\n * });\n * ```\n */\n [CallEventHandlerEvent.Incoming]: (call: MatrixCall) => void;\n};\n\nexport class CallEventHandler {\n // XXX: Most of these are only public because of the tests\n public calls: Map<string, MatrixCall>;\n public callEventBuffer: MatrixEvent[];\n public nextSeqByCall: Map<string, number> = new Map();\n public toDeviceEventBuffers: Map<string, Array<MatrixEvent>> = new Map();\n\n private client: MatrixClient;\n private candidateEventsByCall: Map<string, Array<MatrixEvent>>;\n private eventBufferPromiseChain?: Promise<void>;\n\n public constructor(client: MatrixClient) {\n this.client = client;\n this.calls = new Map<string, MatrixCall>();\n // The sync code always emits one event at a time, so it will patiently\n // wait for us to finish processing a call invite before delivering the\n // next event, even if that next event is a hangup. We therefore accumulate\n // all our call events and then process them on the 'sync' event, ie.\n // each time a sync has completed. This way, we can avoid emitting incoming\n // call events if we get both the invite and answer/hangup in the same sync.\n // This happens quite often, eg. replaying sync from storage, catchup sync\n // after loading and after we've been offline for a bit.\n this.callEventBuffer = [];\n this.candidateEventsByCall = new Map<string, Array<MatrixEvent>>();\n }\n\n public start(): void {\n this.client.on(ClientEvent.Sync, this.onSync);\n this.client.on(RoomEvent.Timeline, this.onRoomTimeline);\n this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);\n }\n\n public stop(): void {\n this.client.removeListener(ClientEvent.Sync, this.onSync);\n this.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);\n this.client.removeListener(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);\n }\n\n private onSync = (): void => {\n // Process the current event buffer and start queuing into a new one.\n const currentEventBuffer = this.callEventBuffer;\n this.callEventBuffer = [];\n\n // Ensure correct ordering by only processing this queue after the previous one has finished processing\n if (this.eventBufferPromiseChain) {\n this.eventBufferPromiseChain = this.eventBufferPromiseChain.then(() =>\n this.evaluateEventBuffer(currentEventBuffer),\n );\n } else {\n this.eventBufferPromiseChain = this.evaluateEventBuffer(currentEventBuffer);\n }\n };\n\n private async evaluateEventBuffer(eventBuffer: MatrixEvent[]): Promise<void> {\n await Promise.all(eventBuffer.map((event) => this.client.decryptEventIfNeeded(event)));\n\n const callEvents = eventBuffer.filter((event) => {\n const eventType = event.getType();\n return eventType.startsWith(\"m.call.\") || eventType.startsWith(\"org.matrix.call.\");\n });\n\n const ignoreCallIds = new Set<string>();\n\n // inspect the buffer and mark all calls which have been answered\n // or hung up before passing them to the call event handler.\n for (const event of callEvents) {\n const eventType = event.getType();\n\n if (eventType === EventType.CallAnswer || eventType === EventType.CallHangup) {\n ignoreCallIds.add(event.getContent().call_id);\n }\n }\n\n // Process call events in the order that they were received\n for (const event of callEvents) {\n const eventType = event.getType();\n const callId = event.getContent().call_id;\n\n if (eventType === EventType.CallInvite && ignoreCallIds.has(callId)) {\n // This call has previously been answered or hung up: ignore it\n continue;\n }\n\n try {\n await this.handleCallEvent(event);\n } catch (e) {\n logger.error(\"CallEventHandler evaluateEventBuffer() caught exception handling call event\", e);\n }\n }\n }\n\n private onRoomTimeline = (event: MatrixEvent): void => {\n this.callEventBuffer.push(event);\n };\n\n private onToDeviceEvent = (event: MatrixEvent): void => {\n const content = event.getContent();\n\n if (!content.call_id) {\n this.callEventBuffer.push(event);\n return;\n }\n\n if (!this.nextSeqByCall.has(content.call_id)) {\n this.nextSeqByCall.set(content.call_id, 0);\n }\n\n if (content.seq === undefined) {\n this.callEventBuffer.push(event);\n return;\n }\n\n const nextSeq = this.nextSeqByCall.get(content.call_id) || 0;\n\n if (content.seq !== nextSeq) {\n if (!this.toDeviceEventBuffers.has(content.call_id)) {\n this.toDeviceEventBuffers.set(content.call_id, []);\n }\n\n const buffer = this.toDeviceEventBuffers.get(content.call_id)!;\n const index = buffer.findIndex((e) => e.getContent().seq > content.seq);\n\n if (index === -1) {\n buffer.push(event);\n } else {\n buffer.splice(index, 0, event);\n }\n } else {\n const callId = content.call_id;\n this.callEventBuffer.push(event);\n this.nextSeqByCall.set(callId, content.seq + 1);\n\n const buffer = this.toDeviceEventBuffers.get(callId);\n\n let nextEvent = buffer && buffer.shift();\n\n while (nextEvent && nextEvent.getContent().seq === this.nextSeqByCall.get(callId)) {\n this.callEventBuffer.push(nextEvent);\n this.nextSeqByCall.set(callId, nextEvent.getContent().seq + 1);\n nextEvent = buffer!.shift();\n }\n }\n };\n\n private async handleCallEvent(event: MatrixEvent): Promise<void> {\n this.client.emit(ClientEvent.ReceivedVoipEvent, event);\n\n const content = event.getContent();\n const callRoomId =\n event.getRoomId() || this.client.groupCallEventHandler!.getGroupCallById(content.conf_id)?.room?.roomId;\n const groupCallId = content.conf_id;\n const type = event.getType() as EventType;\n const senderId = event.getSender()!;\n let call = content.call_id ? this.calls.get(content.call_id) : undefined;\n\n let opponentDeviceId: string | undefined;\n\n let groupCall: GroupCall | undefined;\n if (groupCallId) {\n groupCall = this.client.groupCallEventHandler!.getGroupCallById(groupCallId);\n\n if (!groupCall) {\n logger.warn(\n `CallEventHandler handleCallEvent() could not find a group call - ignoring event (groupCallId=${groupCallId}, type=${type})`,\n );\n return;\n }\n\n opponentDeviceId = content.device_id;\n\n if (!opponentDeviceId) {\n logger.warn(\n `CallEventHandler handleCallEvent() could not find a device id - ignoring event (senderId=${senderId})`,\n );\n groupCall.emit(GroupCallEvent.Error, new GroupCallUnknownDeviceError(senderId));\n return;\n }\n\n if (content.dest_session_id !== this.client.getSessionId()) {\n logger.warn(\n \"CallEventHandler handleCallEvent() call event does not match current session id - ignoring\",\n );\n return;\n }\n }\n\n const weSentTheEvent =\n senderId === this.client.credentials.userId &&\n (opponentDeviceId === undefined || opponentDeviceId === this.client.getDeviceId()!);\n\n if (!callRoomId) return;\n\n if (type === EventType.CallInvite) {\n // ignore invites you send\n if (weSentTheEvent) return;\n // expired call\n if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) return;\n // stale/old invite event\n if (call && call.state === CallState.Ended) return;\n\n if (call) {\n logger.warn(\n `CallEventHandler handleCallEvent() already has a call but got an invite - clobbering (callId=${content.call_id})`,\n );\n }\n\n if (content.invitee && content.invitee !== this.client.getUserId()) {\n return; // This invite was meant for another user in the room\n }\n\n const timeUntilTurnCresExpire = (this.client.getTurnServersExpiry() ?? 0) - Date.now();\n logger.info(\n \"CallEventHandler handleCallEvent() current turn creds expire in \" + timeUntilTurnCresExpire + \" ms\",\n );\n call =\n createNewMatrixCall(this.client, callRoomId, {\n forceTURN: this.client.forceTURN,\n opponentDeviceId,\n groupCallId,\n opponentSessionId: content.sender_session_id,\n }) ?? undefined;\n if (!call) {\n logger.log(\n `CallEventHandler handleCallEvent() this client does not support WebRTC (callId=${content.call_id})`,\n );\n // don't hang up the call: there could be other clients\n // connected that do support WebRTC and declining the\n // the call on their behalf would be really annoying.\n return;\n }\n\n call.callId = content.call_id;\n const stats = groupCall?.getGroupCallStats();\n if (stats) {\n call.initStats(stats);\n }\n\n try {\n await call.initWithInvite(event);\n } catch (e) {\n if (e instanceof CallError) {\n if (e.code === GroupCallErrorCode.UnknownDevice) {\n groupCall?.emit(GroupCallEvent.Error, e);\n } else {\n logger.error(e);\n }\n }\n }\n this.calls.set(call.callId, call);\n\n // if we stashed candidate events for that call ID, play them back now\n if (this.candidateEventsByCall.get(call.callId)) {\n for (const ev of this.candidateEventsByCall.get(call.callId)!) {\n call.onRemoteIceCandidatesReceived(ev);\n }\n }\n\n // Were we trying to call that user (room)?\n let existingCall: MatrixCall | undefined;\n for (const thisCall of this.calls.values()) {\n const isCalling = [CallState.WaitLocalMedia, CallState.CreateOffer, CallState.InviteSent].includes(\n thisCall.state,\n );\n\n if (\n call.roomId === thisCall.roomId &&\n thisCall.direction === CallDirection.Outbound &&\n call.getOpponentMember()?.userId === thisCall.invitee &&\n isCalling\n ) {\n existingCall = thisCall;\n break;\n }\n }\n\n if (existingCall) {\n if (existingCall.callId > call.callId) {\n logger.log(\n `CallEventHandler handleCallEvent() detected glare - answering incoming call and canceling outgoing call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`,\n );\n existingCall.replacedBy(call);\n } else {\n logger.log(\n `CallEventHandler handleCallEvent() detected glare - hanging up incoming call (incomingId=${call.callId}, outgoingId=${existingCall.callId})`,\n );\n call.hangup(CallErrorCode.Replaced, true);\n }\n } else {\n this.client.emit(CallEventHandlerEvent.Incoming, call);\n }\n return;\n } else if (type === EventType.CallCandidates) {\n if (weSentTheEvent) return;\n\n if (!call) {\n // store the candidates; we may get a call eventually.\n if (!this.candidateEventsByCall.has(content.call_id)) {\n this.candidateEventsByCall.set(content.call_id, []);\n }\n this.candidateEventsByCall.get(content.call_id)!.push(event);\n } else {\n call.onRemoteIceCandidatesReceived(event);\n }\n return;\n } else if ([EventType.CallHangup, EventType.CallReject].includes(type)) {\n // Note that we also observe our own hangups here so we can see\n // if we've already rejected a call that would otherwise be valid\n if (!call) {\n // if not live, store the fact that the call has ended because\n // we're probably getting events backwards so\n // the hangup will come before the invite\n call =\n createNewMatrixCall(this.client, callRoomId, {\n opponentDeviceId,\n opponentSessionId: content.sender_session_id,\n }) ?? undefined;\n if (call) {\n call.callId = content.call_id;\n call.initWithHangup(event);\n this.calls.set(content.call_id, call);\n }\n } else {\n if (call.state !== CallState.Ended) {\n if (type === EventType.CallHangup) {\n call.onHangupReceived(content as MCallHangupReject);\n } else {\n call.onRejectReceived(content as MCallHangupReject);\n }\n\n // @ts-expect-error typescript thinks the state can't be 'ended' because we're\n // inside the if block where it wasn't, but it could have changed because\n // on[Hangup|Reject]Received are side-effecty.\n if (call.state === CallState.Ended) this.calls.delete(content.call_id);\n }\n }\n return;\n }\n\n // The following events need a call and a peer connection\n if (!call || !call.hasPeerConnection) {\n logger.info(\n `CallEventHandler handleCallEvent() discarding possible call event as we don't have a call (type=${type})`,\n );\n return;\n }\n // Ignore remote echo\n if (event.getContent().party_id === call.ourPartyId) return;\n\n switch (type) {\n case EventType.CallAnswer:\n if (weSentTheEvent) {\n if (call.state === CallState.Ringing) {\n call.onAnsweredElsewhere(content as MCallAnswer);\n }\n } else {\n call.onAnswerReceived(event);\n }\n break;\n case EventType.CallSelectAnswer:\n call.onSelectAnswerReceived(event);\n break;\n\n case EventType.CallNegotiate:\n call.onNegotiateReceived(event);\n break;\n\n case EventType.CallAssertedIdentity:\n case EventType.CallAssertedIdentityPrefix:\n call.onAssertedIdentityReceived(event);\n break;\n\n case EventType.CallSDPStreamMetadataChanged:\n case EventType.CallSDPStreamMetadataChangedPrefix:\n call.onSDPStreamMetadataChangedReceived(event);\n break;\n }\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.d.ts new file mode 100644 index 0000000..09a6397 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.d.ts @@ -0,0 +1,72 @@ +import { CallErrorCode } from "./call"; +export declare const SDPStreamMetadataKey = "org.matrix.msc3077.sdp_stream_metadata"; +export declare enum SDPStreamMetadataPurpose { + Usermedia = "m.usermedia", + Screenshare = "m.screenshare" +} +export interface SDPStreamMetadataObject { + purpose: SDPStreamMetadataPurpose; + audio_muted: boolean; + video_muted: boolean; +} +export interface SDPStreamMetadata { + [key: string]: SDPStreamMetadataObject; +} +export interface CallCapabilities { + "m.call.transferee": boolean; + "m.call.dtmf": boolean; +} +export interface CallReplacesTarget { + id: string; + display_name: string; + avatar_url: string; +} +export interface MCallBase { + call_id: string; + version: string | number; + party_id?: string; + sender_session_id?: string; + dest_session_id?: string; +} +export interface MCallAnswer extends MCallBase { + answer: RTCSessionDescription; + capabilities?: CallCapabilities; + [SDPStreamMetadataKey]: SDPStreamMetadata; +} +export interface MCallSelectAnswer extends MCallBase { + selected_party_id: string; +} +export interface MCallInviteNegotiate extends MCallBase { + offer: RTCSessionDescription; + description: RTCSessionDescription; + lifetime: number; + capabilities?: CallCapabilities; + invitee?: string; + sender_session_id?: string; + dest_session_id?: string; + [SDPStreamMetadataKey]: SDPStreamMetadata; +} +export interface MCallSDPStreamMetadataChanged extends MCallBase { + [SDPStreamMetadataKey]: SDPStreamMetadata; +} +export interface MCallReplacesEvent extends MCallBase { + replacement_id: string; + target_user: CallReplacesTarget; + create_call: string; + await_call: string; + target_room: string; +} +export interface MCAllAssertedIdentity extends MCallBase { + asserted_identity: { + id: string; + display_name: string; + avatar_url: string; + }; +} +export interface MCallCandidates extends MCallBase { + candidates: RTCIceCandidate[]; +} +export interface MCallHangupReject extends MCallBase { + reason?: CallErrorCode; +} +//# sourceMappingURL=callEventTypes.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.d.ts.map new file mode 100644 index 0000000..1975acd --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"callEventTypes.d.ts","sourceRoot":"","sources":["../../src/webrtc/callEventTypes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAGvC,eAAO,MAAM,oBAAoB,2CAA2C,CAAC;AAE7E,oBAAY,wBAAwB;IAChC,SAAS,gBAAgB;IACzB,WAAW,kBAAkB;CAChC;AAED,MAAM,WAAW,uBAAuB;IACpC,OAAO,EAAE,wBAAwB,CAAC;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,uBAAuB,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC7B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC1C,MAAM,EAAE,qBAAqB,CAAC;IAC9B,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,CAAC,oBAAoB,CAAC,EAAE,iBAAiB,CAAC;CAC7C;AAED,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAChD,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAqB,SAAQ,SAAS;IACnD,KAAK,EAAE,qBAAqB,CAAC;IAC7B,WAAW,EAAE,qBAAqB,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,CAAC,oBAAoB,CAAC,EAAE,iBAAiB,CAAC;CAC7C;AAED,MAAM,WAAW,6BAA8B,SAAQ,SAAS;IAC5D,CAAC,oBAAoB,CAAC,EAAE,iBAAiB,CAAC;CAC7C;AAED,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,kBAAkB,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACpD,iBAAiB,EAAE;QACf,EAAE,EAAE,MAAM,CAAC;QACX,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;CACL;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAC9C,UAAU,EAAE,eAAe,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAChD,MAAM,CAAC,EAAE,aAAa,CAAC;CAC1B"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.js new file mode 100644 index 0000000..5b10142 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.js @@ -0,0 +1,20 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SDPStreamMetadataPurpose = exports.SDPStreamMetadataKey = void 0; +// allow non-camelcase as these are events type that go onto the wire +/* eslint-disable camelcase */ + +// TODO: Change to "sdp_stream_metadata" when MSC3077 is merged +const SDPStreamMetadataKey = "org.matrix.msc3077.sdp_stream_metadata"; +exports.SDPStreamMetadataKey = SDPStreamMetadataKey; +let SDPStreamMetadataPurpose; +exports.SDPStreamMetadataPurpose = SDPStreamMetadataPurpose; +(function (SDPStreamMetadataPurpose) { + SDPStreamMetadataPurpose["Usermedia"] = "m.usermedia"; + SDPStreamMetadataPurpose["Screenshare"] = "m.screenshare"; +})(SDPStreamMetadataPurpose || (exports.SDPStreamMetadataPurpose = SDPStreamMetadataPurpose = {})); +/* eslint-enable camelcase */ +//# sourceMappingURL=callEventTypes.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.js.map new file mode 100644 index 0000000..5670198 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callEventTypes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"callEventTypes.js","names":["SDPStreamMetadataKey","exports","SDPStreamMetadataPurpose"],"sources":["../../src/webrtc/callEventTypes.ts"],"sourcesContent":["// allow non-camelcase as these are events type that go onto the wire\n/* eslint-disable camelcase */\n\nimport { CallErrorCode } from \"./call\";\n\n// TODO: Change to \"sdp_stream_metadata\" when MSC3077 is merged\nexport const SDPStreamMetadataKey = \"org.matrix.msc3077.sdp_stream_metadata\";\n\nexport enum SDPStreamMetadataPurpose {\n Usermedia = \"m.usermedia\",\n Screenshare = \"m.screenshare\",\n}\n\nexport interface SDPStreamMetadataObject {\n purpose: SDPStreamMetadataPurpose;\n audio_muted: boolean;\n video_muted: boolean;\n}\n\nexport interface SDPStreamMetadata {\n [key: string]: SDPStreamMetadataObject;\n}\n\nexport interface CallCapabilities {\n \"m.call.transferee\": boolean;\n \"m.call.dtmf\": boolean;\n}\n\nexport interface CallReplacesTarget {\n id: string;\n display_name: string;\n avatar_url: string;\n}\n\nexport interface MCallBase {\n call_id: string;\n version: string | number;\n party_id?: string;\n sender_session_id?: string;\n dest_session_id?: string;\n}\n\nexport interface MCallAnswer extends MCallBase {\n answer: RTCSessionDescription;\n capabilities?: CallCapabilities;\n [SDPStreamMetadataKey]: SDPStreamMetadata;\n}\n\nexport interface MCallSelectAnswer extends MCallBase {\n selected_party_id: string;\n}\n\nexport interface MCallInviteNegotiate extends MCallBase {\n offer: RTCSessionDescription;\n description: RTCSessionDescription;\n lifetime: number;\n capabilities?: CallCapabilities;\n invitee?: string;\n sender_session_id?: string;\n dest_session_id?: string;\n [SDPStreamMetadataKey]: SDPStreamMetadata;\n}\n\nexport interface MCallSDPStreamMetadataChanged extends MCallBase {\n [SDPStreamMetadataKey]: SDPStreamMetadata;\n}\n\nexport interface MCallReplacesEvent extends MCallBase {\n replacement_id: string;\n target_user: CallReplacesTarget;\n create_call: string;\n await_call: string;\n target_room: string;\n}\n\nexport interface MCAllAssertedIdentity extends MCallBase {\n asserted_identity: {\n id: string;\n display_name: string;\n avatar_url: string;\n };\n}\n\nexport interface MCallCandidates extends MCallBase {\n candidates: RTCIceCandidate[];\n}\n\nexport interface MCallHangupReject extends MCallBase {\n reason?: CallErrorCode;\n}\n\n/* eslint-enable camelcase */\n"],"mappings":";;;;;;AAAA;AACA;;AAIA;AACO,MAAMA,oBAAoB,GAAG,wCAAwC;AAACC,OAAA,CAAAD,oBAAA,GAAAA,oBAAA;AAAA,IAEjEE,wBAAwB;AAAAD,OAAA,CAAAC,wBAAA,GAAAA,wBAAA;AAAA,WAAxBA,wBAAwB;EAAxBA,wBAAwB;EAAxBA,wBAAwB;AAAA,GAAxBA,wBAAwB,KAAAD,OAAA,CAAAC,wBAAA,GAAxBA,wBAAwB;AAmFpC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.d.ts new file mode 100644 index 0000000..e9ffa64 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.d.ts @@ -0,0 +1,128 @@ +import { SDPStreamMetadataPurpose } from "./callEventTypes"; +import { MatrixClient } from "../client"; +import { RoomMember } from "../models/room-member"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { MatrixCall } from "./call"; +export declare const SPEAKING_THRESHOLD = -60; +export interface ICallFeedOpts { + client: MatrixClient; + roomId?: string; + userId: string; + deviceId: string | undefined; + stream: MediaStream; + purpose: SDPStreamMetadataPurpose; + /** + * Whether or not the remote SDPStreamMetadata says audio is muted + */ + audioMuted: boolean; + /** + * Whether or not the remote SDPStreamMetadata says video is muted + */ + videoMuted: boolean; + /** + * The MatrixCall which is the source of this CallFeed + */ + call?: MatrixCall; +} +export declare enum CallFeedEvent { + NewStream = "new_stream", + MuteStateChanged = "mute_state_changed", + LocalVolumeChanged = "local_volume_changed", + VolumeChanged = "volume_changed", + ConnectedChanged = "connected_changed", + Speaking = "speaking", + Disposed = "disposed" +} +type EventHandlerMap = { + [CallFeedEvent.NewStream]: (stream: MediaStream) => void; + [CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void; + [CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void; + [CallFeedEvent.VolumeChanged]: (volume: number) => void; + [CallFeedEvent.ConnectedChanged]: (connected: boolean) => void; + [CallFeedEvent.Speaking]: (speaking: boolean) => void; + [CallFeedEvent.Disposed]: () => void; +}; +export declare class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap> { + stream: MediaStream; + sdpMetadataStreamId: string; + userId: string; + readonly deviceId: string | undefined; + purpose: SDPStreamMetadataPurpose; + speakingVolumeSamples: number[]; + private client; + private call?; + private roomId?; + private audioMuted; + private videoMuted; + private localVolume; + private measuringVolumeActivity; + private audioContext?; + private analyser?; + private frequencyBinCount?; + private speakingThreshold; + private speaking; + private volumeLooperTimeout?; + private _disposed; + private _connected; + constructor(opts: ICallFeedOpts); + get connected(): boolean; + private set connected(value); + private get hasAudioTrack(); + private updateStream; + private initVolumeMeasuring; + private onAddTrack; + private onCallState; + /** + * Returns callRoom member + * @returns member of the callRoom + */ + getMember(): RoomMember | null; + /** + * Returns true if CallFeed is local, otherwise returns false + * @returns is local? + */ + isLocal(): boolean; + /** + * Returns true if audio is muted or if there are no audio + * tracks, otherwise returns false + * @returns is audio muted? + */ + isAudioMuted(): boolean; + /** + * Returns true video is muted or if there are no video + * tracks, otherwise returns false + * @returns is video muted? + */ + isVideoMuted(): boolean; + isSpeaking(): boolean; + /** + * Replaces the current MediaStream with a new one. + * The stream will be different and new stream as remote parties are + * concerned, but this can be used for convenience locally to set up + * volume listeners automatically on the new stream etc. + * @param newStream - new stream with which to replace the current one + */ + setNewStream(newStream: MediaStream): void; + /** + * Set one or both of feed's internal audio and video video mute state + * Either value may be null to leave it as-is + * @param audioMuted - is the feed's audio muted? + * @param videoMuted - is the feed's video muted? + */ + setAudioVideoMuted(audioMuted: boolean | null, videoMuted: boolean | null): void; + /** + * Starts emitting volume_changed events where the emitter value is in decibels + * @param enabled - emit volume changes + */ + measureVolumeActivity(enabled: boolean): void; + setSpeakingThreshold(threshold: number): void; + private volumeLooper; + clone(): CallFeed; + dispose(): void; + get disposed(): boolean; + private set disposed(value); + getLocalVolume(): number; + setLocalVolume(localVolume: number): void; +} +export {}; +//# sourceMappingURL=callFeed.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.d.ts.map new file mode 100644 index 0000000..85e5615 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"callFeed.d.ts","sourceRoot":"","sources":["../../src/webrtc/callFeed.ts"],"names":[],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.js new file mode 100644 index 0000000..3638765 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.js @@ -0,0 +1,296 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.SPEAKING_THRESHOLD = exports.CallFeedEvent = exports.CallFeed = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _callEventTypes = require("./callEventTypes"); +var _audioContext = require("./audioContext"); +var _logger = require("../logger"); +var _typedEventEmitter = require("../models/typed-event-emitter"); +var _call = require("./call"); +/* +Copyright 2021 Å imon Brandner <simon.bra.ag@gmail.com> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const POLLING_INTERVAL = 200; // ms +const SPEAKING_THRESHOLD = -60; // dB +exports.SPEAKING_THRESHOLD = SPEAKING_THRESHOLD; +const SPEAKING_SAMPLE_COUNT = 8; // samples +let CallFeedEvent; +exports.CallFeedEvent = CallFeedEvent; +(function (CallFeedEvent) { + CallFeedEvent["NewStream"] = "new_stream"; + CallFeedEvent["MuteStateChanged"] = "mute_state_changed"; + CallFeedEvent["LocalVolumeChanged"] = "local_volume_changed"; + CallFeedEvent["VolumeChanged"] = "volume_changed"; + CallFeedEvent["ConnectedChanged"] = "connected_changed"; + CallFeedEvent["Speaking"] = "speaking"; + CallFeedEvent["Disposed"] = "disposed"; +})(CallFeedEvent || (exports.CallFeedEvent = CallFeedEvent = {})); +class CallFeed extends _typedEventEmitter.TypedEventEmitter { + constructor(opts) { + super(); + (0, _defineProperty2.default)(this, "stream", void 0); + (0, _defineProperty2.default)(this, "sdpMetadataStreamId", void 0); + (0, _defineProperty2.default)(this, "userId", void 0); + (0, _defineProperty2.default)(this, "deviceId", void 0); + (0, _defineProperty2.default)(this, "purpose", void 0); + (0, _defineProperty2.default)(this, "speakingVolumeSamples", void 0); + (0, _defineProperty2.default)(this, "client", void 0); + (0, _defineProperty2.default)(this, "call", void 0); + (0, _defineProperty2.default)(this, "roomId", void 0); + (0, _defineProperty2.default)(this, "audioMuted", void 0); + (0, _defineProperty2.default)(this, "videoMuted", void 0); + (0, _defineProperty2.default)(this, "localVolume", 1); + (0, _defineProperty2.default)(this, "measuringVolumeActivity", false); + (0, _defineProperty2.default)(this, "audioContext", void 0); + (0, _defineProperty2.default)(this, "analyser", void 0); + (0, _defineProperty2.default)(this, "frequencyBinCount", void 0); + (0, _defineProperty2.default)(this, "speakingThreshold", SPEAKING_THRESHOLD); + (0, _defineProperty2.default)(this, "speaking", false); + (0, _defineProperty2.default)(this, "volumeLooperTimeout", void 0); + (0, _defineProperty2.default)(this, "_disposed", false); + (0, _defineProperty2.default)(this, "_connected", false); + (0, _defineProperty2.default)(this, "onAddTrack", () => { + this.emit(CallFeedEvent.NewStream, this.stream); + }); + (0, _defineProperty2.default)(this, "onCallState", state => { + if (state === _call.CallState.Connected) { + this.connected = true; + } else if (state === _call.CallState.Connecting) { + this.connected = false; + } + }); + (0, _defineProperty2.default)(this, "volumeLooper", () => { + if (!this.analyser) return; + if (!this.measuringVolumeActivity) return; + this.analyser.getFloatFrequencyData(this.frequencyBinCount); + let maxVolume = -Infinity; + for (const volume of this.frequencyBinCount) { + if (volume > maxVolume) { + maxVolume = volume; + } + } + this.speakingVolumeSamples.shift(); + this.speakingVolumeSamples.push(maxVolume); + this.emit(CallFeedEvent.VolumeChanged, maxVolume); + let newSpeaking = false; + for (const volume of this.speakingVolumeSamples) { + if (volume > this.speakingThreshold) { + newSpeaking = true; + break; + } + } + if (this.speaking !== newSpeaking) { + this.speaking = newSpeaking; + this.emit(CallFeedEvent.Speaking, this.speaking); + } + this.volumeLooperTimeout = setTimeout(this.volumeLooper, POLLING_INTERVAL); + }); + this.client = opts.client; + this.call = opts.call; + this.roomId = opts.roomId; + this.userId = opts.userId; + this.deviceId = opts.deviceId; + this.purpose = opts.purpose; + this.audioMuted = opts.audioMuted; + this.videoMuted = opts.videoMuted; + this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity); + this.sdpMetadataStreamId = opts.stream.id; + this.updateStream(null, opts.stream); + this.stream = opts.stream; // updateStream does this, but this makes TS happier + + if (this.hasAudioTrack) { + this.initVolumeMeasuring(); + } + if (opts.call) { + opts.call.addListener(_call.CallEvent.State, this.onCallState); + this.onCallState(opts.call.state); + } + } + get connected() { + // Local feeds are always considered connected + return this.isLocal() || this._connected; + } + set connected(connected) { + this._connected = connected; + this.emit(CallFeedEvent.ConnectedChanged, this.connected); + } + get hasAudioTrack() { + return this.stream.getAudioTracks().length > 0; + } + updateStream(oldStream, newStream) { + if (newStream === oldStream) return; + if (oldStream) { + oldStream.removeEventListener("addtrack", this.onAddTrack); + this.measureVolumeActivity(false); + } + this.stream = newStream; + newStream.addEventListener("addtrack", this.onAddTrack); + if (this.hasAudioTrack) { + this.initVolumeMeasuring(); + } else { + this.measureVolumeActivity(false); + } + this.emit(CallFeedEvent.NewStream, this.stream); + } + initVolumeMeasuring() { + if (!this.hasAudioTrack) return; + if (!this.audioContext) this.audioContext = (0, _audioContext.acquireContext)(); + this.analyser = this.audioContext.createAnalyser(); + this.analyser.fftSize = 512; + this.analyser.smoothingTimeConstant = 0.1; + const mediaStreamAudioSourceNode = this.audioContext.createMediaStreamSource(this.stream); + mediaStreamAudioSourceNode.connect(this.analyser); + this.frequencyBinCount = new Float32Array(this.analyser.frequencyBinCount); + } + /** + * Returns callRoom member + * @returns member of the callRoom + */ + getMember() { + var _callRoom$getMember; + const callRoom = this.client.getRoom(this.roomId); + return (_callRoom$getMember = callRoom === null || callRoom === void 0 ? void 0 : callRoom.getMember(this.userId)) !== null && _callRoom$getMember !== void 0 ? _callRoom$getMember : null; + } + + /** + * Returns true if CallFeed is local, otherwise returns false + * @returns is local? + */ + isLocal() { + return this.userId === this.client.getUserId() && (this.deviceId === undefined || this.deviceId === this.client.getDeviceId()); + } + + /** + * Returns true if audio is muted or if there are no audio + * tracks, otherwise returns false + * @returns is audio muted? + */ + isAudioMuted() { + return this.stream.getAudioTracks().length === 0 || this.audioMuted; + } + + /** + * Returns true video is muted or if there are no video + * tracks, otherwise returns false + * @returns is video muted? + */ + isVideoMuted() { + // We assume only one video track + return this.stream.getVideoTracks().length === 0 || this.videoMuted; + } + isSpeaking() { + return this.speaking; + } + + /** + * Replaces the current MediaStream with a new one. + * The stream will be different and new stream as remote parties are + * concerned, but this can be used for convenience locally to set up + * volume listeners automatically on the new stream etc. + * @param newStream - new stream with which to replace the current one + */ + setNewStream(newStream) { + this.updateStream(this.stream, newStream); + } + + /** + * Set one or both of feed's internal audio and video video mute state + * Either value may be null to leave it as-is + * @param audioMuted - is the feed's audio muted? + * @param videoMuted - is the feed's video muted? + */ + setAudioVideoMuted(audioMuted, videoMuted) { + if (audioMuted !== null) { + if (this.audioMuted !== audioMuted) { + this.speakingVolumeSamples.fill(-Infinity); + } + this.audioMuted = audioMuted; + } + if (videoMuted !== null) this.videoMuted = videoMuted; + this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted); + } + + /** + * Starts emitting volume_changed events where the emitter value is in decibels + * @param enabled - emit volume changes + */ + measureVolumeActivity(enabled) { + if (enabled) { + if (!this.analyser || !this.frequencyBinCount || !this.hasAudioTrack) return; + this.measuringVolumeActivity = true; + this.volumeLooper(); + } else { + this.measuringVolumeActivity = false; + this.speakingVolumeSamples.fill(-Infinity); + this.emit(CallFeedEvent.VolumeChanged, -Infinity); + } + } + setSpeakingThreshold(threshold) { + this.speakingThreshold = threshold; + } + clone() { + const mediaHandler = this.client.getMediaHandler(); + const stream = this.stream.clone(); + _logger.logger.log(`CallFeed clone() cloning stream (originalStreamId=${this.stream.id}, newStreamId${stream.id})`); + if (this.purpose === _callEventTypes.SDPStreamMetadataPurpose.Usermedia) { + mediaHandler.userMediaStreams.push(stream); + } else { + mediaHandler.screensharingStreams.push(stream); + } + return new CallFeed({ + client: this.client, + roomId: this.roomId, + userId: this.userId, + deviceId: this.deviceId, + stream, + purpose: this.purpose, + audioMuted: this.audioMuted, + videoMuted: this.videoMuted + }); + } + dispose() { + var _this$stream, _this$call; + clearTimeout(this.volumeLooperTimeout); + (_this$stream = this.stream) === null || _this$stream === void 0 ? void 0 : _this$stream.removeEventListener("addtrack", this.onAddTrack); + (_this$call = this.call) === null || _this$call === void 0 ? void 0 : _this$call.removeListener(_call.CallEvent.State, this.onCallState); + if (this.audioContext) { + this.audioContext = undefined; + this.analyser = undefined; + (0, _audioContext.releaseContext)(); + } + this._disposed = true; + this.emit(CallFeedEvent.Disposed); + } + get disposed() { + return this._disposed; + } + set disposed(value) { + this._disposed = value; + } + getLocalVolume() { + return this.localVolume; + } + setLocalVolume(localVolume) { + this.localVolume = localVolume; + this.emit(CallFeedEvent.LocalVolumeChanged, localVolume); + } +} +exports.CallFeed = CallFeed; +//# sourceMappingURL=callFeed.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.js.map new file mode 100644 index 0000000..8085af3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/callFeed.js.map @@ -0,0 +1 @@ +{"version":3,"file":"callFeed.js","names":["_callEventTypes","require","_audioContext","_logger","_typedEventEmitter","_call","POLLING_INTERVAL","SPEAKING_THRESHOLD","exports","SPEAKING_SAMPLE_COUNT","CallFeedEvent","CallFeed","TypedEventEmitter","constructor","opts","_defineProperty2","default","emit","NewStream","stream","state","CallState","Connected","connected","Connecting","analyser","measuringVolumeActivity","getFloatFrequencyData","frequencyBinCount","maxVolume","Infinity","volume","speakingVolumeSamples","shift","push","VolumeChanged","newSpeaking","speakingThreshold","speaking","Speaking","volumeLooperTimeout","setTimeout","volumeLooper","client","call","roomId","userId","deviceId","purpose","audioMuted","videoMuted","Array","fill","sdpMetadataStreamId","id","updateStream","hasAudioTrack","initVolumeMeasuring","addListener","CallEvent","State","onCallState","isLocal","_connected","ConnectedChanged","getAudioTracks","length","oldStream","newStream","removeEventListener","onAddTrack","measureVolumeActivity","addEventListener","audioContext","acquireContext","createAnalyser","fftSize","smoothingTimeConstant","mediaStreamAudioSourceNode","createMediaStreamSource","connect","Float32Array","getMember","_callRoom$getMember","callRoom","getRoom","getUserId","undefined","getDeviceId","isAudioMuted","isVideoMuted","getVideoTracks","isSpeaking","setNewStream","setAudioVideoMuted","MuteStateChanged","enabled","setSpeakingThreshold","threshold","clone","mediaHandler","getMediaHandler","logger","log","SDPStreamMetadataPurpose","Usermedia","userMediaStreams","screensharingStreams","dispose","_this$stream","_this$call","clearTimeout","removeListener","releaseContext","_disposed","Disposed","disposed","value","getLocalVolume","localVolume","setLocalVolume","LocalVolumeChanged"],"sources":["../../src/webrtc/callFeed.ts"],"sourcesContent":["/*\nCopyright 2021 Å imon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { SDPStreamMetadataPurpose } from \"./callEventTypes\";\nimport { acquireContext, releaseContext } from \"./audioContext\";\nimport { MatrixClient } from \"../client\";\nimport { RoomMember } from \"../models/room-member\";\nimport { logger } from \"../logger\";\nimport { TypedEventEmitter } from \"../models/typed-event-emitter\";\nimport { CallEvent, CallState, MatrixCall } from \"./call\";\n\nconst POLLING_INTERVAL = 200; // ms\nexport const SPEAKING_THRESHOLD = -60; // dB\nconst SPEAKING_SAMPLE_COUNT = 8; // samples\n\nexport interface ICallFeedOpts {\n client: MatrixClient;\n roomId?: string;\n userId: string;\n deviceId: string | undefined;\n stream: MediaStream;\n purpose: SDPStreamMetadataPurpose;\n /**\n * Whether or not the remote SDPStreamMetadata says audio is muted\n */\n audioMuted: boolean;\n /**\n * Whether or not the remote SDPStreamMetadata says video is muted\n */\n videoMuted: boolean;\n /**\n * The MatrixCall which is the source of this CallFeed\n */\n call?: MatrixCall;\n}\n\nexport enum CallFeedEvent {\n NewStream = \"new_stream\",\n MuteStateChanged = \"mute_state_changed\",\n LocalVolumeChanged = \"local_volume_changed\",\n VolumeChanged = \"volume_changed\",\n ConnectedChanged = \"connected_changed\",\n Speaking = \"speaking\",\n Disposed = \"disposed\",\n}\n\ntype EventHandlerMap = {\n [CallFeedEvent.NewStream]: (stream: MediaStream) => void;\n [CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;\n [CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void;\n [CallFeedEvent.VolumeChanged]: (volume: number) => void;\n [CallFeedEvent.ConnectedChanged]: (connected: boolean) => void;\n [CallFeedEvent.Speaking]: (speaking: boolean) => void;\n [CallFeedEvent.Disposed]: () => void;\n};\n\nexport class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap> {\n public stream: MediaStream;\n public sdpMetadataStreamId: string;\n public userId: string;\n public readonly deviceId: string | undefined;\n public purpose: SDPStreamMetadataPurpose;\n public speakingVolumeSamples: number[];\n\n private client: MatrixClient;\n private call?: MatrixCall;\n private roomId?: string;\n private audioMuted: boolean;\n private videoMuted: boolean;\n private localVolume = 1;\n private measuringVolumeActivity = false;\n private audioContext?: AudioContext;\n private analyser?: AnalyserNode;\n private frequencyBinCount?: Float32Array;\n private speakingThreshold = SPEAKING_THRESHOLD;\n private speaking = false;\n private volumeLooperTimeout?: ReturnType<typeof setTimeout>;\n private _disposed = false;\n private _connected = false;\n\n public constructor(opts: ICallFeedOpts) {\n super();\n\n this.client = opts.client;\n this.call = opts.call;\n this.roomId = opts.roomId;\n this.userId = opts.userId;\n this.deviceId = opts.deviceId;\n this.purpose = opts.purpose;\n this.audioMuted = opts.audioMuted;\n this.videoMuted = opts.videoMuted;\n this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity);\n this.sdpMetadataStreamId = opts.stream.id;\n\n this.updateStream(null, opts.stream);\n this.stream = opts.stream; // updateStream does this, but this makes TS happier\n\n if (this.hasAudioTrack) {\n this.initVolumeMeasuring();\n }\n\n if (opts.call) {\n opts.call.addListener(CallEvent.State, this.onCallState);\n this.onCallState(opts.call.state);\n }\n }\n\n public get connected(): boolean {\n // Local feeds are always considered connected\n return this.isLocal() || this._connected;\n }\n\n private set connected(connected: boolean) {\n this._connected = connected;\n this.emit(CallFeedEvent.ConnectedChanged, this.connected);\n }\n\n private get hasAudioTrack(): boolean {\n return this.stream.getAudioTracks().length > 0;\n }\n\n private updateStream(oldStream: MediaStream | null, newStream: MediaStream): void {\n if (newStream === oldStream) return;\n\n if (oldStream) {\n oldStream.removeEventListener(\"addtrack\", this.onAddTrack);\n this.measureVolumeActivity(false);\n }\n\n this.stream = newStream;\n newStream.addEventListener(\"addtrack\", this.onAddTrack);\n\n if (this.hasAudioTrack) {\n this.initVolumeMeasuring();\n } else {\n this.measureVolumeActivity(false);\n }\n\n this.emit(CallFeedEvent.NewStream, this.stream);\n }\n\n private initVolumeMeasuring(): void {\n if (!this.hasAudioTrack) return;\n if (!this.audioContext) this.audioContext = acquireContext();\n\n this.analyser = this.audioContext.createAnalyser();\n this.analyser.fftSize = 512;\n this.analyser.smoothingTimeConstant = 0.1;\n\n const mediaStreamAudioSourceNode = this.audioContext.createMediaStreamSource(this.stream);\n mediaStreamAudioSourceNode.connect(this.analyser);\n\n this.frequencyBinCount = new Float32Array(this.analyser.frequencyBinCount);\n }\n\n private onAddTrack = (): void => {\n this.emit(CallFeedEvent.NewStream, this.stream);\n };\n\n private onCallState = (state: CallState): void => {\n if (state === CallState.Connected) {\n this.connected = true;\n } else if (state === CallState.Connecting) {\n this.connected = false;\n }\n };\n\n /**\n * Returns callRoom member\n * @returns member of the callRoom\n */\n public getMember(): RoomMember | null {\n const callRoom = this.client.getRoom(this.roomId);\n return callRoom?.getMember(this.userId) ?? null;\n }\n\n /**\n * Returns true if CallFeed is local, otherwise returns false\n * @returns is local?\n */\n public isLocal(): boolean {\n return (\n this.userId === this.client.getUserId() &&\n (this.deviceId === undefined || this.deviceId === this.client.getDeviceId())\n );\n }\n\n /**\n * Returns true if audio is muted or if there are no audio\n * tracks, otherwise returns false\n * @returns is audio muted?\n */\n public isAudioMuted(): boolean {\n return this.stream.getAudioTracks().length === 0 || this.audioMuted;\n }\n\n /**\n * Returns true video is muted or if there are no video\n * tracks, otherwise returns false\n * @returns is video muted?\n */\n public isVideoMuted(): boolean {\n // We assume only one video track\n return this.stream.getVideoTracks().length === 0 || this.videoMuted;\n }\n\n public isSpeaking(): boolean {\n return this.speaking;\n }\n\n /**\n * Replaces the current MediaStream with a new one.\n * The stream will be different and new stream as remote parties are\n * concerned, but this can be used for convenience locally to set up\n * volume listeners automatically on the new stream etc.\n * @param newStream - new stream with which to replace the current one\n */\n public setNewStream(newStream: MediaStream): void {\n this.updateStream(this.stream, newStream);\n }\n\n /**\n * Set one or both of feed's internal audio and video video mute state\n * Either value may be null to leave it as-is\n * @param audioMuted - is the feed's audio muted?\n * @param videoMuted - is the feed's video muted?\n */\n public setAudioVideoMuted(audioMuted: boolean | null, videoMuted: boolean | null): void {\n if (audioMuted !== null) {\n if (this.audioMuted !== audioMuted) {\n this.speakingVolumeSamples.fill(-Infinity);\n }\n this.audioMuted = audioMuted;\n }\n if (videoMuted !== null) this.videoMuted = videoMuted;\n this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted);\n }\n\n /**\n * Starts emitting volume_changed events where the emitter value is in decibels\n * @param enabled - emit volume changes\n */\n public measureVolumeActivity(enabled: boolean): void {\n if (enabled) {\n if (!this.analyser || !this.frequencyBinCount || !this.hasAudioTrack) return;\n\n this.measuringVolumeActivity = true;\n this.volumeLooper();\n } else {\n this.measuringVolumeActivity = false;\n this.speakingVolumeSamples.fill(-Infinity);\n this.emit(CallFeedEvent.VolumeChanged, -Infinity);\n }\n }\n\n public setSpeakingThreshold(threshold: number): void {\n this.speakingThreshold = threshold;\n }\n\n private volumeLooper = (): void => {\n if (!this.analyser) return;\n\n if (!this.measuringVolumeActivity) return;\n\n this.analyser.getFloatFrequencyData(this.frequencyBinCount!);\n\n let maxVolume = -Infinity;\n for (const volume of this.frequencyBinCount!) {\n if (volume > maxVolume) {\n maxVolume = volume;\n }\n }\n\n this.speakingVolumeSamples.shift();\n this.speakingVolumeSamples.push(maxVolume);\n\n this.emit(CallFeedEvent.VolumeChanged, maxVolume);\n\n let newSpeaking = false;\n\n for (const volume of this.speakingVolumeSamples) {\n if (volume > this.speakingThreshold) {\n newSpeaking = true;\n break;\n }\n }\n\n if (this.speaking !== newSpeaking) {\n this.speaking = newSpeaking;\n this.emit(CallFeedEvent.Speaking, this.speaking);\n }\n\n this.volumeLooperTimeout = setTimeout(this.volumeLooper, POLLING_INTERVAL);\n };\n\n public clone(): CallFeed {\n const mediaHandler = this.client.getMediaHandler();\n const stream = this.stream.clone();\n logger.log(`CallFeed clone() cloning stream (originalStreamId=${this.stream.id}, newStreamId${stream.id})`);\n\n if (this.purpose === SDPStreamMetadataPurpose.Usermedia) {\n mediaHandler.userMediaStreams.push(stream);\n } else {\n mediaHandler.screensharingStreams.push(stream);\n }\n\n return new CallFeed({\n client: this.client,\n roomId: this.roomId,\n userId: this.userId,\n deviceId: this.deviceId,\n stream,\n purpose: this.purpose,\n audioMuted: this.audioMuted,\n videoMuted: this.videoMuted,\n });\n }\n\n public dispose(): void {\n clearTimeout(this.volumeLooperTimeout);\n this.stream?.removeEventListener(\"addtrack\", this.onAddTrack);\n this.call?.removeListener(CallEvent.State, this.onCallState);\n if (this.audioContext) {\n this.audioContext = undefined;\n this.analyser = undefined;\n releaseContext();\n }\n this._disposed = true;\n this.emit(CallFeedEvent.Disposed);\n }\n\n public get disposed(): boolean {\n return this._disposed;\n }\n\n private set disposed(value: boolean) {\n this._disposed = value;\n }\n\n public getLocalVolume(): number {\n return this.localVolume;\n }\n\n public setLocalVolume(localVolume: number): void {\n this.localVolume = localVolume;\n this.emit(CallFeedEvent.LocalVolumeChanged, localVolume);\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.d.ts new file mode 100644 index 0000000..2c1cb3e --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.d.ts @@ -0,0 +1,287 @@ +import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { CallFeed } from "./callFeed"; +import { MatrixClient } from "../client"; +import { CallEvent, CallEventHandlerMap, MatrixCall } from "./call"; +import { RoomMember } from "../models/room-member"; +import { Room } from "../models/room"; +import { SDPStreamMetadataPurpose } from "./callEventTypes"; +import { IScreensharingOpts } from "./mediaHandler"; +import { GroupCallStats } from "./stats/groupCallStats"; +import { ByteSentStatsReport, ConnectionStatsReport } from "./stats/statsReport"; +export declare enum GroupCallIntent { + Ring = "m.ring", + Prompt = "m.prompt", + Room = "m.room" +} +export declare enum GroupCallType { + Video = "m.video", + Voice = "m.voice" +} +export declare enum GroupCallTerminationReason { + CallEnded = "call_ended" +} +export type CallsByUserAndDevice = Map<string, Map<string, MatrixCall>>; +/** + * Because event names are just strings, they do need + * to be unique over all event types of event emitter. + * Some objects could emit more then one set of events. + */ +export declare enum GroupCallEvent { + GroupCallStateChanged = "group_call_state_changed", + ActiveSpeakerChanged = "active_speaker_changed", + CallsChanged = "calls_changed", + UserMediaFeedsChanged = "user_media_feeds_changed", + ScreenshareFeedsChanged = "screenshare_feeds_changed", + LocalScreenshareStateChanged = "local_screenshare_state_changed", + LocalMuteStateChanged = "local_mute_state_changed", + ParticipantsChanged = "participants_changed", + Error = "group_call_error" +} +export type GroupCallEventHandlerMap = { + [GroupCallEvent.GroupCallStateChanged]: (newState: GroupCallState, oldState: GroupCallState) => void; + [GroupCallEvent.ActiveSpeakerChanged]: (activeSpeaker: CallFeed | undefined) => void; + [GroupCallEvent.CallsChanged]: (calls: CallsByUserAndDevice) => void; + [GroupCallEvent.UserMediaFeedsChanged]: (feeds: CallFeed[]) => void; + [GroupCallEvent.ScreenshareFeedsChanged]: (feeds: CallFeed[]) => void; + [GroupCallEvent.LocalScreenshareStateChanged]: (isScreensharing: boolean, feed?: CallFeed, sourceId?: string) => void; + [GroupCallEvent.LocalMuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void; + [GroupCallEvent.ParticipantsChanged]: (participants: Map<RoomMember, Map<string, ParticipantState>>) => void; + /** + * Fires whenever an error occurs when call.js encounters an issue with setting up the call. + * <p> + * The error given will have a code equal to either `MatrixCall.ERR_LOCAL_OFFER_FAILED` or + * `MatrixCall.ERR_NO_USER_MEDIA`. `ERR_LOCAL_OFFER_FAILED` is emitted when the local client + * fails to create an offer. `ERR_NO_USER_MEDIA` is emitted when the user has denied access + * to their audio/video hardware. + * @param err - The error raised by MatrixCall. + * @example + * ``` + * matrixCall.on("error", function(err){ + * console.error(err.code, err); + * }); + * ``` + */ + [GroupCallEvent.Error]: (error: GroupCallError) => void; +}; +export declare enum GroupCallStatsReportEvent { + ConnectionStats = "GroupCall.connection_stats", + ByteSentStats = "GroupCall.byte_sent_stats" +} +export type GroupCallStatsReportEventHandlerMap = { + [GroupCallStatsReportEvent.ConnectionStats]: (report: GroupCallStatsReport<ConnectionStatsReport>) => void; + [GroupCallStatsReportEvent.ByteSentStats]: (report: GroupCallStatsReport<ByteSentStatsReport>) => void; +}; +export declare enum GroupCallErrorCode { + NoUserMedia = "no_user_media", + UnknownDevice = "unknown_device", + PlaceCallFailed = "place_call_failed" +} +export interface GroupCallStatsReport<T extends ConnectionStatsReport | ByteSentStatsReport> { + report: T; +} +export declare class GroupCallError extends Error { + code: string; + constructor(code: GroupCallErrorCode, msg: string, err?: Error); +} +export declare class GroupCallUnknownDeviceError extends GroupCallError { + userId: string; + constructor(userId: string); +} +export declare class OtherUserSpeakingError extends Error { + constructor(); +} +export interface IGroupCallDataChannelOptions { + ordered: boolean; + maxPacketLifeTime: number; + maxRetransmits: number; + protocol: string; +} +export interface IGroupCallRoomState { + "m.intent": GroupCallIntent; + "m.type": GroupCallType; + "io.element.ptt"?: boolean; + "dataChannelsEnabled"?: boolean; + "dataChannelOptions"?: IGroupCallDataChannelOptions; +} +export interface IGroupCallRoomMemberFeed { + purpose: SDPStreamMetadataPurpose; +} +export interface IGroupCallRoomMemberDevice { + device_id: string; + session_id: string; + expires_ts: number; + feeds: IGroupCallRoomMemberFeed[]; +} +export interface IGroupCallRoomMemberCallState { + "m.call_id": string; + "m.foci"?: string[]; + "m.devices": IGroupCallRoomMemberDevice[]; +} +export interface IGroupCallRoomMemberState { + "m.calls": IGroupCallRoomMemberCallState[]; +} +export declare enum GroupCallState { + LocalCallFeedUninitialized = "local_call_feed_uninitialized", + InitializingLocalCallFeed = "initializing_local_call_feed", + LocalCallFeedInitialized = "local_call_feed_initialized", + Entered = "entered", + Ended = "ended" +} +export interface ParticipantState { + sessionId: string; + screensharing: boolean; +} +export declare class GroupCall extends TypedEventEmitter<GroupCallEvent | CallEvent | GroupCallStatsReportEvent, GroupCallEventHandlerMap & CallEventHandlerMap & GroupCallStatsReportEventHandlerMap> { + private client; + room: Room; + type: GroupCallType; + isPtt: boolean; + intent: GroupCallIntent; + private dataChannelsEnabled?; + private dataChannelOptions?; + activeSpeakerInterval: number; + retryCallInterval: number; + participantTimeout: number; + pttMaxTransmitTime: number; + activeSpeaker?: CallFeed; + localCallFeed?: CallFeed; + localScreenshareFeed?: CallFeed; + localDesktopCapturerSourceId?: string; + readonly userMediaFeeds: CallFeed[]; + readonly screenshareFeeds: CallFeed[]; + groupCallId: string; + readonly allowCallWithoutVideoAndAudio: boolean; + private readonly calls; + private callHandlers; + private activeSpeakerLoopInterval?; + private retryCallLoopInterval?; + private retryCallCounts; + private reEmitter; + private transmitTimer; + private participantsExpirationTimer; + private resendMemberStateTimer; + private initWithAudioMuted; + private initWithVideoMuted; + private initCallFeedPromise?; + private readonly stats; + constructor(client: MatrixClient, room: Room, type: GroupCallType, isPtt: boolean, intent: GroupCallIntent, groupCallId?: string, dataChannelsEnabled?: boolean | undefined, dataChannelOptions?: IGroupCallDataChannelOptions | undefined, isCallWithoutVideoAndAudio?: boolean); + private onConnectionStats; + private onByteSentStats; + create(): Promise<GroupCall>; + private _state; + /** + * The group call's state. + */ + get state(): GroupCallState; + private set state(value); + private _participants; + /** + * The current participants in the call, as a map from members to device IDs + * to participant info. + */ + get participants(): Map<RoomMember, Map<string, ParticipantState>>; + private set participants(value); + private _creationTs; + /** + * The timestamp at which the call was created, or null if it has not yet + * been created. + */ + get creationTs(): number | null; + private set creationTs(value); + private _enteredViaAnotherSession; + /** + * Whether the local device has entered this call via another session, such + * as a widget. + */ + get enteredViaAnotherSession(): boolean; + set enteredViaAnotherSession(value: boolean); + /** + * Executes the given callback on all calls in this group call. + * @param f - The callback. + */ + forEachCall(f: (call: MatrixCall) => void): void; + getLocalFeeds(): CallFeed[]; + hasLocalParticipant(): boolean; + /** + * Determines whether the given call is one that we were expecting to exist + * given our knowledge of who is participating in the group call. + */ + private callExpected; + initLocalCallFeed(): Promise<void>; + private initLocalCallFeedInternal; + updateLocalUsermediaStream(stream: MediaStream): Promise<void>; + enter(): Promise<void>; + private dispose; + leave(): void; + terminate(emitStateEvent?: boolean): Promise<void>; + isLocalVideoMuted(): boolean; + isMicrophoneMuted(): boolean; + /** + * Sets the mute state of the local participants's microphone. + * @param muted - Whether to mute the microphone + * @returns Whether muting/unmuting was successful + */ + setMicrophoneMuted(muted: boolean): Promise<boolean>; + /** + * Sets the mute state of the local participants's video. + * @param muted - Whether to mute the video + * @returns Whether muting/unmuting was successful + */ + setLocalVideoMuted(muted: boolean): Promise<boolean>; + setScreensharingEnabled(enabled: boolean, opts?: IScreensharingOpts): Promise<boolean>; + isScreensharing(): boolean; + private onIncomingCall; + /** + * Determines whether a given participant expects us to call them (versus + * them calling us). + * @param userId - The participant's user ID. + * @param deviceId - The participant's device ID. + * @returns Whether we need to place an outgoing call to the participant. + */ + private wantsOutgoingCall; + /** + * Places calls to all participants that we're responsible for calling. + */ + private placeOutgoingCalls; + private getMemberStateEvents; + private onRetryCallLoop; + private initCall; + private disposeCall; + private onCallFeedsChanged; + private onCallStateChanged; + private onCallHangup; + private onCallReplaced; + getUserMediaFeed(userId: string, deviceId: string): CallFeed | undefined; + private addUserMediaFeed; + private replaceUserMediaFeed; + private removeUserMediaFeed; + private onActiveSpeakerLoop; + getScreenshareFeed(userId: string, deviceId: string): CallFeed | undefined; + private addScreenshareFeed; + private replaceScreenshareFeed; + private removeScreenshareFeed; + /** + * Recalculates and updates the participant map to match the room state. + */ + private updateParticipants; + /** + * Updates the local user's member state with the devices returned by the given function. + * @param fn - A function from the current devices to the new devices. If it + * returns null, the update will be skipped. + * @param keepAlive - Whether the request should outlive the window. + */ + private updateDevices; + private addDeviceToMemberState; + private updateMemberState; + /** + * Cleans up our member state by filtering out logged out devices, inactive + * devices, and our own device (if we know we haven't entered). + */ + cleanMemberState(): Promise<void>; + private onRoomState; + private onParticipantsChanged; + private onStateChanged; + private onLocalFeedsChanged; + getGroupCallStats(): GroupCallStats; +} +//# sourceMappingURL=groupCall.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.d.ts.map new file mode 100644 index 0000000..89a0eb0 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"groupCall.d.ts","sourceRoot":"","sources":["../../src/webrtc/groupCall.ts"],"names":[],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js new file mode 100644 index 0000000..d9c044b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js @@ -0,0 +1,1184 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.OtherUserSpeakingError = exports.GroupCallUnknownDeviceError = exports.GroupCallType = exports.GroupCallTerminationReason = exports.GroupCallStatsReportEvent = exports.GroupCallState = exports.GroupCallIntent = exports.GroupCallEvent = exports.GroupCallErrorCode = exports.GroupCallError = exports.GroupCall = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _typedEventEmitter = require("../models/typed-event-emitter"); +var _callFeed = require("./callFeed"); +var _call = require("./call"); +var _roomState = require("../models/room-state"); +var _logger = require("../logger"); +var _ReEmitter = require("../ReEmitter"); +var _callEventTypes = require("./callEventTypes"); +var _event = require("../@types/event"); +var _callEventHandler = require("./callEventHandler"); +var _groupCallEventHandler = require("./groupCallEventHandler"); +var _utils = require("../utils"); +var _groupCallStats = require("./stats/groupCallStats"); +var _statsReport = require("./stats/statsReport"); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } +let GroupCallIntent; +exports.GroupCallIntent = GroupCallIntent; +(function (GroupCallIntent) { + GroupCallIntent["Ring"] = "m.ring"; + GroupCallIntent["Prompt"] = "m.prompt"; + GroupCallIntent["Room"] = "m.room"; +})(GroupCallIntent || (exports.GroupCallIntent = GroupCallIntent = {})); +let GroupCallType; +exports.GroupCallType = GroupCallType; +(function (GroupCallType) { + GroupCallType["Video"] = "m.video"; + GroupCallType["Voice"] = "m.voice"; +})(GroupCallType || (exports.GroupCallType = GroupCallType = {})); +let GroupCallTerminationReason; +exports.GroupCallTerminationReason = GroupCallTerminationReason; +(function (GroupCallTerminationReason) { + GroupCallTerminationReason["CallEnded"] = "call_ended"; +})(GroupCallTerminationReason || (exports.GroupCallTerminationReason = GroupCallTerminationReason = {})); +/** + * Because event names are just strings, they do need + * to be unique over all event types of event emitter. + * Some objects could emit more then one set of events. + */ +let GroupCallEvent; +exports.GroupCallEvent = GroupCallEvent; +(function (GroupCallEvent) { + GroupCallEvent["GroupCallStateChanged"] = "group_call_state_changed"; + GroupCallEvent["ActiveSpeakerChanged"] = "active_speaker_changed"; + GroupCallEvent["CallsChanged"] = "calls_changed"; + GroupCallEvent["UserMediaFeedsChanged"] = "user_media_feeds_changed"; + GroupCallEvent["ScreenshareFeedsChanged"] = "screenshare_feeds_changed"; + GroupCallEvent["LocalScreenshareStateChanged"] = "local_screenshare_state_changed"; + GroupCallEvent["LocalMuteStateChanged"] = "local_mute_state_changed"; + GroupCallEvent["ParticipantsChanged"] = "participants_changed"; + GroupCallEvent["Error"] = "group_call_error"; +})(GroupCallEvent || (exports.GroupCallEvent = GroupCallEvent = {})); +let GroupCallStatsReportEvent; +exports.GroupCallStatsReportEvent = GroupCallStatsReportEvent; +(function (GroupCallStatsReportEvent) { + GroupCallStatsReportEvent["ConnectionStats"] = "GroupCall.connection_stats"; + GroupCallStatsReportEvent["ByteSentStats"] = "GroupCall.byte_sent_stats"; +})(GroupCallStatsReportEvent || (exports.GroupCallStatsReportEvent = GroupCallStatsReportEvent = {})); +let GroupCallErrorCode; +exports.GroupCallErrorCode = GroupCallErrorCode; +(function (GroupCallErrorCode) { + GroupCallErrorCode["NoUserMedia"] = "no_user_media"; + GroupCallErrorCode["UnknownDevice"] = "unknown_device"; + GroupCallErrorCode["PlaceCallFailed"] = "place_call_failed"; +})(GroupCallErrorCode || (exports.GroupCallErrorCode = GroupCallErrorCode = {})); +class GroupCallError extends Error { + constructor(code, msg, err) { + // Still don't think there's any way to have proper nested errors + if (err) { + super(msg + ": " + err); + (0, _defineProperty2.default)(this, "code", void 0); + } else { + super(msg); + (0, _defineProperty2.default)(this, "code", void 0); + } + this.code = code; + } +} +exports.GroupCallError = GroupCallError; +class GroupCallUnknownDeviceError extends GroupCallError { + constructor(userId) { + super(GroupCallErrorCode.UnknownDevice, "No device found for " + userId); + this.userId = userId; + } +} +exports.GroupCallUnknownDeviceError = GroupCallUnknownDeviceError; +class OtherUserSpeakingError extends Error { + constructor() { + super("Cannot unmute: another user is speaking"); + } +} +exports.OtherUserSpeakingError = OtherUserSpeakingError; +let GroupCallState; +exports.GroupCallState = GroupCallState; +(function (GroupCallState) { + GroupCallState["LocalCallFeedUninitialized"] = "local_call_feed_uninitialized"; + GroupCallState["InitializingLocalCallFeed"] = "initializing_local_call_feed"; + GroupCallState["LocalCallFeedInitialized"] = "local_call_feed_initialized"; + GroupCallState["Entered"] = "entered"; + GroupCallState["Ended"] = "ended"; +})(GroupCallState || (exports.GroupCallState = GroupCallState = {})); +const DEVICE_TIMEOUT = 1000 * 60 * 60; // 1 hour + +function getCallUserId(call) { + var _call$getOpponentMemb; + return ((_call$getOpponentMemb = call.getOpponentMember()) === null || _call$getOpponentMemb === void 0 ? void 0 : _call$getOpponentMemb.userId) || call.invitee || null; +} +class GroupCall extends _typedEventEmitter.TypedEventEmitter { + // Config + + // user_id -> device_id -> MatrixCall + // user_id -> device_id -> ICallHandlers + + // user_id -> device_id -> count + + constructor(client, room, type, isPtt, intent, groupCallId, dataChannelsEnabled, dataChannelOptions, isCallWithoutVideoAndAudio) { + var _room$currentState$ge, _room$currentState$ge2; + super(); + this.client = client; + this.room = room; + this.type = type; + this.isPtt = isPtt; + this.intent = intent; + this.dataChannelsEnabled = dataChannelsEnabled; + this.dataChannelOptions = dataChannelOptions; + (0, _defineProperty2.default)(this, "activeSpeakerInterval", 1000); + (0, _defineProperty2.default)(this, "retryCallInterval", 5000); + (0, _defineProperty2.default)(this, "participantTimeout", 1000 * 15); + (0, _defineProperty2.default)(this, "pttMaxTransmitTime", 1000 * 20); + (0, _defineProperty2.default)(this, "activeSpeaker", void 0); + (0, _defineProperty2.default)(this, "localCallFeed", void 0); + (0, _defineProperty2.default)(this, "localScreenshareFeed", void 0); + (0, _defineProperty2.default)(this, "localDesktopCapturerSourceId", void 0); + (0, _defineProperty2.default)(this, "userMediaFeeds", []); + (0, _defineProperty2.default)(this, "screenshareFeeds", []); + (0, _defineProperty2.default)(this, "groupCallId", void 0); + (0, _defineProperty2.default)(this, "allowCallWithoutVideoAndAudio", void 0); + (0, _defineProperty2.default)(this, "calls", new Map()); + (0, _defineProperty2.default)(this, "callHandlers", new Map()); + (0, _defineProperty2.default)(this, "activeSpeakerLoopInterval", void 0); + (0, _defineProperty2.default)(this, "retryCallLoopInterval", void 0); + (0, _defineProperty2.default)(this, "retryCallCounts", new Map()); + (0, _defineProperty2.default)(this, "reEmitter", void 0); + (0, _defineProperty2.default)(this, "transmitTimer", null); + (0, _defineProperty2.default)(this, "participantsExpirationTimer", null); + (0, _defineProperty2.default)(this, "resendMemberStateTimer", null); + (0, _defineProperty2.default)(this, "initWithAudioMuted", false); + (0, _defineProperty2.default)(this, "initWithVideoMuted", false); + (0, _defineProperty2.default)(this, "initCallFeedPromise", void 0); + (0, _defineProperty2.default)(this, "stats", void 0); + (0, _defineProperty2.default)(this, "onConnectionStats", report => { + // @TODO: Implement data argumentation + this.emit(GroupCallStatsReportEvent.ConnectionStats, { + report + }); + }); + (0, _defineProperty2.default)(this, "onByteSentStats", report => { + // @TODO: Implement data argumentation + this.emit(GroupCallStatsReportEvent.ByteSentStats, { + report + }); + }); + (0, _defineProperty2.default)(this, "_state", GroupCallState.LocalCallFeedUninitialized); + (0, _defineProperty2.default)(this, "_participants", new Map()); + (0, _defineProperty2.default)(this, "_creationTs", null); + (0, _defineProperty2.default)(this, "_enteredViaAnotherSession", false); + (0, _defineProperty2.default)(this, "onIncomingCall", newCall => { + var _newCall$getOpponentM, _this$calls$get; + // The incoming calls may be for another room, which we will ignore. + if (newCall.roomId !== this.room.roomId) { + return; + } + if (newCall.state !== _call.CallState.Ringing) { + _logger.logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call no longer in ringing state - ignoring`); + return; + } + if (!newCall.groupCallId || newCall.groupCallId !== this.groupCallId) { + _logger.logger.log(`GroupCall ${this.groupCallId} onIncomingCall() ignored because it doesn't match the current group call`); + newCall.reject(); + return; + } + const opponentUserId = (_newCall$getOpponentM = newCall.getOpponentMember()) === null || _newCall$getOpponentM === void 0 ? void 0 : _newCall$getOpponentM.userId; + if (opponentUserId === undefined) { + _logger.logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call with no member - ignoring`); + return; + } + const deviceMap = (_this$calls$get = this.calls.get(opponentUserId)) !== null && _this$calls$get !== void 0 ? _this$calls$get : new Map(); + const prevCall = deviceMap.get(newCall.getOpponentDeviceId()); + if ((prevCall === null || prevCall === void 0 ? void 0 : prevCall.callId) === newCall.callId) return; + _logger.logger.log(`GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`); + if (prevCall) prevCall.hangup(_call.CallErrorCode.Replaced, false); + this.initCall(newCall); + const feeds = this.getLocalFeeds().map(feed => feed.clone()); + if (!this.callExpected(newCall)) { + // Disable our tracks for users not explicitly participating in the + // call but trying to receive the feeds + for (const feed of feeds) { + (0, _call.setTracksEnabled)(feed.stream.getAudioTracks(), false); + (0, _call.setTracksEnabled)(feed.stream.getVideoTracks(), false); + } + } + newCall.answerWithCallFeeds(feeds); + deviceMap.set(newCall.getOpponentDeviceId(), newCall); + this.calls.set(opponentUserId, deviceMap); + this.emit(GroupCallEvent.CallsChanged, this.calls); + }); + (0, _defineProperty2.default)(this, "onRetryCallLoop", () => { + let needsRetry = false; + for (const [{ + userId + }, participantMap] of this.participants) { + const callMap = this.calls.get(userId); + let retriesMap = this.retryCallCounts.get(userId); + for (const [deviceId, participant] of participantMap) { + var _retriesMap$get, _retriesMap; + const call = callMap === null || callMap === void 0 ? void 0 : callMap.get(deviceId); + const retries = (_retriesMap$get = (_retriesMap = retriesMap) === null || _retriesMap === void 0 ? void 0 : _retriesMap.get(deviceId)) !== null && _retriesMap$get !== void 0 ? _retriesMap$get : 0; + if ((call === null || call === void 0 ? void 0 : call.getOpponentSessionId()) !== participant.sessionId && this.wantsOutgoingCall(userId, deviceId) && retries < 3) { + if (retriesMap === undefined) { + retriesMap = new Map(); + this.retryCallCounts.set(userId, retriesMap); + } + retriesMap.set(deviceId, retries + 1); + needsRetry = true; + } + } + } + if (needsRetry) this.placeOutgoingCalls(); + }); + (0, _defineProperty2.default)(this, "onCallFeedsChanged", call => { + const opponentMemberId = getCallUserId(call); + const opponentDeviceId = call.getOpponentDeviceId(); + if (!opponentMemberId) { + throw new Error("Cannot change call feeds without user id"); + } + const currentUserMediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId); + const remoteUsermediaFeed = call.remoteUsermediaFeed; + const remoteFeedChanged = remoteUsermediaFeed !== currentUserMediaFeed; + if (remoteFeedChanged) { + if (!currentUserMediaFeed && remoteUsermediaFeed) { + this.addUserMediaFeed(remoteUsermediaFeed); + } else if (currentUserMediaFeed && remoteUsermediaFeed) { + this.replaceUserMediaFeed(currentUserMediaFeed, remoteUsermediaFeed); + } else if (currentUserMediaFeed && !remoteUsermediaFeed) { + this.removeUserMediaFeed(currentUserMediaFeed); + } + } + const currentScreenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId); + const remoteScreensharingFeed = call.remoteScreensharingFeed; + const remoteScreenshareFeedChanged = remoteScreensharingFeed !== currentScreenshareFeed; + if (remoteScreenshareFeedChanged) { + if (!currentScreenshareFeed && remoteScreensharingFeed) { + this.addScreenshareFeed(remoteScreensharingFeed); + } else if (currentScreenshareFeed && remoteScreensharingFeed) { + this.replaceScreenshareFeed(currentScreenshareFeed, remoteScreensharingFeed); + } else if (currentScreenshareFeed && !remoteScreensharingFeed) { + this.removeScreenshareFeed(currentScreenshareFeed); + } + } + }); + (0, _defineProperty2.default)(this, "onCallStateChanged", (call, state, _oldState) => { + var _call$getOpponentMemb2; + if (state === _call.CallState.Ended) return; + const audioMuted = this.localCallFeed.isAudioMuted(); + if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) { + call.setMicrophoneMuted(audioMuted); + } + const videoMuted = this.localCallFeed.isVideoMuted(); + if (call.localUsermediaStream && call.isLocalVideoMuted() !== videoMuted) { + call.setLocalVideoMuted(videoMuted); + } + const opponentUserId = (_call$getOpponentMemb2 = call.getOpponentMember()) === null || _call$getOpponentMemb2 === void 0 ? void 0 : _call$getOpponentMemb2.userId; + if (state === _call.CallState.Connected && opponentUserId) { + const retriesMap = this.retryCallCounts.get(opponentUserId); + retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.delete(call.getOpponentDeviceId()); + if ((retriesMap === null || retriesMap === void 0 ? void 0 : retriesMap.size) === 0) this.retryCallCounts.delete(opponentUserId); + } + }); + (0, _defineProperty2.default)(this, "onCallHangup", call => { + var _call$getOpponentMemb3, _call$getOpponentMemb4; + if (call.hangupReason === _call.CallErrorCode.Replaced) return; + const opponentUserId = (_call$getOpponentMemb3 = (_call$getOpponentMemb4 = call.getOpponentMember()) === null || _call$getOpponentMemb4 === void 0 ? void 0 : _call$getOpponentMemb4.userId) !== null && _call$getOpponentMemb3 !== void 0 ? _call$getOpponentMemb3 : this.room.getMember(call.invitee).userId; + const deviceMap = this.calls.get(opponentUserId); + + // Sanity check that this call is in fact in the map + if ((deviceMap === null || deviceMap === void 0 ? void 0 : deviceMap.get(call.getOpponentDeviceId())) === call) { + this.disposeCall(call, call.hangupReason); + deviceMap.delete(call.getOpponentDeviceId()); + if (deviceMap.size === 0) this.calls.delete(opponentUserId); + this.emit(GroupCallEvent.CallsChanged, this.calls); + } + }); + (0, _defineProperty2.default)(this, "onCallReplaced", (prevCall, newCall) => { + const opponentUserId = prevCall.getOpponentMember().userId; + let deviceMap = this.calls.get(opponentUserId); + if (deviceMap === undefined) { + deviceMap = new Map(); + this.calls.set(opponentUserId, deviceMap); + } + prevCall.hangup(_call.CallErrorCode.Replaced, false); + this.initCall(newCall); + deviceMap.set(prevCall.getOpponentDeviceId(), newCall); + this.emit(GroupCallEvent.CallsChanged, this.calls); + }); + (0, _defineProperty2.default)(this, "onActiveSpeakerLoop", () => { + let topAvg = undefined; + let nextActiveSpeaker = undefined; + for (const callFeed of this.userMediaFeeds) { + if (callFeed.isLocal() && this.userMediaFeeds.length > 1) continue; + const total = callFeed.speakingVolumeSamples.reduce((acc, volume) => acc + Math.max(volume, _callFeed.SPEAKING_THRESHOLD)); + const avg = total / callFeed.speakingVolumeSamples.length; + if (!topAvg || avg > topAvg) { + topAvg = avg; + nextActiveSpeaker = callFeed; + } + } + if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg && topAvg > _callFeed.SPEAKING_THRESHOLD) { + this.activeSpeaker = nextActiveSpeaker; + this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker); + } + }); + (0, _defineProperty2.default)(this, "onRoomState", () => this.updateParticipants()); + (0, _defineProperty2.default)(this, "onParticipantsChanged", () => { + // Re-run setTracksEnabled on all calls, so that participants that just + // left get denied access to our media, and participants that just + // joined get granted access + this.forEachCall(call => { + const expected = this.callExpected(call); + for (const feed of call.getLocalFeeds()) { + (0, _call.setTracksEnabled)(feed.stream.getAudioTracks(), !feed.isAudioMuted() && expected); + (0, _call.setTracksEnabled)(feed.stream.getVideoTracks(), !feed.isVideoMuted() && expected); + } + }); + if (this.state === GroupCallState.Entered) this.placeOutgoingCalls(); + }); + (0, _defineProperty2.default)(this, "onStateChanged", (newState, oldState) => { + if (newState === GroupCallState.Entered || oldState === GroupCallState.Entered || newState === GroupCallState.Ended) { + // We either entered, left, or ended the call + this.updateParticipants(); + this.updateMemberState().catch(e => _logger.logger.error(`GroupCall ${this.groupCallId} onStateChanged() failed to update member state devices"`, e)); + } + }); + (0, _defineProperty2.default)(this, "onLocalFeedsChanged", () => { + if (this.state === GroupCallState.Entered) { + this.updateMemberState().catch(e => _logger.logger.error(`GroupCall ${this.groupCallId} onLocalFeedsChanged() failed to update member state feeds`, e)); + } + }); + this.reEmitter = new _ReEmitter.ReEmitter(this); + this.groupCallId = groupCallId !== null && groupCallId !== void 0 ? groupCallId : (0, _call.genCallID)(); + this.creationTs = (_room$currentState$ge = (_room$currentState$ge2 = room.currentState.getStateEvents(_event.EventType.GroupCallPrefix, this.groupCallId)) === null || _room$currentState$ge2 === void 0 ? void 0 : _room$currentState$ge2.getTs()) !== null && _room$currentState$ge !== void 0 ? _room$currentState$ge : null; + this.updateParticipants(); + room.on(_roomState.RoomStateEvent.Update, this.onRoomState); + this.on(GroupCallEvent.ParticipantsChanged, this.onParticipantsChanged); + this.on(GroupCallEvent.GroupCallStateChanged, this.onStateChanged); + this.on(GroupCallEvent.LocalScreenshareStateChanged, this.onLocalFeedsChanged); + this.allowCallWithoutVideoAndAudio = !!isCallWithoutVideoAndAudio; + const userID = this.client.getUserId() || "unknown"; + this.stats = new _groupCallStats.GroupCallStats(this.groupCallId, userID); + this.stats.reports.on(_statsReport.StatsReport.CONNECTION_STATS, this.onConnectionStats); + this.stats.reports.on(_statsReport.StatsReport.BYTE_SENT_STATS, this.onByteSentStats); + } + async create() { + this.creationTs = Date.now(); + this.client.groupCallEventHandler.groupCalls.set(this.room.roomId, this); + this.client.emit(_groupCallEventHandler.GroupCallEventHandlerEvent.Outgoing, this); + const groupCallState = { + "m.intent": this.intent, + "m.type": this.type, + "io.element.ptt": this.isPtt, + // TODO: Specify data-channels better + "dataChannelsEnabled": this.dataChannelsEnabled, + "dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined + }; + await this.client.sendStateEvent(this.room.roomId, _event.EventType.GroupCallPrefix, groupCallState, this.groupCallId); + return this; + } + /** + * The group call's state. + */ + get state() { + return this._state; + } + set state(value) { + const prevValue = this._state; + if (value !== prevValue) { + this._state = value; + this.emit(GroupCallEvent.GroupCallStateChanged, value, prevValue); + } + } + /** + * The current participants in the call, as a map from members to device IDs + * to participant info. + */ + get participants() { + return this._participants; + } + set participants(value) { + const prevValue = this._participants; + const participantStateEqual = (x, y) => x.sessionId === y.sessionId && x.screensharing === y.screensharing; + const deviceMapsEqual = (x, y) => (0, _utils.mapsEqual)(x, y, participantStateEqual); + + // Only update if the map actually changed + if (!(0, _utils.mapsEqual)(value, prevValue, deviceMapsEqual)) { + this._participants = value; + this.emit(GroupCallEvent.ParticipantsChanged, value); + } + } + /** + * The timestamp at which the call was created, or null if it has not yet + * been created. + */ + get creationTs() { + return this._creationTs; + } + set creationTs(value) { + this._creationTs = value; + } + /** + * Whether the local device has entered this call via another session, such + * as a widget. + */ + get enteredViaAnotherSession() { + return this._enteredViaAnotherSession; + } + set enteredViaAnotherSession(value) { + this._enteredViaAnotherSession = value; + this.updateParticipants(); + } + + /** + * Executes the given callback on all calls in this group call. + * @param f - The callback. + */ + forEachCall(f) { + for (const deviceMap of this.calls.values()) { + for (const call of deviceMap.values()) f(call); + } + } + getLocalFeeds() { + const feeds = []; + if (this.localCallFeed) feeds.push(this.localCallFeed); + if (this.localScreenshareFeed) feeds.push(this.localScreenshareFeed); + return feeds; + } + hasLocalParticipant() { + var _this$participants$ge, _this$participants$ge2; + return (_this$participants$ge = (_this$participants$ge2 = this.participants.get(this.room.getMember(this.client.getUserId()))) === null || _this$participants$ge2 === void 0 ? void 0 : _this$participants$ge2.has(this.client.getDeviceId())) !== null && _this$participants$ge !== void 0 ? _this$participants$ge : false; + } + + /** + * Determines whether the given call is one that we were expecting to exist + * given our knowledge of who is participating in the group call. + */ + callExpected(call) { + var _this$participants$ge3; + const userId = getCallUserId(call); + const member = userId === null ? null : this.room.getMember(userId); + const deviceId = call.getOpponentDeviceId(); + return member !== null && deviceId !== undefined && ((_this$participants$ge3 = this.participants.get(member)) === null || _this$participants$ge3 === void 0 ? void 0 : _this$participants$ge3.get(deviceId)) !== undefined; + } + async initLocalCallFeed() { + if (this.state !== GroupCallState.LocalCallFeedUninitialized) { + throw new Error(`Cannot initialize local call feed in the "${this.state}" state.`); + } + this.state = GroupCallState.InitializingLocalCallFeed; + + // wraps the real method to serialise calls, because we don't want to try starting + // multiple call feeds at once + if (this.initCallFeedPromise) return this.initCallFeedPromise; + try { + this.initCallFeedPromise = this.initLocalCallFeedInternal(); + await this.initCallFeedPromise; + } finally { + this.initCallFeedPromise = undefined; + } + } + async initLocalCallFeedInternal() { + _logger.logger.log(`GroupCall ${this.groupCallId} initLocalCallFeedInternal() running`); + let stream; + try { + stream = await this.client.getMediaHandler().getUserMediaStream(true, this.type === GroupCallType.Video); + } catch (error) { + // If is allowed to join a call without a media stream, then we + // don't throw an error here. But we need an empty Local Feed to establish + // a connection later. + if (this.allowCallWithoutVideoAndAudio) { + stream = new MediaStream(); + } else { + this.state = GroupCallState.LocalCallFeedUninitialized; + throw error; + } + } + + // The call could've been disposed while we were waiting, and could + // also have been started back up again (hello, React 18) so if we're + // still in this 'initializing' state, carry on, otherwise bail. + if (this._state !== GroupCallState.InitializingLocalCallFeed) { + this.client.getMediaHandler().stopUserMediaStream(stream); + throw new Error("Group call disposed while gathering media stream"); + } + const callFeed = new _callFeed.CallFeed({ + client: this.client, + roomId: this.room.roomId, + userId: this.client.getUserId(), + deviceId: this.client.getDeviceId(), + stream, + purpose: _callEventTypes.SDPStreamMetadataPurpose.Usermedia, + audioMuted: this.initWithAudioMuted || stream.getAudioTracks().length === 0 || this.isPtt, + videoMuted: this.initWithVideoMuted || stream.getVideoTracks().length === 0 + }); + (0, _call.setTracksEnabled)(stream.getAudioTracks(), !callFeed.isAudioMuted()); + (0, _call.setTracksEnabled)(stream.getVideoTracks(), !callFeed.isVideoMuted()); + this.localCallFeed = callFeed; + this.addUserMediaFeed(callFeed); + this.state = GroupCallState.LocalCallFeedInitialized; + } + async updateLocalUsermediaStream(stream) { + if (this.localCallFeed) { + const oldStream = this.localCallFeed.stream; + this.localCallFeed.setNewStream(stream); + const micShouldBeMuted = this.localCallFeed.isAudioMuted(); + const vidShouldBeMuted = this.localCallFeed.isVideoMuted(); + _logger.logger.log(`GroupCall ${this.groupCallId} updateLocalUsermediaStream() (oldStreamId=${oldStream.id}, newStreamId=${stream.id}, micShouldBeMuted=${micShouldBeMuted}, vidShouldBeMuted=${vidShouldBeMuted})`); + (0, _call.setTracksEnabled)(stream.getAudioTracks(), !micShouldBeMuted); + (0, _call.setTracksEnabled)(stream.getVideoTracks(), !vidShouldBeMuted); + this.client.getMediaHandler().stopUserMediaStream(oldStream); + } + } + async enter() { + if (this.state === GroupCallState.LocalCallFeedUninitialized) { + await this.initLocalCallFeed(); + } else if (this.state !== GroupCallState.LocalCallFeedInitialized) { + throw new Error(`Cannot enter call in the "${this.state}" state`); + } + _logger.logger.log(`GroupCall ${this.groupCallId} enter() running`); + this.state = GroupCallState.Entered; + this.client.on(_callEventHandler.CallEventHandlerEvent.Incoming, this.onIncomingCall); + for (const call of this.client.callEventHandler.calls.values()) { + this.onIncomingCall(call); + } + this.retryCallLoopInterval = setInterval(this.onRetryCallLoop, this.retryCallInterval); + this.activeSpeaker = undefined; + this.onActiveSpeakerLoop(); + this.activeSpeakerLoopInterval = setInterval(this.onActiveSpeakerLoop, this.activeSpeakerInterval); + } + dispose() { + if (this.localCallFeed) { + this.removeUserMediaFeed(this.localCallFeed); + this.localCallFeed = undefined; + } + if (this.localScreenshareFeed) { + this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream); + this.removeScreenshareFeed(this.localScreenshareFeed); + this.localScreenshareFeed = undefined; + this.localDesktopCapturerSourceId = undefined; + } + this.client.getMediaHandler().stopAllStreams(); + if (this.transmitTimer !== null) { + clearTimeout(this.transmitTimer); + this.transmitTimer = null; + } + if (this.retryCallLoopInterval !== undefined) { + clearInterval(this.retryCallLoopInterval); + this.retryCallLoopInterval = undefined; + } + if (this.participantsExpirationTimer !== null) { + clearTimeout(this.participantsExpirationTimer); + this.participantsExpirationTimer = null; + } + if (this.state !== GroupCallState.Entered) { + return; + } + this.forEachCall(call => call.hangup(_call.CallErrorCode.UserHangup, false)); + this.activeSpeaker = undefined; + clearInterval(this.activeSpeakerLoopInterval); + this.retryCallCounts.clear(); + clearInterval(this.retryCallLoopInterval); + this.client.removeListener(_callEventHandler.CallEventHandlerEvent.Incoming, this.onIncomingCall); + this.stats.stop(); + } + leave() { + this.dispose(); + this.state = GroupCallState.LocalCallFeedUninitialized; + } + async terminate(emitStateEvent = true) { + this.dispose(); + this.room.off(_roomState.RoomStateEvent.Update, this.onRoomState); + this.client.groupCallEventHandler.groupCalls.delete(this.room.roomId); + this.client.emit(_groupCallEventHandler.GroupCallEventHandlerEvent.Ended, this); + this.state = GroupCallState.Ended; + if (emitStateEvent) { + const existingStateEvent = this.room.currentState.getStateEvents(_event.EventType.GroupCallPrefix, this.groupCallId); + await this.client.sendStateEvent(this.room.roomId, _event.EventType.GroupCallPrefix, _objectSpread(_objectSpread({}, existingStateEvent.getContent()), {}, { + "m.terminated": GroupCallTerminationReason.CallEnded + }), this.groupCallId); + } + } + + /* + * Local Usermedia + */ + + isLocalVideoMuted() { + if (this.localCallFeed) { + return this.localCallFeed.isVideoMuted(); + } + return true; + } + isMicrophoneMuted() { + if (this.localCallFeed) { + return this.localCallFeed.isAudioMuted(); + } + return true; + } + + /** + * Sets the mute state of the local participants's microphone. + * @param muted - Whether to mute the microphone + * @returns Whether muting/unmuting was successful + */ + async setMicrophoneMuted(muted) { + // hasAudioDevice can block indefinitely if the window has lost focus, + // and it doesn't make much sense to keep a device from being muted, so + // we always allow muted = true changes to go through + if (!muted && !(await this.client.getMediaHandler().hasAudioDevice())) { + return false; + } + const sendUpdatesBefore = !muted && this.isPtt; + + // set a timer for the maximum transmit time on PTT calls + if (this.isPtt) { + // Set or clear the max transmit timer + if (!muted && this.isMicrophoneMuted()) { + this.transmitTimer = setTimeout(() => { + this.setMicrophoneMuted(true); + }, this.pttMaxTransmitTime); + } else if (muted && !this.isMicrophoneMuted()) { + if (this.transmitTimer !== null) clearTimeout(this.transmitTimer); + this.transmitTimer = null; + } + } + this.forEachCall(call => { + var _call$localUsermediaF; + return (_call$localUsermediaF = call.localUsermediaFeed) === null || _call$localUsermediaF === void 0 ? void 0 : _call$localUsermediaF.setAudioVideoMuted(muted, null); + }); + const sendUpdates = async () => { + const updates = []; + this.forEachCall(call => updates.push(call.sendMetadataUpdate())); + await Promise.all(updates).catch(e => _logger.logger.info(`GroupCall ${this.groupCallId} setMicrophoneMuted() failed to send some metadata updates`, e)); + }; + if (sendUpdatesBefore) await sendUpdates(); + if (this.localCallFeed) { + _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`); + + // We needed this here to avoid an error in case user join a call without a device. + // I can not use .then .catch functions because linter :-( + try { + if (!muted) { + const stream = await this.client.getMediaHandler().getUserMediaStream(true, !this.localCallFeed.isVideoMuted()); + if (stream === null) { + // if case permission denied to get a stream stop this here + /* istanbul ignore next */ + _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`); + return false; + } + } + } catch (e) { + /* istanbul ignore next */ + _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`); + return false; + } + this.localCallFeed.setAudioVideoMuted(muted, null); + // I don't believe its actually necessary to enable these tracks: they + // are the one on the GroupCall's own CallFeed and are cloned before being + // given to any of the actual calls, so these tracks don't actually go + // anywhere. Let's do it anyway to avoid confusion. + (0, _call.setTracksEnabled)(this.localCallFeed.stream.getAudioTracks(), !muted); + } else { + _logger.logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no stream muted (muted=${muted})`); + this.initWithAudioMuted = muted; + } + this.forEachCall(call => (0, _call.setTracksEnabled)(call.localUsermediaFeed.stream.getAudioTracks(), !muted && this.callExpected(call))); + this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted()); + if (!sendUpdatesBefore) await sendUpdates(); + return true; + } + + /** + * Sets the mute state of the local participants's video. + * @param muted - Whether to mute the video + * @returns Whether muting/unmuting was successful + */ + async setLocalVideoMuted(muted) { + // hasAudioDevice can block indefinitely if the window has lost focus, + // and it doesn't make much sense to keep a device from being muted, so + // we always allow muted = true changes to go through + if (!muted && !(await this.client.getMediaHandler().hasVideoDevice())) { + return false; + } + if (this.localCallFeed) { + /* istanbul ignore next */ + _logger.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() (stream=${this.localCallFeed.stream.id}, muted=${muted})`); + try { + const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted); + await this.updateLocalUsermediaStream(stream); + this.localCallFeed.setAudioVideoMuted(null, muted); + (0, _call.setTracksEnabled)(this.localCallFeed.stream.getVideoTracks(), !muted); + } catch (_) { + // No permission to video device + /* istanbul ignore next */ + _logger.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no device or permission to receive local stream, muted=${muted}`); + return false; + } + } else { + _logger.logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no stream muted (muted=${muted})`); + this.initWithVideoMuted = muted; + } + const updates = []; + this.forEachCall(call => updates.push(call.setLocalVideoMuted(muted))); + await Promise.all(updates); + + // We setTracksEnabled again, independently from the call doing it + // internally, since we might not be expecting the call + this.forEachCall(call => (0, _call.setTracksEnabled)(call.localUsermediaFeed.stream.getVideoTracks(), !muted && this.callExpected(call))); + this.emit(GroupCallEvent.LocalMuteStateChanged, this.isMicrophoneMuted(), muted); + return true; + } + async setScreensharingEnabled(enabled, opts = {}) { + if (enabled === this.isScreensharing()) { + return enabled; + } + if (enabled) { + try { + _logger.logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() is asking for screensharing permissions`); + const stream = await this.client.getMediaHandler().getScreensharingStream(opts); + for (const track of stream.getTracks()) { + const onTrackEnded = () => { + this.setScreensharingEnabled(false); + track.removeEventListener("ended", onTrackEnded); + }; + track.addEventListener("ended", onTrackEnded); + } + _logger.logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls`); + this.localDesktopCapturerSourceId = opts.desktopCapturerSourceId; + this.localScreenshareFeed = new _callFeed.CallFeed({ + client: this.client, + roomId: this.room.roomId, + userId: this.client.getUserId(), + deviceId: this.client.getDeviceId(), + stream, + purpose: _callEventTypes.SDPStreamMetadataPurpose.Screenshare, + audioMuted: false, + videoMuted: false + }); + this.addScreenshareFeed(this.localScreenshareFeed); + this.emit(GroupCallEvent.LocalScreenshareStateChanged, true, this.localScreenshareFeed, this.localDesktopCapturerSourceId); + + // TODO: handle errors + this.forEachCall(call => call.pushLocalFeed(this.localScreenshareFeed.clone())); + return true; + } catch (error) { + if (opts.throwOnFail) throw error; + _logger.logger.error(`GroupCall ${this.groupCallId} setScreensharingEnabled() enabling screensharing error`, error); + this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", error)); + return false; + } + } else { + this.forEachCall(call => { + if (call.localScreensharingFeed) call.removeLocalFeed(call.localScreensharingFeed); + }); + this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream); + this.removeScreenshareFeed(this.localScreenshareFeed); + this.localScreenshareFeed = undefined; + this.localDesktopCapturerSourceId = undefined; + this.emit(GroupCallEvent.LocalScreenshareStateChanged, false, undefined, undefined); + return false; + } + } + isScreensharing() { + return !!this.localScreenshareFeed; + } + + /* + * Call Setup + * + * There are two different paths for calls to be created: + * 1. Incoming calls triggered by the Call.incoming event. + * 2. Outgoing calls to the initial members of a room or new members + * as they are observed by the RoomState.members event. + */ + + /** + * Determines whether a given participant expects us to call them (versus + * them calling us). + * @param userId - The participant's user ID. + * @param deviceId - The participant's device ID. + * @returns Whether we need to place an outgoing call to the participant. + */ + wantsOutgoingCall(userId, deviceId) { + const localUserId = this.client.getUserId(); + const localDeviceId = this.client.getDeviceId(); + return ( + // If a user's ID is less than our own, they'll call us + userId >= localUserId && ( + // If this is another one of our devices, compare device IDs to tell whether it'll call us + userId !== localUserId || deviceId > localDeviceId) + ); + } + + /** + * Places calls to all participants that we're responsible for calling. + */ + placeOutgoingCalls() { + let callsChanged = false; + for (const [{ + userId + }, participantMap] of this.participants) { + var _this$calls$get2; + const callMap = (_this$calls$get2 = this.calls.get(userId)) !== null && _this$calls$get2 !== void 0 ? _this$calls$get2 : new Map(); + for (const [deviceId, participant] of participantMap) { + const prevCall = callMap.get(deviceId); + if ((prevCall === null || prevCall === void 0 ? void 0 : prevCall.getOpponentSessionId()) !== participant.sessionId && this.wantsOutgoingCall(userId, deviceId)) { + callsChanged = true; + if (prevCall !== undefined) { + _logger.logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`); + prevCall.hangup(_call.CallErrorCode.NewSession, false); + } + const newCall = (0, _call.createNewMatrixCall)(this.client, this.room.roomId, { + invitee: userId, + opponentDeviceId: deviceId, + opponentSessionId: participant.sessionId, + groupCallId: this.groupCallId + }); + if (newCall === null) { + _logger.logger.error(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to create call (userId=${userId}, device=${deviceId})`); + callMap.delete(deviceId); + } else { + this.initCall(newCall); + callMap.set(deviceId, newCall); + _logger.logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() placing call (userId=${userId}, deviceId=${deviceId}, sessionId=${participant.sessionId})`); + newCall.placeCallWithCallFeeds(this.getLocalFeeds().map(feed => feed.clone()), participant.screensharing).then(() => { + if (this.dataChannelsEnabled) { + newCall.createDataChannel("datachannel", this.dataChannelOptions); + } + }).catch(e => { + _logger.logger.warn(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to place call (userId=${userId})`, e); + if (e instanceof _call.CallError && e.code === GroupCallErrorCode.UnknownDevice) { + this.emit(GroupCallEvent.Error, e); + } else { + this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.PlaceCallFailed, `Failed to place call to ${userId}`)); + } + newCall.hangup(_call.CallErrorCode.SignallingFailed, false); + if (callMap.get(deviceId) === newCall) callMap.delete(deviceId); + }); + } + } + } + if (callMap.size > 0) { + this.calls.set(userId, callMap); + } else { + this.calls.delete(userId); + } + } + if (callsChanged) this.emit(GroupCallEvent.CallsChanged, this.calls); + } + + /* + * Room Member State + */ + + getMemberStateEvents(userId) { + return userId === undefined ? this.room.currentState.getStateEvents(_event.EventType.GroupCallMemberPrefix) : this.room.currentState.getStateEvents(_event.EventType.GroupCallMemberPrefix, userId); + } + initCall(call) { + const opponentMemberId = getCallUserId(call); + if (!opponentMemberId) { + throw new Error("Cannot init call without user id"); + } + const onCallFeedsChanged = () => this.onCallFeedsChanged(call); + const onCallStateChanged = (state, oldState) => this.onCallStateChanged(call, state, oldState); + const onCallHangup = this.onCallHangup; + const onCallReplaced = newCall => this.onCallReplaced(call, newCall); + let deviceMap = this.callHandlers.get(opponentMemberId); + if (deviceMap === undefined) { + deviceMap = new Map(); + this.callHandlers.set(opponentMemberId, deviceMap); + } + deviceMap.set(call.getOpponentDeviceId(), { + onCallFeedsChanged, + onCallStateChanged, + onCallHangup, + onCallReplaced + }); + call.on(_call.CallEvent.FeedsChanged, onCallFeedsChanged); + call.on(_call.CallEvent.State, onCallStateChanged); + call.on(_call.CallEvent.Hangup, onCallHangup); + call.on(_call.CallEvent.Replaced, onCallReplaced); + call.isPtt = this.isPtt; + this.reEmitter.reEmit(call, Object.values(_call.CallEvent)); + call.initStats(this.stats); + onCallFeedsChanged(); + } + disposeCall(call, hangupReason) { + const opponentMemberId = getCallUserId(call); + const opponentDeviceId = call.getOpponentDeviceId(); + if (!opponentMemberId) { + throw new Error("Cannot dispose call without user id"); + } + const deviceMap = this.callHandlers.get(opponentMemberId); + const { + onCallFeedsChanged, + onCallStateChanged, + onCallHangup, + onCallReplaced + } = deviceMap.get(opponentDeviceId); + call.removeListener(_call.CallEvent.FeedsChanged, onCallFeedsChanged); + call.removeListener(_call.CallEvent.State, onCallStateChanged); + call.removeListener(_call.CallEvent.Hangup, onCallHangup); + call.removeListener(_call.CallEvent.Replaced, onCallReplaced); + deviceMap.delete(opponentMemberId); + if (deviceMap.size === 0) this.callHandlers.delete(opponentMemberId); + if (call.hangupReason === _call.CallErrorCode.Replaced) { + return; + } + const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId); + if (usermediaFeed) { + this.removeUserMediaFeed(usermediaFeed); + } + const screenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId); + if (screenshareFeed) { + this.removeScreenshareFeed(screenshareFeed); + } + } + /* + * UserMedia CallFeed Event Handlers + */ + + getUserMediaFeed(userId, deviceId) { + return this.userMediaFeeds.find(f => f.userId === userId && f.deviceId === deviceId); + } + addUserMediaFeed(callFeed) { + this.userMediaFeeds.push(callFeed); + callFeed.measureVolumeActivity(true); + this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds); + } + replaceUserMediaFeed(existingFeed, replacementFeed) { + const feedIndex = this.userMediaFeeds.findIndex(f => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find user media feed to replace"); + } + this.userMediaFeeds.splice(feedIndex, 1, replacementFeed); + existingFeed.dispose(); + replacementFeed.measureVolumeActivity(true); + this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds); + } + removeUserMediaFeed(callFeed) { + const feedIndex = this.userMediaFeeds.findIndex(f => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find user media feed to remove"); + } + this.userMediaFeeds.splice(feedIndex, 1); + callFeed.dispose(); + this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds); + if (this.activeSpeaker === callFeed) { + this.activeSpeaker = this.userMediaFeeds[0]; + this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker); + } + } + /* + * Screenshare Call Feed Event Handlers + */ + + getScreenshareFeed(userId, deviceId) { + return this.screenshareFeeds.find(f => f.userId === userId && f.deviceId === deviceId); + } + addScreenshareFeed(callFeed) { + this.screenshareFeeds.push(callFeed); + this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds); + } + replaceScreenshareFeed(existingFeed, replacementFeed) { + const feedIndex = this.screenshareFeeds.findIndex(f => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find screenshare feed to replace"); + } + this.screenshareFeeds.splice(feedIndex, 1, replacementFeed); + existingFeed.dispose(); + this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds); + } + removeScreenshareFeed(callFeed) { + const feedIndex = this.screenshareFeeds.findIndex(f => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId); + if (feedIndex === -1) { + throw new Error("Couldn't find screenshare feed to remove"); + } + this.screenshareFeeds.splice(feedIndex, 1); + callFeed.dispose(); + this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds); + } + + /** + * Recalculates and updates the participant map to match the room state. + */ + updateParticipants() { + const localMember = this.room.getMember(this.client.getUserId()); + if (!localMember) { + // The client hasn't fetched enough of the room state to get our own member + // event. This probably shouldn't happen, but sanity check & exit for now. + _logger.logger.warn(`GroupCall ${this.groupCallId} updateParticipants() tried to update participants before local room member is available`); + return; + } + if (this.participantsExpirationTimer !== null) { + clearTimeout(this.participantsExpirationTimer); + this.participantsExpirationTimer = null; + } + if (this.state === GroupCallState.Ended) { + this.participants = new Map(); + return; + } + const participants = new Map(); + const now = Date.now(); + const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession; + let nextExpiration = Infinity; + for (const e of this.getMemberStateEvents()) { + const member = this.room.getMember(e.getStateKey()); + const content = e.getContent(); + const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : []; + const call = calls.find(call => call["m.call_id"] === this.groupCallId); + const devices = Array.isArray(call === null || call === void 0 ? void 0 : call["m.devices"]) ? call["m.devices"] : []; + + // Filter out invalid and expired devices + let validDevices = devices.filter(d => typeof d.device_id === "string" && typeof d.session_id === "string" && typeof d.expires_ts === "number" && d.expires_ts > now && Array.isArray(d.feeds)); + + // Apply local echo for the unentered case + if (!entered && (member === null || member === void 0 ? void 0 : member.userId) === this.client.getUserId()) { + validDevices = validDevices.filter(d => d.device_id !== this.client.getDeviceId()); + } + + // Must have a connected device and be joined to the room + if (validDevices.length > 0 && (member === null || member === void 0 ? void 0 : member.membership) === "join") { + const deviceMap = new Map(); + participants.set(member, deviceMap); + for (const d of validDevices) { + deviceMap.set(d.device_id, { + sessionId: d.session_id, + screensharing: d.feeds.some(f => f.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare) + }); + if (d.expires_ts < nextExpiration) nextExpiration = d.expires_ts; + } + } + } + + // Apply local echo for the entered case + if (entered) { + let deviceMap = participants.get(localMember); + if (deviceMap === undefined) { + deviceMap = new Map(); + participants.set(localMember, deviceMap); + } + if (!deviceMap.has(this.client.getDeviceId())) { + deviceMap.set(this.client.getDeviceId(), { + sessionId: this.client.getSessionId(), + screensharing: this.getLocalFeeds().some(f => f.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare) + }); + } + } + this.participants = participants; + if (nextExpiration < Infinity) { + this.participantsExpirationTimer = setTimeout(() => this.updateParticipants(), nextExpiration - now); + } + } + + /** + * Updates the local user's member state with the devices returned by the given function. + * @param fn - A function from the current devices to the new devices. If it + * returns null, the update will be skipped. + * @param keepAlive - Whether the request should outlive the window. + */ + async updateDevices(fn, keepAlive = false) { + var _event$getContent; + const now = Date.now(); + const localUserId = this.client.getUserId(); + const event = this.getMemberStateEvents(localUserId); + const content = (_event$getContent = event === null || event === void 0 ? void 0 : event.getContent()) !== null && _event$getContent !== void 0 ? _event$getContent : {}; + const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : []; + let call = null; + const otherCalls = []; + for (const c of calls) { + if (c["m.call_id"] === this.groupCallId) { + call = c; + } else { + otherCalls.push(c); + } + } + if (call === null) call = {}; + const devices = Array.isArray(call["m.devices"]) ? call["m.devices"] : []; + + // Filter out invalid and expired devices + const validDevices = devices.filter(d => typeof d.device_id === "string" && typeof d.session_id === "string" && typeof d.expires_ts === "number" && d.expires_ts > now && Array.isArray(d.feeds)); + const newDevices = fn(validDevices); + if (newDevices === null) return; + const newCalls = [...otherCalls]; + if (newDevices.length > 0) { + newCalls.push(_objectSpread(_objectSpread({}, call), {}, { + "m.call_id": this.groupCallId, + "m.devices": newDevices + })); + } + const newContent = { + "m.calls": newCalls + }; + await this.client.sendStateEvent(this.room.roomId, _event.EventType.GroupCallMemberPrefix, newContent, localUserId, { + keepAlive + }); + } + async addDeviceToMemberState() { + await this.updateDevices(devices => [...devices.filter(d => d.device_id !== this.client.getDeviceId()), { + device_id: this.client.getDeviceId(), + session_id: this.client.getSessionId(), + expires_ts: Date.now() + DEVICE_TIMEOUT, + feeds: this.getLocalFeeds().map(feed => ({ + purpose: feed.purpose + })) + // TODO: Add data channels + }]); + } + + async updateMemberState() { + // Clear the old update interval before proceeding + if (this.resendMemberStateTimer !== null) { + clearInterval(this.resendMemberStateTimer); + this.resendMemberStateTimer = null; + } + if (this.state === GroupCallState.Entered) { + // Add the local device + await this.addDeviceToMemberState(); + + // Resend the state event every so often so it doesn't become stale + this.resendMemberStateTimer = setInterval(async () => { + _logger.logger.log(`GroupCall ${this.groupCallId} updateMemberState() resending call member state"`); + try { + await this.addDeviceToMemberState(); + } catch (e) { + _logger.logger.error(`GroupCall ${this.groupCallId} updateMemberState() failed to resend call member state`, e); + } + }, DEVICE_TIMEOUT * 3 / 4); + } else { + // Remove the local device + await this.updateDevices(devices => devices.filter(d => d.device_id !== this.client.getDeviceId()), true); + } + } + + /** + * Cleans up our member state by filtering out logged out devices, inactive + * devices, and our own device (if we know we haven't entered). + */ + async cleanMemberState() { + const { + devices: myDevices + } = await this.client.getDevices(); + const deviceMap = new Map(myDevices.map(d => [d.device_id, d])); + + // updateDevices takes care of filtering out inactive devices for us + await this.updateDevices(devices => { + const newDevices = devices.filter(d => { + const device = deviceMap.get(d.device_id); + return (device === null || device === void 0 ? void 0 : device.last_seen_ts) !== undefined && !(d.device_id === this.client.getDeviceId() && this.state !== GroupCallState.Entered && !this.enteredViaAnotherSession); + }); + + // Skip the update if the devices are unchanged + return newDevices.length === devices.length ? null : newDevices; + }); + } + getGroupCallStats() { + return this.stats; + } +} +exports.GroupCall = GroupCall; +//# sourceMappingURL=groupCall.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js.map new file mode 100644 index 0000000..aee367e --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCall.js.map @@ -0,0 +1 @@ +{"version":3,"file":"groupCall.js","names":["_typedEventEmitter","require","_callFeed","_call","_roomState","_logger","_ReEmitter","_callEventTypes","_event","_callEventHandler","_groupCallEventHandler","_utils","_groupCallStats","_statsReport","ownKeys","object","enumerableOnly","keys","Object","getOwnPropertySymbols","symbols","filter","sym","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","target","i","arguments","length","source","forEach","key","_defineProperty2","default","getOwnPropertyDescriptors","defineProperties","defineProperty","GroupCallIntent","exports","GroupCallType","GroupCallTerminationReason","GroupCallEvent","GroupCallStatsReportEvent","GroupCallErrorCode","GroupCallError","Error","constructor","code","msg","err","GroupCallUnknownDeviceError","userId","UnknownDevice","OtherUserSpeakingError","GroupCallState","DEVICE_TIMEOUT","getCallUserId","call","_call$getOpponentMemb","getOpponentMember","invitee","GroupCall","TypedEventEmitter","client","room","type","isPtt","intent","groupCallId","dataChannelsEnabled","dataChannelOptions","isCallWithoutVideoAndAudio","_room$currentState$ge","_room$currentState$ge2","Map","report","emit","ConnectionStats","ByteSentStats","LocalCallFeedUninitialized","newCall","_newCall$getOpponentM","_this$calls$get","roomId","state","CallState","Ringing","logger","warn","log","reject","opponentUserId","undefined","deviceMap","calls","get","prevCall","getOpponentDeviceId","callId","hangup","CallErrorCode","Replaced","initCall","feeds","getLocalFeeds","map","feed","clone","callExpected","setTracksEnabled","stream","getAudioTracks","getVideoTracks","answerWithCallFeeds","set","CallsChanged","needsRetry","participantMap","participants","callMap","retriesMap","retryCallCounts","deviceId","participant","_retriesMap$get","_retriesMap","retries","getOpponentSessionId","sessionId","wantsOutgoingCall","placeOutgoingCalls","opponentMemberId","opponentDeviceId","currentUserMediaFeed","getUserMediaFeed","remoteUsermediaFeed","remoteFeedChanged","addUserMediaFeed","replaceUserMediaFeed","removeUserMediaFeed","currentScreenshareFeed","getScreenshareFeed","remoteScreensharingFeed","remoteScreenshareFeedChanged","addScreenshareFeed","replaceScreenshareFeed","removeScreenshareFeed","_oldState","_call$getOpponentMemb2","Ended","audioMuted","localCallFeed","isAudioMuted","localUsermediaStream","isMicrophoneMuted","setMicrophoneMuted","videoMuted","isVideoMuted","isLocalVideoMuted","setLocalVideoMuted","Connected","delete","size","_call$getOpponentMemb3","_call$getOpponentMemb4","hangupReason","getMember","disposeCall","topAvg","nextActiveSpeaker","callFeed","userMediaFeeds","isLocal","total","speakingVolumeSamples","reduce","acc","volume","Math","max","SPEAKING_THRESHOLD","avg","activeSpeaker","ActiveSpeakerChanged","updateParticipants","forEachCall","expected","Entered","newState","oldState","updateMemberState","catch","e","error","reEmitter","ReEmitter","genCallID","creationTs","currentState","getStateEvents","EventType","GroupCallPrefix","getTs","on","RoomStateEvent","Update","onRoomState","ParticipantsChanged","onParticipantsChanged","GroupCallStateChanged","onStateChanged","LocalScreenshareStateChanged","onLocalFeedsChanged","allowCallWithoutVideoAndAudio","userID","getUserId","stats","GroupCallStats","reports","StatsReport","CONNECTION_STATS","onConnectionStats","BYTE_SENT_STATS","onByteSentStats","create","Date","now","groupCallEventHandler","groupCalls","GroupCallEventHandlerEvent","Outgoing","groupCallState","sendStateEvent","_state","value","prevValue","_participants","participantStateEqual","x","y","screensharing","deviceMapsEqual","mapsEqual","_creationTs","enteredViaAnotherSession","_enteredViaAnotherSession","f","values","localScreenshareFeed","hasLocalParticipant","_this$participants$ge","_this$participants$ge2","has","getDeviceId","_this$participants$ge3","member","initLocalCallFeed","InitializingLocalCallFeed","initCallFeedPromise","initLocalCallFeedInternal","getMediaHandler","getUserMediaStream","Video","MediaStream","stopUserMediaStream","CallFeed","purpose","SDPStreamMetadataPurpose","Usermedia","initWithAudioMuted","initWithVideoMuted","LocalCallFeedInitialized","updateLocalUsermediaStream","oldStream","setNewStream","micShouldBeMuted","vidShouldBeMuted","id","enter","CallEventHandlerEvent","Incoming","onIncomingCall","callEventHandler","retryCallLoopInterval","setInterval","onRetryCallLoop","retryCallInterval","onActiveSpeakerLoop","activeSpeakerLoopInterval","activeSpeakerInterval","dispose","stopScreensharingStream","localDesktopCapturerSourceId","stopAllStreams","transmitTimer","clearTimeout","clearInterval","participantsExpirationTimer","UserHangup","clear","removeListener","stop","leave","terminate","emitStateEvent","off","existingStateEvent","getContent","CallEnded","muted","hasAudioDevice","sendUpdatesBefore","setTimeout","pttMaxTransmitTime","_call$localUsermediaF","localUsermediaFeed","setAudioVideoMuted","sendUpdates","updates","sendMetadataUpdate","Promise","all","info","LocalMuteStateChanged","hasVideoDevice","_","setScreensharingEnabled","enabled","opts","isScreensharing","getScreensharingStream","track","getTracks","onTrackEnded","removeEventListener","addEventListener","desktopCapturerSourceId","Screenshare","pushLocalFeed","throwOnFail","NoUserMedia","localScreensharingFeed","removeLocalFeed","localUserId","localDeviceId","callsChanged","_this$calls$get2","debug","NewSession","createNewMatrixCall","opponentSessionId","placeCallWithCallFeeds","then","createDataChannel","CallError","PlaceCallFailed","SignallingFailed","getMemberStateEvents","GroupCallMemberPrefix","onCallFeedsChanged","onCallStateChanged","onCallHangup","onCallReplaced","callHandlers","CallEvent","FeedsChanged","State","Hangup","reEmit","initStats","usermediaFeed","screenshareFeed","find","measureVolumeActivity","UserMediaFeedsChanged","existingFeed","replacementFeed","feedIndex","findIndex","splice","screenshareFeeds","ScreenshareFeedsChanged","localMember","entered","nextExpiration","Infinity","getStateKey","content","Array","isArray","devices","validDevices","d","device_id","session_id","expires_ts","membership","some","getSessionId","updateDevices","fn","keepAlive","_event$getContent","event","otherCalls","c","newDevices","newCalls","newContent","addDeviceToMemberState","resendMemberStateTimer","cleanMemberState","myDevices","getDevices","device","last_seen_ts","getGroupCallStats"],"sources":["../../src/webrtc/groupCall.ts"],"sourcesContent":["import { TypedEventEmitter } from \"../models/typed-event-emitter\";\nimport { CallFeed, SPEAKING_THRESHOLD } from \"./callFeed\";\nimport { MatrixClient, IMyDevice } from \"../client\";\nimport {\n CallErrorCode,\n CallEvent,\n CallEventHandlerMap,\n CallState,\n genCallID,\n MatrixCall,\n setTracksEnabled,\n createNewMatrixCall,\n CallError,\n} from \"./call\";\nimport { RoomMember } from \"../models/room-member\";\nimport { Room } from \"../models/room\";\nimport { RoomStateEvent } from \"../models/room-state\";\nimport { logger } from \"../logger\";\nimport { ReEmitter } from \"../ReEmitter\";\nimport { SDPStreamMetadataPurpose } from \"./callEventTypes\";\nimport { MatrixEvent } from \"../models/event\";\nimport { EventType } from \"../@types/event\";\nimport { CallEventHandlerEvent } from \"./callEventHandler\";\nimport { GroupCallEventHandlerEvent } from \"./groupCallEventHandler\";\nimport { IScreensharingOpts } from \"./mediaHandler\";\nimport { mapsEqual } from \"../utils\";\nimport { GroupCallStats } from \"./stats/groupCallStats\";\nimport { ByteSentStatsReport, ConnectionStatsReport, StatsReport } from \"./stats/statsReport\";\n\nexport enum GroupCallIntent {\n Ring = \"m.ring\",\n Prompt = \"m.prompt\",\n Room = \"m.room\",\n}\n\nexport enum GroupCallType {\n Video = \"m.video\",\n Voice = \"m.voice\",\n}\n\nexport enum GroupCallTerminationReason {\n CallEnded = \"call_ended\",\n}\n\nexport type CallsByUserAndDevice = Map<string, Map<string, MatrixCall>>;\n\n/**\n * Because event names are just strings, they do need\n * to be unique over all event types of event emitter.\n * Some objects could emit more then one set of events.\n */\nexport enum GroupCallEvent {\n GroupCallStateChanged = \"group_call_state_changed\",\n ActiveSpeakerChanged = \"active_speaker_changed\",\n CallsChanged = \"calls_changed\",\n UserMediaFeedsChanged = \"user_media_feeds_changed\",\n ScreenshareFeedsChanged = \"screenshare_feeds_changed\",\n LocalScreenshareStateChanged = \"local_screenshare_state_changed\",\n LocalMuteStateChanged = \"local_mute_state_changed\",\n ParticipantsChanged = \"participants_changed\",\n Error = \"group_call_error\",\n}\n\nexport type GroupCallEventHandlerMap = {\n [GroupCallEvent.GroupCallStateChanged]: (newState: GroupCallState, oldState: GroupCallState) => void;\n [GroupCallEvent.ActiveSpeakerChanged]: (activeSpeaker: CallFeed | undefined) => void;\n [GroupCallEvent.CallsChanged]: (calls: CallsByUserAndDevice) => void;\n [GroupCallEvent.UserMediaFeedsChanged]: (feeds: CallFeed[]) => void;\n [GroupCallEvent.ScreenshareFeedsChanged]: (feeds: CallFeed[]) => void;\n [GroupCallEvent.LocalScreenshareStateChanged]: (\n isScreensharing: boolean,\n feed?: CallFeed,\n sourceId?: string,\n ) => void;\n [GroupCallEvent.LocalMuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;\n [GroupCallEvent.ParticipantsChanged]: (participants: Map<RoomMember, Map<string, ParticipantState>>) => void;\n /**\n * Fires whenever an error occurs when call.js encounters an issue with setting up the call.\n * <p>\n * The error given will have a code equal to either `MatrixCall.ERR_LOCAL_OFFER_FAILED` or\n * `MatrixCall.ERR_NO_USER_MEDIA`. `ERR_LOCAL_OFFER_FAILED` is emitted when the local client\n * fails to create an offer. `ERR_NO_USER_MEDIA` is emitted when the user has denied access\n * to their audio/video hardware.\n * @param err - The error raised by MatrixCall.\n * @example\n * ```\n * matrixCall.on(\"error\", function(err){\n * console.error(err.code, err);\n * });\n * ```\n */\n [GroupCallEvent.Error]: (error: GroupCallError) => void;\n};\n\nexport enum GroupCallStatsReportEvent {\n ConnectionStats = \"GroupCall.connection_stats\",\n ByteSentStats = \"GroupCall.byte_sent_stats\",\n}\n\nexport type GroupCallStatsReportEventHandlerMap = {\n [GroupCallStatsReportEvent.ConnectionStats]: (report: GroupCallStatsReport<ConnectionStatsReport>) => void;\n [GroupCallStatsReportEvent.ByteSentStats]: (report: GroupCallStatsReport<ByteSentStatsReport>) => void;\n};\n\nexport enum GroupCallErrorCode {\n NoUserMedia = \"no_user_media\",\n UnknownDevice = \"unknown_device\",\n PlaceCallFailed = \"place_call_failed\",\n}\n\nexport interface GroupCallStatsReport<T extends ConnectionStatsReport | ByteSentStatsReport> {\n report: T;\n}\n\nexport class GroupCallError extends Error {\n public code: string;\n\n public constructor(code: GroupCallErrorCode, msg: string, err?: Error) {\n // Still don't think there's any way to have proper nested errors\n if (err) {\n super(msg + \": \" + err);\n } else {\n super(msg);\n }\n\n this.code = code;\n }\n}\n\nexport class GroupCallUnknownDeviceError extends GroupCallError {\n public constructor(public userId: string) {\n super(GroupCallErrorCode.UnknownDevice, \"No device found for \" + userId);\n }\n}\n\nexport class OtherUserSpeakingError extends Error {\n public constructor() {\n super(\"Cannot unmute: another user is speaking\");\n }\n}\n\nexport interface IGroupCallDataChannelOptions {\n ordered: boolean;\n maxPacketLifeTime: number;\n maxRetransmits: number;\n protocol: string;\n}\n\nexport interface IGroupCallRoomState {\n \"m.intent\": GroupCallIntent;\n \"m.type\": GroupCallType;\n \"io.element.ptt\"?: boolean;\n // TODO: Specify data-channels\n \"dataChannelsEnabled\"?: boolean;\n \"dataChannelOptions\"?: IGroupCallDataChannelOptions;\n}\n\nexport interface IGroupCallRoomMemberFeed {\n purpose: SDPStreamMetadataPurpose;\n}\n\nexport interface IGroupCallRoomMemberDevice {\n device_id: string;\n session_id: string;\n expires_ts: number;\n feeds: IGroupCallRoomMemberFeed[];\n}\n\nexport interface IGroupCallRoomMemberCallState {\n \"m.call_id\": string;\n \"m.foci\"?: string[];\n \"m.devices\": IGroupCallRoomMemberDevice[];\n}\n\nexport interface IGroupCallRoomMemberState {\n \"m.calls\": IGroupCallRoomMemberCallState[];\n}\n\nexport enum GroupCallState {\n LocalCallFeedUninitialized = \"local_call_feed_uninitialized\",\n InitializingLocalCallFeed = \"initializing_local_call_feed\",\n LocalCallFeedInitialized = \"local_call_feed_initialized\",\n Entered = \"entered\",\n Ended = \"ended\",\n}\n\nexport interface ParticipantState {\n sessionId: string;\n screensharing: boolean;\n}\n\ninterface ICallHandlers {\n onCallFeedsChanged: (feeds: CallFeed[]) => void;\n onCallStateChanged: (state: CallState, oldState: CallState | undefined) => void;\n onCallHangup: (call: MatrixCall) => void;\n onCallReplaced: (newCall: MatrixCall) => void;\n}\n\nconst DEVICE_TIMEOUT = 1000 * 60 * 60; // 1 hour\n\nfunction getCallUserId(call: MatrixCall): string | null {\n return call.getOpponentMember()?.userId || call.invitee || null;\n}\n\nexport class GroupCall extends TypedEventEmitter<\n GroupCallEvent | CallEvent | GroupCallStatsReportEvent,\n GroupCallEventHandlerMap & CallEventHandlerMap & GroupCallStatsReportEventHandlerMap\n> {\n // Config\n public activeSpeakerInterval = 1000;\n public retryCallInterval = 5000;\n public participantTimeout = 1000 * 15;\n public pttMaxTransmitTime = 1000 * 20;\n\n public activeSpeaker?: CallFeed;\n public localCallFeed?: CallFeed;\n public localScreenshareFeed?: CallFeed;\n public localDesktopCapturerSourceId?: string;\n public readonly userMediaFeeds: CallFeed[] = [];\n public readonly screenshareFeeds: CallFeed[] = [];\n public groupCallId: string;\n public readonly allowCallWithoutVideoAndAudio: boolean;\n\n private readonly calls = new Map<string, Map<string, MatrixCall>>(); // user_id -> device_id -> MatrixCall\n private callHandlers = new Map<string, Map<string, ICallHandlers>>(); // user_id -> device_id -> ICallHandlers\n private activeSpeakerLoopInterval?: ReturnType<typeof setTimeout>;\n private retryCallLoopInterval?: ReturnType<typeof setTimeout>;\n private retryCallCounts: Map<string, Map<string, number>> = new Map(); // user_id -> device_id -> count\n private reEmitter: ReEmitter;\n private transmitTimer: ReturnType<typeof setTimeout> | null = null;\n private participantsExpirationTimer: ReturnType<typeof setTimeout> | null = null;\n private resendMemberStateTimer: ReturnType<typeof setInterval> | null = null;\n private initWithAudioMuted = false;\n private initWithVideoMuted = false;\n private initCallFeedPromise?: Promise<void>;\n\n private readonly stats: GroupCallStats;\n\n public constructor(\n private client: MatrixClient,\n public room: Room,\n public type: GroupCallType,\n public isPtt: boolean,\n public intent: GroupCallIntent,\n groupCallId?: string,\n private dataChannelsEnabled?: boolean,\n private dataChannelOptions?: IGroupCallDataChannelOptions,\n isCallWithoutVideoAndAudio?: boolean,\n ) {\n super();\n this.reEmitter = new ReEmitter(this);\n this.groupCallId = groupCallId ?? genCallID();\n this.creationTs =\n room.currentState.getStateEvents(EventType.GroupCallPrefix, this.groupCallId)?.getTs() ?? null;\n this.updateParticipants();\n\n room.on(RoomStateEvent.Update, this.onRoomState);\n this.on(GroupCallEvent.ParticipantsChanged, this.onParticipantsChanged);\n this.on(GroupCallEvent.GroupCallStateChanged, this.onStateChanged);\n this.on(GroupCallEvent.LocalScreenshareStateChanged, this.onLocalFeedsChanged);\n this.allowCallWithoutVideoAndAudio = !!isCallWithoutVideoAndAudio;\n\n const userID = this.client.getUserId() || \"unknown\";\n this.stats = new GroupCallStats(this.groupCallId, userID);\n this.stats.reports.on(StatsReport.CONNECTION_STATS, this.onConnectionStats);\n this.stats.reports.on(StatsReport.BYTE_SENT_STATS, this.onByteSentStats);\n }\n\n private onConnectionStats = (report: ConnectionStatsReport): void => {\n // @TODO: Implement data argumentation\n this.emit(GroupCallStatsReportEvent.ConnectionStats, { report });\n };\n\n private onByteSentStats = (report: ByteSentStatsReport): void => {\n // @TODO: Implement data argumentation\n this.emit(GroupCallStatsReportEvent.ByteSentStats, { report });\n };\n\n public async create(): Promise<GroupCall> {\n this.creationTs = Date.now();\n this.client.groupCallEventHandler!.groupCalls.set(this.room.roomId, this);\n this.client.emit(GroupCallEventHandlerEvent.Outgoing, this);\n\n const groupCallState: IGroupCallRoomState = {\n \"m.intent\": this.intent,\n \"m.type\": this.type,\n \"io.element.ptt\": this.isPtt,\n // TODO: Specify data-channels better\n \"dataChannelsEnabled\": this.dataChannelsEnabled,\n \"dataChannelOptions\": this.dataChannelsEnabled ? this.dataChannelOptions : undefined,\n };\n\n await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallPrefix, groupCallState, this.groupCallId);\n\n return this;\n }\n\n private _state = GroupCallState.LocalCallFeedUninitialized;\n\n /**\n * The group call's state.\n */\n public get state(): GroupCallState {\n return this._state;\n }\n\n private set state(value: GroupCallState) {\n const prevValue = this._state;\n if (value !== prevValue) {\n this._state = value;\n this.emit(GroupCallEvent.GroupCallStateChanged, value, prevValue);\n }\n }\n\n private _participants = new Map<RoomMember, Map<string, ParticipantState>>();\n\n /**\n * The current participants in the call, as a map from members to device IDs\n * to participant info.\n */\n public get participants(): Map<RoomMember, Map<string, ParticipantState>> {\n return this._participants;\n }\n\n private set participants(value: Map<RoomMember, Map<string, ParticipantState>>) {\n const prevValue = this._participants;\n const participantStateEqual = (x: ParticipantState, y: ParticipantState): boolean =>\n x.sessionId === y.sessionId && x.screensharing === y.screensharing;\n const deviceMapsEqual = (x: Map<string, ParticipantState>, y: Map<string, ParticipantState>): boolean =>\n mapsEqual(x, y, participantStateEqual);\n\n // Only update if the map actually changed\n if (!mapsEqual(value, prevValue, deviceMapsEqual)) {\n this._participants = value;\n this.emit(GroupCallEvent.ParticipantsChanged, value);\n }\n }\n\n private _creationTs: number | null = null;\n\n /**\n * The timestamp at which the call was created, or null if it has not yet\n * been created.\n */\n public get creationTs(): number | null {\n return this._creationTs;\n }\n\n private set creationTs(value: number | null) {\n this._creationTs = value;\n }\n\n private _enteredViaAnotherSession = false;\n\n /**\n * Whether the local device has entered this call via another session, such\n * as a widget.\n */\n public get enteredViaAnotherSession(): boolean {\n return this._enteredViaAnotherSession;\n }\n\n public set enteredViaAnotherSession(value: boolean) {\n this._enteredViaAnotherSession = value;\n this.updateParticipants();\n }\n\n /**\n * Executes the given callback on all calls in this group call.\n * @param f - The callback.\n */\n public forEachCall(f: (call: MatrixCall) => void): void {\n for (const deviceMap of this.calls.values()) {\n for (const call of deviceMap.values()) f(call);\n }\n }\n\n public getLocalFeeds(): CallFeed[] {\n const feeds: CallFeed[] = [];\n\n if (this.localCallFeed) feeds.push(this.localCallFeed);\n if (this.localScreenshareFeed) feeds.push(this.localScreenshareFeed);\n\n return feeds;\n }\n\n public hasLocalParticipant(): boolean {\n return (\n this.participants.get(this.room.getMember(this.client.getUserId()!)!)?.has(this.client.getDeviceId()!) ??\n false\n );\n }\n\n /**\n * Determines whether the given call is one that we were expecting to exist\n * given our knowledge of who is participating in the group call.\n */\n private callExpected(call: MatrixCall): boolean {\n const userId = getCallUserId(call);\n const member = userId === null ? null : this.room.getMember(userId);\n const deviceId = call.getOpponentDeviceId();\n return member !== null && deviceId !== undefined && this.participants.get(member)?.get(deviceId) !== undefined;\n }\n\n public async initLocalCallFeed(): Promise<void> {\n if (this.state !== GroupCallState.LocalCallFeedUninitialized) {\n throw new Error(`Cannot initialize local call feed in the \"${this.state}\" state.`);\n }\n this.state = GroupCallState.InitializingLocalCallFeed;\n\n // wraps the real method to serialise calls, because we don't want to try starting\n // multiple call feeds at once\n if (this.initCallFeedPromise) return this.initCallFeedPromise;\n\n try {\n this.initCallFeedPromise = this.initLocalCallFeedInternal();\n await this.initCallFeedPromise;\n } finally {\n this.initCallFeedPromise = undefined;\n }\n }\n\n private async initLocalCallFeedInternal(): Promise<void> {\n logger.log(`GroupCall ${this.groupCallId} initLocalCallFeedInternal() running`);\n\n let stream: MediaStream;\n\n try {\n stream = await this.client.getMediaHandler().getUserMediaStream(true, this.type === GroupCallType.Video);\n } catch (error) {\n // If is allowed to join a call without a media stream, then we\n // don't throw an error here. But we need an empty Local Feed to establish\n // a connection later.\n if (this.allowCallWithoutVideoAndAudio) {\n stream = new MediaStream();\n } else {\n this.state = GroupCallState.LocalCallFeedUninitialized;\n throw error;\n }\n }\n\n // The call could've been disposed while we were waiting, and could\n // also have been started back up again (hello, React 18) so if we're\n // still in this 'initializing' state, carry on, otherwise bail.\n if (this._state !== GroupCallState.InitializingLocalCallFeed) {\n this.client.getMediaHandler().stopUserMediaStream(stream);\n throw new Error(\"Group call disposed while gathering media stream\");\n }\n\n const callFeed = new CallFeed({\n client: this.client,\n roomId: this.room.roomId,\n userId: this.client.getUserId()!,\n deviceId: this.client.getDeviceId()!,\n stream,\n purpose: SDPStreamMetadataPurpose.Usermedia,\n audioMuted: this.initWithAudioMuted || stream.getAudioTracks().length === 0 || this.isPtt,\n videoMuted: this.initWithVideoMuted || stream.getVideoTracks().length === 0,\n });\n\n setTracksEnabled(stream.getAudioTracks(), !callFeed.isAudioMuted());\n setTracksEnabled(stream.getVideoTracks(), !callFeed.isVideoMuted());\n\n this.localCallFeed = callFeed;\n this.addUserMediaFeed(callFeed);\n\n this.state = GroupCallState.LocalCallFeedInitialized;\n }\n\n public async updateLocalUsermediaStream(stream: MediaStream): Promise<void> {\n if (this.localCallFeed) {\n const oldStream = this.localCallFeed.stream;\n this.localCallFeed.setNewStream(stream);\n const micShouldBeMuted = this.localCallFeed.isAudioMuted();\n const vidShouldBeMuted = this.localCallFeed.isVideoMuted();\n logger.log(\n `GroupCall ${this.groupCallId} updateLocalUsermediaStream() (oldStreamId=${oldStream.id}, newStreamId=${stream.id}, micShouldBeMuted=${micShouldBeMuted}, vidShouldBeMuted=${vidShouldBeMuted})`,\n );\n setTracksEnabled(stream.getAudioTracks(), !micShouldBeMuted);\n setTracksEnabled(stream.getVideoTracks(), !vidShouldBeMuted);\n this.client.getMediaHandler().stopUserMediaStream(oldStream);\n }\n }\n\n public async enter(): Promise<void> {\n if (this.state === GroupCallState.LocalCallFeedUninitialized) {\n await this.initLocalCallFeed();\n } else if (this.state !== GroupCallState.LocalCallFeedInitialized) {\n throw new Error(`Cannot enter call in the \"${this.state}\" state`);\n }\n\n logger.log(`GroupCall ${this.groupCallId} enter() running`);\n this.state = GroupCallState.Entered;\n\n this.client.on(CallEventHandlerEvent.Incoming, this.onIncomingCall);\n\n for (const call of this.client.callEventHandler!.calls.values()) {\n this.onIncomingCall(call);\n }\n\n this.retryCallLoopInterval = setInterval(this.onRetryCallLoop, this.retryCallInterval);\n\n this.activeSpeaker = undefined;\n this.onActiveSpeakerLoop();\n this.activeSpeakerLoopInterval = setInterval(this.onActiveSpeakerLoop, this.activeSpeakerInterval);\n }\n\n private dispose(): void {\n if (this.localCallFeed) {\n this.removeUserMediaFeed(this.localCallFeed);\n this.localCallFeed = undefined;\n }\n\n if (this.localScreenshareFeed) {\n this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream);\n this.removeScreenshareFeed(this.localScreenshareFeed);\n this.localScreenshareFeed = undefined;\n this.localDesktopCapturerSourceId = undefined;\n }\n\n this.client.getMediaHandler().stopAllStreams();\n\n if (this.transmitTimer !== null) {\n clearTimeout(this.transmitTimer);\n this.transmitTimer = null;\n }\n\n if (this.retryCallLoopInterval !== undefined) {\n clearInterval(this.retryCallLoopInterval);\n this.retryCallLoopInterval = undefined;\n }\n\n if (this.participantsExpirationTimer !== null) {\n clearTimeout(this.participantsExpirationTimer);\n this.participantsExpirationTimer = null;\n }\n\n if (this.state !== GroupCallState.Entered) {\n return;\n }\n\n this.forEachCall((call) => call.hangup(CallErrorCode.UserHangup, false));\n\n this.activeSpeaker = undefined;\n clearInterval(this.activeSpeakerLoopInterval);\n\n this.retryCallCounts.clear();\n clearInterval(this.retryCallLoopInterval);\n\n this.client.removeListener(CallEventHandlerEvent.Incoming, this.onIncomingCall);\n this.stats.stop();\n }\n\n public leave(): void {\n this.dispose();\n this.state = GroupCallState.LocalCallFeedUninitialized;\n }\n\n public async terminate(emitStateEvent = true): Promise<void> {\n this.dispose();\n\n this.room.off(RoomStateEvent.Update, this.onRoomState);\n this.client.groupCallEventHandler!.groupCalls.delete(this.room.roomId);\n this.client.emit(GroupCallEventHandlerEvent.Ended, this);\n this.state = GroupCallState.Ended;\n\n if (emitStateEvent) {\n const existingStateEvent = this.room.currentState.getStateEvents(\n EventType.GroupCallPrefix,\n this.groupCallId,\n )!;\n\n await this.client.sendStateEvent(\n this.room.roomId,\n EventType.GroupCallPrefix,\n {\n ...existingStateEvent.getContent(),\n \"m.terminated\": GroupCallTerminationReason.CallEnded,\n },\n this.groupCallId,\n );\n }\n }\n\n /*\n * Local Usermedia\n */\n\n public isLocalVideoMuted(): boolean {\n if (this.localCallFeed) {\n return this.localCallFeed.isVideoMuted();\n }\n\n return true;\n }\n\n public isMicrophoneMuted(): boolean {\n if (this.localCallFeed) {\n return this.localCallFeed.isAudioMuted();\n }\n\n return true;\n }\n\n /**\n * Sets the mute state of the local participants's microphone.\n * @param muted - Whether to mute the microphone\n * @returns Whether muting/unmuting was successful\n */\n public async setMicrophoneMuted(muted: boolean): Promise<boolean> {\n // hasAudioDevice can block indefinitely if the window has lost focus,\n // and it doesn't make much sense to keep a device from being muted, so\n // we always allow muted = true changes to go through\n if (!muted && !(await this.client.getMediaHandler().hasAudioDevice())) {\n return false;\n }\n\n const sendUpdatesBefore = !muted && this.isPtt;\n\n // set a timer for the maximum transmit time on PTT calls\n if (this.isPtt) {\n // Set or clear the max transmit timer\n if (!muted && this.isMicrophoneMuted()) {\n this.transmitTimer = setTimeout(() => {\n this.setMicrophoneMuted(true);\n }, this.pttMaxTransmitTime);\n } else if (muted && !this.isMicrophoneMuted()) {\n if (this.transmitTimer !== null) clearTimeout(this.transmitTimer);\n this.transmitTimer = null;\n }\n }\n\n this.forEachCall((call) => call.localUsermediaFeed?.setAudioVideoMuted(muted, null));\n\n const sendUpdates = async (): Promise<void> => {\n const updates: Promise<void>[] = [];\n this.forEachCall((call) => updates.push(call.sendMetadataUpdate()));\n\n await Promise.all(updates).catch((e) =>\n logger.info(\n `GroupCall ${this.groupCallId} setMicrophoneMuted() failed to send some metadata updates`,\n e,\n ),\n );\n };\n\n if (sendUpdatesBefore) await sendUpdates();\n\n if (this.localCallFeed) {\n logger.log(\n `GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`,\n );\n\n // We needed this here to avoid an error in case user join a call without a device.\n // I can not use .then .catch functions because linter :-(\n try {\n if (!muted) {\n const stream = await this.client\n .getMediaHandler()\n .getUserMediaStream(true, !this.localCallFeed.isVideoMuted());\n if (stream === null) {\n // if case permission denied to get a stream stop this here\n /* istanbul ignore next */\n logger.log(\n `GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`,\n );\n return false;\n }\n }\n } catch (e) {\n /* istanbul ignore next */\n logger.log(\n `GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`,\n );\n return false;\n }\n\n this.localCallFeed.setAudioVideoMuted(muted, null);\n // I don't believe its actually necessary to enable these tracks: they\n // are the one on the GroupCall's own CallFeed and are cloned before being\n // given to any of the actual calls, so these tracks don't actually go\n // anywhere. Let's do it anyway to avoid confusion.\n setTracksEnabled(this.localCallFeed.stream.getAudioTracks(), !muted);\n } else {\n logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no stream muted (muted=${muted})`);\n this.initWithAudioMuted = muted;\n }\n\n this.forEachCall((call) =>\n setTracksEnabled(call.localUsermediaFeed!.stream.getAudioTracks(), !muted && this.callExpected(call)),\n );\n this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted());\n\n if (!sendUpdatesBefore) await sendUpdates();\n\n return true;\n }\n\n /**\n * Sets the mute state of the local participants's video.\n * @param muted - Whether to mute the video\n * @returns Whether muting/unmuting was successful\n */\n public async setLocalVideoMuted(muted: boolean): Promise<boolean> {\n // hasAudioDevice can block indefinitely if the window has lost focus,\n // and it doesn't make much sense to keep a device from being muted, so\n // we always allow muted = true changes to go through\n if (!muted && !(await this.client.getMediaHandler().hasVideoDevice())) {\n return false;\n }\n\n if (this.localCallFeed) {\n /* istanbul ignore next */\n logger.log(\n `GroupCall ${this.groupCallId} setLocalVideoMuted() (stream=${this.localCallFeed.stream.id}, muted=${muted})`,\n );\n\n try {\n const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted);\n await this.updateLocalUsermediaStream(stream);\n this.localCallFeed.setAudioVideoMuted(null, muted);\n setTracksEnabled(this.localCallFeed.stream.getVideoTracks(), !muted);\n } catch (_) {\n // No permission to video device\n /* istanbul ignore next */\n logger.log(\n `GroupCall ${this.groupCallId} setLocalVideoMuted() no device or permission to receive local stream, muted=${muted}`,\n );\n return false;\n }\n } else {\n logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no stream muted (muted=${muted})`);\n this.initWithVideoMuted = muted;\n }\n\n const updates: Promise<unknown>[] = [];\n this.forEachCall((call) => updates.push(call.setLocalVideoMuted(muted)));\n await Promise.all(updates);\n\n // We setTracksEnabled again, independently from the call doing it\n // internally, since we might not be expecting the call\n this.forEachCall((call) =>\n setTracksEnabled(call.localUsermediaFeed!.stream.getVideoTracks(), !muted && this.callExpected(call)),\n );\n\n this.emit(GroupCallEvent.LocalMuteStateChanged, this.isMicrophoneMuted(), muted);\n\n return true;\n }\n\n public async setScreensharingEnabled(enabled: boolean, opts: IScreensharingOpts = {}): Promise<boolean> {\n if (enabled === this.isScreensharing()) {\n return enabled;\n }\n\n if (enabled) {\n try {\n logger.log(\n `GroupCall ${this.groupCallId} setScreensharingEnabled() is asking for screensharing permissions`,\n );\n const stream = await this.client.getMediaHandler().getScreensharingStream(opts);\n\n for (const track of stream.getTracks()) {\n const onTrackEnded = (): void => {\n this.setScreensharingEnabled(false);\n track.removeEventListener(\"ended\", onTrackEnded);\n };\n\n track.addEventListener(\"ended\", onTrackEnded);\n }\n\n logger.log(\n `GroupCall ${this.groupCallId} setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls`,\n );\n\n this.localDesktopCapturerSourceId = opts.desktopCapturerSourceId;\n this.localScreenshareFeed = new CallFeed({\n client: this.client,\n roomId: this.room.roomId,\n userId: this.client.getUserId()!,\n deviceId: this.client.getDeviceId()!,\n stream,\n purpose: SDPStreamMetadataPurpose.Screenshare,\n audioMuted: false,\n videoMuted: false,\n });\n this.addScreenshareFeed(this.localScreenshareFeed);\n\n this.emit(\n GroupCallEvent.LocalScreenshareStateChanged,\n true,\n this.localScreenshareFeed,\n this.localDesktopCapturerSourceId,\n );\n\n // TODO: handle errors\n this.forEachCall((call) => call.pushLocalFeed(this.localScreenshareFeed!.clone()));\n\n return true;\n } catch (error) {\n if (opts.throwOnFail) throw error;\n logger.error(\n `GroupCall ${this.groupCallId} setScreensharingEnabled() enabling screensharing error`,\n error,\n );\n this.emit(\n GroupCallEvent.Error,\n new GroupCallError(\n GroupCallErrorCode.NoUserMedia,\n \"Failed to get screen-sharing stream: \",\n error as Error,\n ),\n );\n return false;\n }\n } else {\n this.forEachCall((call) => {\n if (call.localScreensharingFeed) call.removeLocalFeed(call.localScreensharingFeed);\n });\n this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed!.stream);\n this.removeScreenshareFeed(this.localScreenshareFeed!);\n this.localScreenshareFeed = undefined;\n this.localDesktopCapturerSourceId = undefined;\n this.emit(GroupCallEvent.LocalScreenshareStateChanged, false, undefined, undefined);\n return false;\n }\n }\n\n public isScreensharing(): boolean {\n return !!this.localScreenshareFeed;\n }\n\n /*\n * Call Setup\n *\n * There are two different paths for calls to be created:\n * 1. Incoming calls triggered by the Call.incoming event.\n * 2. Outgoing calls to the initial members of a room or new members\n * as they are observed by the RoomState.members event.\n */\n\n private onIncomingCall = (newCall: MatrixCall): void => {\n // The incoming calls may be for another room, which we will ignore.\n if (newCall.roomId !== this.room.roomId) {\n return;\n }\n\n if (newCall.state !== CallState.Ringing) {\n logger.warn(\n `GroupCall ${this.groupCallId} onIncomingCall() incoming call no longer in ringing state - ignoring`,\n );\n return;\n }\n\n if (!newCall.groupCallId || newCall.groupCallId !== this.groupCallId) {\n logger.log(\n `GroupCall ${this.groupCallId} onIncomingCall() ignored because it doesn't match the current group call`,\n );\n newCall.reject();\n return;\n }\n\n const opponentUserId = newCall.getOpponentMember()?.userId;\n if (opponentUserId === undefined) {\n logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call with no member - ignoring`);\n return;\n }\n\n const deviceMap = this.calls.get(opponentUserId) ?? new Map<string, MatrixCall>();\n const prevCall = deviceMap.get(newCall.getOpponentDeviceId()!);\n\n if (prevCall?.callId === newCall.callId) return;\n\n logger.log(\n `GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`,\n );\n\n if (prevCall) prevCall.hangup(CallErrorCode.Replaced, false);\n\n this.initCall(newCall);\n\n const feeds = this.getLocalFeeds().map((feed) => feed.clone());\n if (!this.callExpected(newCall)) {\n // Disable our tracks for users not explicitly participating in the\n // call but trying to receive the feeds\n for (const feed of feeds) {\n setTracksEnabled(feed.stream.getAudioTracks(), false);\n setTracksEnabled(feed.stream.getVideoTracks(), false);\n }\n }\n newCall.answerWithCallFeeds(feeds);\n\n deviceMap.set(newCall.getOpponentDeviceId()!, newCall);\n this.calls.set(opponentUserId, deviceMap);\n this.emit(GroupCallEvent.CallsChanged, this.calls);\n };\n\n /**\n * Determines whether a given participant expects us to call them (versus\n * them calling us).\n * @param userId - The participant's user ID.\n * @param deviceId - The participant's device ID.\n * @returns Whether we need to place an outgoing call to the participant.\n */\n private wantsOutgoingCall(userId: string, deviceId: string): boolean {\n const localUserId = this.client.getUserId()!;\n const localDeviceId = this.client.getDeviceId()!;\n return (\n // If a user's ID is less than our own, they'll call us\n userId >= localUserId &&\n // If this is another one of our devices, compare device IDs to tell whether it'll call us\n (userId !== localUserId || deviceId > localDeviceId)\n );\n }\n\n /**\n * Places calls to all participants that we're responsible for calling.\n */\n private placeOutgoingCalls(): void {\n let callsChanged = false;\n\n for (const [{ userId }, participantMap] of this.participants) {\n const callMap = this.calls.get(userId) ?? new Map<string, MatrixCall>();\n\n for (const [deviceId, participant] of participantMap) {\n const prevCall = callMap.get(deviceId);\n\n if (\n prevCall?.getOpponentSessionId() !== participant.sessionId &&\n this.wantsOutgoingCall(userId, deviceId)\n ) {\n callsChanged = true;\n\n if (prevCall !== undefined) {\n logger.debug(\n `GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`,\n );\n prevCall.hangup(CallErrorCode.NewSession, false);\n }\n\n const newCall = createNewMatrixCall(this.client, this.room.roomId, {\n invitee: userId,\n opponentDeviceId: deviceId,\n opponentSessionId: participant.sessionId,\n groupCallId: this.groupCallId,\n });\n\n if (newCall === null) {\n logger.error(\n `GroupCall ${this.groupCallId} placeOutgoingCalls() failed to create call (userId=${userId}, device=${deviceId})`,\n );\n callMap.delete(deviceId);\n } else {\n this.initCall(newCall);\n callMap.set(deviceId, newCall);\n\n logger.debug(\n `GroupCall ${this.groupCallId} placeOutgoingCalls() placing call (userId=${userId}, deviceId=${deviceId}, sessionId=${participant.sessionId})`,\n );\n\n newCall\n .placeCallWithCallFeeds(\n this.getLocalFeeds().map((feed) => feed.clone()),\n participant.screensharing,\n )\n .then(() => {\n if (this.dataChannelsEnabled) {\n newCall.createDataChannel(\"datachannel\", this.dataChannelOptions);\n }\n })\n .catch((e) => {\n logger.warn(\n `GroupCall ${this.groupCallId} placeOutgoingCalls() failed to place call (userId=${userId})`,\n e,\n );\n\n if (e instanceof CallError && e.code === GroupCallErrorCode.UnknownDevice) {\n this.emit(GroupCallEvent.Error, e);\n } else {\n this.emit(\n GroupCallEvent.Error,\n new GroupCallError(\n GroupCallErrorCode.PlaceCallFailed,\n `Failed to place call to ${userId}`,\n ),\n );\n }\n\n newCall.hangup(CallErrorCode.SignallingFailed, false);\n if (callMap.get(deviceId) === newCall) callMap.delete(deviceId);\n });\n }\n }\n }\n\n if (callMap.size > 0) {\n this.calls.set(userId, callMap);\n } else {\n this.calls.delete(userId);\n }\n }\n\n if (callsChanged) this.emit(GroupCallEvent.CallsChanged, this.calls);\n }\n\n /*\n * Room Member State\n */\n\n private getMemberStateEvents(): MatrixEvent[];\n private getMemberStateEvents(userId: string): MatrixEvent | null;\n private getMemberStateEvents(userId?: string): MatrixEvent[] | MatrixEvent | null {\n return userId === undefined\n ? this.room.currentState.getStateEvents(EventType.GroupCallMemberPrefix)\n : this.room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, userId);\n }\n\n private onRetryCallLoop = (): void => {\n let needsRetry = false;\n\n for (const [{ userId }, participantMap] of this.participants) {\n const callMap = this.calls.get(userId);\n let retriesMap = this.retryCallCounts.get(userId);\n\n for (const [deviceId, participant] of participantMap) {\n const call = callMap?.get(deviceId);\n const retries = retriesMap?.get(deviceId) ?? 0;\n\n if (\n call?.getOpponentSessionId() !== participant.sessionId &&\n this.wantsOutgoingCall(userId, deviceId) &&\n retries < 3\n ) {\n if (retriesMap === undefined) {\n retriesMap = new Map();\n this.retryCallCounts.set(userId, retriesMap);\n }\n retriesMap.set(deviceId, retries + 1);\n needsRetry = true;\n }\n }\n }\n\n if (needsRetry) this.placeOutgoingCalls();\n };\n\n private initCall(call: MatrixCall): void {\n const opponentMemberId = getCallUserId(call);\n\n if (!opponentMemberId) {\n throw new Error(\"Cannot init call without user id\");\n }\n\n const onCallFeedsChanged = (): void => this.onCallFeedsChanged(call);\n const onCallStateChanged = (state: CallState, oldState?: CallState): void =>\n this.onCallStateChanged(call, state, oldState);\n const onCallHangup = this.onCallHangup;\n const onCallReplaced = (newCall: MatrixCall): void => this.onCallReplaced(call, newCall);\n\n let deviceMap = this.callHandlers.get(opponentMemberId);\n if (deviceMap === undefined) {\n deviceMap = new Map();\n this.callHandlers.set(opponentMemberId, deviceMap);\n }\n\n deviceMap.set(call.getOpponentDeviceId()!, {\n onCallFeedsChanged,\n onCallStateChanged,\n onCallHangup,\n onCallReplaced,\n });\n\n call.on(CallEvent.FeedsChanged, onCallFeedsChanged);\n call.on(CallEvent.State, onCallStateChanged);\n call.on(CallEvent.Hangup, onCallHangup);\n call.on(CallEvent.Replaced, onCallReplaced);\n\n call.isPtt = this.isPtt;\n\n this.reEmitter.reEmit(call, Object.values(CallEvent));\n\n call.initStats(this.stats);\n\n onCallFeedsChanged();\n }\n\n private disposeCall(call: MatrixCall, hangupReason: CallErrorCode): void {\n const opponentMemberId = getCallUserId(call);\n const opponentDeviceId = call.getOpponentDeviceId()!;\n\n if (!opponentMemberId) {\n throw new Error(\"Cannot dispose call without user id\");\n }\n\n const deviceMap = this.callHandlers.get(opponentMemberId)!;\n const { onCallFeedsChanged, onCallStateChanged, onCallHangup, onCallReplaced } =\n deviceMap.get(opponentDeviceId)!;\n\n call.removeListener(CallEvent.FeedsChanged, onCallFeedsChanged);\n call.removeListener(CallEvent.State, onCallStateChanged);\n call.removeListener(CallEvent.Hangup, onCallHangup);\n call.removeListener(CallEvent.Replaced, onCallReplaced);\n\n deviceMap.delete(opponentMemberId);\n if (deviceMap.size === 0) this.callHandlers.delete(opponentMemberId);\n\n if (call.hangupReason === CallErrorCode.Replaced) {\n return;\n }\n\n const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);\n\n if (usermediaFeed) {\n this.removeUserMediaFeed(usermediaFeed);\n }\n\n const screenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);\n\n if (screenshareFeed) {\n this.removeScreenshareFeed(screenshareFeed);\n }\n }\n\n private onCallFeedsChanged = (call: MatrixCall): void => {\n const opponentMemberId = getCallUserId(call);\n const opponentDeviceId = call.getOpponentDeviceId()!;\n\n if (!opponentMemberId) {\n throw new Error(\"Cannot change call feeds without user id\");\n }\n\n const currentUserMediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);\n const remoteUsermediaFeed = call.remoteUsermediaFeed;\n const remoteFeedChanged = remoteUsermediaFeed !== currentUserMediaFeed;\n\n if (remoteFeedChanged) {\n if (!currentUserMediaFeed && remoteUsermediaFeed) {\n this.addUserMediaFeed(remoteUsermediaFeed);\n } else if (currentUserMediaFeed && remoteUsermediaFeed) {\n this.replaceUserMediaFeed(currentUserMediaFeed, remoteUsermediaFeed);\n } else if (currentUserMediaFeed && !remoteUsermediaFeed) {\n this.removeUserMediaFeed(currentUserMediaFeed);\n }\n }\n\n const currentScreenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);\n const remoteScreensharingFeed = call.remoteScreensharingFeed;\n const remoteScreenshareFeedChanged = remoteScreensharingFeed !== currentScreenshareFeed;\n\n if (remoteScreenshareFeedChanged) {\n if (!currentScreenshareFeed && remoteScreensharingFeed) {\n this.addScreenshareFeed(remoteScreensharingFeed);\n } else if (currentScreenshareFeed && remoteScreensharingFeed) {\n this.replaceScreenshareFeed(currentScreenshareFeed, remoteScreensharingFeed);\n } else if (currentScreenshareFeed && !remoteScreensharingFeed) {\n this.removeScreenshareFeed(currentScreenshareFeed);\n }\n }\n };\n\n private onCallStateChanged = (call: MatrixCall, state: CallState, _oldState: CallState | undefined): void => {\n if (state === CallState.Ended) return;\n\n const audioMuted = this.localCallFeed!.isAudioMuted();\n\n if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) {\n call.setMicrophoneMuted(audioMuted);\n }\n\n const videoMuted = this.localCallFeed!.isVideoMuted();\n\n if (call.localUsermediaStream && call.isLocalVideoMuted() !== videoMuted) {\n call.setLocalVideoMuted(videoMuted);\n }\n\n const opponentUserId = call.getOpponentMember()?.userId;\n if (state === CallState.Connected && opponentUserId) {\n const retriesMap = this.retryCallCounts.get(opponentUserId);\n retriesMap?.delete(call.getOpponentDeviceId()!);\n if (retriesMap?.size === 0) this.retryCallCounts.delete(opponentUserId);\n }\n };\n\n private onCallHangup = (call: MatrixCall): void => {\n if (call.hangupReason === CallErrorCode.Replaced) return;\n\n const opponentUserId = call.getOpponentMember()?.userId ?? this.room.getMember(call.invitee!)!.userId;\n const deviceMap = this.calls.get(opponentUserId);\n\n // Sanity check that this call is in fact in the map\n if (deviceMap?.get(call.getOpponentDeviceId()!) === call) {\n this.disposeCall(call, call.hangupReason as CallErrorCode);\n deviceMap.delete(call.getOpponentDeviceId()!);\n if (deviceMap.size === 0) this.calls.delete(opponentUserId);\n this.emit(GroupCallEvent.CallsChanged, this.calls);\n }\n };\n\n private onCallReplaced = (prevCall: MatrixCall, newCall: MatrixCall): void => {\n const opponentUserId = prevCall.getOpponentMember()!.userId;\n\n let deviceMap = this.calls.get(opponentUserId);\n if (deviceMap === undefined) {\n deviceMap = new Map();\n this.calls.set(opponentUserId, deviceMap);\n }\n\n prevCall.hangup(CallErrorCode.Replaced, false);\n this.initCall(newCall);\n deviceMap.set(prevCall.getOpponentDeviceId()!, newCall);\n this.emit(GroupCallEvent.CallsChanged, this.calls);\n };\n\n /*\n * UserMedia CallFeed Event Handlers\n */\n\n public getUserMediaFeed(userId: string, deviceId: string): CallFeed | undefined {\n return this.userMediaFeeds.find((f) => f.userId === userId && f.deviceId! === deviceId);\n }\n\n private addUserMediaFeed(callFeed: CallFeed): void {\n this.userMediaFeeds.push(callFeed);\n callFeed.measureVolumeActivity(true);\n this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);\n }\n\n private replaceUserMediaFeed(existingFeed: CallFeed, replacementFeed: CallFeed): void {\n const feedIndex = this.userMediaFeeds.findIndex(\n (f) => f.userId === existingFeed.userId && f.deviceId! === existingFeed.deviceId,\n );\n\n if (feedIndex === -1) {\n throw new Error(\"Couldn't find user media feed to replace\");\n }\n\n this.userMediaFeeds.splice(feedIndex, 1, replacementFeed);\n\n existingFeed.dispose();\n replacementFeed.measureVolumeActivity(true);\n this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);\n }\n\n private removeUserMediaFeed(callFeed: CallFeed): void {\n const feedIndex = this.userMediaFeeds.findIndex(\n (f) => f.userId === callFeed.userId && f.deviceId! === callFeed.deviceId,\n );\n\n if (feedIndex === -1) {\n throw new Error(\"Couldn't find user media feed to remove\");\n }\n\n this.userMediaFeeds.splice(feedIndex, 1);\n\n callFeed.dispose();\n this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);\n\n if (this.activeSpeaker === callFeed) {\n this.activeSpeaker = this.userMediaFeeds[0];\n this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);\n }\n }\n\n private onActiveSpeakerLoop = (): void => {\n let topAvg: number | undefined = undefined;\n let nextActiveSpeaker: CallFeed | undefined = undefined;\n\n for (const callFeed of this.userMediaFeeds) {\n if (callFeed.isLocal() && this.userMediaFeeds.length > 1) continue;\n\n const total = callFeed.speakingVolumeSamples.reduce(\n (acc, volume) => acc + Math.max(volume, SPEAKING_THRESHOLD),\n );\n const avg = total / callFeed.speakingVolumeSamples.length;\n\n if (!topAvg || avg > topAvg) {\n topAvg = avg;\n nextActiveSpeaker = callFeed;\n }\n }\n\n if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg && topAvg > SPEAKING_THRESHOLD) {\n this.activeSpeaker = nextActiveSpeaker;\n this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);\n }\n };\n\n /*\n * Screenshare Call Feed Event Handlers\n */\n\n public getScreenshareFeed(userId: string, deviceId: string): CallFeed | undefined {\n return this.screenshareFeeds.find((f) => f.userId === userId && f.deviceId! === deviceId);\n }\n\n private addScreenshareFeed(callFeed: CallFeed): void {\n this.screenshareFeeds.push(callFeed);\n this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);\n }\n\n private replaceScreenshareFeed(existingFeed: CallFeed, replacementFeed: CallFeed): void {\n const feedIndex = this.screenshareFeeds.findIndex(\n (f) => f.userId === existingFeed.userId && f.deviceId! === existingFeed.deviceId,\n );\n\n if (feedIndex === -1) {\n throw new Error(\"Couldn't find screenshare feed to replace\");\n }\n\n this.screenshareFeeds.splice(feedIndex, 1, replacementFeed);\n\n existingFeed.dispose();\n this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);\n }\n\n private removeScreenshareFeed(callFeed: CallFeed): void {\n const feedIndex = this.screenshareFeeds.findIndex(\n (f) => f.userId === callFeed.userId && f.deviceId! === callFeed.deviceId,\n );\n\n if (feedIndex === -1) {\n throw new Error(\"Couldn't find screenshare feed to remove\");\n }\n\n this.screenshareFeeds.splice(feedIndex, 1);\n\n callFeed.dispose();\n this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);\n }\n\n /**\n * Recalculates and updates the participant map to match the room state.\n */\n private updateParticipants(): void {\n const localMember = this.room.getMember(this.client.getUserId()!)!;\n if (!localMember) {\n // The client hasn't fetched enough of the room state to get our own member\n // event. This probably shouldn't happen, but sanity check & exit for now.\n logger.warn(\n `GroupCall ${this.groupCallId} updateParticipants() tried to update participants before local room member is available`,\n );\n return;\n }\n\n if (this.participantsExpirationTimer !== null) {\n clearTimeout(this.participantsExpirationTimer);\n this.participantsExpirationTimer = null;\n }\n\n if (this.state === GroupCallState.Ended) {\n this.participants = new Map();\n return;\n }\n\n const participants = new Map<RoomMember, Map<string, ParticipantState>>();\n const now = Date.now();\n const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession;\n let nextExpiration = Infinity;\n\n for (const e of this.getMemberStateEvents()) {\n const member = this.room.getMember(e.getStateKey()!);\n const content = e.getContent<Record<any, unknown>>();\n const calls: Record<any, unknown>[] = Array.isArray(content[\"m.calls\"]) ? content[\"m.calls\"] : [];\n const call = calls.find((call) => call[\"m.call_id\"] === this.groupCallId);\n const devices: Record<any, unknown>[] = Array.isArray(call?.[\"m.devices\"]) ? call![\"m.devices\"] : [];\n\n // Filter out invalid and expired devices\n let validDevices = devices.filter(\n (d) =>\n typeof d.device_id === \"string\" &&\n typeof d.session_id === \"string\" &&\n typeof d.expires_ts === \"number\" &&\n d.expires_ts > now &&\n Array.isArray(d.feeds),\n ) as unknown as IGroupCallRoomMemberDevice[];\n\n // Apply local echo for the unentered case\n if (!entered && member?.userId === this.client.getUserId()!) {\n validDevices = validDevices.filter((d) => d.device_id !== this.client.getDeviceId()!);\n }\n\n // Must have a connected device and be joined to the room\n if (validDevices.length > 0 && member?.membership === \"join\") {\n const deviceMap = new Map<string, ParticipantState>();\n participants.set(member, deviceMap);\n\n for (const d of validDevices) {\n deviceMap.set(d.device_id, {\n sessionId: d.session_id,\n screensharing: d.feeds.some((f) => f.purpose === SDPStreamMetadataPurpose.Screenshare),\n });\n if (d.expires_ts < nextExpiration) nextExpiration = d.expires_ts;\n }\n }\n }\n\n // Apply local echo for the entered case\n if (entered) {\n let deviceMap = participants.get(localMember);\n if (deviceMap === undefined) {\n deviceMap = new Map();\n participants.set(localMember, deviceMap);\n }\n\n if (!deviceMap.has(this.client.getDeviceId()!)) {\n deviceMap.set(this.client.getDeviceId()!, {\n sessionId: this.client.getSessionId(),\n screensharing: this.getLocalFeeds().some((f) => f.purpose === SDPStreamMetadataPurpose.Screenshare),\n });\n }\n }\n\n this.participants = participants;\n if (nextExpiration < Infinity) {\n this.participantsExpirationTimer = setTimeout(() => this.updateParticipants(), nextExpiration - now);\n }\n }\n\n /**\n * Updates the local user's member state with the devices returned by the given function.\n * @param fn - A function from the current devices to the new devices. If it\n * returns null, the update will be skipped.\n * @param keepAlive - Whether the request should outlive the window.\n */\n private async updateDevices(\n fn: (devices: IGroupCallRoomMemberDevice[]) => IGroupCallRoomMemberDevice[] | null,\n keepAlive = false,\n ): Promise<void> {\n const now = Date.now();\n const localUserId = this.client.getUserId()!;\n\n const event = this.getMemberStateEvents(localUserId);\n const content = event?.getContent<Record<any, unknown>>() ?? {};\n const calls: Record<any, unknown>[] = Array.isArray(content[\"m.calls\"]) ? content[\"m.calls\"] : [];\n\n let call: Record<any, unknown> | null = null;\n const otherCalls: Record<any, unknown>[] = [];\n for (const c of calls) {\n if (c[\"m.call_id\"] === this.groupCallId) {\n call = c;\n } else {\n otherCalls.push(c);\n }\n }\n if (call === null) call = {};\n\n const devices: Record<any, unknown>[] = Array.isArray(call[\"m.devices\"]) ? call[\"m.devices\"] : [];\n\n // Filter out invalid and expired devices\n const validDevices = devices.filter(\n (d) =>\n typeof d.device_id === \"string\" &&\n typeof d.session_id === \"string\" &&\n typeof d.expires_ts === \"number\" &&\n d.expires_ts > now &&\n Array.isArray(d.feeds),\n ) as unknown as IGroupCallRoomMemberDevice[];\n\n const newDevices = fn(validDevices);\n if (newDevices === null) return;\n\n const newCalls = [...(otherCalls as unknown as IGroupCallRoomMemberCallState[])];\n if (newDevices.length > 0) {\n newCalls.push({\n ...call,\n \"m.call_id\": this.groupCallId,\n \"m.devices\": newDevices,\n });\n }\n\n const newContent: IGroupCallRoomMemberState = { \"m.calls\": newCalls };\n\n await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, newContent, localUserId, {\n keepAlive,\n });\n }\n\n private async addDeviceToMemberState(): Promise<void> {\n await this.updateDevices((devices) => [\n ...devices.filter((d) => d.device_id !== this.client.getDeviceId()!),\n {\n device_id: this.client.getDeviceId()!,\n session_id: this.client.getSessionId(),\n expires_ts: Date.now() + DEVICE_TIMEOUT,\n feeds: this.getLocalFeeds().map((feed) => ({ purpose: feed.purpose })),\n // TODO: Add data channels\n },\n ]);\n }\n\n private async updateMemberState(): Promise<void> {\n // Clear the old update interval before proceeding\n if (this.resendMemberStateTimer !== null) {\n clearInterval(this.resendMemberStateTimer);\n this.resendMemberStateTimer = null;\n }\n\n if (this.state === GroupCallState.Entered) {\n // Add the local device\n await this.addDeviceToMemberState();\n\n // Resend the state event every so often so it doesn't become stale\n this.resendMemberStateTimer = setInterval(async () => {\n logger.log(`GroupCall ${this.groupCallId} updateMemberState() resending call member state\"`);\n try {\n await this.addDeviceToMemberState();\n } catch (e) {\n logger.error(\n `GroupCall ${this.groupCallId} updateMemberState() failed to resend call member state`,\n e,\n );\n }\n }, (DEVICE_TIMEOUT * 3) / 4);\n } else {\n // Remove the local device\n await this.updateDevices(\n (devices) => devices.filter((d) => d.device_id !== this.client.getDeviceId()!),\n true,\n );\n }\n }\n\n /**\n * Cleans up our member state by filtering out logged out devices, inactive\n * devices, and our own device (if we know we haven't entered).\n */\n public async cleanMemberState(): Promise<void> {\n const { devices: myDevices } = await this.client.getDevices();\n const deviceMap = new Map<string, IMyDevice>(myDevices.map((d) => [d.device_id, d]));\n\n // updateDevices takes care of filtering out inactive devices for us\n await this.updateDevices((devices) => {\n const newDevices = devices.filter((d) => {\n const device = deviceMap.get(d.device_id);\n return (\n device?.last_seen_ts !== undefined &&\n !(\n d.device_id === this.client.getDeviceId()! &&\n this.state !== GroupCallState.Entered &&\n !this.enteredViaAnotherSession\n )\n );\n });\n\n // Skip the update if the devices are unchanged\n return newDevices.length === devices.length ? null : newDevices;\n });\n }\n\n private onRoomState = (): void => this.updateParticipants();\n\n private onParticipantsChanged = (): void => {\n // Re-run setTracksEnabled on all calls, so that participants that just\n // left get denied access to our media, and participants that just\n // joined get granted access\n this.forEachCall((call) => {\n const expected = this.callExpected(call);\n for (const feed of call.getLocalFeeds()) {\n setTracksEnabled(feed.stream.getAudioTracks(), !feed.isAudioMuted() && expected);\n setTracksEnabled(feed.stream.getVideoTracks(), !feed.isVideoMuted() && expected);\n }\n });\n\n if (this.state === GroupCallState.Entered) this.placeOutgoingCalls();\n };\n\n private onStateChanged = (newState: GroupCallState, oldState: GroupCallState): void => {\n if (\n newState === GroupCallState.Entered ||\n oldState === GroupCallState.Entered ||\n newState === GroupCallState.Ended\n ) {\n // We either entered, left, or ended the call\n this.updateParticipants();\n this.updateMemberState().catch((e) =>\n logger.error(\n `GroupCall ${this.groupCallId} onStateChanged() failed to update member state devices\"`,\n e,\n ),\n );\n }\n };\n\n private onLocalFeedsChanged = (): void => {\n if (this.state === GroupCallState.Entered) {\n this.updateMemberState().catch((e) =>\n logger.error(\n `GroupCall ${this.groupCallId} onLocalFeedsChanged() failed to update member state feeds`,\n e,\n ),\n );\n }\n };\n\n public getGroupCallStats(): GroupCallStats {\n return this.stats;\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.d.ts new file mode 100644 index 0000000..bbaa3ba --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.d.ts @@ -0,0 +1,31 @@ +import { MatrixClient } from "../client"; +import { GroupCall } from "./groupCall"; +import { RoomMember } from "../models/room-member"; +export declare enum GroupCallEventHandlerEvent { + Incoming = "GroupCall.incoming", + Outgoing = "GroupCall.outgoing", + Ended = "GroupCall.ended", + Participants = "GroupCall.participants" +} +export type GroupCallEventHandlerEventHandlerMap = { + [GroupCallEventHandlerEvent.Incoming]: (call: GroupCall) => void; + [GroupCallEventHandlerEvent.Outgoing]: (call: GroupCall) => void; + [GroupCallEventHandlerEvent.Ended]: (call: GroupCall) => void; + [GroupCallEventHandlerEvent.Participants]: (participants: RoomMember[], call: GroupCall) => void; +}; +export declare class GroupCallEventHandler { + private client; + groupCalls: Map<string, GroupCall>; + private roomDeferreds; + constructor(client: MatrixClient); + start(): Promise<void>; + stop(): void; + private getRoomDeferred; + waitUntilRoomReadyForGroupCalls(roomId: string): Promise<void>; + getGroupCallById(groupCallId: string): GroupCall | undefined; + private createGroupCallForRoom; + private createGroupCallFromRoomStateEvent; + private onRoomsChanged; + private onRoomStateChanged; +} +//# sourceMappingURL=groupCallEventHandler.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.d.ts.map new file mode 100644 index 0000000..bbab3a2 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"groupCallEventHandler.d.ts","sourceRoot":"","sources":["../../src/webrtc/groupCallEventHandler.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,SAAS,EAAgE,MAAM,aAAa,CAAC;AAGtG,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAKnD,oBAAY,0BAA0B;IAClC,QAAQ,uBAAuB;IAC/B,QAAQ,uBAAuB;IAC/B,KAAK,oBAAoB;IACzB,YAAY,2BAA2B;CAC1C;AAED,MAAM,MAAM,oCAAoC,GAAG;IAC/C,CAAC,0BAA0B,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACjE,CAAC,0BAA0B,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACjE,CAAC,0BAA0B,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAC9D,CAAC,0BAA0B,CAAC,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACpG,CAAC;AAOF,qBAAa,qBAAqB;IASX,OAAO,CAAC,MAAM;IAR1B,UAAU,yBAAgC;IAMjD,OAAO,CAAC,aAAa,CAAmC;gBAE7B,MAAM,EAAE,YAAY;IAElC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B5B,IAAI,IAAI,IAAI;IAInB,OAAO,CAAC,eAAe;IAgBhB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAInE,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,iCAAiC;IA6DzC,OAAO,CAAC,cAAc,CAEpB;IAEF,OAAO,CAAC,kBAAkB,CA2BxB;CACL"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.js new file mode 100644 index 0000000..c043b40 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.js @@ -0,0 +1,184 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.GroupCallEventHandlerEvent = exports.GroupCallEventHandler = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _client = require("../client"); +var _groupCall = require("./groupCall"); +var _roomState = require("../models/room-state"); +var _logger = require("../logger"); +var _event = require("../@types/event"); +var _sync = require("../sync"); +/* +Copyright 2021 Å imon Brandner <simon.bra.ag@gmail.com> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +let GroupCallEventHandlerEvent; +exports.GroupCallEventHandlerEvent = GroupCallEventHandlerEvent; +(function (GroupCallEventHandlerEvent) { + GroupCallEventHandlerEvent["Incoming"] = "GroupCall.incoming"; + GroupCallEventHandlerEvent["Outgoing"] = "GroupCall.outgoing"; + GroupCallEventHandlerEvent["Ended"] = "GroupCall.ended"; + GroupCallEventHandlerEvent["Participants"] = "GroupCall.participants"; +})(GroupCallEventHandlerEvent || (exports.GroupCallEventHandlerEvent = GroupCallEventHandlerEvent = {})); +class GroupCallEventHandler { + // roomId -> GroupCall + + // All rooms we know about and whether we've seen a 'Room' event + // for them. The promise will be fulfilled once we've processed that + // event which means we're "up to date" on what calls are in a room + // and get + + constructor(client) { + this.client = client; + (0, _defineProperty2.default)(this, "groupCalls", new Map()); + (0, _defineProperty2.default)(this, "roomDeferreds", new Map()); + (0, _defineProperty2.default)(this, "onRoomsChanged", room => { + this.createGroupCallForRoom(room); + }); + (0, _defineProperty2.default)(this, "onRoomStateChanged", (event, state) => { + const eventType = event.getType(); + if (eventType === _event.EventType.GroupCallPrefix) { + const groupCallId = event.getStateKey(); + const content = event.getContent(); + const currentGroupCall = this.groupCalls.get(state.roomId); + if (!currentGroupCall && !content["m.terminated"] && !event.isRedacted()) { + this.createGroupCallFromRoomStateEvent(event); + } else if (currentGroupCall && currentGroupCall.groupCallId === groupCallId) { + if (content["m.terminated"] || event.isRedacted()) { + currentGroupCall.terminate(false); + } else if (content["m.type"] !== currentGroupCall.type) { + // TODO: Handle the callType changing when the room state changes + _logger.logger.warn(`GroupCallEventHandler onRoomStateChanged() currently does not support changing type (roomId=${state.roomId})`); + } + } else if (currentGroupCall && currentGroupCall.groupCallId !== groupCallId) { + // TODO: Handle new group calls and multiple group calls + _logger.logger.warn(`GroupCallEventHandler onRoomStateChanged() currently does not support multiple calls (roomId=${state.roomId})`); + } + } + }); + } + async start() { + // We wait until the client has started syncing for real. + // This is because we only support one call at a time, and want + // the latest. We therefore want the latest state of the room before + // we create a group call for the room so we can be fairly sure that + // the group call we create is really the latest one. + if (this.client.getSyncState() !== _sync.SyncState.Syncing) { + _logger.logger.debug("GroupCallEventHandler start() waiting for client to start syncing"); + await new Promise(resolve => { + const onSync = () => { + if (this.client.getSyncState() === _sync.SyncState.Syncing) { + this.client.off(_client.ClientEvent.Sync, onSync); + return resolve(); + } + }; + this.client.on(_client.ClientEvent.Sync, onSync); + }); + } + const rooms = this.client.getRooms(); + for (const room of rooms) { + this.createGroupCallForRoom(room); + } + this.client.on(_client.ClientEvent.Room, this.onRoomsChanged); + this.client.on(_roomState.RoomStateEvent.Events, this.onRoomStateChanged); + } + stop() { + this.client.removeListener(_roomState.RoomStateEvent.Events, this.onRoomStateChanged); + } + getRoomDeferred(roomId) { + let deferred = this.roomDeferreds.get(roomId); + if (deferred === undefined) { + let resolveFunc; + deferred = { + prom: new Promise(resolve => { + resolveFunc = resolve; + }) + }; + deferred.resolve = resolveFunc; + this.roomDeferreds.set(roomId, deferred); + } + return deferred; + } + waitUntilRoomReadyForGroupCalls(roomId) { + return this.getRoomDeferred(roomId).prom; + } + getGroupCallById(groupCallId) { + return [...this.groupCalls.values()].find(groupCall => groupCall.groupCallId === groupCallId); + } + createGroupCallForRoom(room) { + const callEvents = room.currentState.getStateEvents(_event.EventType.GroupCallPrefix); + const sortedCallEvents = callEvents.sort((a, b) => b.getTs() - a.getTs()); + for (const callEvent of sortedCallEvents) { + const content = callEvent.getContent(); + if (content["m.terminated"] || callEvent.isRedacted()) { + continue; + } + _logger.logger.debug(`GroupCallEventHandler createGroupCallForRoom() choosing group call from possible calls (stateKey=${callEvent.getStateKey()}, ts=${callEvent.getTs()}, roomId=${room.roomId}, numOfPossibleCalls=${callEvents.length})`); + this.createGroupCallFromRoomStateEvent(callEvent); + break; + } + _logger.logger.info(`GroupCallEventHandler createGroupCallForRoom() processed room (roomId=${room.roomId})`); + this.getRoomDeferred(room.roomId).resolve(); + } + createGroupCallFromRoomStateEvent(event) { + const roomId = event.getRoomId(); + const content = event.getContent(); + const room = this.client.getRoom(roomId); + if (!room) { + _logger.logger.warn(`GroupCallEventHandler createGroupCallFromRoomStateEvent() couldn't find room for call (roomId=${roomId})`); + return; + } + const groupCallId = event.getStateKey(); + const callType = content["m.type"]; + if (!Object.values(_groupCall.GroupCallType).includes(callType)) { + _logger.logger.warn(`GroupCallEventHandler createGroupCallFromRoomStateEvent() received invalid call type (type=${callType}, roomId=${roomId})`); + return; + } + const callIntent = content["m.intent"]; + if (!Object.values(_groupCall.GroupCallIntent).includes(callIntent)) { + _logger.logger.warn(`Received invalid group call intent (type=${callType}, roomId=${roomId})`); + return; + } + const isPtt = Boolean(content["io.element.ptt"]); + let dataChannelOptions; + if (content !== null && content !== void 0 && content.dataChannelsEnabled && content !== null && content !== void 0 && content.dataChannelOptions) { + // Pull out just the dataChannelOptions we want to support. + const { + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + } = content.dataChannelOptions; + dataChannelOptions = { + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + } + const groupCall = new _groupCall.GroupCall(this.client, room, callType, isPtt, callIntent, groupCallId, + // Because without Media section a WebRTC connection is not possible, so need a RTCDataChannel to set up a + // no media WebRTC connection anyway. + (content === null || content === void 0 ? void 0 : content.dataChannelsEnabled) || this.client.isVoipWithNoMediaAllowed, dataChannelOptions, this.client.isVoipWithNoMediaAllowed); + this.groupCalls.set(room.roomId, groupCall); + this.client.emit(GroupCallEventHandlerEvent.Incoming, groupCall); + return groupCall; + } +} +exports.GroupCallEventHandler = GroupCallEventHandler; +//# sourceMappingURL=groupCallEventHandler.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.js.map new file mode 100644 index 0000000..b5da56c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/groupCallEventHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"groupCallEventHandler.js","names":["_client","require","_groupCall","_roomState","_logger","_event","_sync","GroupCallEventHandlerEvent","exports","GroupCallEventHandler","constructor","client","_defineProperty2","default","Map","room","createGroupCallForRoom","event","state","eventType","getType","EventType","GroupCallPrefix","groupCallId","getStateKey","content","getContent","currentGroupCall","groupCalls","get","roomId","isRedacted","createGroupCallFromRoomStateEvent","terminate","type","logger","warn","start","getSyncState","SyncState","Syncing","debug","Promise","resolve","onSync","off","ClientEvent","Sync","on","rooms","getRooms","Room","onRoomsChanged","RoomStateEvent","Events","onRoomStateChanged","stop","removeListener","getRoomDeferred","deferred","roomDeferreds","undefined","resolveFunc","prom","set","waitUntilRoomReadyForGroupCalls","getGroupCallById","values","find","groupCall","callEvents","currentState","getStateEvents","sortedCallEvents","sort","a","b","getTs","callEvent","length","info","getRoomId","getRoom","callType","Object","GroupCallType","includes","callIntent","GroupCallIntent","isPtt","Boolean","dataChannelOptions","dataChannelsEnabled","ordered","maxPacketLifeTime","maxRetransmits","protocol","GroupCall","isVoipWithNoMediaAllowed","emit","Incoming"],"sources":["../../src/webrtc/groupCallEventHandler.ts"],"sourcesContent":["/*\nCopyright 2021 Å imon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { MatrixEvent } from \"../models/event\";\nimport { MatrixClient, ClientEvent } from \"../client\";\nimport { GroupCall, GroupCallIntent, GroupCallType, IGroupCallDataChannelOptions } from \"./groupCall\";\nimport { Room } from \"../models/room\";\nimport { RoomState, RoomStateEvent } from \"../models/room-state\";\nimport { RoomMember } from \"../models/room-member\";\nimport { logger } from \"../logger\";\nimport { EventType } from \"../@types/event\";\nimport { SyncState } from \"../sync\";\n\nexport enum GroupCallEventHandlerEvent {\n Incoming = \"GroupCall.incoming\",\n Outgoing = \"GroupCall.outgoing\",\n Ended = \"GroupCall.ended\",\n Participants = \"GroupCall.participants\",\n}\n\nexport type GroupCallEventHandlerEventHandlerMap = {\n [GroupCallEventHandlerEvent.Incoming]: (call: GroupCall) => void;\n [GroupCallEventHandlerEvent.Outgoing]: (call: GroupCall) => void;\n [GroupCallEventHandlerEvent.Ended]: (call: GroupCall) => void;\n [GroupCallEventHandlerEvent.Participants]: (participants: RoomMember[], call: GroupCall) => void;\n};\n\ninterface RoomDeferred {\n prom: Promise<void>;\n resolve?: () => void;\n}\n\nexport class GroupCallEventHandler {\n public groupCalls = new Map<string, GroupCall>(); // roomId -> GroupCall\n\n // All rooms we know about and whether we've seen a 'Room' event\n // for them. The promise will be fulfilled once we've processed that\n // event which means we're \"up to date\" on what calls are in a room\n // and get\n private roomDeferreds = new Map<string, RoomDeferred>();\n\n public constructor(private client: MatrixClient) {}\n\n public async start(): Promise<void> {\n // We wait until the client has started syncing for real.\n // This is because we only support one call at a time, and want\n // the latest. We therefore want the latest state of the room before\n // we create a group call for the room so we can be fairly sure that\n // the group call we create is really the latest one.\n if (this.client.getSyncState() !== SyncState.Syncing) {\n logger.debug(\"GroupCallEventHandler start() waiting for client to start syncing\");\n await new Promise<void>((resolve) => {\n const onSync = (): void => {\n if (this.client.getSyncState() === SyncState.Syncing) {\n this.client.off(ClientEvent.Sync, onSync);\n return resolve();\n }\n };\n this.client.on(ClientEvent.Sync, onSync);\n });\n }\n\n const rooms = this.client.getRooms();\n\n for (const room of rooms) {\n this.createGroupCallForRoom(room);\n }\n\n this.client.on(ClientEvent.Room, this.onRoomsChanged);\n this.client.on(RoomStateEvent.Events, this.onRoomStateChanged);\n }\n\n public stop(): void {\n this.client.removeListener(RoomStateEvent.Events, this.onRoomStateChanged);\n }\n\n private getRoomDeferred(roomId: string): RoomDeferred {\n let deferred = this.roomDeferreds.get(roomId);\n if (deferred === undefined) {\n let resolveFunc: () => void;\n deferred = {\n prom: new Promise<void>((resolve) => {\n resolveFunc = resolve;\n }),\n };\n deferred.resolve = resolveFunc!;\n this.roomDeferreds.set(roomId, deferred);\n }\n\n return deferred;\n }\n\n public waitUntilRoomReadyForGroupCalls(roomId: string): Promise<void> {\n return this.getRoomDeferred(roomId).prom;\n }\n\n public getGroupCallById(groupCallId: string): GroupCall | undefined {\n return [...this.groupCalls.values()].find((groupCall) => groupCall.groupCallId === groupCallId);\n }\n\n private createGroupCallForRoom(room: Room): void {\n const callEvents = room.currentState.getStateEvents(EventType.GroupCallPrefix);\n const sortedCallEvents = callEvents.sort((a, b) => b.getTs() - a.getTs());\n\n for (const callEvent of sortedCallEvents) {\n const content = callEvent.getContent();\n\n if (content[\"m.terminated\"] || callEvent.isRedacted()) {\n continue;\n }\n\n logger.debug(\n `GroupCallEventHandler createGroupCallForRoom() choosing group call from possible calls (stateKey=${callEvent.getStateKey()}, ts=${callEvent.getTs()}, roomId=${\n room.roomId\n }, numOfPossibleCalls=${callEvents.length})`,\n );\n\n this.createGroupCallFromRoomStateEvent(callEvent);\n break;\n }\n\n logger.info(`GroupCallEventHandler createGroupCallForRoom() processed room (roomId=${room.roomId})`);\n this.getRoomDeferred(room.roomId).resolve!();\n }\n\n private createGroupCallFromRoomStateEvent(event: MatrixEvent): GroupCall | undefined {\n const roomId = event.getRoomId();\n const content = event.getContent();\n\n const room = this.client.getRoom(roomId);\n\n if (!room) {\n logger.warn(\n `GroupCallEventHandler createGroupCallFromRoomStateEvent() couldn't find room for call (roomId=${roomId})`,\n );\n return;\n }\n\n const groupCallId = event.getStateKey();\n\n const callType = content[\"m.type\"];\n\n if (!Object.values(GroupCallType).includes(callType)) {\n logger.warn(\n `GroupCallEventHandler createGroupCallFromRoomStateEvent() received invalid call type (type=${callType}, roomId=${roomId})`,\n );\n return;\n }\n\n const callIntent = content[\"m.intent\"];\n\n if (!Object.values(GroupCallIntent).includes(callIntent)) {\n logger.warn(`Received invalid group call intent (type=${callType}, roomId=${roomId})`);\n return;\n }\n\n const isPtt = Boolean(content[\"io.element.ptt\"]);\n\n let dataChannelOptions: IGroupCallDataChannelOptions | undefined;\n\n if (content?.dataChannelsEnabled && content?.dataChannelOptions) {\n // Pull out just the dataChannelOptions we want to support.\n const { ordered, maxPacketLifeTime, maxRetransmits, protocol } = content.dataChannelOptions;\n dataChannelOptions = { ordered, maxPacketLifeTime, maxRetransmits, protocol };\n }\n\n const groupCall = new GroupCall(\n this.client,\n room,\n callType,\n isPtt,\n callIntent,\n groupCallId,\n // Because without Media section a WebRTC connection is not possible, so need a RTCDataChannel to set up a\n // no media WebRTC connection anyway.\n content?.dataChannelsEnabled || this.client.isVoipWithNoMediaAllowed,\n dataChannelOptions,\n this.client.isVoipWithNoMediaAllowed,\n );\n\n this.groupCalls.set(room.roomId, groupCall);\n this.client.emit(GroupCallEventHandlerEvent.Incoming, groupCall);\n\n return groupCall;\n }\n\n private onRoomsChanged = (room: Room): void => {\n this.createGroupCallForRoom(room);\n };\n\n private onRoomStateChanged = (event: MatrixEvent, state: RoomState): void => {\n const eventType = event.getType();\n\n if (eventType === EventType.GroupCallPrefix) {\n const groupCallId = event.getStateKey();\n const content = event.getContent();\n\n const currentGroupCall = this.groupCalls.get(state.roomId);\n\n if (!currentGroupCall && !content[\"m.terminated\"] && !event.isRedacted()) {\n this.createGroupCallFromRoomStateEvent(event);\n } else if (currentGroupCall && currentGroupCall.groupCallId === groupCallId) {\n if (content[\"m.terminated\"] || event.isRedacted()) {\n currentGroupCall.terminate(false);\n } else if (content[\"m.type\"] !== currentGroupCall.type) {\n // TODO: Handle the callType changing when the room state changes\n logger.warn(\n `GroupCallEventHandler onRoomStateChanged() currently does not support changing type (roomId=${state.roomId})`,\n );\n }\n } else if (currentGroupCall && currentGroupCall.groupCallId !== groupCallId) {\n // TODO: Handle new group calls and multiple group calls\n logger.warn(\n `GroupCallEventHandler onRoomStateChanged() currently does not support multiple calls (roomId=${state.roomId})`,\n );\n }\n }\n };\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.d.ts new file mode 100644 index 0000000..8841691 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.d.ts @@ -0,0 +1,86 @@ +import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { MatrixClient } from "../client"; +export declare enum MediaHandlerEvent { + LocalStreamsChanged = "local_streams_changed" +} +export type MediaHandlerEventHandlerMap = { + [MediaHandlerEvent.LocalStreamsChanged]: () => void; +}; +export interface IScreensharingOpts { + desktopCapturerSourceId?: string; + audio?: boolean; + throwOnFail?: boolean; +} +export interface AudioSettings { + autoGainControl: boolean; + echoCancellation: boolean; + noiseSuppression: boolean; +} +export declare class MediaHandler extends TypedEventEmitter<MediaHandlerEvent.LocalStreamsChanged, MediaHandlerEventHandlerMap> { + private client; + private audioInput?; + private audioSettings?; + private videoInput?; + private localUserMediaStream?; + userMediaStreams: MediaStream[]; + screensharingStreams: MediaStream[]; + private getMediaStreamPromise?; + constructor(client: MatrixClient); + restoreMediaSettings(audioInput: string, videoInput: string): void; + /** + * Set an audio input device to use for MatrixCalls + * @param deviceId - the identifier for the device + * undefined treated as unset + */ + setAudioInput(deviceId: string): Promise<void>; + /** + * Set audio settings for MatrixCalls + * @param opts - audio options to set + */ + setAudioSettings(opts: AudioSettings): Promise<void>; + /** + * Set a video input device to use for MatrixCalls + * @param deviceId - the identifier for the device + * undefined treated as unset + */ + setVideoInput(deviceId: string): Promise<void>; + /** + * Set media input devices to use for MatrixCalls + * @param audioInput - the identifier for the audio device + * @param videoInput - the identifier for the video device + * undefined treated as unset + */ + setMediaInputs(audioInput: string, videoInput: string): Promise<void>; + updateLocalUsermediaStreams(): Promise<void>; + hasAudioDevice(): Promise<boolean>; + hasVideoDevice(): Promise<boolean>; + /** + * @param audio - should have an audio track + * @param video - should have a video track + * @param reusable - is allowed to be reused by the MediaHandler + * @returns based on passed parameters + */ + getUserMediaStream(audio: boolean, video: boolean, reusable?: boolean): Promise<MediaStream>; + private getUserMediaStreamInternal; + /** + * Stops all tracks on the provided usermedia stream + */ + stopUserMediaStream(mediaStream: MediaStream): void; + /** + * @param desktopCapturerSourceId - sourceId for Electron DesktopCapturer + * @param reusable - is allowed to be reused by the MediaHandler + * @returns based on passed parameters + */ + getScreensharingStream(opts?: IScreensharingOpts, reusable?: boolean): Promise<MediaStream>; + /** + * Stops all tracks on the provided screensharing stream + */ + stopScreensharingStream(mediaStream: MediaStream): void; + /** + * Stops all local media tracks + */ + stopAllStreams(): void; + private getUserMediaContraints; + private getScreenshareContraints; +} +//# sourceMappingURL=mediaHandler.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.d.ts.map new file mode 100644 index 0000000..46b2943 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaHandler.d.ts","sourceRoot":"","sources":["../../src/webrtc/mediaHandler.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGlE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,oBAAY,iBAAiB;IACzB,mBAAmB,0BAA0B;CAChD;AAED,MAAM,MAAM,2BAA2B,GAAG;IACtC,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CACvD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAKhB,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED,qBAAa,YAAa,SAAQ,iBAAiB,CAC/C,iBAAiB,CAAC,mBAAmB,EACrC,2BAA2B,CAC9B;IAWsB,OAAO,CAAC,MAAM;IAVjC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,oBAAoB,CAAC,CAAc;IACpC,gBAAgB,EAAE,WAAW,EAAE,CAAM;IACrC,oBAAoB,EAAE,WAAW,EAAE,CAAM;IAGhD,OAAO,CAAC,qBAAqB,CAAC,CAAuB;gBAE1B,MAAM,EAAE,YAAY;IAIxC,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAKzE;;;;OAIG;IACU,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3D;;;OAGG;IACU,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjE;;;;OAIG;IACU,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3D;;;;;OAKG;IACU,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrE,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC;IA4D5C,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAUlC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAU/C;;;;;OAKG;IACU,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,UAAO,GAAG,OAAO,CAAC,WAAW,CAAC;YAaxF,0BAA0B;IAyFxC;;OAEG;IACI,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAuB1D;;;;OAIG;IACU,sBAAsB,CAAC,IAAI,GAAE,kBAAuB,EAAE,QAAQ,UAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAkCzG;;OAEG;IACI,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAgB9D;;OAEG;IACI,cAAc,IAAI,IAAI;IAqB7B,OAAO,CAAC,sBAAsB;IA2B9B,OAAO,CAAC,wBAAwB;CAmBnC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.js new file mode 100644 index 0000000..b82bb4a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.js @@ -0,0 +1,399 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MediaHandlerEvent = exports.MediaHandler = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _typedEventEmitter = require("../models/typed-event-emitter"); +var _groupCall = require("../webrtc/groupCall"); +var _logger = require("../logger"); +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2022 Å imon Brandner <simon.bra.ag@gmail.com> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +let MediaHandlerEvent; +exports.MediaHandlerEvent = MediaHandlerEvent; +(function (MediaHandlerEvent) { + MediaHandlerEvent["LocalStreamsChanged"] = "local_streams_changed"; +})(MediaHandlerEvent || (exports.MediaHandlerEvent = MediaHandlerEvent = {})); +class MediaHandler extends _typedEventEmitter.TypedEventEmitter { + // Promise chain to serialise calls to getMediaStream + + constructor(client) { + super(); + this.client = client; + (0, _defineProperty2.default)(this, "audioInput", void 0); + (0, _defineProperty2.default)(this, "audioSettings", void 0); + (0, _defineProperty2.default)(this, "videoInput", void 0); + (0, _defineProperty2.default)(this, "localUserMediaStream", void 0); + (0, _defineProperty2.default)(this, "userMediaStreams", []); + (0, _defineProperty2.default)(this, "screensharingStreams", []); + (0, _defineProperty2.default)(this, "getMediaStreamPromise", void 0); + } + restoreMediaSettings(audioInput, videoInput) { + this.audioInput = audioInput; + this.videoInput = videoInput; + } + + /** + * Set an audio input device to use for MatrixCalls + * @param deviceId - the identifier for the device + * undefined treated as unset + */ + async setAudioInput(deviceId) { + _logger.logger.info(`MediaHandler setAudioInput() running (deviceId=${deviceId})`); + if (this.audioInput === deviceId) return; + this.audioInput = deviceId; + await this.updateLocalUsermediaStreams(); + } + + /** + * Set audio settings for MatrixCalls + * @param opts - audio options to set + */ + async setAudioSettings(opts) { + _logger.logger.info(`MediaHandler setAudioSettings() running (opts=${JSON.stringify(opts)})`); + this.audioSettings = Object.assign({}, opts); + await this.updateLocalUsermediaStreams(); + } + + /** + * Set a video input device to use for MatrixCalls + * @param deviceId - the identifier for the device + * undefined treated as unset + */ + async setVideoInput(deviceId) { + _logger.logger.info(`MediaHandler setVideoInput() running (deviceId=${deviceId})`); + if (this.videoInput === deviceId) return; + this.videoInput = deviceId; + await this.updateLocalUsermediaStreams(); + } + + /** + * Set media input devices to use for MatrixCalls + * @param audioInput - the identifier for the audio device + * @param videoInput - the identifier for the video device + * undefined treated as unset + */ + async setMediaInputs(audioInput, videoInput) { + _logger.logger.log(`MediaHandler setMediaInputs() running (audioInput: ${audioInput} videoInput: ${videoInput})`); + this.audioInput = audioInput; + this.videoInput = videoInput; + await this.updateLocalUsermediaStreams(); + } + + /* + * Requests new usermedia streams and replace the old ones + */ + async updateLocalUsermediaStreams() { + if (this.userMediaStreams.length === 0) return; + const callMediaStreamParams = new Map(); + for (const call of this.client.callEventHandler.calls.values()) { + callMediaStreamParams.set(call.callId, { + audio: call.hasLocalUserMediaAudioTrack, + video: call.hasLocalUserMediaVideoTrack + }); + } + for (const stream of this.userMediaStreams) { + _logger.logger.log(`MediaHandler updateLocalUsermediaStreams() stopping all tracks (streamId=${stream.id})`); + for (const track of stream.getTracks()) { + track.stop(); + } + } + this.userMediaStreams = []; + this.localUserMediaStream = undefined; + for (const call of this.client.callEventHandler.calls.values()) { + if (call.callHasEnded() || !callMediaStreamParams.has(call.callId)) { + continue; + } + const { + audio, + video + } = callMediaStreamParams.get(call.callId); + _logger.logger.log(`MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (callId=${call.callId})`); + const stream = await this.getUserMediaStream(audio, video); + if (call.callHasEnded()) { + continue; + } + await call.updateLocalUsermediaStream(stream); + } + for (const groupCall of this.client.groupCallEventHandler.groupCalls.values()) { + if (!groupCall.localCallFeed) { + continue; + } + _logger.logger.log(`MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (groupCallId=${groupCall.groupCallId})`); + const stream = await this.getUserMediaStream(true, groupCall.type === _groupCall.GroupCallType.Video); + if (groupCall.state === _groupCall.GroupCallState.Ended) { + continue; + } + await groupCall.updateLocalUsermediaStream(stream); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + } + async hasAudioDevice() { + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.filter(device => device.kind === "audioinput").length > 0; + } catch (err) { + _logger.logger.log(`MediaHandler hasAudioDevice() calling navigator.mediaDevices.enumerateDevices with error`, err); + return false; + } + } + async hasVideoDevice() { + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.filter(device => device.kind === "videoinput").length > 0; + } catch (err) { + _logger.logger.log(`MediaHandler hasVideoDevice() calling navigator.mediaDevices.enumerateDevices with error`, err); + return false; + } + } + + /** + * @param audio - should have an audio track + * @param video - should have a video track + * @param reusable - is allowed to be reused by the MediaHandler + * @returns based on passed parameters + */ + async getUserMediaStream(audio, video, reusable = true) { + // Serialise calls, othertwise we can't sensibly re-use the stream + if (this.getMediaStreamPromise) { + this.getMediaStreamPromise = this.getMediaStreamPromise.then(() => { + return this.getUserMediaStreamInternal(audio, video, reusable); + }); + } else { + this.getMediaStreamPromise = this.getUserMediaStreamInternal(audio, video, reusable); + } + return this.getMediaStreamPromise; + } + async getUserMediaStreamInternal(audio, video, reusable) { + const shouldRequestAudio = audio && (await this.hasAudioDevice()); + const shouldRequestVideo = video && (await this.hasVideoDevice()); + let stream; + let canReuseStream = true; + if (this.localUserMediaStream) { + var _this$localUserMediaS, _this$localUserMediaS2, _this$localUserMediaS3, _this$localUserMediaS4; + // This figures out if we can reuse the current localUsermediaStream + // based on whether or not the "mute state" (presence of tracks of a + // given kind) matches what is being requested + if (shouldRequestAudio !== this.localUserMediaStream.getAudioTracks().length > 0) { + canReuseStream = false; + } + if (shouldRequestVideo !== this.localUserMediaStream.getVideoTracks().length > 0) { + canReuseStream = false; + } + + // This code checks that the device ID is the same as the localUserMediaStream stream, but we update + // the localUserMediaStream whenever the device ID changes (apart from when restoring) so it's not + // clear why this would ever be different, unless there's a race. + if (shouldRequestAudio && ((_this$localUserMediaS = this.localUserMediaStream.getAudioTracks()[0]) === null || _this$localUserMediaS === void 0 ? void 0 : (_this$localUserMediaS2 = _this$localUserMediaS.getSettings()) === null || _this$localUserMediaS2 === void 0 ? void 0 : _this$localUserMediaS2.deviceId) !== this.audioInput) { + canReuseStream = false; + } + if (shouldRequestVideo && ((_this$localUserMediaS3 = this.localUserMediaStream.getVideoTracks()[0]) === null || _this$localUserMediaS3 === void 0 ? void 0 : (_this$localUserMediaS4 = _this$localUserMediaS3.getSettings()) === null || _this$localUserMediaS4 === void 0 ? void 0 : _this$localUserMediaS4.deviceId) !== this.videoInput) { + canReuseStream = false; + } + } else { + canReuseStream = false; + } + if (!canReuseStream) { + const constraints = this.getUserMediaContraints(shouldRequestAudio, shouldRequestVideo); + stream = await navigator.mediaDevices.getUserMedia(constraints); + _logger.logger.log(`MediaHandler getUserMediaStreamInternal() calling getUserMediaStream (streamId=${stream.id}, shouldRequestAudio=${shouldRequestAudio}, shouldRequestVideo=${shouldRequestVideo}, constraints=${JSON.stringify(constraints)})`); + for (const track of stream.getTracks()) { + const settings = track.getSettings(); + if (track.kind === "audio") { + this.audioInput = settings.deviceId; + } else if (track.kind === "video") { + this.videoInput = settings.deviceId; + } + } + if (reusable) { + this.localUserMediaStream = stream; + } + } else { + var _this$localUserMediaS5; + stream = this.localUserMediaStream.clone(); + _logger.logger.log(`MediaHandler getUserMediaStreamInternal() cloning (oldStreamId=${(_this$localUserMediaS5 = this.localUserMediaStream) === null || _this$localUserMediaS5 === void 0 ? void 0 : _this$localUserMediaS5.id} newStreamId=${stream.id} shouldRequestAudio=${shouldRequestAudio} shouldRequestVideo=${shouldRequestVideo})`); + if (!shouldRequestAudio) { + for (const track of stream.getAudioTracks()) { + stream.removeTrack(track); + } + } + if (!shouldRequestVideo) { + for (const track of stream.getVideoTracks()) { + stream.removeTrack(track); + } + } + } + if (reusable) { + this.userMediaStreams.push(stream); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + return stream; + } + + /** + * Stops all tracks on the provided usermedia stream + */ + stopUserMediaStream(mediaStream) { + _logger.logger.log(`MediaHandler stopUserMediaStream() stopping (streamId=${mediaStream.id})`); + for (const track of mediaStream.getTracks()) { + track.stop(); + } + const index = this.userMediaStreams.indexOf(mediaStream); + if (index !== -1) { + _logger.logger.debug(`MediaHandler stopUserMediaStream() splicing usermedia stream out stream array (streamId=${mediaStream.id})`, mediaStream.id); + this.userMediaStreams.splice(index, 1); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + if (this.localUserMediaStream === mediaStream) { + this.localUserMediaStream = undefined; + } + } + + /** + * @param desktopCapturerSourceId - sourceId for Electron DesktopCapturer + * @param reusable - is allowed to be reused by the MediaHandler + * @returns based on passed parameters + */ + async getScreensharingStream(opts = {}, reusable = true) { + let stream; + if (this.screensharingStreams.length === 0) { + const screenshareConstraints = this.getScreenshareContraints(opts); + if (opts.desktopCapturerSourceId) { + // We are using Electron + _logger.logger.debug(`MediaHandler getScreensharingStream() calling getUserMedia() (opts=${JSON.stringify(opts)})`); + stream = await navigator.mediaDevices.getUserMedia(screenshareConstraints); + } else { + // We are not using Electron + _logger.logger.debug(`MediaHandler getScreensharingStream() calling getDisplayMedia() (opts=${JSON.stringify(opts)})`); + stream = await navigator.mediaDevices.getDisplayMedia(screenshareConstraints); + } + } else { + const matchingStream = this.screensharingStreams[this.screensharingStreams.length - 1]; + _logger.logger.log(`MediaHandler getScreensharingStream() cloning (streamId=${matchingStream.id})`); + stream = matchingStream.clone(); + } + if (reusable) { + this.screensharingStreams.push(stream); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + return stream; + } + + /** + * Stops all tracks on the provided screensharing stream + */ + stopScreensharingStream(mediaStream) { + _logger.logger.debug(`MediaHandler stopScreensharingStream() stopping stream (streamId=${mediaStream.id})`); + for (const track of mediaStream.getTracks()) { + track.stop(); + } + const index = this.screensharingStreams.indexOf(mediaStream); + if (index !== -1) { + _logger.logger.debug(`MediaHandler stopScreensharingStream() splicing stream out (streamId=${mediaStream.id})`); + this.screensharingStreams.splice(index, 1); + } + this.emit(MediaHandlerEvent.LocalStreamsChanged); + } + + /** + * Stops all local media tracks + */ + stopAllStreams() { + for (const stream of this.userMediaStreams) { + _logger.logger.log(`MediaHandler stopAllStreams() stopping (streamId=${stream.id})`); + for (const track of stream.getTracks()) { + track.stop(); + } + } + for (const stream of this.screensharingStreams) { + for (const track of stream.getTracks()) { + track.stop(); + } + } + this.userMediaStreams = []; + this.screensharingStreams = []; + this.localUserMediaStream = undefined; + this.emit(MediaHandlerEvent.LocalStreamsChanged); + } + getUserMediaContraints(audio, video) { + const isWebkit = !!navigator.webkitGetUserMedia; + return { + audio: audio ? { + deviceId: this.audioInput ? { + ideal: this.audioInput + } : undefined, + autoGainControl: this.audioSettings ? { + ideal: this.audioSettings.autoGainControl + } : undefined, + echoCancellation: this.audioSettings ? { + ideal: this.audioSettings.echoCancellation + } : undefined, + noiseSuppression: this.audioSettings ? { + ideal: this.audioSettings.noiseSuppression + } : undefined + } : false, + video: video ? { + deviceId: this.videoInput ? { + ideal: this.videoInput + } : undefined, + /* We want 640x360. Chrome will give it only if we ask exactly, + FF refuses entirely if we ask exactly, so have to ask for ideal + instead + XXX: Is this still true? + */ + width: isWebkit ? { + exact: 640 + } : { + ideal: 640 + }, + height: isWebkit ? { + exact: 360 + } : { + ideal: 360 + } + } : false + }; + } + getScreenshareContraints(opts) { + const { + desktopCapturerSourceId, + audio + } = opts; + if (desktopCapturerSourceId) { + return { + audio: audio !== null && audio !== void 0 ? audio : false, + video: { + mandatory: { + chromeMediaSource: "desktop", + chromeMediaSourceId: desktopCapturerSourceId + } + } + }; + } else { + return { + audio: audio !== null && audio !== void 0 ? audio : false, + video: true + }; + } + } +} +exports.MediaHandler = MediaHandler; +//# sourceMappingURL=mediaHandler.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.js.map new file mode 100644 index 0000000..ca5a93f --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/mediaHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaHandler.js","names":["_typedEventEmitter","require","_groupCall","_logger","MediaHandlerEvent","exports","MediaHandler","TypedEventEmitter","constructor","client","_defineProperty2","default","restoreMediaSettings","audioInput","videoInput","setAudioInput","deviceId","logger","info","updateLocalUsermediaStreams","setAudioSettings","opts","JSON","stringify","audioSettings","Object","assign","setVideoInput","setMediaInputs","log","userMediaStreams","length","callMediaStreamParams","Map","call","callEventHandler","calls","values","set","callId","audio","hasLocalUserMediaAudioTrack","video","hasLocalUserMediaVideoTrack","stream","id","track","getTracks","stop","localUserMediaStream","undefined","callHasEnded","has","get","getUserMediaStream","updateLocalUsermediaStream","groupCall","groupCallEventHandler","groupCalls","localCallFeed","groupCallId","type","GroupCallType","Video","state","GroupCallState","Ended","emit","LocalStreamsChanged","hasAudioDevice","devices","navigator","mediaDevices","enumerateDevices","filter","device","kind","err","hasVideoDevice","reusable","getMediaStreamPromise","then","getUserMediaStreamInternal","shouldRequestAudio","shouldRequestVideo","canReuseStream","_this$localUserMediaS","_this$localUserMediaS2","_this$localUserMediaS3","_this$localUserMediaS4","getAudioTracks","getVideoTracks","getSettings","constraints","getUserMediaContraints","getUserMedia","settings","_this$localUserMediaS5","clone","removeTrack","push","stopUserMediaStream","mediaStream","index","indexOf","debug","splice","getScreensharingStream","screensharingStreams","screenshareConstraints","getScreenshareContraints","desktopCapturerSourceId","getDisplayMedia","matchingStream","stopScreensharingStream","stopAllStreams","isWebkit","webkitGetUserMedia","ideal","autoGainControl","echoCancellation","noiseSuppression","width","exact","height","mandatory","chromeMediaSource","chromeMediaSourceId"],"sources":["../../src/webrtc/mediaHandler.ts"],"sourcesContent":["/*\nCopyright 2015, 2016 OpenMarket Ltd\nCopyright 2017 New Vector Ltd\nCopyright 2019, 2020 The Matrix.org Foundation C.I.C.\nCopyright 2021 - 2022 Å imon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { TypedEventEmitter } from \"../models/typed-event-emitter\";\nimport { GroupCallType, GroupCallState } from \"../webrtc/groupCall\";\nimport { logger } from \"../logger\";\nimport { MatrixClient } from \"../client\";\n\nexport enum MediaHandlerEvent {\n LocalStreamsChanged = \"local_streams_changed\",\n}\n\nexport type MediaHandlerEventHandlerMap = {\n [MediaHandlerEvent.LocalStreamsChanged]: () => void;\n};\n\nexport interface IScreensharingOpts {\n desktopCapturerSourceId?: string;\n audio?: boolean;\n // For electron screen capture, there are very few options for detecting electron\n // apart from inspecting the user agent or just trying getDisplayMedia() and\n // catching the failure, so we do the latter - this flag tells the function to just\n // throw an error so we can catch it in this case, rather than logging and emitting.\n throwOnFail?: boolean;\n}\n\nexport interface AudioSettings {\n autoGainControl: boolean;\n echoCancellation: boolean;\n noiseSuppression: boolean;\n}\n\nexport class MediaHandler extends TypedEventEmitter<\n MediaHandlerEvent.LocalStreamsChanged,\n MediaHandlerEventHandlerMap\n> {\n private audioInput?: string;\n private audioSettings?: AudioSettings;\n private videoInput?: string;\n private localUserMediaStream?: MediaStream;\n public userMediaStreams: MediaStream[] = [];\n public screensharingStreams: MediaStream[] = [];\n\n // Promise chain to serialise calls to getMediaStream\n private getMediaStreamPromise?: Promise<MediaStream>;\n\n public constructor(private client: MatrixClient) {\n super();\n }\n\n public restoreMediaSettings(audioInput: string, videoInput: string): void {\n this.audioInput = audioInput;\n this.videoInput = videoInput;\n }\n\n /**\n * Set an audio input device to use for MatrixCalls\n * @param deviceId - the identifier for the device\n * undefined treated as unset\n */\n public async setAudioInput(deviceId: string): Promise<void> {\n logger.info(`MediaHandler setAudioInput() running (deviceId=${deviceId})`);\n\n if (this.audioInput === deviceId) return;\n\n this.audioInput = deviceId;\n await this.updateLocalUsermediaStreams();\n }\n\n /**\n * Set audio settings for MatrixCalls\n * @param opts - audio options to set\n */\n public async setAudioSettings(opts: AudioSettings): Promise<void> {\n logger.info(`MediaHandler setAudioSettings() running (opts=${JSON.stringify(opts)})`);\n\n this.audioSettings = Object.assign({}, opts) as AudioSettings;\n await this.updateLocalUsermediaStreams();\n }\n\n /**\n * Set a video input device to use for MatrixCalls\n * @param deviceId - the identifier for the device\n * undefined treated as unset\n */\n public async setVideoInput(deviceId: string): Promise<void> {\n logger.info(`MediaHandler setVideoInput() running (deviceId=${deviceId})`);\n\n if (this.videoInput === deviceId) return;\n\n this.videoInput = deviceId;\n await this.updateLocalUsermediaStreams();\n }\n\n /**\n * Set media input devices to use for MatrixCalls\n * @param audioInput - the identifier for the audio device\n * @param videoInput - the identifier for the video device\n * undefined treated as unset\n */\n public async setMediaInputs(audioInput: string, videoInput: string): Promise<void> {\n logger.log(`MediaHandler setMediaInputs() running (audioInput: ${audioInput} videoInput: ${videoInput})`);\n this.audioInput = audioInput;\n this.videoInput = videoInput;\n await this.updateLocalUsermediaStreams();\n }\n\n /*\n * Requests new usermedia streams and replace the old ones\n */\n public async updateLocalUsermediaStreams(): Promise<void> {\n if (this.userMediaStreams.length === 0) return;\n\n const callMediaStreamParams: Map<string, { audio: boolean; video: boolean }> = new Map();\n for (const call of this.client.callEventHandler!.calls.values()) {\n callMediaStreamParams.set(call.callId, {\n audio: call.hasLocalUserMediaAudioTrack,\n video: call.hasLocalUserMediaVideoTrack,\n });\n }\n\n for (const stream of this.userMediaStreams) {\n logger.log(`MediaHandler updateLocalUsermediaStreams() stopping all tracks (streamId=${stream.id})`);\n for (const track of stream.getTracks()) {\n track.stop();\n }\n }\n\n this.userMediaStreams = [];\n this.localUserMediaStream = undefined;\n\n for (const call of this.client.callEventHandler!.calls.values()) {\n if (call.callHasEnded() || !callMediaStreamParams.has(call.callId)) {\n continue;\n }\n\n const { audio, video } = callMediaStreamParams.get(call.callId)!;\n\n logger.log(\n `MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (callId=${call.callId})`,\n );\n const stream = await this.getUserMediaStream(audio, video);\n\n if (call.callHasEnded()) {\n continue;\n }\n\n await call.updateLocalUsermediaStream(stream);\n }\n\n for (const groupCall of this.client.groupCallEventHandler!.groupCalls.values()) {\n if (!groupCall.localCallFeed) {\n continue;\n }\n\n logger.log(\n `MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (groupCallId=${groupCall.groupCallId})`,\n );\n const stream = await this.getUserMediaStream(true, groupCall.type === GroupCallType.Video);\n\n if (groupCall.state === GroupCallState.Ended) {\n continue;\n }\n\n await groupCall.updateLocalUsermediaStream(stream);\n }\n\n this.emit(MediaHandlerEvent.LocalStreamsChanged);\n }\n\n public async hasAudioDevice(): Promise<boolean> {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.filter((device) => device.kind === \"audioinput\").length > 0;\n } catch (err) {\n logger.log(`MediaHandler hasAudioDevice() calling navigator.mediaDevices.enumerateDevices with error`, err);\n return false;\n }\n }\n\n public async hasVideoDevice(): Promise<boolean> {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.filter((device) => device.kind === \"videoinput\").length > 0;\n } catch (err) {\n logger.log(`MediaHandler hasVideoDevice() calling navigator.mediaDevices.enumerateDevices with error`, err);\n return false;\n }\n }\n\n /**\n * @param audio - should have an audio track\n * @param video - should have a video track\n * @param reusable - is allowed to be reused by the MediaHandler\n * @returns based on passed parameters\n */\n public async getUserMediaStream(audio: boolean, video: boolean, reusable = true): Promise<MediaStream> {\n // Serialise calls, othertwise we can't sensibly re-use the stream\n if (this.getMediaStreamPromise) {\n this.getMediaStreamPromise = this.getMediaStreamPromise.then(() => {\n return this.getUserMediaStreamInternal(audio, video, reusable);\n });\n } else {\n this.getMediaStreamPromise = this.getUserMediaStreamInternal(audio, video, reusable);\n }\n\n return this.getMediaStreamPromise;\n }\n\n private async getUserMediaStreamInternal(audio: boolean, video: boolean, reusable: boolean): Promise<MediaStream> {\n const shouldRequestAudio = audio && (await this.hasAudioDevice());\n const shouldRequestVideo = video && (await this.hasVideoDevice());\n\n let stream: MediaStream;\n\n let canReuseStream = true;\n if (this.localUserMediaStream) {\n // This figures out if we can reuse the current localUsermediaStream\n // based on whether or not the \"mute state\" (presence of tracks of a\n // given kind) matches what is being requested\n if (shouldRequestAudio !== this.localUserMediaStream.getAudioTracks().length > 0) {\n canReuseStream = false;\n }\n if (shouldRequestVideo !== this.localUserMediaStream.getVideoTracks().length > 0) {\n canReuseStream = false;\n }\n\n // This code checks that the device ID is the same as the localUserMediaStream stream, but we update\n // the localUserMediaStream whenever the device ID changes (apart from when restoring) so it's not\n // clear why this would ever be different, unless there's a race.\n if (\n shouldRequestAudio &&\n this.localUserMediaStream.getAudioTracks()[0]?.getSettings()?.deviceId !== this.audioInput\n ) {\n canReuseStream = false;\n }\n if (\n shouldRequestVideo &&\n this.localUserMediaStream.getVideoTracks()[0]?.getSettings()?.deviceId !== this.videoInput\n ) {\n canReuseStream = false;\n }\n } else {\n canReuseStream = false;\n }\n\n if (!canReuseStream) {\n const constraints = this.getUserMediaContraints(shouldRequestAudio, shouldRequestVideo);\n stream = await navigator.mediaDevices.getUserMedia(constraints);\n logger.log(\n `MediaHandler getUserMediaStreamInternal() calling getUserMediaStream (streamId=${\n stream.id\n }, shouldRequestAudio=${shouldRequestAudio}, shouldRequestVideo=${shouldRequestVideo}, constraints=${JSON.stringify(\n constraints,\n )})`,\n );\n\n for (const track of stream.getTracks()) {\n const settings = track.getSettings();\n\n if (track.kind === \"audio\") {\n this.audioInput = settings.deviceId!;\n } else if (track.kind === \"video\") {\n this.videoInput = settings.deviceId!;\n }\n }\n\n if (reusable) {\n this.localUserMediaStream = stream;\n }\n } else {\n stream = this.localUserMediaStream!.clone();\n logger.log(\n `MediaHandler getUserMediaStreamInternal() cloning (oldStreamId=${this.localUserMediaStream?.id} newStreamId=${stream.id} shouldRequestAudio=${shouldRequestAudio} shouldRequestVideo=${shouldRequestVideo})`,\n );\n\n if (!shouldRequestAudio) {\n for (const track of stream.getAudioTracks()) {\n stream.removeTrack(track);\n }\n }\n\n if (!shouldRequestVideo) {\n for (const track of stream.getVideoTracks()) {\n stream.removeTrack(track);\n }\n }\n }\n\n if (reusable) {\n this.userMediaStreams.push(stream);\n }\n\n this.emit(MediaHandlerEvent.LocalStreamsChanged);\n\n return stream;\n }\n\n /**\n * Stops all tracks on the provided usermedia stream\n */\n public stopUserMediaStream(mediaStream: MediaStream): void {\n logger.log(`MediaHandler stopUserMediaStream() stopping (streamId=${mediaStream.id})`);\n for (const track of mediaStream.getTracks()) {\n track.stop();\n }\n\n const index = this.userMediaStreams.indexOf(mediaStream);\n\n if (index !== -1) {\n logger.debug(\n `MediaHandler stopUserMediaStream() splicing usermedia stream out stream array (streamId=${mediaStream.id})`,\n mediaStream.id,\n );\n this.userMediaStreams.splice(index, 1);\n }\n\n this.emit(MediaHandlerEvent.LocalStreamsChanged);\n\n if (this.localUserMediaStream === mediaStream) {\n this.localUserMediaStream = undefined;\n }\n }\n\n /**\n * @param desktopCapturerSourceId - sourceId for Electron DesktopCapturer\n * @param reusable - is allowed to be reused by the MediaHandler\n * @returns based on passed parameters\n */\n public async getScreensharingStream(opts: IScreensharingOpts = {}, reusable = true): Promise<MediaStream> {\n let stream: MediaStream;\n\n if (this.screensharingStreams.length === 0) {\n const screenshareConstraints = this.getScreenshareContraints(opts);\n\n if (opts.desktopCapturerSourceId) {\n // We are using Electron\n logger.debug(\n `MediaHandler getScreensharingStream() calling getUserMedia() (opts=${JSON.stringify(opts)})`,\n );\n stream = await navigator.mediaDevices.getUserMedia(screenshareConstraints);\n } else {\n // We are not using Electron\n logger.debug(\n `MediaHandler getScreensharingStream() calling getDisplayMedia() (opts=${JSON.stringify(opts)})`,\n );\n stream = await navigator.mediaDevices.getDisplayMedia(screenshareConstraints);\n }\n } else {\n const matchingStream = this.screensharingStreams[this.screensharingStreams.length - 1];\n logger.log(`MediaHandler getScreensharingStream() cloning (streamId=${matchingStream.id})`);\n stream = matchingStream.clone();\n }\n\n if (reusable) {\n this.screensharingStreams.push(stream);\n }\n\n this.emit(MediaHandlerEvent.LocalStreamsChanged);\n\n return stream;\n }\n\n /**\n * Stops all tracks on the provided screensharing stream\n */\n public stopScreensharingStream(mediaStream: MediaStream): void {\n logger.debug(`MediaHandler stopScreensharingStream() stopping stream (streamId=${mediaStream.id})`);\n for (const track of mediaStream.getTracks()) {\n track.stop();\n }\n\n const index = this.screensharingStreams.indexOf(mediaStream);\n\n if (index !== -1) {\n logger.debug(`MediaHandler stopScreensharingStream() splicing stream out (streamId=${mediaStream.id})`);\n this.screensharingStreams.splice(index, 1);\n }\n\n this.emit(MediaHandlerEvent.LocalStreamsChanged);\n }\n\n /**\n * Stops all local media tracks\n */\n public stopAllStreams(): void {\n for (const stream of this.userMediaStreams) {\n logger.log(`MediaHandler stopAllStreams() stopping (streamId=${stream.id})`);\n for (const track of stream.getTracks()) {\n track.stop();\n }\n }\n\n for (const stream of this.screensharingStreams) {\n for (const track of stream.getTracks()) {\n track.stop();\n }\n }\n\n this.userMediaStreams = [];\n this.screensharingStreams = [];\n this.localUserMediaStream = undefined;\n\n this.emit(MediaHandlerEvent.LocalStreamsChanged);\n }\n\n private getUserMediaContraints(audio: boolean, video: boolean): MediaStreamConstraints {\n const isWebkit = !!navigator.webkitGetUserMedia;\n\n return {\n audio: audio\n ? {\n deviceId: this.audioInput ? { ideal: this.audioInput } : undefined,\n autoGainControl: this.audioSettings ? { ideal: this.audioSettings.autoGainControl } : undefined,\n echoCancellation: this.audioSettings ? { ideal: this.audioSettings.echoCancellation } : undefined,\n noiseSuppression: this.audioSettings ? { ideal: this.audioSettings.noiseSuppression } : undefined,\n }\n : false,\n video: video\n ? {\n deviceId: this.videoInput ? { ideal: this.videoInput } : undefined,\n /* We want 640x360. Chrome will give it only if we ask exactly,\n FF refuses entirely if we ask exactly, so have to ask for ideal\n instead\n XXX: Is this still true?\n */\n width: isWebkit ? { exact: 640 } : { ideal: 640 },\n height: isWebkit ? { exact: 360 } : { ideal: 360 },\n }\n : false,\n };\n }\n\n private getScreenshareContraints(opts: IScreensharingOpts): DesktopCapturerConstraints {\n const { desktopCapturerSourceId, audio } = opts;\n if (desktopCapturerSourceId) {\n return {\n audio: audio ?? false,\n video: {\n mandatory: {\n chromeMediaSource: \"desktop\",\n chromeMediaSourceId: desktopCapturerSourceId,\n },\n },\n };\n } else {\n return {\n audio: audio ?? false,\n video: true,\n };\n }\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.d.ts new file mode 100644 index 0000000..cbc970b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.d.ts @@ -0,0 +1,28 @@ +import { TransportStats } from "./transportStats"; +import { Bitrate } from "./media/mediaTrackStats"; +export interface ConnectionStatsBandwidth { + /** + * bytes per second + */ + download: number; + /** + * bytes per second + */ + upload: number; +} +export interface ConnectionStatsBitrate extends Bitrate { + audio?: Bitrate; + video?: Bitrate; +} +export interface PacketLoos { + total: number; + download: number; + upload: number; +} +export declare class ConnectionStats { + bandwidth: ConnectionStatsBitrate; + bitrate: ConnectionStatsBitrate; + packetLoss: PacketLoos; + transport: TransportStats[]; +} +//# sourceMappingURL=connectionStats.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.d.ts.map new file mode 100644 index 0000000..bf88119 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"connectionStats.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/connectionStats.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACrC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAuB,SAAQ,OAAO;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAe;IACjB,SAAS,EAAE,sBAAsB,CAAgC;IACjE,OAAO,EAAE,sBAAsB,CAAgC;IAC/D,UAAU,EAAE,UAAU,CAAoB;IAC1C,SAAS,EAAE,cAAc,EAAE,CAAM;CAC3C"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.js new file mode 100644 index 0000000..bed0ee3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.js @@ -0,0 +1,34 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ConnectionStats = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class ConnectionStats { + constructor() { + (0, _defineProperty2.default)(this, "bandwidth", {}); + (0, _defineProperty2.default)(this, "bitrate", {}); + (0, _defineProperty2.default)(this, "packetLoss", {}); + (0, _defineProperty2.default)(this, "transport", []); + } +} +exports.ConnectionStats = ConnectionStats; +//# sourceMappingURL=connectionStats.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.js.map new file mode 100644 index 0000000..c326eb6 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStats.js.map @@ -0,0 +1 @@ +{"version":3,"file":"connectionStats.js","names":["ConnectionStats","constructor","_defineProperty2","default","exports"],"sources":["../../../src/webrtc/stats/connectionStats.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { TransportStats } from \"./transportStats\";\nimport { Bitrate } from \"./media/mediaTrackStats\";\n\nexport interface ConnectionStatsBandwidth {\n /**\n * bytes per second\n */\n download: number;\n /**\n * bytes per second\n */\n upload: number;\n}\n\nexport interface ConnectionStatsBitrate extends Bitrate {\n audio?: Bitrate;\n video?: Bitrate;\n}\n\nexport interface PacketLoos {\n total: number;\n download: number;\n upload: number;\n}\n\nexport class ConnectionStats {\n public bandwidth: ConnectionStatsBitrate = {} as ConnectionStatsBitrate;\n public bitrate: ConnectionStatsBitrate = {} as ConnectionStatsBitrate;\n public packetLoss: PacketLoos = {} as PacketLoos;\n public transport: TransportStats[] = [];\n}\n"],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA2BO,MAAMA,eAAe,CAAC;EAAAC,YAAA;IAAA,IAAAC,gBAAA,CAAAC,OAAA,qBACkB,CAAC,CAAC;IAAA,IAAAD,gBAAA,CAAAC,OAAA,mBACJ,CAAC,CAAC;IAAA,IAAAD,gBAAA,CAAAC,OAAA,sBACX,CAAC,CAAC;IAAA,IAAAD,gBAAA,CAAAC,OAAA,qBACG,EAAE;EAAA;AAC3C;AAACC,OAAA,CAAAJ,eAAA,GAAAA,eAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.d.ts new file mode 100644 index 0000000..3794554 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.d.ts @@ -0,0 +1,5 @@ +import { Bitrate } from "./media/mediaTrackStats"; +export declare class ConnectionStatsReporter { + static buildBandwidthReport(now: RTCIceCandidatePairStats): Bitrate; +} +//# sourceMappingURL=connectionStatsReporter.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.d.ts.map new file mode 100644 index 0000000..de97f25 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"connectionStatsReporter.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/connectionStatsReporter.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,qBAAa,uBAAuB;WAClB,oBAAoB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO;CAS7E"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.js new file mode 100644 index 0000000..ad884af --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ConnectionStatsReporter = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class ConnectionStatsReporter { + static buildBandwidthReport(now) { + const availableIncomingBitrate = now.availableIncomingBitrate; + const availableOutgoingBitrate = now.availableOutgoingBitrate; + return { + download: availableIncomingBitrate ? Math.round(availableIncomingBitrate / 1000) : 0, + upload: availableOutgoingBitrate ? Math.round(availableOutgoingBitrate / 1000) : 0 + }; + } +} +exports.ConnectionStatsReporter = ConnectionStatsReporter; +//# sourceMappingURL=connectionStatsReporter.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.js.map new file mode 100644 index 0000000..8f3f3fe --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/connectionStatsReporter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"connectionStatsReporter.js","names":["ConnectionStatsReporter","buildBandwidthReport","now","availableIncomingBitrate","availableOutgoingBitrate","download","Math","round","upload","exports"],"sources":["../../../src/webrtc/stats/connectionStatsReporter.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\nimport { Bitrate } from \"./media/mediaTrackStats\";\n\nexport class ConnectionStatsReporter {\n public static buildBandwidthReport(now: RTCIceCandidatePairStats): Bitrate {\n const availableIncomingBitrate = now.availableIncomingBitrate;\n const availableOutgoingBitrate = now.availableOutgoingBitrate;\n\n return {\n download: availableIncomingBitrate ? Math.round(availableIncomingBitrate / 1000) : 0,\n upload: availableOutgoingBitrate ? Math.round(availableOutgoingBitrate / 1000) : 0,\n };\n }\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGO,MAAMA,uBAAuB,CAAC;EACjC,OAAcC,oBAAoBA,CAACC,GAA6B,EAAW;IACvE,MAAMC,wBAAwB,GAAGD,GAAG,CAACC,wBAAwB;IAC7D,MAAMC,wBAAwB,GAAGF,GAAG,CAACE,wBAAwB;IAE7D,OAAO;MACHC,QAAQ,EAAEF,wBAAwB,GAAGG,IAAI,CAACC,KAAK,CAACJ,wBAAwB,GAAG,IAAI,CAAC,GAAG,CAAC;MACpFK,MAAM,EAAEJ,wBAAwB,GAAGE,IAAI,CAACC,KAAK,CAACH,wBAAwB,GAAG,IAAI,CAAC,GAAG;IACrF,CAAC;EACL;AACJ;AAACK,OAAA,CAAAT,uBAAA,GAAAA,uBAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.d.ts new file mode 100644 index 0000000..bbb2d3a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.d.ts @@ -0,0 +1,19 @@ +import { StatsReportGatherer } from "./statsReportGatherer"; +import { StatsReportEmitter } from "./statsReportEmitter"; +export declare class GroupCallStats { + private groupCallId; + private userId; + private interval; + private timer; + private readonly gatherers; + readonly reports: StatsReportEmitter; + constructor(groupCallId: string, userId: string, interval?: number); + start(): void; + stop(): void; + hasStatsReportGatherer(callId: string): boolean; + addStatsReportGatherer(callId: string, userId: string, peerConnection: RTCPeerConnection): boolean; + removeStatsReportGatherer(callId: string): boolean; + getStatsReportGatherer(callId: string): StatsReportGatherer | undefined; + private processStats; +} +//# sourceMappingURL=groupCallStats.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.d.ts.map new file mode 100644 index 0000000..f115e7c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"groupCallStats.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/groupCallStats.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,qBAAa,cAAc;IAKJ,OAAO,CAAC,WAAW;IAAU,OAAO,CAAC,MAAM;IAAU,OAAO,CAAC,QAAQ;IAJxF,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4E;IACtG,SAAgB,OAAO,qBAA4B;gBAExB,WAAW,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM,EAAU,QAAQ,GAAE,MAAc;IAEjG,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAOZ,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI/C,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,iBAAiB,GAAG,OAAO;IAQlG,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlD,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI9E,OAAO,CAAC,YAAY;CAGvB"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.js new file mode 100644 index 0000000..eeb3bc9 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.js @@ -0,0 +1,70 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.GroupCallStats = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _statsReportGatherer = require("./statsReportGatherer"); +var _statsReportEmitter = require("./statsReportEmitter"); +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class GroupCallStats { + constructor(groupCallId, userId, interval = 10000) { + this.groupCallId = groupCallId; + this.userId = userId; + this.interval = interval; + (0, _defineProperty2.default)(this, "timer", void 0); + (0, _defineProperty2.default)(this, "gatherers", new Map()); + (0, _defineProperty2.default)(this, "reports", new _statsReportEmitter.StatsReportEmitter()); + } + start() { + if (this.timer === undefined) { + this.timer = setInterval(() => { + this.processStats(); + }, this.interval); + } + } + stop() { + if (this.timer !== undefined) { + clearInterval(this.timer); + this.gatherers.forEach(c => c.stopProcessingStats()); + } + } + hasStatsReportGatherer(callId) { + return this.gatherers.has(callId); + } + addStatsReportGatherer(callId, userId, peerConnection) { + if (this.hasStatsReportGatherer(callId)) { + return false; + } + this.gatherers.set(callId, new _statsReportGatherer.StatsReportGatherer(callId, userId, peerConnection, this.reports)); + return true; + } + removeStatsReportGatherer(callId) { + return this.gatherers.delete(callId); + } + getStatsReportGatherer(callId) { + return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined; + } + processStats() { + this.gatherers.forEach(c => c.processStats(this.groupCallId, this.userId)); + } +} +exports.GroupCallStats = GroupCallStats; +//# sourceMappingURL=groupCallStats.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.js.map new file mode 100644 index 0000000..374418c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/groupCallStats.js.map @@ -0,0 +1 @@ +{"version":3,"file":"groupCallStats.js","names":["_statsReportGatherer","require","_statsReportEmitter","GroupCallStats","constructor","groupCallId","userId","interval","_defineProperty2","default","Map","StatsReportEmitter","start","timer","undefined","setInterval","processStats","stop","clearInterval","gatherers","forEach","c","stopProcessingStats","hasStatsReportGatherer","callId","has","addStatsReportGatherer","peerConnection","set","StatsReportGatherer","reports","removeStatsReportGatherer","delete","getStatsReportGatherer","get","exports"],"sources":["../../../src/webrtc/stats/groupCallStats.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\nimport { StatsReportGatherer } from \"./statsReportGatherer\";\nimport { StatsReportEmitter } from \"./statsReportEmitter\";\n\nexport class GroupCallStats {\n private timer: undefined | ReturnType<typeof setTimeout>;\n private readonly gatherers: Map<string, StatsReportGatherer> = new Map<string, StatsReportGatherer>();\n public readonly reports = new StatsReportEmitter();\n\n public constructor(private groupCallId: string, private userId: string, private interval: number = 10000) {}\n\n public start(): void {\n if (this.timer === undefined) {\n this.timer = setInterval(() => {\n this.processStats();\n }, this.interval);\n }\n }\n\n public stop(): void {\n if (this.timer !== undefined) {\n clearInterval(this.timer);\n this.gatherers.forEach((c) => c.stopProcessingStats());\n }\n }\n\n public hasStatsReportGatherer(callId: string): boolean {\n return this.gatherers.has(callId);\n }\n\n public addStatsReportGatherer(callId: string, userId: string, peerConnection: RTCPeerConnection): boolean {\n if (this.hasStatsReportGatherer(callId)) {\n return false;\n }\n this.gatherers.set(callId, new StatsReportGatherer(callId, userId, peerConnection, this.reports));\n return true;\n }\n\n public removeStatsReportGatherer(callId: string): boolean {\n return this.gatherers.delete(callId);\n }\n\n public getStatsReportGatherer(callId: string): StatsReportGatherer | undefined {\n return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined;\n }\n\n private processStats(): void {\n this.gatherers.forEach((c) => c.processStats(this.groupCallId, this.userId));\n }\n}\n"],"mappings":";;;;;;;;AAeA,IAAAA,oBAAA,GAAAC,OAAA;AACA,IAAAC,mBAAA,GAAAD,OAAA;AAhBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIO,MAAME,cAAc,CAAC;EAKjBC,WAAWA,CAASC,WAAmB,EAAUC,MAAc,EAAUC,QAAgB,GAAG,KAAK,EAAE;IAAA,KAA/EF,WAAmB,GAAnBA,WAAmB;IAAA,KAAUC,MAAc,GAAdA,MAAc;IAAA,KAAUC,QAAgB,GAAhBA,QAAgB;IAAA,IAAAC,gBAAA,CAAAC,OAAA;IAAA,IAAAD,gBAAA,CAAAC,OAAA,qBAHjC,IAAIC,GAAG,EAA+B;IAAA,IAAAF,gBAAA,CAAAC,OAAA,mBAC3E,IAAIE,sCAAkB,EAAE;EAEyD;EAEpGC,KAAKA,CAAA,EAAS;IACjB,IAAI,IAAI,CAACC,KAAK,KAAKC,SAAS,EAAE;MAC1B,IAAI,CAACD,KAAK,GAAGE,WAAW,CAAC,MAAM;QAC3B,IAAI,CAACC,YAAY,EAAE;MACvB,CAAC,EAAE,IAAI,CAACT,QAAQ,CAAC;IACrB;EACJ;EAEOU,IAAIA,CAAA,EAAS;IAChB,IAAI,IAAI,CAACJ,KAAK,KAAKC,SAAS,EAAE;MAC1BI,aAAa,CAAC,IAAI,CAACL,KAAK,CAAC;MACzB,IAAI,CAACM,SAAS,CAACC,OAAO,CAAEC,CAAC,IAAKA,CAAC,CAACC,mBAAmB,EAAE,CAAC;IAC1D;EACJ;EAEOC,sBAAsBA,CAACC,MAAc,EAAW;IACnD,OAAO,IAAI,CAACL,SAAS,CAACM,GAAG,CAACD,MAAM,CAAC;EACrC;EAEOE,sBAAsBA,CAACF,MAAc,EAAElB,MAAc,EAAEqB,cAAiC,EAAW;IACtG,IAAI,IAAI,CAACJ,sBAAsB,CAACC,MAAM,CAAC,EAAE;MACrC,OAAO,KAAK;IAChB;IACA,IAAI,CAACL,SAAS,CAACS,GAAG,CAACJ,MAAM,EAAE,IAAIK,wCAAmB,CAACL,MAAM,EAAElB,MAAM,EAAEqB,cAAc,EAAE,IAAI,CAACG,OAAO,CAAC,CAAC;IACjG,OAAO,IAAI;EACf;EAEOC,yBAAyBA,CAACP,MAAc,EAAW;IACtD,OAAO,IAAI,CAACL,SAAS,CAACa,MAAM,CAACR,MAAM,CAAC;EACxC;EAEOS,sBAAsBA,CAACT,MAAc,EAAmC;IAC3E,OAAO,IAAI,CAACD,sBAAsB,CAACC,MAAM,CAAC,GAAG,IAAI,CAACL,SAAS,CAACe,GAAG,CAACV,MAAM,CAAC,GAAGV,SAAS;EACvF;EAEQE,YAAYA,CAAA,EAAS;IACzB,IAAI,CAACG,SAAS,CAACC,OAAO,CAAEC,CAAC,IAAKA,CAAC,CAACL,YAAY,CAAC,IAAI,CAACX,WAAW,EAAE,IAAI,CAACC,MAAM,CAAC,CAAC;EAChF;AACJ;AAAC6B,OAAA,CAAAhC,cAAA,GAAAA,cAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.d.ts new file mode 100644 index 0000000..0837c2b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.d.ts @@ -0,0 +1,10 @@ +export type Mid = string; +export type Ssrc = string; +export type MapType = "local" | "remote"; +export declare class MediaSsrcHandler { + private readonly ssrcToMid; + findMidBySsrc(ssrc: Ssrc, type: "local" | "remote"): Mid | undefined; + parse(description: string, type: MapType): void; + getSsrcToMidMap(type: MapType): Map<Mid, Ssrc[]>; +} +//# sourceMappingURL=mediaSsrcHandler.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.d.ts.map new file mode 100644 index 0000000..2021449 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaSsrcHandler.d.ts","sourceRoot":"","sources":["../../../../src/webrtc/stats/media/mediaSsrcHandler.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC;AACzB,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC;AAC1B,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzC,qBAAa,gBAAgB;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqE;IAExF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,SAAS;IAWpE,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAiB/C,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;CAG1D"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.js new file mode 100644 index 0000000..971c39d --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.js @@ -0,0 +1,65 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MediaSsrcHandler = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _sdpTransform = require("sdp-transform"); +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class MediaSsrcHandler { + constructor() { + (0, _defineProperty2.default)(this, "ssrcToMid", { + local: new Map(), + remote: new Map() + }); + } + findMidBySsrc(ssrc, type) { + let mid; + this.ssrcToMid[type].forEach((ssrcs, m) => { + if (ssrcs.find(s => s == ssrc)) { + mid = m; + return; + } + }); + return mid; + } + parse(description, type) { + const sdp = (0, _sdpTransform.parse)(description); + const ssrcToMid = new Map(); + sdp.media.forEach(m => { + if (!!m.mid && m.type === "video" || m.type === "audio") { + var _m$ssrcs; + const ssrcs = []; + (_m$ssrcs = m.ssrcs) === null || _m$ssrcs === void 0 ? void 0 : _m$ssrcs.forEach(ssrc => { + if (ssrc.attribute === "cname") { + ssrcs.push(`${ssrc.id}`); + } + }); + ssrcToMid.set(`${m.mid}`, ssrcs); + } + }); + this.ssrcToMid[type] = ssrcToMid; + } + getSsrcToMidMap(type) { + return this.ssrcToMid[type]; + } +} +exports.MediaSsrcHandler = MediaSsrcHandler; +//# sourceMappingURL=mediaSsrcHandler.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.js.map new file mode 100644 index 0000000..abaf728 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaSsrcHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaSsrcHandler.js","names":["_sdpTransform","require","MediaSsrcHandler","constructor","_defineProperty2","default","local","Map","remote","findMidBySsrc","ssrc","type","mid","ssrcToMid","forEach","ssrcs","m","find","s","parse","description","sdp","parseSdp","media","_m$ssrcs","attribute","push","id","set","getSsrcToMidMap","exports"],"sources":["../../../../src/webrtc/stats/media/mediaSsrcHandler.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { parse as parseSdp } from \"sdp-transform\";\n\nexport type Mid = string;\nexport type Ssrc = string;\nexport type MapType = \"local\" | \"remote\";\n\nexport class MediaSsrcHandler {\n private readonly ssrcToMid = { local: new Map<Mid, Ssrc[]>(), remote: new Map<Mid, Ssrc[]>() };\n\n public findMidBySsrc(ssrc: Ssrc, type: \"local\" | \"remote\"): Mid | undefined {\n let mid: Mid | undefined;\n this.ssrcToMid[type].forEach((ssrcs, m) => {\n if (ssrcs.find((s) => s == ssrc)) {\n mid = m;\n return;\n }\n });\n return mid;\n }\n\n public parse(description: string, type: MapType): void {\n const sdp = parseSdp(description);\n const ssrcToMid = new Map<Mid, Ssrc[]>();\n sdp.media.forEach((m) => {\n if ((!!m.mid && m.type === \"video\") || m.type === \"audio\") {\n const ssrcs: Ssrc[] = [];\n m.ssrcs?.forEach((ssrc) => {\n if (ssrc.attribute === \"cname\") {\n ssrcs.push(`${ssrc.id}`);\n }\n });\n ssrcToMid.set(`${m.mid}`, ssrcs);\n }\n });\n this.ssrcToMid[type] = ssrcToMid;\n }\n\n public getSsrcToMidMap(type: MapType): Map<Mid, Ssrc[]> {\n return this.ssrcToMid[type];\n }\n}\n"],"mappings":";;;;;;;;AAgBA,IAAAA,aAAA,GAAAC,OAAA;AAhBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQO,MAAMC,gBAAgB,CAAC;EAAAC,YAAA;IAAA,IAAAC,gBAAA,CAAAC,OAAA,qBACG;MAAEC,KAAK,EAAE,IAAIC,GAAG,EAAe;MAAEC,MAAM,EAAE,IAAID,GAAG;IAAgB,CAAC;EAAA;EAEvFE,aAAaA,CAACC,IAAU,EAAEC,IAAwB,EAAmB;IACxE,IAAIC,GAAoB;IACxB,IAAI,CAACC,SAAS,CAACF,IAAI,CAAC,CAACG,OAAO,CAAC,CAACC,KAAK,EAAEC,CAAC,KAAK;MACvC,IAAID,KAAK,CAACE,IAAI,CAAEC,CAAC,IAAKA,CAAC,IAAIR,IAAI,CAAC,EAAE;QAC9BE,GAAG,GAAGI,CAAC;QACP;MACJ;IACJ,CAAC,CAAC;IACF,OAAOJ,GAAG;EACd;EAEOO,KAAKA,CAACC,WAAmB,EAAET,IAAa,EAAQ;IACnD,MAAMU,GAAG,GAAG,IAAAC,mBAAQ,EAACF,WAAW,CAAC;IACjC,MAAMP,SAAS,GAAG,IAAIN,GAAG,EAAe;IACxCc,GAAG,CAACE,KAAK,CAACT,OAAO,CAAEE,CAAC,IAAK;MACrB,IAAK,CAAC,CAACA,CAAC,CAACJ,GAAG,IAAII,CAAC,CAACL,IAAI,KAAK,OAAO,IAAKK,CAAC,CAACL,IAAI,KAAK,OAAO,EAAE;QAAA,IAAAa,QAAA;QACvD,MAAMT,KAAa,GAAG,EAAE;QACxB,CAAAS,QAAA,GAAAR,CAAC,CAACD,KAAK,cAAAS,QAAA,uBAAPA,QAAA,CAASV,OAAO,CAAEJ,IAAI,IAAK;UACvB,IAAIA,IAAI,CAACe,SAAS,KAAK,OAAO,EAAE;YAC5BV,KAAK,CAACW,IAAI,CAAE,GAAEhB,IAAI,CAACiB,EAAG,EAAC,CAAC;UAC5B;QACJ,CAAC,CAAC;QACFd,SAAS,CAACe,GAAG,CAAE,GAAEZ,CAAC,CAACJ,GAAI,EAAC,EAAEG,KAAK,CAAC;MACpC;IACJ,CAAC,CAAC;IACF,IAAI,CAACF,SAAS,CAACF,IAAI,CAAC,GAAGE,SAAS;EACpC;EAEOgB,eAAeA,CAAClB,IAAa,EAAoB;IACpD,OAAO,IAAI,CAACE,SAAS,CAACF,IAAI,CAAC;EAC/B;AACJ;AAACmB,OAAA,CAAA5B,gBAAA,GAAAA,gBAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.d.ts new file mode 100644 index 0000000..e03ae98 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.d.ts @@ -0,0 +1,11 @@ +export type TrackId = string; +export declare class MediaTrackHandler { + private readonly pc; + constructor(pc: RTCPeerConnection); + getLocalTracks(kind: "audio" | "video"): MediaStreamTrack[]; + getTackById(trackId: string): MediaStreamTrack | undefined; + getLocalTrackIdByMid(mid: string): string | undefined; + getRemoteTrackIdByMid(mid: string): string | undefined; + getActiveSimulcastStreams(): number; +} +//# sourceMappingURL=mediaTrackHandler.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.d.ts.map new file mode 100644 index 0000000..c107f8b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaTrackHandler.d.ts","sourceRoot":"","sources":["../../../../src/webrtc/stats/media/mediaTrackHandler.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,qBAAa,iBAAiB;IACP,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,iBAAiB;IAElD,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,gBAAgB,EAAE;IAc3D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAe1D,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQrD,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQtD,yBAAyB,IAAI,MAAM;CAI7C"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.js new file mode 100644 index 0000000..f74599f --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.js @@ -0,0 +1,65 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MediaTrackHandler = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class MediaTrackHandler { + constructor(pc) { + this.pc = pc; + } + getLocalTracks(kind) { + const isNotNullAndKind = track => { + return track !== null && track.kind === kind; + }; + // @ts-ignore The linter don't get it + return this.pc.getTransceivers().filter(t => t.currentDirection === "sendonly" || t.currentDirection === "sendrecv").filter(t => t.sender !== null).map(t => t.sender).map(s => s.track).filter(isNotNullAndKind); + } + getTackById(trackId) { + return this.pc.getTransceivers().map(t => { + if ((t === null || t === void 0 ? void 0 : t.sender.track) !== null && t.sender.track.id === trackId) { + return t.sender.track; + } + if ((t === null || t === void 0 ? void 0 : t.receiver.track) !== null && t.receiver.track.id === trackId) { + return t.receiver.track; + } + return undefined; + }).find(t => t !== undefined); + } + getLocalTrackIdByMid(mid) { + const transceiver = this.pc.getTransceivers().find(t => t.mid === mid); + if (transceiver !== undefined && !!transceiver.sender && !!transceiver.sender.track) { + return transceiver.sender.track.id; + } + return undefined; + } + getRemoteTrackIdByMid(mid) { + const transceiver = this.pc.getTransceivers().find(t => t.mid === mid); + if (transceiver !== undefined && !!transceiver.receiver && !!transceiver.receiver.track) { + return transceiver.receiver.track.id; + } + return undefined; + } + getActiveSimulcastStreams() { + //@TODO implement this right.. Check how many layer configured + return 3; + } +} +exports.MediaTrackHandler = MediaTrackHandler; +//# sourceMappingURL=mediaTrackHandler.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.js.map new file mode 100644 index 0000000..7600899 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaTrackHandler.js","names":["MediaTrackHandler","constructor","pc","getLocalTracks","kind","isNotNullAndKind","track","getTransceivers","filter","t","currentDirection","sender","map","s","getTackById","trackId","id","receiver","undefined","find","getLocalTrackIdByMid","mid","transceiver","getRemoteTrackIdByMid","getActiveSimulcastStreams","exports"],"sources":["../../../../src/webrtc/stats/media/mediaTrackHandler.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport type TrackId = string;\n\nexport class MediaTrackHandler {\n public constructor(private readonly pc: RTCPeerConnection) {}\n\n public getLocalTracks(kind: \"audio\" | \"video\"): MediaStreamTrack[] {\n const isNotNullAndKind = (track: MediaStreamTrack | null): boolean => {\n return track !== null && track.kind === kind;\n };\n // @ts-ignore The linter don't get it\n return this.pc\n .getTransceivers()\n .filter((t) => t.currentDirection === \"sendonly\" || t.currentDirection === \"sendrecv\")\n .filter((t) => t.sender !== null)\n .map((t) => t.sender)\n .map((s) => s.track)\n .filter(isNotNullAndKind);\n }\n\n public getTackById(trackId: string): MediaStreamTrack | undefined {\n return this.pc\n .getTransceivers()\n .map((t) => {\n if (t?.sender.track !== null && t.sender.track.id === trackId) {\n return t.sender.track;\n }\n if (t?.receiver.track !== null && t.receiver.track.id === trackId) {\n return t.receiver.track;\n }\n return undefined;\n })\n .find((t) => t !== undefined);\n }\n\n public getLocalTrackIdByMid(mid: string): string | undefined {\n const transceiver = this.pc.getTransceivers().find((t) => t.mid === mid);\n if (transceiver !== undefined && !!transceiver.sender && !!transceiver.sender.track) {\n return transceiver.sender.track.id;\n }\n return undefined;\n }\n\n public getRemoteTrackIdByMid(mid: string): string | undefined {\n const transceiver = this.pc.getTransceivers().find((t) => t.mid === mid);\n if (transceiver !== undefined && !!transceiver.receiver && !!transceiver.receiver.track) {\n return transceiver.receiver.track.id;\n }\n return undefined;\n }\n\n public getActiveSimulcastStreams(): number {\n //@TODO implement this right.. Check how many layer configured\n return 3;\n }\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIO,MAAMA,iBAAiB,CAAC;EACpBC,WAAWA,CAAkBC,EAAqB,EAAE;IAAA,KAAvBA,EAAqB,GAArBA,EAAqB;EAAG;EAErDC,cAAcA,CAACC,IAAuB,EAAsB;IAC/D,MAAMC,gBAAgB,GAAIC,KAA8B,IAAc;MAClE,OAAOA,KAAK,KAAK,IAAI,IAAIA,KAAK,CAACF,IAAI,KAAKA,IAAI;IAChD,CAAC;IACD;IACA,OAAO,IAAI,CAACF,EAAE,CACTK,eAAe,EAAE,CACjBC,MAAM,CAAEC,CAAC,IAAKA,CAAC,CAACC,gBAAgB,KAAK,UAAU,IAAID,CAAC,CAACC,gBAAgB,KAAK,UAAU,CAAC,CACrFF,MAAM,CAAEC,CAAC,IAAKA,CAAC,CAACE,MAAM,KAAK,IAAI,CAAC,CAChCC,GAAG,CAAEH,CAAC,IAAKA,CAAC,CAACE,MAAM,CAAC,CACpBC,GAAG,CAAEC,CAAC,IAAKA,CAAC,CAACP,KAAK,CAAC,CACnBE,MAAM,CAACH,gBAAgB,CAAC;EACjC;EAEOS,WAAWA,CAACC,OAAe,EAAgC;IAC9D,OAAO,IAAI,CAACb,EAAE,CACTK,eAAe,EAAE,CACjBK,GAAG,CAAEH,CAAC,IAAK;MACR,IAAI,CAAAA,CAAC,aAADA,CAAC,uBAADA,CAAC,CAAEE,MAAM,CAACL,KAAK,MAAK,IAAI,IAAIG,CAAC,CAACE,MAAM,CAACL,KAAK,CAACU,EAAE,KAAKD,OAAO,EAAE;QAC3D,OAAON,CAAC,CAACE,MAAM,CAACL,KAAK;MACzB;MACA,IAAI,CAAAG,CAAC,aAADA,CAAC,uBAADA,CAAC,CAAEQ,QAAQ,CAACX,KAAK,MAAK,IAAI,IAAIG,CAAC,CAACQ,QAAQ,CAACX,KAAK,CAACU,EAAE,KAAKD,OAAO,EAAE;QAC/D,OAAON,CAAC,CAACQ,QAAQ,CAACX,KAAK;MAC3B;MACA,OAAOY,SAAS;IACpB,CAAC,CAAC,CACDC,IAAI,CAAEV,CAAC,IAAKA,CAAC,KAAKS,SAAS,CAAC;EACrC;EAEOE,oBAAoBA,CAACC,GAAW,EAAsB;IACzD,MAAMC,WAAW,GAAG,IAAI,CAACpB,EAAE,CAACK,eAAe,EAAE,CAACY,IAAI,CAAEV,CAAC,IAAKA,CAAC,CAACY,GAAG,KAAKA,GAAG,CAAC;IACxE,IAAIC,WAAW,KAAKJ,SAAS,IAAI,CAAC,CAACI,WAAW,CAACX,MAAM,IAAI,CAAC,CAACW,WAAW,CAACX,MAAM,CAACL,KAAK,EAAE;MACjF,OAAOgB,WAAW,CAACX,MAAM,CAACL,KAAK,CAACU,EAAE;IACtC;IACA,OAAOE,SAAS;EACpB;EAEOK,qBAAqBA,CAACF,GAAW,EAAsB;IAC1D,MAAMC,WAAW,GAAG,IAAI,CAACpB,EAAE,CAACK,eAAe,EAAE,CAACY,IAAI,CAAEV,CAAC,IAAKA,CAAC,CAACY,GAAG,KAAKA,GAAG,CAAC;IACxE,IAAIC,WAAW,KAAKJ,SAAS,IAAI,CAAC,CAACI,WAAW,CAACL,QAAQ,IAAI,CAAC,CAACK,WAAW,CAACL,QAAQ,CAACX,KAAK,EAAE;MACrF,OAAOgB,WAAW,CAACL,QAAQ,CAACX,KAAK,CAACU,EAAE;IACxC;IACA,OAAOE,SAAS;EACpB;EAEOM,yBAAyBA,CAAA,EAAW;IACvC;IACA,OAAO,CAAC;EACZ;AACJ;AAACC,OAAA,CAAAzB,iBAAA,GAAAA,iBAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.d.ts new file mode 100644 index 0000000..0f11ce8 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.d.ts @@ -0,0 +1,45 @@ +import { TrackId } from "./mediaTrackHandler"; +export interface PacketLoss { + packetsTotal: number; + packetsLost: number; + isDownloadStream: boolean; +} +export interface Bitrate { + /** + * bytes per second + */ + download: number; + /** + * bytes per second + */ + upload: number; +} +export interface Resolution { + width: number; + height: number; +} +export type TrackStatsType = "local" | "remote"; +export declare class MediaTrackStats { + readonly trackId: TrackId; + readonly type: TrackStatsType; + readonly kind: "audio" | "video"; + private loss; + private bitrate; + private resolution; + private framerate; + private codec; + constructor(trackId: TrackId, type: TrackStatsType, kind: "audio" | "video"); + getType(): TrackStatsType; + setLoss(loos: PacketLoss): void; + getLoss(): PacketLoss; + setResolution(resolution: Resolution): void; + getResolution(): Resolution; + setFramerate(framerate: number): void; + getFramerate(): number; + setBitrate(bitrate: Bitrate): void; + getBitrate(): Bitrate; + setCodec(codecShortType: string): boolean; + getCodec(): string; + resetBitrate(): void; +} +//# sourceMappingURL=mediaTrackStats.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.d.ts.map new file mode 100644 index 0000000..9ef892c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaTrackStats.d.ts","sourceRoot":"","sources":["../../../../src/webrtc/stats/media/mediaTrackStats.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE9C,MAAM,WAAW,UAAU;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACpB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhD,qBAAa,eAAe;aAQJ,OAAO,EAAE,OAAO;aAChB,IAAI,EAAE,cAAc;aACpB,IAAI,EAAE,OAAO,GAAG,OAAO;IAT3C,OAAO,CAAC,IAAI,CAA4E;IACxF,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,UAAU,CAAyC;IAC3D,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,KAAK,CAAM;gBAGC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,OAAO,GAAG,OAAO;IAGpC,OAAO,IAAI,cAAc;IAIzB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAI/B,OAAO,IAAI,UAAU;IAIrB,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAI3C,aAAa,IAAI,UAAU;IAI3B,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrC,YAAY,IAAI,MAAM;IAItB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIlC,UAAU,IAAI,OAAO;IAIrB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAKzC,QAAQ,IAAI,MAAM;IAIlB,YAAY,IAAI,IAAI;CAG9B"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.js new file mode 100644 index 0000000..581ea8a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.js @@ -0,0 +1,88 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MediaTrackStats = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class MediaTrackStats { + constructor(trackId, type, kind) { + this.trackId = trackId; + this.type = type; + this.kind = kind; + (0, _defineProperty2.default)(this, "loss", { + packetsTotal: 0, + packetsLost: 0, + isDownloadStream: false + }); + (0, _defineProperty2.default)(this, "bitrate", { + download: 0, + upload: 0 + }); + (0, _defineProperty2.default)(this, "resolution", { + width: -1, + height: -1 + }); + (0, _defineProperty2.default)(this, "framerate", 0); + (0, _defineProperty2.default)(this, "codec", ""); + } + getType() { + return this.type; + } + setLoss(loos) { + this.loss = loos; + } + getLoss() { + return this.loss; + } + setResolution(resolution) { + this.resolution = resolution; + } + getResolution() { + return this.resolution; + } + setFramerate(framerate) { + this.framerate = framerate; + } + getFramerate() { + return this.framerate; + } + setBitrate(bitrate) { + this.bitrate = bitrate; + } + getBitrate() { + return this.bitrate; + } + setCodec(codecShortType) { + this.codec = codecShortType; + return true; + } + getCodec() { + return this.codec; + } + resetBitrate() { + this.bitrate = { + download: 0, + upload: 0 + }; + } +} +exports.MediaTrackStats = MediaTrackStats; +//# sourceMappingURL=mediaTrackStats.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.js.map new file mode 100644 index 0000000..1552f24 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStats.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaTrackStats.js","names":["MediaTrackStats","constructor","trackId","type","kind","_defineProperty2","default","packetsTotal","packetsLost","isDownloadStream","download","upload","width","height","getType","setLoss","loos","loss","getLoss","setResolution","resolution","getResolution","setFramerate","framerate","getFramerate","setBitrate","bitrate","getBitrate","setCodec","codecShortType","codec","getCodec","resetBitrate","exports"],"sources":["../../../../src/webrtc/stats/media/mediaTrackStats.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { TrackId } from \"./mediaTrackHandler\";\n\nexport interface PacketLoss {\n packetsTotal: number;\n packetsLost: number;\n isDownloadStream: boolean;\n}\n\nexport interface Bitrate {\n /**\n * bytes per second\n */\n download: number;\n /**\n * bytes per second\n */\n upload: number;\n}\n\nexport interface Resolution {\n width: number;\n height: number;\n}\n\nexport type TrackStatsType = \"local\" | \"remote\";\n\nexport class MediaTrackStats {\n private loss: PacketLoss = { packetsTotal: 0, packetsLost: 0, isDownloadStream: false };\n private bitrate: Bitrate = { download: 0, upload: 0 };\n private resolution: Resolution = { width: -1, height: -1 };\n private framerate = 0;\n private codec = \"\";\n\n public constructor(\n public readonly trackId: TrackId,\n public readonly type: TrackStatsType,\n public readonly kind: \"audio\" | \"video\",\n ) {}\n\n public getType(): TrackStatsType {\n return this.type;\n }\n\n public setLoss(loos: PacketLoss): void {\n this.loss = loos;\n }\n\n public getLoss(): PacketLoss {\n return this.loss;\n }\n\n public setResolution(resolution: Resolution): void {\n this.resolution = resolution;\n }\n\n public getResolution(): Resolution {\n return this.resolution;\n }\n\n public setFramerate(framerate: number): void {\n this.framerate = framerate;\n }\n\n public getFramerate(): number {\n return this.framerate;\n }\n\n public setBitrate(bitrate: Bitrate): void {\n this.bitrate = bitrate;\n }\n\n public getBitrate(): Bitrate {\n return this.bitrate;\n }\n\n public setCodec(codecShortType: string): boolean {\n this.codec = codecShortType;\n return true;\n }\n\n public getCodec(): string {\n return this.codec;\n }\n\n public resetBitrate(): void {\n this.bitrate = { download: 0, upload: 0 };\n }\n}\n"],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA4BO,MAAMA,eAAe,CAAC;EAOlBC,WAAWA,CACEC,OAAgB,EAChBC,IAAoB,EACpBC,IAAuB,EACzC;IAAA,KAHkBF,OAAgB,GAAhBA,OAAgB;IAAA,KAChBC,IAAoB,GAApBA,IAAoB;IAAA,KACpBC,IAAuB,GAAvBA,IAAuB;IAAA,IAAAC,gBAAA,CAAAC,OAAA,gBAThB;MAAEC,YAAY,EAAE,CAAC;MAAEC,WAAW,EAAE,CAAC;MAAEC,gBAAgB,EAAE;IAAM,CAAC;IAAA,IAAAJ,gBAAA,CAAAC,OAAA,mBAC5D;MAAEI,QAAQ,EAAE,CAAC;MAAEC,MAAM,EAAE;IAAE,CAAC;IAAA,IAAAN,gBAAA,CAAAC,OAAA,sBACpB;MAAEM,KAAK,EAAE,CAAC,CAAC;MAAEC,MAAM,EAAE,CAAC;IAAE,CAAC;IAAA,IAAAR,gBAAA,CAAAC,OAAA,qBACtC,CAAC;IAAA,IAAAD,gBAAA,CAAAC,OAAA,iBACL,EAAE;EAMf;EAEIQ,OAAOA,CAAA,EAAmB;IAC7B,OAAO,IAAI,CAACX,IAAI;EACpB;EAEOY,OAAOA,CAACC,IAAgB,EAAQ;IACnC,IAAI,CAACC,IAAI,GAAGD,IAAI;EACpB;EAEOE,OAAOA,CAAA,EAAe;IACzB,OAAO,IAAI,CAACD,IAAI;EACpB;EAEOE,aAAaA,CAACC,UAAsB,EAAQ;IAC/C,IAAI,CAACA,UAAU,GAAGA,UAAU;EAChC;EAEOC,aAAaA,CAAA,EAAe;IAC/B,OAAO,IAAI,CAACD,UAAU;EAC1B;EAEOE,YAAYA,CAACC,SAAiB,EAAQ;IACzC,IAAI,CAACA,SAAS,GAAGA,SAAS;EAC9B;EAEOC,YAAYA,CAAA,EAAW;IAC1B,OAAO,IAAI,CAACD,SAAS;EACzB;EAEOE,UAAUA,CAACC,OAAgB,EAAQ;IACtC,IAAI,CAACA,OAAO,GAAGA,OAAO;EAC1B;EAEOC,UAAUA,CAAA,EAAY;IACzB,OAAO,IAAI,CAACD,OAAO;EACvB;EAEOE,QAAQA,CAACC,cAAsB,EAAW;IAC7C,IAAI,CAACC,KAAK,GAAGD,cAAc;IAC3B,OAAO,IAAI;EACf;EAEOE,QAAQA,CAAA,EAAW;IACtB,OAAO,IAAI,CAACD,KAAK;EACrB;EAEOE,YAAYA,CAAA,EAAS;IACxB,IAAI,CAACN,OAAO,GAAG;MAAEhB,QAAQ,EAAE,CAAC;MAAEC,MAAM,EAAE;IAAE,CAAC;EAC7C;AACJ;AAACsB,OAAA,CAAAjC,eAAA,GAAAA,eAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.d.ts new file mode 100644 index 0000000..15abb2b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.d.ts @@ -0,0 +1,21 @@ +import { TrackID } from "../statsReport"; +import { MediaTrackStats } from "./mediaTrackStats"; +import { MediaTrackHandler } from "./mediaTrackHandler"; +import { MediaSsrcHandler } from "./mediaSsrcHandler"; +export declare class MediaTrackStatsHandler { + readonly mediaSsrcHandler: MediaSsrcHandler; + readonly mediaTrackHandler: MediaTrackHandler; + private readonly track2stats; + constructor(mediaSsrcHandler: MediaSsrcHandler, mediaTrackHandler: MediaTrackHandler); + /** + * Find tracks by rtc stats + * Argument report is any because the stats api is not consistent: + * For example `trackIdentifier`, `mid` not existing in every implementations + * https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats + * https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats + */ + findTrack2Stats(report: any, type: "remote" | "local"): MediaTrackStats | undefined; + findLocalVideoTrackStats(report: any): MediaTrackStats | undefined; + getTrack2stats(): Map<TrackID, MediaTrackStats>; +} +//# sourceMappingURL=mediaTrackStatsHandler.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.d.ts.map new file mode 100644 index 0000000..80ac0a0 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaTrackStatsHandler.d.ts","sourceRoot":"","sources":["../../../../src/webrtc/stats/media/mediaTrackStatsHandler.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,qBAAa,sBAAsB;aAIX,gBAAgB,EAAE,gBAAgB;aAClC,iBAAiB,EAAE,iBAAiB;IAJxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuC;gBAG/C,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,iBAAiB;IAGxD;;;;;;OAMG;IACI,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS;IAuCnF,wBAAwB,CAAC,MAAM,EAAE,GAAG,GAAG,eAAe,GAAG,SAAS;IAQlE,cAAc,IAAI,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC;CAGzD"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.js new file mode 100644 index 0000000..6f42939 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.js @@ -0,0 +1,81 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MediaTrackStatsHandler = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _mediaTrackStats = require("./mediaTrackStats"); +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class MediaTrackStatsHandler { + constructor(mediaSsrcHandler, mediaTrackHandler) { + this.mediaSsrcHandler = mediaSsrcHandler; + this.mediaTrackHandler = mediaTrackHandler; + (0, _defineProperty2.default)(this, "track2stats", new Map()); + } + + /** + * Find tracks by rtc stats + * Argument report is any because the stats api is not consistent: + * For example `trackIdentifier`, `mid` not existing in every implementations + * https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats + * https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats + */ + findTrack2Stats(report, type) { + let trackID; + if (report.trackIdentifier) { + trackID = report.trackIdentifier; + } else if (report.mid) { + trackID = type === "remote" ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid) : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid); + } else if (report.ssrc) { + const mid = this.mediaSsrcHandler.findMidBySsrc(report.ssrc, type); + if (!mid) { + return undefined; + } + trackID = type === "remote" ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid) : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid); + } + if (!trackID) { + return undefined; + } + let trackStats = this.track2stats.get(trackID); + if (!trackStats) { + const track = this.mediaTrackHandler.getTackById(trackID); + if (track !== undefined) { + const kind = track.kind === "audio" ? track.kind : "video"; + trackStats = new _mediaTrackStats.MediaTrackStats(trackID, type, kind); + this.track2stats.set(trackID, trackStats); + } else { + return undefined; + } + } + return trackStats; + } + findLocalVideoTrackStats(report) { + const localVideoTracks = this.mediaTrackHandler.getLocalTracks("video"); + if (localVideoTracks.length === 0) { + return undefined; + } + return this.findTrack2Stats(report, "local"); + } + getTrack2stats() { + return this.track2stats; + } +} +exports.MediaTrackStatsHandler = MediaTrackStatsHandler; +//# sourceMappingURL=mediaTrackStatsHandler.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.js.map new file mode 100644 index 0000000..8140116 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/media/mediaTrackStatsHandler.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mediaTrackStatsHandler.js","names":["_mediaTrackStats","require","MediaTrackStatsHandler","constructor","mediaSsrcHandler","mediaTrackHandler","_defineProperty2","default","Map","findTrack2Stats","report","type","trackID","trackIdentifier","mid","getRemoteTrackIdByMid","getLocalTrackIdByMid","ssrc","findMidBySsrc","undefined","trackStats","track2stats","get","track","getTackById","kind","MediaTrackStats","set","findLocalVideoTrackStats","localVideoTracks","getLocalTracks","length","getTrack2stats","exports"],"sources":["../../../../src/webrtc/stats/media/mediaTrackStatsHandler.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\nimport { TrackID } from \"../statsReport\";\nimport { MediaTrackStats } from \"./mediaTrackStats\";\nimport { MediaTrackHandler } from \"./mediaTrackHandler\";\nimport { MediaSsrcHandler } from \"./mediaSsrcHandler\";\n\nexport class MediaTrackStatsHandler {\n private readonly track2stats = new Map<TrackID, MediaTrackStats>();\n\n public constructor(\n public readonly mediaSsrcHandler: MediaSsrcHandler,\n public readonly mediaTrackHandler: MediaTrackHandler,\n ) {}\n\n /**\n * Find tracks by rtc stats\n * Argument report is any because the stats api is not consistent:\n * For example `trackIdentifier`, `mid` not existing in every implementations\n * https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats\n * https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats\n */\n public findTrack2Stats(report: any, type: \"remote\" | \"local\"): MediaTrackStats | undefined {\n let trackID;\n if (report.trackIdentifier) {\n trackID = report.trackIdentifier;\n } else if (report.mid) {\n trackID =\n type === \"remote\"\n ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid)\n : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid);\n } else if (report.ssrc) {\n const mid = this.mediaSsrcHandler.findMidBySsrc(report.ssrc, type);\n if (!mid) {\n return undefined;\n }\n trackID =\n type === \"remote\"\n ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid)\n : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid);\n }\n\n if (!trackID) {\n return undefined;\n }\n\n let trackStats = this.track2stats.get(trackID);\n\n if (!trackStats) {\n const track = this.mediaTrackHandler.getTackById(trackID);\n if (track !== undefined) {\n const kind: \"audio\" | \"video\" = track.kind === \"audio\" ? track.kind : \"video\";\n trackStats = new MediaTrackStats(trackID, type, kind);\n this.track2stats.set(trackID, trackStats);\n } else {\n return undefined;\n }\n }\n return trackStats;\n }\n\n public findLocalVideoTrackStats(report: any): MediaTrackStats | undefined {\n const localVideoTracks = this.mediaTrackHandler.getLocalTracks(\"video\");\n if (localVideoTracks.length === 0) {\n return undefined;\n }\n return this.findTrack2Stats(report, \"local\");\n }\n\n public getTrack2stats(): Map<TrackID, MediaTrackStats> {\n return this.track2stats;\n }\n}\n"],"mappings":";;;;;;;;AAgBA,IAAAA,gBAAA,GAAAC,OAAA;AAhBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMO,MAAMC,sBAAsB,CAAC;EAGzBC,WAAWA,CACEC,gBAAkC,EAClCC,iBAAoC,EACtD;IAAA,KAFkBD,gBAAkC,GAAlCA,gBAAkC;IAAA,KAClCC,iBAAoC,GAApCA,iBAAoC;IAAA,IAAAC,gBAAA,CAAAC,OAAA,uBAJzB,IAAIC,GAAG,EAA4B;EAK/D;;EAEH;AACJ;AACA;AACA;AACA;AACA;AACA;EACWC,eAAeA,CAACC,MAAW,EAAEC,IAAwB,EAA+B;IACvF,IAAIC,OAAO;IACX,IAAIF,MAAM,CAACG,eAAe,EAAE;MACxBD,OAAO,GAAGF,MAAM,CAACG,eAAe;IACpC,CAAC,MAAM,IAAIH,MAAM,CAACI,GAAG,EAAE;MACnBF,OAAO,GACHD,IAAI,KAAK,QAAQ,GACX,IAAI,CAACN,iBAAiB,CAACU,qBAAqB,CAACL,MAAM,CAACI,GAAG,CAAC,GACxD,IAAI,CAACT,iBAAiB,CAACW,oBAAoB,CAACN,MAAM,CAACI,GAAG,CAAC;IACrE,CAAC,MAAM,IAAIJ,MAAM,CAACO,IAAI,EAAE;MACpB,MAAMH,GAAG,GAAG,IAAI,CAACV,gBAAgB,CAACc,aAAa,CAACR,MAAM,CAACO,IAAI,EAAEN,IAAI,CAAC;MAClE,IAAI,CAACG,GAAG,EAAE;QACN,OAAOK,SAAS;MACpB;MACAP,OAAO,GACHD,IAAI,KAAK,QAAQ,GACX,IAAI,CAACN,iBAAiB,CAACU,qBAAqB,CAACL,MAAM,CAACI,GAAG,CAAC,GACxD,IAAI,CAACT,iBAAiB,CAACW,oBAAoB,CAACN,MAAM,CAACI,GAAG,CAAC;IACrE;IAEA,IAAI,CAACF,OAAO,EAAE;MACV,OAAOO,SAAS;IACpB;IAEA,IAAIC,UAAU,GAAG,IAAI,CAACC,WAAW,CAACC,GAAG,CAACV,OAAO,CAAC;IAE9C,IAAI,CAACQ,UAAU,EAAE;MACb,MAAMG,KAAK,GAAG,IAAI,CAAClB,iBAAiB,CAACmB,WAAW,CAACZ,OAAO,CAAC;MACzD,IAAIW,KAAK,KAAKJ,SAAS,EAAE;QACrB,MAAMM,IAAuB,GAAGF,KAAK,CAACE,IAAI,KAAK,OAAO,GAAGF,KAAK,CAACE,IAAI,GAAG,OAAO;QAC7EL,UAAU,GAAG,IAAIM,gCAAe,CAACd,OAAO,EAAED,IAAI,EAAEc,IAAI,CAAC;QACrD,IAAI,CAACJ,WAAW,CAACM,GAAG,CAACf,OAAO,EAAEQ,UAAU,CAAC;MAC7C,CAAC,MAAM;QACH,OAAOD,SAAS;MACpB;IACJ;IACA,OAAOC,UAAU;EACrB;EAEOQ,wBAAwBA,CAAClB,MAAW,EAA+B;IACtE,MAAMmB,gBAAgB,GAAG,IAAI,CAACxB,iBAAiB,CAACyB,cAAc,CAAC,OAAO,CAAC;IACvE,IAAID,gBAAgB,CAACE,MAAM,KAAK,CAAC,EAAE;MAC/B,OAAOZ,SAAS;IACpB;IACA,OAAO,IAAI,CAACV,eAAe,CAACC,MAAM,EAAE,OAAO,CAAC;EAChD;EAEOsB,cAAcA,CAAA,EAAkC;IACnD,OAAO,IAAI,CAACX,WAAW;EAC3B;AACJ;AAACY,OAAA,CAAA/B,sBAAA,GAAAA,sBAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.d.ts new file mode 100644 index 0000000..cedabd1 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.d.ts @@ -0,0 +1,33 @@ +import { ConnectionStatsBandwidth, ConnectionStatsBitrate, PacketLoos } from "./connectionStats"; +import { TransportStats } from "./transportStats"; +import { Resolution } from "./media/mediaTrackStats"; +export declare enum StatsReport { + CONNECTION_STATS = "StatsReport.connection_stats", + BYTE_SENT_STATS = "StatsReport.byte_sent_stats" +} +export type TrackID = string; +export type ByteSend = number; +export interface ByteSentStatsReport extends Map<TrackID, ByteSend> { +} +export interface ConnectionStatsReport { + bandwidth: ConnectionStatsBandwidth; + bitrate: ConnectionStatsBitrate; + packetLoss: PacketLoos; + resolution: ResolutionMap; + framerate: FramerateMap; + codec: CodecMap; + transport: TransportStats[]; +} +export interface ResolutionMap { + local: Map<TrackID, Resolution>; + remote: Map<TrackID, Resolution>; +} +export interface FramerateMap { + local: Map<TrackID, number>; + remote: Map<TrackID, number>; +} +export interface CodecMap { + local: Map<TrackID, string>; + remote: Map<TrackID, string>; +} +//# sourceMappingURL=statsReport.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.d.ts.map new file mode 100644 index 0000000..2be9d9b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReport.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/statsReport.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,oBAAY,WAAW;IACnB,gBAAgB,iCAAiC;IACjD,eAAe,gCAAgC;CAClD;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,MAAM,WAAW,mBAAoB,SAAQ,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC;CAElE;AAED,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,wBAAwB,CAAC;IACpC,OAAO,EAAE,sBAAsB,CAAC;IAChC,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,aAAa,CAAC;IAC1B,SAAS,EAAE,YAAY,CAAC;IACxB,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAChC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,QAAQ;IACrB,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.js new file mode 100644 index 0000000..0b2cd05 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.js @@ -0,0 +1,28 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.StatsReport = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +let StatsReport; +exports.StatsReport = StatsReport; +(function (StatsReport) { + StatsReport["CONNECTION_STATS"] = "StatsReport.connection_stats"; + StatsReport["BYTE_SENT_STATS"] = "StatsReport.byte_sent_stats"; +})(StatsReport || (exports.StatsReport = StatsReport = {})); +//# sourceMappingURL=statsReport.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.js.map new file mode 100644 index 0000000..731606b --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReport.js.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReport.js","names":["StatsReport","exports"],"sources":["../../../src/webrtc/stats/statsReport.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { ConnectionStatsBandwidth, ConnectionStatsBitrate, PacketLoos } from \"./connectionStats\";\nimport { TransportStats } from \"./transportStats\";\nimport { Resolution } from \"./media/mediaTrackStats\";\n\nexport enum StatsReport {\n CONNECTION_STATS = \"StatsReport.connection_stats\",\n BYTE_SENT_STATS = \"StatsReport.byte_sent_stats\",\n}\n\nexport type TrackID = string;\nexport type ByteSend = number;\n\nexport interface ByteSentStatsReport extends Map<TrackID, ByteSend> {\n // is a map: `local trackID` => byte send\n}\n\nexport interface ConnectionStatsReport {\n bandwidth: ConnectionStatsBandwidth;\n bitrate: ConnectionStatsBitrate;\n packetLoss: PacketLoos;\n resolution: ResolutionMap;\n framerate: FramerateMap;\n codec: CodecMap;\n transport: TransportStats[];\n}\n\nexport interface ResolutionMap {\n local: Map<TrackID, Resolution>;\n remote: Map<TrackID, Resolution>;\n}\n\nexport interface FramerateMap {\n local: Map<TrackID, number>;\n remote: Map<TrackID, number>;\n}\n\nexport interface CodecMap {\n local: Map<TrackID, string>;\n remote: Map<TrackID, string>;\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAdA,IAoBYA,WAAW;AAAAC,OAAA,CAAAD,WAAA,GAAAA,WAAA;AAAA,WAAXA,WAAW;EAAXA,WAAW;EAAXA,WAAW;AAAA,GAAXA,WAAW,KAAAC,OAAA,CAAAD,WAAA,GAAXA,WAAW"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.d.ts new file mode 100644 index 0000000..31de157 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.d.ts @@ -0,0 +1,7 @@ +import { ConnectionStatsReport, TrackID } from "./statsReport"; +import { MediaTrackStats } from "./media/mediaTrackStats"; +export declare class StatsReportBuilder { + static build(stats: Map<TrackID, MediaTrackStats>): ConnectionStatsReport; + private static calculatePacketLoss; +} +//# sourceMappingURL=statsReportBuilder.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.d.ts.map new file mode 100644 index 0000000..2446b14 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReportBuilder.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/statsReportBuilder.ts"],"names":[],"mappings":"AAeA,OAAO,EAAY,qBAAqB,EAA+B,OAAO,EAAE,MAAM,eAAe,CAAC;AACtG,OAAO,EAAE,eAAe,EAAc,MAAM,yBAAyB,CAAC;AAEtE,qBAAa,kBAAkB;WACb,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,qBAAqB;IAmFhF,OAAO,CAAC,MAAM,CAAC,mBAAmB;CAOrC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.js new file mode 100644 index 0000000..edd7094 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.js @@ -0,0 +1,108 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.StatsReportBuilder = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class StatsReportBuilder { + static build(stats) { + const report = {}; + + // process stats + const totalPackets = { + download: 0, + upload: 0 + }; + const lostPackets = { + download: 0, + upload: 0 + }; + let bitrateDownload = 0; + let bitrateUpload = 0; + const resolutions = { + local: new Map(), + remote: new Map() + }; + const framerates = { + local: new Map(), + remote: new Map() + }; + const codecs = { + local: new Map(), + remote: new Map() + }; + let audioBitrateDownload = 0; + let audioBitrateUpload = 0; + let videoBitrateDownload = 0; + let videoBitrateUpload = 0; + for (const [trackId, trackStats] of stats) { + // process packet loss stats + const loss = trackStats.getLoss(); + const type = loss.isDownloadStream ? "download" : "upload"; + totalPackets[type] += loss.packetsTotal; + lostPackets[type] += loss.packetsLost; + + // process bitrate stats + bitrateDownload += trackStats.getBitrate().download; + bitrateUpload += trackStats.getBitrate().upload; + + // collect resolutions and framerates + if (trackStats.kind === "audio") { + audioBitrateDownload += trackStats.getBitrate().download; + audioBitrateUpload += trackStats.getBitrate().upload; + } else { + videoBitrateDownload += trackStats.getBitrate().download; + videoBitrateUpload += trackStats.getBitrate().upload; + } + resolutions[trackStats.getType()].set(trackId, trackStats.getResolution()); + framerates[trackStats.getType()].set(trackId, trackStats.getFramerate()); + codecs[trackStats.getType()].set(trackId, trackStats.getCodec()); + trackStats.resetBitrate(); + } + report.bitrate = { + upload: bitrateUpload, + download: bitrateDownload + }; + report.bitrate.audio = { + upload: audioBitrateUpload, + download: audioBitrateDownload + }; + report.bitrate.video = { + upload: videoBitrateUpload, + download: videoBitrateDownload + }; + report.packetLoss = { + total: StatsReportBuilder.calculatePacketLoss(lostPackets.download + lostPackets.upload, totalPackets.download + totalPackets.upload), + download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download), + upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload) + }; + report.framerate = framerates; + report.resolution = resolutions; + report.codec = codecs; + return report; + } + static calculatePacketLoss(lostPackets, totalPackets) { + if (!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0) { + return 0; + } + return Math.round(lostPackets / totalPackets * 100); + } +} +exports.StatsReportBuilder = StatsReportBuilder; +//# sourceMappingURL=statsReportBuilder.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.js.map new file mode 100644 index 0000000..914dcf3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportBuilder.js.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReportBuilder.js","names":["StatsReportBuilder","build","stats","report","totalPackets","download","upload","lostPackets","bitrateDownload","bitrateUpload","resolutions","local","Map","remote","framerates","codecs","audioBitrateDownload","audioBitrateUpload","videoBitrateDownload","videoBitrateUpload","trackId","trackStats","loss","getLoss","type","isDownloadStream","packetsTotal","packetsLost","getBitrate","kind","getType","set","getResolution","getFramerate","getCodec","resetBitrate","bitrate","audio","video","packetLoss","total","calculatePacketLoss","framerate","resolution","codec","Math","round","exports"],"sources":["../../../src/webrtc/stats/statsReportBuilder.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\nimport { CodecMap, ConnectionStatsReport, FramerateMap, ResolutionMap, TrackID } from \"./statsReport\";\nimport { MediaTrackStats, Resolution } from \"./media/mediaTrackStats\";\n\nexport class StatsReportBuilder {\n public static build(stats: Map<TrackID, MediaTrackStats>): ConnectionStatsReport {\n const report = {} as ConnectionStatsReport;\n\n // process stats\n const totalPackets = {\n download: 0,\n upload: 0,\n };\n const lostPackets = {\n download: 0,\n upload: 0,\n };\n let bitrateDownload = 0;\n let bitrateUpload = 0;\n const resolutions: ResolutionMap = {\n local: new Map<TrackID, Resolution>(),\n remote: new Map<TrackID, Resolution>(),\n };\n const framerates: FramerateMap = { local: new Map<TrackID, number>(), remote: new Map<TrackID, number>() };\n const codecs: CodecMap = { local: new Map<TrackID, string>(), remote: new Map<TrackID, string>() };\n\n let audioBitrateDownload = 0;\n let audioBitrateUpload = 0;\n let videoBitrateDownload = 0;\n let videoBitrateUpload = 0;\n\n for (const [trackId, trackStats] of stats) {\n // process packet loss stats\n const loss = trackStats.getLoss();\n const type = loss.isDownloadStream ? \"download\" : \"upload\";\n\n totalPackets[type] += loss.packetsTotal;\n lostPackets[type] += loss.packetsLost;\n\n // process bitrate stats\n bitrateDownload += trackStats.getBitrate().download;\n bitrateUpload += trackStats.getBitrate().upload;\n\n // collect resolutions and framerates\n if (trackStats.kind === \"audio\") {\n audioBitrateDownload += trackStats.getBitrate().download;\n audioBitrateUpload += trackStats.getBitrate().upload;\n } else {\n videoBitrateDownload += trackStats.getBitrate().download;\n videoBitrateUpload += trackStats.getBitrate().upload;\n }\n\n resolutions[trackStats.getType()].set(trackId, trackStats.getResolution());\n framerates[trackStats.getType()].set(trackId, trackStats.getFramerate());\n codecs[trackStats.getType()].set(trackId, trackStats.getCodec());\n\n trackStats.resetBitrate();\n }\n\n report.bitrate = {\n upload: bitrateUpload,\n download: bitrateDownload,\n };\n\n report.bitrate.audio = {\n upload: audioBitrateUpload,\n download: audioBitrateDownload,\n };\n\n report.bitrate.video = {\n upload: videoBitrateUpload,\n download: videoBitrateDownload,\n };\n\n report.packetLoss = {\n total: StatsReportBuilder.calculatePacketLoss(\n lostPackets.download + lostPackets.upload,\n totalPackets.download + totalPackets.upload,\n ),\n download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download),\n upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload),\n };\n report.framerate = framerates;\n report.resolution = resolutions;\n report.codec = codecs;\n return report;\n }\n\n private static calculatePacketLoss(lostPackets: number, totalPackets: number): number {\n if (!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0) {\n return 0;\n }\n\n return Math.round((lostPackets / totalPackets) * 100);\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.d.ts new file mode 100644 index 0000000..c943bb5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.d.ts @@ -0,0 +1,11 @@ +import { TypedEventEmitter } from "../../models/typed-event-emitter"; +import { ByteSentStatsReport, ConnectionStatsReport, StatsReport } from "./statsReport"; +export type StatsReportHandlerMap = { + [StatsReport.BYTE_SENT_STATS]: (report: ByteSentStatsReport) => void; + [StatsReport.CONNECTION_STATS]: (report: ConnectionStatsReport) => void; +}; +export declare class StatsReportEmitter extends TypedEventEmitter<StatsReport, StatsReportHandlerMap> { + emitByteSendReport(byteSentStats: ByteSentStatsReport): void; + emitConnectionStatsReport(report: ConnectionStatsReport): void; +} +//# sourceMappingURL=statsReportEmitter.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.d.ts.map new file mode 100644 index 0000000..edefe33 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReportEmitter.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/statsReportEmitter.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAExF,MAAM,MAAM,qBAAqB,GAAG;IAChC,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACrE,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC3E,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,iBAAiB,CAAC,WAAW,EAAE,qBAAqB,CAAC;IAClF,kBAAkB,CAAC,aAAa,EAAE,mBAAmB,GAAG,IAAI;IAI5D,yBAAyB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI;CAGxE"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.js new file mode 100644 index 0000000..fec87d5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.js @@ -0,0 +1,34 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.StatsReportEmitter = void 0; +var _typedEventEmitter = require("../../models/typed-event-emitter"); +var _statsReport = require("./statsReport"); +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +class StatsReportEmitter extends _typedEventEmitter.TypedEventEmitter { + emitByteSendReport(byteSentStats) { + this.emit(_statsReport.StatsReport.BYTE_SENT_STATS, byteSentStats); + } + emitConnectionStatsReport(report) { + this.emit(_statsReport.StatsReport.CONNECTION_STATS, report); + } +} +exports.StatsReportEmitter = StatsReportEmitter; +//# sourceMappingURL=statsReportEmitter.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.js.map new file mode 100644 index 0000000..0fae3d9 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportEmitter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReportEmitter.js","names":["_typedEventEmitter","require","_statsReport","StatsReportEmitter","TypedEventEmitter","emitByteSendReport","byteSentStats","emit","StatsReport","BYTE_SENT_STATS","emitConnectionStatsReport","report","CONNECTION_STATS","exports"],"sources":["../../../src/webrtc/stats/statsReportEmitter.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { TypedEventEmitter } from \"../../models/typed-event-emitter\";\nimport { ByteSentStatsReport, ConnectionStatsReport, StatsReport } from \"./statsReport\";\n\nexport type StatsReportHandlerMap = {\n [StatsReport.BYTE_SENT_STATS]: (report: ByteSentStatsReport) => void;\n [StatsReport.CONNECTION_STATS]: (report: ConnectionStatsReport) => void;\n};\n\nexport class StatsReportEmitter extends TypedEventEmitter<StatsReport, StatsReportHandlerMap> {\n public emitByteSendReport(byteSentStats: ByteSentStatsReport): void {\n this.emit(StatsReport.BYTE_SENT_STATS, byteSentStats);\n }\n\n public emitConnectionStatsReport(report: ConnectionStatsReport): void {\n this.emit(StatsReport.CONNECTION_STATS, report);\n }\n}\n"],"mappings":";;;;;;AAgBA,IAAAA,kBAAA,GAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AAjBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAUO,MAAME,kBAAkB,SAASC,oCAAiB,CAAqC;EACnFC,kBAAkBA,CAACC,aAAkC,EAAQ;IAChE,IAAI,CAACC,IAAI,CAACC,wBAAW,CAACC,eAAe,EAAEH,aAAa,CAAC;EACzD;EAEOI,yBAAyBA,CAACC,MAA6B,EAAQ;IAClE,IAAI,CAACJ,IAAI,CAACC,wBAAW,CAACI,gBAAgB,EAAED,MAAM,CAAC;EACnD;AACJ;AAACE,OAAA,CAAAV,kBAAA,GAAAA,kBAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.d.ts new file mode 100644 index 0000000..fc83b29 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.d.ts @@ -0,0 +1,23 @@ +import { StatsReportEmitter } from "./statsReportEmitter"; +export declare class StatsReportGatherer { + readonly callId: string; + readonly remoteUserId: string; + private readonly pc; + private readonly emitter; + private readonly isFocus; + private isActive; + private previousStatsReport; + private currentStatsReport; + private readonly connectionStats; + private readonly trackStats; + constructor(callId: string, remoteUserId: string, pc: RTCPeerConnection, emitter: StatsReportEmitter, isFocus?: boolean); + processStats(groupCallId: string, localUserId: string): Promise<boolean>; + private processStatsReport; + setActive(isActive: boolean): void; + getActive(): boolean; + private handleError; + private processAndEmitReport; + stopProcessingStats(): void; + private onSignalStateChange; +} +//# sourceMappingURL=statsReportGatherer.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.d.ts.map new file mode 100644 index 0000000..04516a6 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReportGatherer.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/statsReportGatherer.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAW1D,qBAAa,mBAAmB;aAWR,MAAM,EAAE,MAAM;aACd,YAAY,EAAE,MAAM;IACpC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd5B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,mBAAmB,CAA6B;IACxD,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IAEzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAKhC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACnB,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,UAAO;IAMtB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BrF,OAAO,CAAC,kBAAkB;IAkEnB,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIlC,SAAS,IAAI,OAAO;IAI3B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,oBAAoB;IAerB,mBAAmB,IAAI,IAAI;IAElC,OAAO,CAAC,mBAAmB;CAU9B"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.js new file mode 100644 index 0000000..02522f5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.js @@ -0,0 +1,143 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.StatsReportGatherer = void 0; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _connectionStats = require("./connectionStats"); +var _connectionStatsReporter = require("./connectionStatsReporter"); +var _transportStatsReporter = require("./transportStatsReporter"); +var _mediaSsrcHandler = require("./media/mediaSsrcHandler"); +var _mediaTrackHandler = require("./media/mediaTrackHandler"); +var _mediaTrackStatsHandler = require("./media/mediaTrackStatsHandler"); +var _trackStatsReporter = require("./trackStatsReporter"); +var _statsReportBuilder = require("./statsReportBuilder"); +var _statsValueFormatter = require("./statsValueFormatter"); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } +class StatsReportGatherer { + // private readonly ssrcToMid = { local: new Map<Mid, Ssrc[]>(), remote: new Map<Mid, Ssrc[]>() }; + + constructor(callId, remoteUserId, pc, emitter, isFocus = true) { + this.callId = callId; + this.remoteUserId = remoteUserId; + this.pc = pc; + this.emitter = emitter; + this.isFocus = isFocus; + (0, _defineProperty2.default)(this, "isActive", true); + (0, _defineProperty2.default)(this, "previousStatsReport", void 0); + (0, _defineProperty2.default)(this, "currentStatsReport", void 0); + (0, _defineProperty2.default)(this, "connectionStats", new _connectionStats.ConnectionStats()); + (0, _defineProperty2.default)(this, "trackStats", void 0); + pc.addEventListener("signalingstatechange", this.onSignalStateChange.bind(this)); + this.trackStats = new _mediaTrackStatsHandler.MediaTrackStatsHandler(new _mediaSsrcHandler.MediaSsrcHandler(), new _mediaTrackHandler.MediaTrackHandler(pc)); + } + async processStats(groupCallId, localUserId) { + if (this.isActive) { + const statsPromise = this.pc.getStats(); + if (typeof (statsPromise === null || statsPromise === void 0 ? void 0 : statsPromise.then) === "function") { + return statsPromise.then(report => { + // @ts-ignore + this.currentStatsReport = typeof (report === null || report === void 0 ? void 0 : report.result) === "function" ? report.result() : report; + try { + this.processStatsReport(groupCallId, localUserId); + } catch (error) { + this.isActive = false; + return false; + } + this.previousStatsReport = this.currentStatsReport; + return true; + }).catch(error => { + this.handleError(error); + return false; + }); + } + this.isActive = false; + } + return Promise.resolve(false); + } + processStatsReport(groupCallId, localUserId) { + var _this$currentStatsRep; + const byteSentStats = new Map(); + (_this$currentStatsRep = this.currentStatsReport) === null || _this$currentStatsRep === void 0 ? void 0 : _this$currentStatsRep.forEach(now => { + const before = this.previousStatsReport ? this.previousStatsReport.get(now.id) : null; + // RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict* + if (now.type === "candidate-pair" && now.nominated && now.state === "succeeded") { + this.connectionStats.bandwidth = _connectionStatsReporter.ConnectionStatsReporter.buildBandwidthReport(now); + this.connectionStats.transport = _transportStatsReporter.TransportStatsReporter.buildReport(this.currentStatsReport, now, this.connectionStats.transport, this.isFocus); + + // RTCReceivedRtpStreamStats + // https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict* + // RTCSentRtpStreamStats + // https://w3c.github.io/webrtc-stats/#sentrtpstats-dict* + } else if (now.type === "inbound-rtp" || now.type === "outbound-rtp") { + const trackStats = this.trackStats.findTrack2Stats(now, now.type === "inbound-rtp" ? "remote" : "local"); + if (!trackStats) { + return; + } + if (before) { + _trackStatsReporter.TrackStatsReporter.buildPacketsLost(trackStats, now, before); + } + + // Get the resolution and framerate for only remote video sources here. For the local video sources, + // 'track' stats will be used since they have the updated resolution based on the simulcast streams + // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be + // more calculations needed to determine what is the highest resolution stream sent by the client if the + // 'outbound-rtp' stats are used. + if (now.type === "inbound-rtp") { + _trackStatsReporter.TrackStatsReporter.buildFramerateResolution(trackStats, now); + if (before) { + _trackStatsReporter.TrackStatsReporter.buildBitrateReceived(trackStats, now, before); + } + } else if (before) { + byteSentStats.set(trackStats.trackId, _statsValueFormatter.StatsValueFormatter.getNonNegativeValue(now.bytesSent)); + _trackStatsReporter.TrackStatsReporter.buildBitrateSend(trackStats, now, before); + } + _trackStatsReporter.TrackStatsReporter.buildCodec(this.currentStatsReport, trackStats, now); + } else if (now.type === "track" && now.kind === "video" && !now.remoteSource) { + const trackStats = this.trackStats.findLocalVideoTrackStats(now); + if (!trackStats) { + return; + } + _trackStatsReporter.TrackStatsReporter.buildFramerateResolution(trackStats, now); + _trackStatsReporter.TrackStatsReporter.calculateSimulcastFramerate(trackStats, now, before, this.trackStats.mediaTrackHandler.getActiveSimulcastStreams()); + } + }); + this.emitter.emitByteSendReport(byteSentStats); + this.processAndEmitReport(); + } + setActive(isActive) { + this.isActive = isActive; + } + getActive() { + return this.isActive; + } + handleError(_) { + this.isActive = false; + } + processAndEmitReport() { + const report = _statsReportBuilder.StatsReportBuilder.build(this.trackStats.getTrack2stats()); + this.connectionStats.bandwidth = report.bandwidth; + this.connectionStats.bitrate = report.bitrate; + this.connectionStats.packetLoss = report.packetLoss; + this.emitter.emitConnectionStatsReport(_objectSpread(_objectSpread({}, report), {}, { + transport: this.connectionStats.transport + })); + this.connectionStats.transport = []; + } + stopProcessingStats() {} + onSignalStateChange() { + if (this.pc.signalingState === "stable") { + if (this.pc.currentRemoteDescription) { + this.trackStats.mediaSsrcHandler.parse(this.pc.currentRemoteDescription.sdp, "remote"); + } + if (this.pc.currentLocalDescription) { + this.trackStats.mediaSsrcHandler.parse(this.pc.currentLocalDescription.sdp, "local"); + } + } + } +} +exports.StatsReportGatherer = StatsReportGatherer; +//# sourceMappingURL=statsReportGatherer.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.js.map new file mode 100644 index 0000000..30fa506 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsReportGatherer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"statsReportGatherer.js","names":["_connectionStats","require","_connectionStatsReporter","_transportStatsReporter","_mediaSsrcHandler","_mediaTrackHandler","_mediaTrackStatsHandler","_trackStatsReporter","_statsReportBuilder","_statsValueFormatter","ownKeys","object","enumerableOnly","keys","Object","getOwnPropertySymbols","symbols","filter","sym","getOwnPropertyDescriptor","enumerable","push","apply","_objectSpread","target","i","arguments","length","source","forEach","key","_defineProperty2","default","getOwnPropertyDescriptors","defineProperties","defineProperty","StatsReportGatherer","constructor","callId","remoteUserId","pc","emitter","isFocus","ConnectionStats","addEventListener","onSignalStateChange","bind","trackStats","MediaTrackStatsHandler","MediaSsrcHandler","MediaTrackHandler","processStats","groupCallId","localUserId","isActive","statsPromise","getStats","then","report","currentStatsReport","result","processStatsReport","error","previousStatsReport","catch","handleError","Promise","resolve","_this$currentStatsRep","byteSentStats","Map","now","before","get","id","type","nominated","state","connectionStats","bandwidth","ConnectionStatsReporter","buildBandwidthReport","transport","TransportStatsReporter","buildReport","findTrack2Stats","TrackStatsReporter","buildPacketsLost","buildFramerateResolution","buildBitrateReceived","set","trackId","StatsValueFormatter","getNonNegativeValue","bytesSent","buildBitrateSend","buildCodec","kind","remoteSource","findLocalVideoTrackStats","calculateSimulcastFramerate","mediaTrackHandler","getActiveSimulcastStreams","emitByteSendReport","processAndEmitReport","setActive","getActive","_","StatsReportBuilder","build","getTrack2stats","bitrate","packetLoss","emitConnectionStatsReport","stopProcessingStats","signalingState","currentRemoteDescription","mediaSsrcHandler","parse","sdp","currentLocalDescription","exports"],"sources":["../../../src/webrtc/stats/statsReportGatherer.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { ConnectionStats } from \"./connectionStats\";\nimport { StatsReportEmitter } from \"./statsReportEmitter\";\nimport { ByteSend, ByteSentStatsReport, TrackID } from \"./statsReport\";\nimport { ConnectionStatsReporter } from \"./connectionStatsReporter\";\nimport { TransportStatsReporter } from \"./transportStatsReporter\";\nimport { MediaSsrcHandler } from \"./media/mediaSsrcHandler\";\nimport { MediaTrackHandler } from \"./media/mediaTrackHandler\";\nimport { MediaTrackStatsHandler } from \"./media/mediaTrackStatsHandler\";\nimport { TrackStatsReporter } from \"./trackStatsReporter\";\nimport { StatsReportBuilder } from \"./statsReportBuilder\";\nimport { StatsValueFormatter } from \"./statsValueFormatter\";\n\nexport class StatsReportGatherer {\n private isActive = true;\n private previousStatsReport: RTCStatsReport | undefined;\n private currentStatsReport: RTCStatsReport | undefined;\n private readonly connectionStats = new ConnectionStats();\n\n private readonly trackStats: MediaTrackStatsHandler;\n\n // private readonly ssrcToMid = { local: new Map<Mid, Ssrc[]>(), remote: new Map<Mid, Ssrc[]>() };\n\n public constructor(\n public readonly callId: string,\n public readonly remoteUserId: string,\n private readonly pc: RTCPeerConnection,\n private readonly emitter: StatsReportEmitter,\n private readonly isFocus = true,\n ) {\n pc.addEventListener(\"signalingstatechange\", this.onSignalStateChange.bind(this));\n this.trackStats = new MediaTrackStatsHandler(new MediaSsrcHandler(), new MediaTrackHandler(pc));\n }\n\n public async processStats(groupCallId: string, localUserId: string): Promise<boolean> {\n if (this.isActive) {\n const statsPromise = this.pc.getStats();\n if (typeof statsPromise?.then === \"function\") {\n return statsPromise\n .then((report) => {\n // @ts-ignore\n this.currentStatsReport = typeof report?.result === \"function\" ? report.result() : report;\n try {\n this.processStatsReport(groupCallId, localUserId);\n } catch (error) {\n this.isActive = false;\n return false;\n }\n\n this.previousStatsReport = this.currentStatsReport;\n return true;\n })\n .catch((error) => {\n this.handleError(error);\n return false;\n });\n }\n this.isActive = false;\n }\n return Promise.resolve(false);\n }\n\n private processStatsReport(groupCallId: string, localUserId: string): void {\n const byteSentStats: ByteSentStatsReport = new Map<TrackID, ByteSend>();\n\n this.currentStatsReport?.forEach((now) => {\n const before = this.previousStatsReport ? this.previousStatsReport.get(now.id) : null;\n // RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict*\n if (now.type === \"candidate-pair\" && now.nominated && now.state === \"succeeded\") {\n this.connectionStats.bandwidth = ConnectionStatsReporter.buildBandwidthReport(now);\n this.connectionStats.transport = TransportStatsReporter.buildReport(\n this.currentStatsReport,\n now,\n this.connectionStats.transport,\n this.isFocus,\n );\n\n // RTCReceivedRtpStreamStats\n // https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict*\n // RTCSentRtpStreamStats\n // https://w3c.github.io/webrtc-stats/#sentrtpstats-dict*\n } else if (now.type === \"inbound-rtp\" || now.type === \"outbound-rtp\") {\n const trackStats = this.trackStats.findTrack2Stats(\n now,\n now.type === \"inbound-rtp\" ? \"remote\" : \"local\",\n );\n if (!trackStats) {\n return;\n }\n\n if (before) {\n TrackStatsReporter.buildPacketsLost(trackStats, now, before);\n }\n\n // Get the resolution and framerate for only remote video sources here. For the local video sources,\n // 'track' stats will be used since they have the updated resolution based on the simulcast streams\n // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be\n // more calculations needed to determine what is the highest resolution stream sent by the client if the\n // 'outbound-rtp' stats are used.\n if (now.type === \"inbound-rtp\") {\n TrackStatsReporter.buildFramerateResolution(trackStats, now);\n if (before) {\n TrackStatsReporter.buildBitrateReceived(trackStats, now, before);\n }\n } else if (before) {\n byteSentStats.set(trackStats.trackId, StatsValueFormatter.getNonNegativeValue(now.bytesSent));\n TrackStatsReporter.buildBitrateSend(trackStats, now, before);\n }\n TrackStatsReporter.buildCodec(this.currentStatsReport, trackStats, now);\n } else if (now.type === \"track\" && now.kind === \"video\" && !now.remoteSource) {\n const trackStats = this.trackStats.findLocalVideoTrackStats(now);\n if (!trackStats) {\n return;\n }\n TrackStatsReporter.buildFramerateResolution(trackStats, now);\n TrackStatsReporter.calculateSimulcastFramerate(\n trackStats,\n now,\n before,\n this.trackStats.mediaTrackHandler.getActiveSimulcastStreams(),\n );\n }\n });\n\n this.emitter.emitByteSendReport(byteSentStats);\n this.processAndEmitReport();\n }\n\n public setActive(isActive: boolean): void {\n this.isActive = isActive;\n }\n\n public getActive(): boolean {\n return this.isActive;\n }\n\n private handleError(_: any): void {\n this.isActive = false;\n }\n\n private processAndEmitReport(): void {\n const report = StatsReportBuilder.build(this.trackStats.getTrack2stats());\n\n this.connectionStats.bandwidth = report.bandwidth;\n this.connectionStats.bitrate = report.bitrate;\n this.connectionStats.packetLoss = report.packetLoss;\n\n this.emitter.emitConnectionStatsReport({\n ...report,\n transport: this.connectionStats.transport,\n });\n\n this.connectionStats.transport = [];\n }\n\n public stopProcessingStats(): void {}\n\n private onSignalStateChange(): void {\n if (this.pc.signalingState === \"stable\") {\n if (this.pc.currentRemoteDescription) {\n this.trackStats.mediaSsrcHandler.parse(this.pc.currentRemoteDescription.sdp, \"remote\");\n }\n if (this.pc.currentLocalDescription) {\n this.trackStats.mediaSsrcHandler.parse(this.pc.currentLocalDescription.sdp, \"local\");\n }\n }\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.d.ts new file mode 100644 index 0000000..08f5cce --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.d.ts @@ -0,0 +1,4 @@ +export declare class StatsValueFormatter { + static getNonNegativeValue(imput: any): number; +} +//# sourceMappingURL=statsValueFormatter.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.d.ts.map new file mode 100644 index 0000000..1bdbab3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"statsValueFormatter.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/statsValueFormatter.ts"],"names":[],"mappings":"AAYA,qBAAa,mBAAmB;WACd,mBAAmB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM;CAaxD"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.js new file mode 100644 index 0000000..5324ad5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.js @@ -0,0 +1,32 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.StatsValueFormatter = void 0; +/* +Copyright 2023 The Matrix.org Foundation C.I.C. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +class StatsValueFormatter { + static getNonNegativeValue(imput) { + let value = imput; + if (typeof value !== "number") { + value = Number(value); + } + if (isNaN(value)) { + return 0; + } + return Math.max(0, value); + } +} +exports.StatsValueFormatter = StatsValueFormatter; +//# sourceMappingURL=statsValueFormatter.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.js.map new file mode 100644 index 0000000..3aa4877 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/statsValueFormatter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"statsValueFormatter.js","names":["StatsValueFormatter","getNonNegativeValue","imput","value","Number","isNaN","Math","max","exports"],"sources":["../../../src/webrtc/stats/statsValueFormatter.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\nexport class StatsValueFormatter {\n public static getNonNegativeValue(imput: any): number {\n let value = imput;\n\n if (typeof value !== \"number\") {\n value = Number(value);\n }\n\n if (isNaN(value)) {\n return 0;\n }\n\n return Math.max(0, value);\n }\n}\n"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMA,mBAAmB,CAAC;EAC7B,OAAcC,mBAAmBA,CAACC,KAAU,EAAU;IAClD,IAAIC,KAAK,GAAGD,KAAK;IAEjB,IAAI,OAAOC,KAAK,KAAK,QAAQ,EAAE;MAC3BA,KAAK,GAAGC,MAAM,CAACD,KAAK,CAAC;IACzB;IAEA,IAAIE,KAAK,CAACF,KAAK,CAAC,EAAE;MACd,OAAO,CAAC;IACZ;IAEA,OAAOG,IAAI,CAACC,GAAG,CAAC,CAAC,EAAEJ,KAAK,CAAC;EAC7B;AACJ;AAACK,OAAA,CAAAR,mBAAA,GAAAA,mBAAA"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.d.ts new file mode 100644 index 0000000..99130d7 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.d.ts @@ -0,0 +1,11 @@ +import { MediaTrackStats } from "./media/mediaTrackStats"; +export declare class TrackStatsReporter { + static buildFramerateResolution(trackStats: MediaTrackStats, now: any): void; + static calculateSimulcastFramerate(trackStats: MediaTrackStats, now: any, before: any, layer: number): void; + static buildCodec(report: RTCStatsReport | undefined, trackStats: MediaTrackStats, now: any): void; + static buildBitrateReceived(trackStats: MediaTrackStats, now: any, before: any): void; + static buildBitrateSend(trackStats: MediaTrackStats, now: any, before: any): void; + static buildPacketsLost(trackStats: MediaTrackStats, now: any, before: any): void; + private static calculateBitrate; +} +//# sourceMappingURL=trackStatsReporter.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.d.ts.map new file mode 100644 index 0000000..159f579 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"trackStatsReporter.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/trackStatsReporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,qBAAa,kBAAkB;WACb,wBAAwB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI;WAarE,2BAA2B,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;WAuBpG,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI;WAe3F,oBAAoB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;WAY9E,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;WAO1E,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;IAsBxF,OAAO,CAAC,MAAM,CAAC,gBAAgB;CAoBlC"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.js new file mode 100644 index 0000000..af6f96a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.js @@ -0,0 +1,94 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TrackStatsReporter = void 0; +var _statsValueFormatter = require("./statsValueFormatter"); +class TrackStatsReporter { + static buildFramerateResolution(trackStats, now) { + const resolution = { + height: now.frameHeight, + width: now.frameWidth + }; + const frameRate = now.framesPerSecond; + if (resolution.height && resolution.width) { + trackStats.setResolution(resolution); + } + trackStats.setFramerate(Math.round(frameRate || 0)); + } + static calculateSimulcastFramerate(trackStats, now, before, layer) { + let frameRate = trackStats.getFramerate(); + if (!frameRate) { + if (before) { + const timeMs = now.timestamp - before.timestamp; + if (timeMs > 0 && now.framesSent) { + const numberOfFramesSinceBefore = now.framesSent - before.framesSent; + frameRate = numberOfFramesSinceBefore / timeMs * 1000; + } + } + if (!frameRate) { + return; + } + } + + // Reset frame rate to 0 when video is suspended as a result of endpoint falling out of last-n. + frameRate = layer ? Math.round(frameRate / layer) : 0; + trackStats.setFramerate(frameRate); + } + static buildCodec(report, trackStats, now) { + const codec = report === null || report === void 0 ? void 0 : report.get(now.codecId); + if (codec) { + /** + * The mime type has the following form: video/VP8 or audio/ISAC, + * so we what to keep just the type after the '/', audio and video + * keys will be added on the processing side. + */ + const codecShortType = codec.mimeType.split("/")[1]; + codecShortType && trackStats.setCodec(codecShortType); + } + } + static buildBitrateReceived(trackStats, now, before) { + trackStats.setBitrate({ + download: TrackStatsReporter.calculateBitrate(now.bytesReceived, before.bytesReceived, now.timestamp, before.timestamp), + upload: 0 + }); + } + static buildBitrateSend(trackStats, now, before) { + trackStats.setBitrate({ + download: 0, + upload: this.calculateBitrate(now.bytesSent, before.bytesSent, now.timestamp, before.timestamp) + }); + } + static buildPacketsLost(trackStats, now, before) { + const key = now.type === "outbound-rtp" ? "packetsSent" : "packetsReceived"; + let packetsNow = now[key]; + if (!packetsNow || packetsNow < 0) { + packetsNow = 0; + } + const packetsBefore = _statsValueFormatter.StatsValueFormatter.getNonNegativeValue(before[key]); + const packetsDiff = Math.max(0, packetsNow - packetsBefore); + const packetsLostNow = _statsValueFormatter.StatsValueFormatter.getNonNegativeValue(now.packetsLost); + const packetsLostBefore = _statsValueFormatter.StatsValueFormatter.getNonNegativeValue(before.packetsLost); + const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore); + trackStats.setLoss({ + packetsTotal: packetsDiff + packetsLostDiff, + packetsLost: packetsLostDiff, + isDownloadStream: now.type !== "outbound-rtp" + }); + } + static calculateBitrate(bytesNowAny, bytesBeforeAny, nowTimestamp, beforeTimestamp) { + const bytesNow = _statsValueFormatter.StatsValueFormatter.getNonNegativeValue(bytesNowAny); + const bytesBefore = _statsValueFormatter.StatsValueFormatter.getNonNegativeValue(bytesBeforeAny); + const bytesProcessed = Math.max(0, bytesNow - bytesBefore); + const timeMs = nowTimestamp - beforeTimestamp; + let bitrateKbps = 0; + if (timeMs > 0) { + // TODO is there any reason to round here? + bitrateKbps = Math.round(bytesProcessed * 8 / timeMs); + } + return bitrateKbps; + } +} +exports.TrackStatsReporter = TrackStatsReporter; +//# sourceMappingURL=trackStatsReporter.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.js.map new file mode 100644 index 0000000..4828aae --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/trackStatsReporter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"trackStatsReporter.js","names":["_statsValueFormatter","require","TrackStatsReporter","buildFramerateResolution","trackStats","now","resolution","height","frameHeight","width","frameWidth","frameRate","framesPerSecond","setResolution","setFramerate","Math","round","calculateSimulcastFramerate","before","layer","getFramerate","timeMs","timestamp","framesSent","numberOfFramesSinceBefore","buildCodec","report","codec","get","codecId","codecShortType","mimeType","split","setCodec","buildBitrateReceived","setBitrate","download","calculateBitrate","bytesReceived","upload","buildBitrateSend","bytesSent","buildPacketsLost","key","type","packetsNow","packetsBefore","StatsValueFormatter","getNonNegativeValue","packetsDiff","max","packetsLostNow","packetsLost","packetsLostBefore","packetsLostDiff","setLoss","packetsTotal","isDownloadStream","bytesNowAny","bytesBeforeAny","nowTimestamp","beforeTimestamp","bytesNow","bytesBefore","bytesProcessed","bitrateKbps","exports"],"sources":["../../../src/webrtc/stats/trackStatsReporter.ts"],"sourcesContent":["import { MediaTrackStats } from \"./media/mediaTrackStats\";\nimport { StatsValueFormatter } from \"./statsValueFormatter\";\n\nexport class TrackStatsReporter {\n public static buildFramerateResolution(trackStats: MediaTrackStats, now: any): void {\n const resolution = {\n height: now.frameHeight,\n width: now.frameWidth,\n };\n const frameRate = now.framesPerSecond;\n\n if (resolution.height && resolution.width) {\n trackStats.setResolution(resolution);\n }\n trackStats.setFramerate(Math.round(frameRate || 0));\n }\n\n public static calculateSimulcastFramerate(trackStats: MediaTrackStats, now: any, before: any, layer: number): void {\n let frameRate = trackStats.getFramerate();\n if (!frameRate) {\n if (before) {\n const timeMs = now.timestamp - before.timestamp;\n\n if (timeMs > 0 && now.framesSent) {\n const numberOfFramesSinceBefore = now.framesSent - before.framesSent;\n\n frameRate = (numberOfFramesSinceBefore / timeMs) * 1000;\n }\n }\n\n if (!frameRate) {\n return;\n }\n }\n\n // Reset frame rate to 0 when video is suspended as a result of endpoint falling out of last-n.\n frameRate = layer ? Math.round(frameRate / layer) : 0;\n trackStats.setFramerate(frameRate);\n }\n\n public static buildCodec(report: RTCStatsReport | undefined, trackStats: MediaTrackStats, now: any): void {\n const codec = report?.get(now.codecId);\n\n if (codec) {\n /**\n * The mime type has the following form: video/VP8 or audio/ISAC,\n * so we what to keep just the type after the '/', audio and video\n * keys will be added on the processing side.\n */\n const codecShortType = codec.mimeType.split(\"/\")[1];\n\n codecShortType && trackStats.setCodec(codecShortType);\n }\n }\n\n public static buildBitrateReceived(trackStats: MediaTrackStats, now: any, before: any): void {\n trackStats.setBitrate({\n download: TrackStatsReporter.calculateBitrate(\n now.bytesReceived,\n before.bytesReceived,\n now.timestamp,\n before.timestamp,\n ),\n upload: 0,\n });\n }\n\n public static buildBitrateSend(trackStats: MediaTrackStats, now: any, before: any): void {\n trackStats.setBitrate({\n download: 0,\n upload: this.calculateBitrate(now.bytesSent, before.bytesSent, now.timestamp, before.timestamp),\n });\n }\n\n public static buildPacketsLost(trackStats: MediaTrackStats, now: any, before: any): void {\n const key = now.type === \"outbound-rtp\" ? \"packetsSent\" : \"packetsReceived\";\n\n let packetsNow = now[key];\n if (!packetsNow || packetsNow < 0) {\n packetsNow = 0;\n }\n\n const packetsBefore = StatsValueFormatter.getNonNegativeValue(before[key]);\n const packetsDiff = Math.max(0, packetsNow - packetsBefore);\n\n const packetsLostNow = StatsValueFormatter.getNonNegativeValue(now.packetsLost);\n const packetsLostBefore = StatsValueFormatter.getNonNegativeValue(before.packetsLost);\n const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);\n\n trackStats.setLoss({\n packetsTotal: packetsDiff + packetsLostDiff,\n packetsLost: packetsLostDiff,\n isDownloadStream: now.type !== \"outbound-rtp\",\n });\n }\n\n private static calculateBitrate(\n bytesNowAny: any,\n bytesBeforeAny: any,\n nowTimestamp: number,\n beforeTimestamp: number,\n ): number {\n const bytesNow = StatsValueFormatter.getNonNegativeValue(bytesNowAny);\n const bytesBefore = StatsValueFormatter.getNonNegativeValue(bytesBeforeAny);\n const bytesProcessed = Math.max(0, bytesNow - bytesBefore);\n\n const timeMs = nowTimestamp - beforeTimestamp;\n let bitrateKbps = 0;\n\n if (timeMs > 0) {\n // TODO is there any reason to round here?\n bitrateKbps = Math.round((bytesProcessed * 8) / timeMs);\n }\n\n return bitrateKbps;\n }\n}\n"],"mappings":"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"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.d.ts new file mode 100644 index 0000000..bffc9b6 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.d.ts @@ -0,0 +1,11 @@ +export interface TransportStats { + ip: string; + type: string; + localIp: string; + isFocus: boolean; + localCandidateType: string; + remoteCandidateType: string; + networkType: string; + rtt: number; +} +//# sourceMappingURL=transportStats.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.d.ts.map new file mode 100644 index 0000000..cee2f59 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"transportStats.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/transportStats.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACf"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.js new file mode 100644 index 0000000..402a6ef --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.js @@ -0,0 +1,6 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +//# sourceMappingURL=transportStats.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.js.map new file mode 100644 index 0000000..a4ac8a7 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStats.js.map @@ -0,0 +1 @@ +{"version":3,"file":"transportStats.js","names":[],"sources":["../../../src/webrtc/stats/transportStats.ts"],"sourcesContent":["/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport interface TransportStats {\n ip: string;\n type: string;\n localIp: string;\n isFocus: boolean;\n localCandidateType: string;\n remoteCandidateType: string;\n networkType: string;\n rtt: number;\n}\n"],"mappings":""}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.d.ts b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.d.ts new file mode 100644 index 0000000..0210a6e --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.d.ts @@ -0,0 +1,5 @@ +import { TransportStats } from "./transportStats"; +export declare class TransportStatsReporter { + static buildReport(report: RTCStatsReport | undefined, now: RTCIceCandidatePairStats, conferenceStatsTransport: TransportStats[], isFocus: boolean): TransportStats[]; +} +//# sourceMappingURL=transportStatsReporter.d.ts.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.d.ts.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.d.ts.map new file mode 100644 index 0000000..ce26080 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"transportStatsReporter.d.ts","sourceRoot":"","sources":["../../../src/webrtc/stats/transportStatsReporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,qBAAa,sBAAsB;WACjB,WAAW,CACrB,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,GAAG,EAAE,wBAAwB,EAC7B,wBAAwB,EAAE,cAAc,EAAE,EAC1C,OAAO,EAAE,OAAO,GACjB,cAAc,EAAE;CAuCtB"}
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.js new file mode 100644 index 0000000..bc768c9 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.js @@ -0,0 +1,41 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TransportStatsReporter = void 0; +class TransportStatsReporter { + static buildReport(report, now, conferenceStatsTransport, isFocus) { + const localUsedCandidate = report === null || report === void 0 ? void 0 : report.get(now.localCandidateId); + const remoteUsedCandidate = report === null || report === void 0 ? void 0 : report.get(now.remoteCandidateId); + + // RTCIceCandidateStats + // https://w3c.github.io/webrtc-stats/#icecandidate-dict* + if (remoteUsedCandidate && localUsedCandidate) { + const remoteIpAddress = remoteUsedCandidate.ip !== undefined ? remoteUsedCandidate.ip : remoteUsedCandidate.address; + const remotePort = remoteUsedCandidate.port; + const ip = `${remoteIpAddress}:${remotePort}`; + const localIpAddress = localUsedCandidate.ip !== undefined ? localUsedCandidate.ip : localUsedCandidate.address; + const localPort = localUsedCandidate.port; + const localIp = `${localIpAddress}:${localPort}`; + const type = remoteUsedCandidate.protocol; + + // Save the address unless it has been saved already. + if (!conferenceStatsTransport.some(t => t.ip === ip && t.type === type && t.localIp === localIp)) { + conferenceStatsTransport.push({ + ip, + type, + localIp, + isFocus, + localCandidateType: localUsedCandidate.candidateType, + remoteCandidateType: remoteUsedCandidate.candidateType, + networkType: localUsedCandidate.networkType, + rtt: now.currentRoundTripTime ? now.currentRoundTripTime * 1000 : NaN + }); + } + } + return conferenceStatsTransport; + } +} +exports.TransportStatsReporter = TransportStatsReporter; +//# sourceMappingURL=transportStatsReporter.js.map
\ No newline at end of file diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.js.map b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.js.map new file mode 100644 index 0000000..c352ddc --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/webrtc/stats/transportStatsReporter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"transportStatsReporter.js","names":["TransportStatsReporter","buildReport","report","now","conferenceStatsTransport","isFocus","localUsedCandidate","get","localCandidateId","remoteUsedCandidate","remoteCandidateId","remoteIpAddress","ip","undefined","address","remotePort","port","localIpAddress","localPort","localIp","type","protocol","some","t","push","localCandidateType","candidateType","remoteCandidateType","networkType","rtt","currentRoundTripTime","NaN","exports"],"sources":["../../../src/webrtc/stats/transportStatsReporter.ts"],"sourcesContent":["import { TransportStats } from \"./transportStats\";\n\nexport class TransportStatsReporter {\n public static buildReport(\n report: RTCStatsReport | undefined,\n now: RTCIceCandidatePairStats,\n conferenceStatsTransport: TransportStats[],\n isFocus: boolean,\n ): TransportStats[] {\n const localUsedCandidate = report?.get(now.localCandidateId);\n const remoteUsedCandidate = report?.get(now.remoteCandidateId);\n\n // RTCIceCandidateStats\n // https://w3c.github.io/webrtc-stats/#icecandidate-dict*\n if (remoteUsedCandidate && localUsedCandidate) {\n const remoteIpAddress =\n remoteUsedCandidate.ip !== undefined ? remoteUsedCandidate.ip : remoteUsedCandidate.address;\n const remotePort = remoteUsedCandidate.port;\n const ip = `${remoteIpAddress}:${remotePort}`;\n\n const localIpAddress =\n localUsedCandidate.ip !== undefined ? localUsedCandidate.ip : localUsedCandidate.address;\n const localPort = localUsedCandidate.port;\n const localIp = `${localIpAddress}:${localPort}`;\n\n const type = remoteUsedCandidate.protocol;\n\n // Save the address unless it has been saved already.\n if (\n !conferenceStatsTransport.some(\n (t: TransportStats) => t.ip === ip && t.type === type && t.localIp === localIp,\n )\n ) {\n conferenceStatsTransport.push({\n ip,\n type,\n localIp,\n isFocus,\n localCandidateType: localUsedCandidate.candidateType,\n remoteCandidateType: remoteUsedCandidate.candidateType,\n networkType: localUsedCandidate.networkType,\n rtt: now.currentRoundTripTime ? now.currentRoundTripTime * 1000 : NaN,\n } as TransportStats);\n }\n }\n return conferenceStatsTransport;\n }\n}\n"],"mappings":";;;;;;AAEO,MAAMA,sBAAsB,CAAC;EAChC,OAAcC,WAAWA,CACrBC,MAAkC,EAClCC,GAA6B,EAC7BC,wBAA0C,EAC1CC,OAAgB,EACA;IAChB,MAAMC,kBAAkB,GAAGJ,MAAM,aAANA,MAAM,uBAANA,MAAM,CAAEK,GAAG,CAACJ,GAAG,CAACK,gBAAgB,CAAC;IAC5D,MAAMC,mBAAmB,GAAGP,MAAM,aAANA,MAAM,uBAANA,MAAM,CAAEK,GAAG,CAACJ,GAAG,CAACO,iBAAiB,CAAC;;IAE9D;IACA;IACA,IAAID,mBAAmB,IAAIH,kBAAkB,EAAE;MAC3C,MAAMK,eAAe,GACjBF,mBAAmB,CAACG,EAAE,KAAKC,SAAS,GAAGJ,mBAAmB,CAACG,EAAE,GAAGH,mBAAmB,CAACK,OAAO;MAC/F,MAAMC,UAAU,GAAGN,mBAAmB,CAACO,IAAI;MAC3C,MAAMJ,EAAE,GAAI,GAAED,eAAgB,IAAGI,UAAW,EAAC;MAE7C,MAAME,cAAc,GAChBX,kBAAkB,CAACM,EAAE,KAAKC,SAAS,GAAGP,kBAAkB,CAACM,EAAE,GAAGN,kBAAkB,CAACQ,OAAO;MAC5F,MAAMI,SAAS,GAAGZ,kBAAkB,CAACU,IAAI;MACzC,MAAMG,OAAO,GAAI,GAAEF,cAAe,IAAGC,SAAU,EAAC;MAEhD,MAAME,IAAI,GAAGX,mBAAmB,CAACY,QAAQ;;MAEzC;MACA,IACI,CAACjB,wBAAwB,CAACkB,IAAI,CACzBC,CAAiB,IAAKA,CAAC,CAACX,EAAE,KAAKA,EAAE,IAAIW,CAAC,CAACH,IAAI,KAAKA,IAAI,IAAIG,CAAC,CAACJ,OAAO,KAAKA,OAAO,CACjF,EACH;QACEf,wBAAwB,CAACoB,IAAI,CAAC;UAC1BZ,EAAE;UACFQ,IAAI;UACJD,OAAO;UACPd,OAAO;UACPoB,kBAAkB,EAAEnB,kBAAkB,CAACoB,aAAa;UACpDC,mBAAmB,EAAElB,mBAAmB,CAACiB,aAAa;UACtDE,WAAW,EAAEtB,kBAAkB,CAACsB,WAAW;UAC3CC,GAAG,EAAE1B,GAAG,CAAC2B,oBAAoB,GAAG3B,GAAG,CAAC2B,oBAAoB,GAAG,IAAI,GAAGC;QACtE,CAAC,CAAmB;MACxB;IACJ;IACA,OAAO3B,wBAAwB;EACnC;AACJ;AAAC4B,OAAA,CAAAhC,sBAAA,GAAAA,sBAAA"}
\ No newline at end of file |