expose remote mute settings
This commit is contained in:
parent
3767f6a420
commit
3198ca6a92
4 changed files with 65 additions and 88 deletions
|
@ -98,7 +98,7 @@ export class PeerCall implements IDisposable {
|
||||||
private _dataChannel?: any;
|
private _dataChannel?: any;
|
||||||
private _hangupReason?: CallErrorCode;
|
private _hangupReason?: CallErrorCode;
|
||||||
private _remoteMedia: RemoteMedia;
|
private _remoteMedia: RemoteMedia;
|
||||||
private remoteMuteSettings?: MuteSettings;
|
private _remoteMuteSettings = new MuteSettings();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private callId: string,
|
private callId: string,
|
||||||
|
@ -166,6 +166,10 @@ export class PeerCall implements IDisposable {
|
||||||
return this._remoteMedia;
|
return this._remoteMedia;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get remoteMuteSettings(): MuteSettings {
|
||||||
|
return this._remoteMuteSettings;
|
||||||
|
}
|
||||||
|
|
||||||
call(localMedia: LocalMedia, localMuteSettings: MuteSettings): Promise<void> {
|
call(localMedia: LocalMedia, localMuteSettings: MuteSettings): Promise<void> {
|
||||||
return this.logItem.wrap("call", async log => {
|
return this.logItem.wrap("call", async log => {
|
||||||
if (this._state !== CallState.Fledgling) {
|
if (this._state !== CallState.Fledgling) {
|
||||||
|
@ -279,7 +283,7 @@ export class PeerCall implements IDisposable {
|
||||||
transceiver.sender.track.enabled = enabled;
|
transceiver.sender.track.enabled = enabled;
|
||||||
}
|
}
|
||||||
log.set("fromDirection", transceiver.direction);
|
log.set("fromDirection", transceiver.direction);
|
||||||
enableSenderOnTransceiver(transceiver, enabled);
|
// enableSenderOnTransceiver(transceiver, enabled);
|
||||||
log.set("toDirection", transceiver.direction);
|
log.set("toDirection", transceiver.direction);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -307,7 +311,7 @@ export class PeerCall implements IDisposable {
|
||||||
await this.handleAnswer(message.content, partyId, log);
|
await this.handleAnswer(message.content, partyId, log);
|
||||||
break;
|
break;
|
||||||
case EventType.Negotiate:
|
case EventType.Negotiate:
|
||||||
await this.handleRemoteNegotiate(message.content, partyId, log);
|
await this.onNegotiateReceived(message.content, log);
|
||||||
break;
|
break;
|
||||||
case EventType.Candidates:
|
case EventType.Candidates:
|
||||||
await this.handleRemoteIceCandidates(message.content, partyId, log);
|
await this.handleRemoteIceCandidates(message.content, partyId, log);
|
||||||
|
@ -344,7 +348,7 @@ export class PeerCall implements IDisposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls are serialized and deduplicated by responsePromiseChain
|
// calls are serialized and deduplicated by responsePromiseChain
|
||||||
private handleNegotiation = async (log: ILogItem): Promise<void> => {
|
private async handleNegotiation(log: ILogItem): Promise<void> {
|
||||||
this.makingOffer = true;
|
this.makingOffer = true;
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
|
@ -429,7 +433,7 @@ export class PeerCall implements IDisposable {
|
||||||
}
|
}
|
||||||
await this.handleInvite(content, partyId, log);
|
await this.handleInvite(content, partyId, log);
|
||||||
// TODO: need to skip state check
|
// TODO: need to skip state check
|
||||||
await this.answer(this.localMedia!);
|
await this.answer(this.localMedia!, this.localMuteSettings!);
|
||||||
} else {
|
} else {
|
||||||
log.log(
|
log.log(
|
||||||
"Glare detected: rejecting incoming call " + newCallId +
|
"Glare detected: rejecting incoming call " + newCallId +
|
||||||
|
@ -546,36 +550,6 @@ export class PeerCall implements IDisposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async handleRemoteNegotiate(content: MCallNegotiate<MCallBase>, partyId: PartyId, log: ILogItem): Promise<void> {
|
|
||||||
if (this._state !== CallState.Connected) {
|
|
||||||
log.log({l: `Ignoring renegotiate because not connected`, status: this._state});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.opponentPartyId !== partyId) {
|
|
||||||
log.log(`Ignoring answer: we already have an answer/reject from ${this.opponentPartyId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sdpStreamMetadata = content[SDPStreamMetadataKey];
|
|
||||||
if (sdpStreamMetadata) {
|
|
||||||
this.updateRemoteSDPStreamMetadata(sdpStreamMetadata, log);
|
|
||||||
} else {
|
|
||||||
log.log(`Did not get any SDPStreamMetadata! Can not send/receive multiple streams`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.peerConnection.setRemoteDescription(content.description);
|
|
||||||
} catch (e) {
|
|
||||||
await log.wrap(`Failed to set remote description`, log => {
|
|
||||||
log.catch(e);
|
|
||||||
this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, log);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleIceGatheringState(state: RTCIceGatheringState, log: ILogItem) {
|
private handleIceGatheringState(state: RTCIceGatheringState, log: ILogItem) {
|
||||||
if (state === 'complete' && !this.sentEndOfCandidates) {
|
if (state === 'complete' && !this.sentEndOfCandidates) {
|
||||||
// If we didn't get an empty-string candidate to signal the end of candidates,
|
// If we didn't get an empty-string candidate to signal the end of candidates,
|
||||||
|
@ -645,55 +619,55 @@ export class PeerCall implements IDisposable {
|
||||||
await this.addIceCandidates(candidates, log);
|
await this.addIceCandidates(candidates, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
// private async onNegotiateReceived(event: MatrixEvent): Promise<void> {
|
private async onNegotiateReceived(content: MCallNegotiate<MCallBase>, log: ILogItem): Promise<void> {
|
||||||
// const content = event.getContent<MCallNegotiate>();
|
const description = content.description;
|
||||||
// const description = content.description;
|
if (!description || !description.sdp || !description.type) {
|
||||||
// if (!description || !description.sdp || !description.type) {
|
log.log(`Ignoring invalid m.call.negotiate event`);
|
||||||
// this.logger.info(`Ignoring invalid m.call.negotiate event`);
|
return;
|
||||||
// return;
|
}
|
||||||
// }
|
// Politeness always follows the direction of the call: in a glare situation,
|
||||||
// // 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
|
||||||
// // we pick either the inbound or outbound call, so one side will always be
|
// inbound and one outbound
|
||||||
// // inbound and one outbound
|
const polite = this.direction === CallDirection.Inbound;
|
||||||
// const polite = this.direction === CallDirection.Inbound;
|
|
||||||
|
|
||||||
// // Here we follow the perfect negotiation logic from
|
// Here we follow the perfect negotiation logic from
|
||||||
// // https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
|
// https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
|
||||||
// const offerCollision = (
|
const offerCollision = (
|
||||||
// (description.type === 'offer') &&
|
(description.type === 'offer') &&
|
||||||
// (this.makingOffer || this.peerConnection.signalingState !== 'stable')
|
(this.makingOffer || this.peerConnection.signalingState !== 'stable')
|
||||||
// );
|
);
|
||||||
|
|
||||||
// this.ignoreOffer = !polite && offerCollision;
|
this.ignoreOffer = !polite && offerCollision;
|
||||||
// if (this.ignoreOffer) {
|
if (this.ignoreOffer) {
|
||||||
// this.logger.info(`Ignoring colliding negotiate event because we're impolite`);
|
log.log(`Ignoring colliding negotiate event because we're impolite`);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const sdpStreamMetadata = content[SDPStreamMetadataKey];
|
const sdpStreamMetadata = content[SDPStreamMetadataKey];
|
||||||
// if (sdpStreamMetadata) {
|
if (sdpStreamMetadata) {
|
||||||
// this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);
|
this.updateRemoteSDPStreamMetadata(sdpStreamMetadata, log);
|
||||||
// } else {
|
} else {
|
||||||
// this.logger.warn(`Received negotiation event without SDPStreamMetadata!`);
|
log.log(`Received negotiation event without SDPStreamMetadata!`);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// await this.peerConnection.setRemoteDescription(description);
|
await this.peerConnection.setRemoteDescription(description);
|
||||||
|
if (description.type === 'offer') {
|
||||||
// if (description.type === 'offer') {
|
await this.peerConnection.setLocalDescription();
|
||||||
// await this.peerConnection.setLocalDescription();
|
const content = {
|
||||||
// await this.sendSignallingMessage({
|
call_id: this.callId,
|
||||||
// type: EventType.CallNegotiate,
|
description: this.peerConnection.localDescription!,
|
||||||
// content: {
|
[SDPStreamMetadataKey]: this.getSDPMetadata(),
|
||||||
// description: this.peerConnection.localDescription!,
|
version: 1,
|
||||||
// [SDPStreamMetadataKey]: this.getSDPMetadata(),
|
seq: this.seq++,
|
||||||
// }
|
lifetime: CALL_TIMEOUT_MS
|
||||||
// });
|
};
|
||||||
// }
|
await this.sendSignallingMessage({type: EventType.Negotiate, content}, log);
|
||||||
// } catch (err) {
|
}
|
||||||
// this.logger.warn(`Failed to complete negotiation`, err);
|
} catch (err) {
|
||||||
// }
|
log.log(`Failed to complete negotiation`, err);
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async sendAnswer(log: ILogItem): Promise<void> {
|
private async sendAnswer(log: ILogItem): Promise<void> {
|
||||||
const localDescription = this.peerConnection.localDescription!;
|
const localDescription = this.peerConnection.localDescription!;
|
||||||
|
@ -980,6 +954,10 @@ export class PeerCall implements IDisposable {
|
||||||
if (videoReceiver) {
|
if (videoReceiver) {
|
||||||
videoReceiver.track.enabled = !metaData.audio_muted;
|
videoReceiver.track.enabled = !metaData.audio_muted;
|
||||||
}
|
}
|
||||||
|
this._remoteMuteSettings = new MuteSettings(
|
||||||
|
metaData.audio_muted || !audioReceiver?.track,
|
||||||
|
metaData.video_muted || !videoReceiver?.track
|
||||||
|
);
|
||||||
} else if (metaData.purpose === SDPStreamMetadataPurpose.Screenshare) {
|
} else if (metaData.purpose === SDPStreamMetadataPurpose.Screenshare) {
|
||||||
this._remoteMedia.screenShare = stream;
|
this._remoteMedia.screenShare = stream;
|
||||||
}
|
}
|
||||||
|
@ -1012,7 +990,8 @@ export class PeerCall implements IDisposable {
|
||||||
// can't replace the track without renegotiating{
|
// can't replace the track without renegotiating{
|
||||||
log.wrap(`adding and removing ${streamPurpose} ${newTrack.kind} track`, log => {
|
log.wrap(`adding and removing ${streamPurpose} ${newTrack.kind} track`, log => {
|
||||||
this.peerConnection.removeTrack(sender);
|
this.peerConnection.removeTrack(sender);
|
||||||
this.peerConnection.addTrack(newTrack);
|
const newSender = this.peerConnection.addTrack(newTrack);
|
||||||
|
this.options.webRTC.prepareSenderForPurpose(this.peerConnection, newSender, streamPurpose);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (!newTrack) {
|
} else if (!newTrack) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// allow non-camelcase as these are events type that go onto the wire
|
// allow non-camelcase as these are events type that go onto the wire
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import type {StateEvent} from "../storage/types";
|
import type {StateEvent} from "../storage/types";
|
||||||
|
import type {SessionDescription} from "../../platform/types/WebRTC";
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
GroupCall = "org.matrix.msc3401.call",
|
GroupCall = "org.matrix.msc3401.call",
|
||||||
GroupCallMember = "org.matrix.msc3401.call.member",
|
GroupCallMember = "org.matrix.msc3401.call.member",
|
||||||
|
@ -36,11 +36,6 @@ export interface CallMemberContent {
|
||||||
["m.calls"]: CallMembership[];
|
["m.calls"]: CallMembership[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SessionDescription {
|
|
||||||
sdp?: string;
|
|
||||||
type: RTCSdpType
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SDPStreamMetadataPurpose {
|
export enum SDPStreamMetadataPurpose {
|
||||||
Usermedia = "m.usermedia",
|
Usermedia = "m.usermedia",
|
||||||
Screenshare = "m.screenshare",
|
Screenshare = "m.screenshare",
|
||||||
|
|
|
@ -65,6 +65,10 @@ export class Member {
|
||||||
return this.peerCall?.remoteMedia;
|
return this.peerCall?.remoteMedia;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get remoteMuteSettings(): MuteSettings | undefined {
|
||||||
|
return this.peerCall?.remoteMuteSettings;
|
||||||
|
}
|
||||||
|
|
||||||
get isConnected(): boolean {
|
get isConnected(): boolean {
|
||||||
return this.peerCall?.state === CallState.Connected;
|
return this.peerCall?.state === CallState.Connected;
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,6 @@ export type TransceiverDirection = "inactive" | "recvonly" | "sendonly" | "sendr
|
||||||
export interface SessionDescription {
|
export interface SessionDescription {
|
||||||
readonly sdp: string;
|
readonly sdp: string;
|
||||||
readonly type: SdpType;
|
readonly type: SdpType;
|
||||||
toJSON(): any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnswerOptions {}
|
export interface AnswerOptions {}
|
||||||
|
|
Reference in a new issue