560 lines
20 KiB
TypeScript
560 lines
20 KiB
TypeScript
import { type MatrixEvent } from "../models/event.ts";
|
|
import { EventType, type TimelineEvents } from "../@types/event.ts";
|
|
import { type RoomMember } from "../models/room-member.ts";
|
|
import { type MCallAnswer, type MCallHangupReject } from "./callEventTypes.ts";
|
|
import { CallFeed } from "./callFeed.ts";
|
|
import { type MatrixClient } from "../client.ts";
|
|
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
|
import { type IScreensharingOpts } from "./mediaHandler.ts";
|
|
import { type GroupCallStats } from "./stats/groupCallStats.ts";
|
|
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",
|
|
PeerConnectionCreated = "peer_connection_created"
|
|
}
|
|
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"
|
|
}
|
|
/** The fallback ICE server to use for STUN or TURN protocols. */
|
|
export declare const FALLBACK_ICE_SERVER = "stun:turn.matrix.org";
|
|
export declare class CallError extends Error {
|
|
readonly code: string;
|
|
constructor(code: CallErrorCode, msg: string, err: Error);
|
|
}
|
|
export declare function genCallID(): string;
|
|
type CallEventType = EventType.CallReplaces | EventType.CallAnswer | EventType.CallSelectAnswer | EventType.CallNegotiate | EventType.CallInvite | EventType.CallCandidates | EventType.CallHangup | EventType.CallReject | EventType.CallSDPStreamMetadataChanged;
|
|
export interface VoipEvent {
|
|
type: "toDevice" | "sendEvent";
|
|
eventType: string;
|
|
userId?: string;
|
|
opponentDeviceId?: string;
|
|
roomId?: string;
|
|
content: TimelineEvents[CallEventType];
|
|
}
|
|
/**
|
|
* 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;
|
|
[CallEvent.PeerConnectionCreated]: (peerConn: RTCPeerConnection, 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 iceReconnectionTimeOut?;
|
|
private inviteTimeout?;
|
|
private readonly removeTrackListeners;
|
|
private remoteOnHold;
|
|
private callStatsAtEnd?;
|
|
private makingOffer;
|
|
private ignoreOffer;
|
|
private isSettingRemoteAnswerPending;
|
|
private responsePromiseChain?;
|
|
private remoteCandidateBuffer;
|
|
private remoteAssertedIdentity?;
|
|
private remoteSDPStreamMetadata?;
|
|
private callLengthInterval?;
|
|
private callStartTime?;
|
|
private opponentDeviceId?;
|
|
private hasOpponentDeviceInfo?;
|
|
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 opts - screen sharing options
|
|
* @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 opts - screen sharing options
|
|
* @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 placeCallFailed;
|
|
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
|