Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/webrtc/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,10 +416,26 @@ export class MatrixCall extends EventEmitter {
return this.localUsermediaFeed?.stream;
}

private get localScreensharingStream(): MediaStream {
public get localScreensharingStream(): MediaStream {
return this.localScreensharingFeed?.stream;
}

public get remoteUsermediaFeed(): CallFeed {
return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);
}

public get remoteScreensharingFeed(): CallFeed {
return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
}

public get remoteUsermediaStream(): MediaStream {
return this.remoteUsermediaFeed?.stream;
}

public get remoteScreensharingStream(): MediaStream {
return this.remoteScreensharingFeed?.stream;
}

private getFeedByStreamId(streamId: string): CallFeed {
return this.getFeeds().find((feed) => feed.stream.id === streamId);
}
Expand Down Expand Up @@ -598,6 +614,24 @@ export class MatrixCall extends EventEmitter {
logger.info(`Pushed local stream (id="${stream.id}", active="${stream.active}", purpose="${purpose}")`);
}

/**
* Removes local call feed from the call and its tracks from the peer
* connection
* @param callFeed to remove
*/
public removeLocalFeed(callFeed: CallFeed): void {
const senderArray = callFeed.purpose === SDPStreamMetadataPurpose.Usermedia
? this.usermediaSenders
: this.screensharingSenders;

for (const sender of senderArray) {
this.peerConn.removeTrack(sender);
}
// Empty the array
senderArray.splice(0, senderArray.length);
this.deleteFeedByStream(callFeed.stream);
}

private deleteAllFeeds(): void {
for (const feed of this.feeds) {
feed.dispose();
Expand Down
61 changes: 42 additions & 19 deletions src/webrtc/callFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import { SDPStreamMetadataPurpose } from "./callEventTypes";
import { MatrixClient } from "../client";
import { RoomMember } from "../models/room-member";

const POLLING_INTERVAL = 250; // ms
const SPEAKING_THRESHOLD = -60; // dB
const POLLING_INTERVAL = 200; // ms
export const SPEAKING_THRESHOLD = -60; // dB
const SPEAKING_SAMPLE_COUNT = 8;

export interface ICallFeedOpts {
client: MatrixClient;
Expand All @@ -43,6 +44,7 @@ export class CallFeed extends EventEmitter {
public stream: MediaStream;
public userId: string;
public purpose: SDPStreamMetadataPurpose;
public speakingVolumeSamples: number[];

private client: MatrixClient;
private roomId: string;
Expand All @@ -65,6 +67,7 @@ export class CallFeed extends EventEmitter {
this.purpose = opts.purpose;
this.audioMuted = opts.audioMuted;
this.videoMuted = opts.videoMuted;
this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity);

this.updateStream(null, opts.stream);

Expand All @@ -78,6 +81,8 @@ export class CallFeed extends EventEmitter {
}

private updateStream(oldStream: MediaStream, newStream: MediaStream): void {
if (newStream === oldStream) return;

if (oldStream) {
oldStream.removeEventListener("addtrack", this.onAddTrack);
this.measureVolumeActivity(false);
Expand Down Expand Up @@ -152,6 +157,10 @@ export class CallFeed extends EventEmitter {
return this.stream.getVideoTracks().length === 0 || this.videoMuted;
}

public isSpeaking(): boolean {
return this.speaking;
}

/**
* Replaces the current MediaStream with a new one.
* This method should be only used by MatrixCall.
Expand All @@ -167,6 +176,7 @@ export class CallFeed extends EventEmitter {
*/
public setAudioMuted(muted: boolean): void {
this.audioMuted = muted;
this.speakingVolumeSamples.fill(-Infinity);
this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted);
}

Expand All @@ -191,6 +201,7 @@ export class CallFeed extends EventEmitter {
this.volumeLooper();
} else {
this.measuringVolumeActivity = false;
this.speakingVolumeSamples.fill(-Infinity);
this.emit(CallFeedEvent.VolumeChanged, -Infinity);
}
}
Expand All @@ -199,31 +210,43 @@ export class CallFeed extends EventEmitter {
this.speakingThreshold = threshold;
}

private volumeLooper(): void {
private volumeLooper = () => {
if (!this.analyser) return;

this.volumeLooperTimeout = setTimeout(() => {
if (!this.measuringVolumeActivity) return;
if (!this.measuringVolumeActivity) return;

this.analyser.getFloatFrequencyData(this.frequencyBinCount);
this.analyser.getFloatFrequencyData(this.frequencyBinCount);

let maxVolume = -Infinity;
for (let i = 0; i < this.frequencyBinCount.length; i++) {
if (this.frequencyBinCount[i] > maxVolume) {
maxVolume = this.frequencyBinCount[i];
}
let maxVolume = -Infinity;
for (let i = 0; i < this.frequencyBinCount.length; i++) {
if (this.frequencyBinCount[i] > maxVolume) {
maxVolume = this.frequencyBinCount[i];
}
}

this.speakingVolumeSamples.shift();
this.speakingVolumeSamples.push(maxVolume);

this.emit(CallFeedEvent.VolumeChanged, maxVolume);

let newSpeaking = false;

this.emit(CallFeedEvent.VolumeChanged, maxVolume);
const newSpeaking = maxVolume > this.speakingThreshold;
if (this.speaking !== newSpeaking) {
this.speaking = newSpeaking;
this.emit(CallFeedEvent.Speaking, this.speaking);
for (let i = 0; i < this.speakingVolumeSamples.length; i++) {
const volume = this.speakingVolumeSamples[i];

if (volume > this.speakingThreshold) {
newSpeaking = true;
break;
}
}

this.volumeLooper();
}, POLLING_INTERVAL);
}
if (this.speaking !== newSpeaking) {
this.speaking = newSpeaking;
this.emit(CallFeedEvent.Speaking, this.speaking);
}

this.volumeLooperTimeout = setTimeout(this.volumeLooper, POLLING_INTERVAL);
};

public dispose(): void {
clearTimeout(this.volumeLooperTimeout);
Expand Down