add muting again, separate from changing media
This commit is contained in:
parent
ac60d1b61d
commit
cdb2a79b62
5 changed files with 111 additions and 14 deletions
|
@ -57,7 +57,7 @@ export class CallViewModel extends ViewModel<Options> {
|
|||
}
|
||||
|
||||
async toggleVideo() {
|
||||
//this.call.setMuted();
|
||||
this.call.setMuted(this.call.muteSettings.toggleCamera());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import {ObservableMap} from "../../observable/map/ObservableMap";
|
||||
import {recursivelyAssign} from "../../utils/recursivelyAssign";
|
||||
import {Disposables, Disposable, IDisposable} from "../../utils/Disposables";
|
||||
import {WebRTC, PeerConnection, Receiver, PeerConnectionEventMap} from "../../platform/types/WebRTC";
|
||||
import {WebRTC, PeerConnection, Transceiver, TransceiverDirection, Sender, Receiver, PeerConnectionEventMap} from "../../platform/types/WebRTC";
|
||||
import {MediaDevices, Track, TrackKind, Stream, StreamTrackEvent} from "../../platform/types/MediaDevices";
|
||||
import {getStreamVideoTrack, getStreamAudioTrack, MuteSettings} from "./common";
|
||||
import {
|
||||
|
@ -166,13 +166,14 @@ export class PeerCall implements IDisposable {
|
|||
return this._remoteMedia;
|
||||
}
|
||||
|
||||
call(localMedia: LocalMedia): Promise<void> {
|
||||
call(localMedia: LocalMedia, localMuteSettings: MuteSettings): Promise<void> {
|
||||
return this.logItem.wrap("call", async log => {
|
||||
if (this._state !== CallState.Fledgling) {
|
||||
return;
|
||||
}
|
||||
this.direction = CallDirection.Outbound;
|
||||
this.setState(CallState.CreateOffer, log);
|
||||
this.localMuteSettings = localMuteSettings;
|
||||
await this.updateLocalMedia(localMedia, log);
|
||||
if (this.localMedia?.dataChannelOptions) {
|
||||
this._dataChannel = this.peerConnection.createDataChannel("channel", this.localMedia.dataChannelOptions);
|
||||
|
@ -183,12 +184,13 @@ export class PeerCall implements IDisposable {
|
|||
});
|
||||
}
|
||||
|
||||
answer(localMedia: LocalMedia): Promise<void> {
|
||||
answer(localMedia: LocalMedia, localMuteSettings: MuteSettings): Promise<void> {
|
||||
return this.logItem.wrap("answer", async log => {
|
||||
if (this._state !== CallState.Ringing) {
|
||||
return;
|
||||
}
|
||||
this.setState(CallState.CreateAnswer, log);
|
||||
this.localMuteSettings = localMuteSettings;
|
||||
await this.updateLocalMedia(localMedia, log);
|
||||
let myAnswer: RTCSessionDescriptionInit;
|
||||
try {
|
||||
|
@ -235,12 +237,54 @@ export class PeerCall implements IDisposable {
|
|||
});
|
||||
}
|
||||
|
||||
setMuted(localMuteSettings: MuteSettings) {
|
||||
return this.logItem.wrap("setMuted", async log => {
|
||||
this.localMuteSettings = localMuteSettings;
|
||||
log.set("cameraMuted", localMuteSettings.camera);
|
||||
log.set("microphoneMuted", localMuteSettings.microphone);
|
||||
|
||||
if (this.localMedia) {
|
||||
const userMediaAudio = getStreamAudioTrack(this.localMedia.userMedia);
|
||||
if (userMediaAudio) {
|
||||
this.muteTrack(userMediaAudio, this.localMuteSettings.microphone, log);
|
||||
}
|
||||
const userMediaVideo = getStreamVideoTrack(this.localMedia.userMedia);
|
||||
if (userMediaVideo) {
|
||||
this.muteTrack(userMediaVideo, this.localMuteSettings.camera, log);
|
||||
}
|
||||
const content: MCallSDPStreamMetadataChanged<MCallBase> = {
|
||||
call_id: this.callId,
|
||||
version: 1,
|
||||
seq: this.seq++,
|
||||
[SDPStreamMetadataKey]: this.getSDPMetadata()
|
||||
};
|
||||
await this.sendSignallingMessage({type: EventType.SDPStreamMetadataChangedPrefix, content}, log);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hangup(errorCode: CallErrorCode): Promise<void> {
|
||||
return this.logItem.wrap("hangup", log => {
|
||||
return this._hangup(errorCode, log);
|
||||
});
|
||||
}
|
||||
|
||||
private muteTrack(track: Track, muted: boolean, log: ILogItem): void {
|
||||
log.wrap({l: "track", kind: track.kind, id: track.id}, log => {
|
||||
const enabled = !muted;
|
||||
log.set("enabled", enabled);
|
||||
const transceiver = this.findTransceiverForTrack(track);
|
||||
if (transceiver) {
|
||||
if (transceiver.sender.track) {
|
||||
transceiver.sender.track.enabled = enabled;
|
||||
}
|
||||
log.set("fromDirection", transceiver.direction);
|
||||
enableSenderOnTransceiver(transceiver, enabled);
|
||||
log.set("toDirection", transceiver.direction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _hangup(errorCode: CallErrorCode, log: ILogItem): Promise<void> {
|
||||
if (this._state === CallState.Ended) {
|
||||
return;
|
||||
|
@ -872,10 +916,17 @@ export class PeerCall implements IDisposable {
|
|||
|
||||
private findReceiverForStream(kind: TrackKind, streamId: string): Receiver | undefined {
|
||||
return this.peerConnection.getReceivers().find(r => {
|
||||
return r.track.kind === "audio" && this._remoteTrackToStreamId.get(r.track.id) === streamId;
|
||||
return r.track.kind === kind && this._remoteTrackToStreamId.get(r.track.id) === streamId;
|
||||
});
|
||||
}
|
||||
|
||||
private findTransceiverForTrack(track: Track): Transceiver | undefined {
|
||||
return this.peerConnection.getTransceivers().find(t => {
|
||||
return t.sender.track?.id === track.id;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private onRemoteTrack(track: Track, streams: ReadonlyArray<Stream>, log: ILogItem) {
|
||||
if (streams.length === 0) {
|
||||
log.log({l: `ignoring ${track.kind} streamless track`, id: track.id});
|
||||
|
@ -1072,7 +1123,22 @@ export function handlesEventType(eventType: string): boolean {
|
|||
eventType === EventType.Negotiate;
|
||||
}
|
||||
|
||||
|
||||
export function tests() {
|
||||
|
||||
function enableSenderOnTransceiver(transceiver: Transceiver, enabled: boolean) {
|
||||
return enableTransceiver(transceiver, enabled, "sendonly", "recvonly");
|
||||
}
|
||||
|
||||
function enableTransceiver(transceiver: Transceiver, enabled: boolean, exclusiveValue: TransceiverDirection, excludedValue: TransceiverDirection) {
|
||||
if (enabled) {
|
||||
if (transceiver.direction === "inactive") {
|
||||
transceiver.direction = exclusiveValue;
|
||||
} else {
|
||||
transceiver.direction = "sendrecv";
|
||||
}
|
||||
} else {
|
||||
if (transceiver.direction === "sendrecv") {
|
||||
transceiver.direction = excludedValue;
|
||||
} else {
|
||||
transceiver.direction = "inactive";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,4 +26,12 @@ export function getStreamVideoTrack(stream: Stream | undefined): Track | undefin
|
|||
|
||||
export class MuteSettings {
|
||||
constructor (public readonly microphone: boolean, public readonly camera: boolean) {}
|
||||
|
||||
toggleCamera(): MuteSettings {
|
||||
return new MuteSettings(this.microphone, !this.camera);
|
||||
}
|
||||
|
||||
toggleMicrophone(): MuteSettings {
|
||||
return new MuteSettings(!this.microphone, this.camera);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
import {ObservableMap} from "../../../observable/map/ObservableMap";
|
||||
import {Member} from "./Member";
|
||||
import {LocalMedia} from "../LocalMedia";
|
||||
import {MuteSettings} from "../common";
|
||||
import {RoomMember} from "../../room/members/RoomMember";
|
||||
import {EventEmitter} from "../../../utils/EventEmitter";
|
||||
import {EventType, CallIntent} from "../callEventTypes";
|
||||
|
@ -63,6 +64,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
|
|||
private _localMedia?: LocalMedia = undefined;
|
||||
private _memberOptions: MemberOptions;
|
||||
private _state: GroupCallState;
|
||||
private localMuteSettings: MuteSettings = new MuteSettings(false, false);
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
|
@ -118,7 +120,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
|
|||
this.emitChange();
|
||||
// send invite to all members that are < my userId
|
||||
for (const [,member] of this._members) {
|
||||
member.connect(this._localMedia!.clone());
|
||||
member.connect(this._localMedia!.clone(), this.localMuteSettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -134,6 +136,19 @@ export class GroupCall extends EventEmitter<{change: never}> {
|
|||
}
|
||||
}
|
||||
|
||||
setMuted(muteSettings: MuteSettings) {
|
||||
this.localMuteSettings = muteSettings;
|
||||
if (this._state === GroupCallState.Joining || this._state === GroupCallState.Joined) {
|
||||
for (const [,member] of this._members) {
|
||||
member.setMuted(this.localMuteSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get muteSettings(): MuteSettings {
|
||||
return this.localMuteSettings;
|
||||
}
|
||||
|
||||
get hasJoined() {
|
||||
return this._state === GroupCallState.Joining || this._state === GroupCallState.Joined;
|
||||
}
|
||||
|
@ -230,7 +245,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
|
|||
this._members.add(memberKey, member);
|
||||
if (this._state === GroupCallState.Joining || this._state === GroupCallState.Joined) {
|
||||
// Safari can't send a MediaStream to multiple sources, so clone it
|
||||
member.connect(this._localMedia!.clone());
|
||||
member.connect(this._localMedia!.clone(), this.localMuteSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import {makeTxnId, makeId} from "../../common";
|
|||
import {EventType, CallErrorCode} from "../callEventTypes";
|
||||
import {formatToDeviceMessagesPayload} from "../../common";
|
||||
|
||||
import type {MuteSettings} from "../common";
|
||||
import type {Options as PeerCallOptions, RemoteMedia} from "../PeerCall";
|
||||
import type {LocalMedia} from "../LocalMedia";
|
||||
import type {HomeServerApi} from "../../net/HomeServerApi";
|
||||
|
@ -50,6 +51,7 @@ const errorCodesWithoutRetry = [
|
|||
export class Member {
|
||||
private peerCall?: PeerCall;
|
||||
private localMedia?: LocalMedia;
|
||||
private localMuteSettings?: MuteSettings;
|
||||
private retryCount: number = 0;
|
||||
|
||||
constructor(
|
||||
|
@ -80,9 +82,10 @@ export class Member {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
connect(localMedia: LocalMedia) {
|
||||
connect(localMedia: LocalMedia, localMuteSettings: MuteSettings) {
|
||||
this.logItem.wrap("connect", () => {
|
||||
this.localMedia = localMedia;
|
||||
this.localMuteSettings = localMuteSettings;
|
||||
// otherwise wait for it to connect
|
||||
let shouldInitiateCall;
|
||||
// the lexicographically lower side initiates the call
|
||||
|
@ -93,7 +96,7 @@ export class Member {
|
|||
}
|
||||
if (shouldInitiateCall) {
|
||||
this.peerCall = this._createPeerCall(makeId("c"));
|
||||
this.peerCall.call(localMedia);
|
||||
this.peerCall.call(localMedia, localMuteSettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -121,7 +124,7 @@ export class Member {
|
|||
/** @internal */
|
||||
emitUpdate = (peerCall: PeerCall, params: any) => {
|
||||
if (peerCall.state === CallState.Ringing) {
|
||||
peerCall.answer(this.localMedia!);
|
||||
peerCall.answer(this.localMedia!, this.localMuteSettings!);
|
||||
}
|
||||
else if (peerCall.state === CallState.Ended) {
|
||||
const hangupReason = peerCall.hangupReason;
|
||||
|
@ -130,7 +133,7 @@ export class Member {
|
|||
if (hangupReason && !errorCodesWithoutRetry.includes(hangupReason)) {
|
||||
this.retryCount += 1;
|
||||
if (this.retryCount <= 3) {
|
||||
this.connect(this.localMedia!);
|
||||
this.connect(this.localMedia!, this.localMuteSettings!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +193,11 @@ export class Member {
|
|||
await this.peerCall?.setMedia(this.localMedia);
|
||||
}
|
||||
|
||||
setMuted(muteSettings: MuteSettings) {
|
||||
this.localMuteSettings = muteSettings;
|
||||
this.peerCall?.setMuted(muteSettings);
|
||||
}
|
||||
|
||||
private _createPeerCall(callId: string): PeerCall {
|
||||
return new PeerCall(callId, Object.assign({}, this.options, {
|
||||
emitUpdate: this.emitUpdate,
|
||||
|
|
Reference in a new issue