expose remote mute settings

This commit is contained in:
Bruno Windels 2022-04-26 14:20:44 +02:00
parent 3767f6a420
commit 3198ca6a92
4 changed files with 65 additions and 88 deletions

View file

@ -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) {

View file

@ -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",

View file

@ -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;
} }

View file

@ -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 {}