mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-07 23:15:50 +00:00
Retain remoteusermedia modifications across sessions
This commit is contained in:
+74
-51
@@ -56,6 +56,7 @@ import { platform } from "../Platform";
|
||||
import { type MediaDevices } from "./MediaDevices";
|
||||
import { type Behavior } from "./Behavior";
|
||||
import { type ObservableScope } from "./ObservableScope";
|
||||
import { RemoteUserSetting } from "./RemoteUserSettings";
|
||||
|
||||
export function observeTrackReference$(
|
||||
participant: Participant,
|
||||
@@ -398,7 +399,6 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel {
|
||||
return this._videoEnabled$;
|
||||
}
|
||||
|
||||
private readonly _cropVideo$ = new BehaviorSubject(true);
|
||||
/**
|
||||
* Whether the tile video should be contained inside the tile or be cropped to fit.
|
||||
*/
|
||||
@@ -416,6 +416,7 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel {
|
||||
mxcAvatarUrl$: Behavior<string | undefined>,
|
||||
public readonly handRaised$: Behavior<Date | null>,
|
||||
public readonly reaction$: Behavior<ReactionOption | null>,
|
||||
public readonly _cropVideo$ = new BehaviorSubject(true),
|
||||
) {
|
||||
super(
|
||||
scope,
|
||||
@@ -610,13 +611,71 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
* The volume to which this participant's audio is set, as a scalar
|
||||
* multiplier.
|
||||
*/
|
||||
public readonly localVolume$ = this.scope.behavior<number>(
|
||||
public readonly localVolume$: Behavior<number>;
|
||||
|
||||
// This private field is used to override the value from the superclass
|
||||
private __videoEnabled$: Behavior<boolean>;
|
||||
public get videoEnabled$(): Behavior<boolean> {
|
||||
return this.__videoEnabled$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this participant's audio is disabled.
|
||||
*/
|
||||
public readonly locallyMuted$: Behavior<boolean>;
|
||||
|
||||
private readonly remoteUserSetting: RemoteUserSetting;
|
||||
|
||||
public constructor(
|
||||
scope: ObservableScope,
|
||||
id: string,
|
||||
userId: string,
|
||||
participant$: Observable<RemoteParticipant | null>,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
livekitRoom$: Behavior<LivekitRoom | undefined>,
|
||||
focusUrl$: Behavior<string | undefined>,
|
||||
private readonly pretendToBeDisconnected$: Behavior<boolean>,
|
||||
displayName$: Behavior<string>,
|
||||
mxcAvatarUrl$: Behavior<string | undefined>,
|
||||
handRaised$: Behavior<Date | null>,
|
||||
reaction$: Behavior<ReactionOption | null>,
|
||||
) {
|
||||
const remoteUserSetting = new RemoteUserSetting(userId);
|
||||
super(
|
||||
scope,
|
||||
id,
|
||||
userId,
|
||||
participant$,
|
||||
encryptionSystem,
|
||||
livekitRoom$,
|
||||
focusUrl$,
|
||||
displayName$,
|
||||
mxcAvatarUrl$,
|
||||
handRaised$,
|
||||
reaction$,
|
||||
new BehaviorSubject(remoteUserSetting.cropVideo),
|
||||
);
|
||||
|
||||
this.__speaking$ = this.scope.behavior(
|
||||
pretendToBeDisconnected$.pipe(
|
||||
switchMap((disconnected) =>
|
||||
disconnected ? of(false) : super.speaking$,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
this.remoteUserSetting = remoteUserSetting;
|
||||
const storedVolume = this.remoteUserSetting.getValue().volume;
|
||||
|
||||
this.localVolume$ = this.scope.behavior<number>(
|
||||
merge(
|
||||
this.locallyMutedToggle$.pipe(map(() => "toggle mute" as const)),
|
||||
this.localVolumeAdjustment$,
|
||||
this.localVolumeCommit$.pipe(map(() => "commit" as const)),
|
||||
).pipe(
|
||||
accumulate({ volume: 1, committedVolume: 1 }, (state, event) => {
|
||||
accumulate(
|
||||
{ volume: storedVolume, committedVolume: storedVolume },
|
||||
(state, event) => {
|
||||
switch (event) {
|
||||
case "toggle mute":
|
||||
return {
|
||||
@@ -636,60 +695,16 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
// Volume adjustment
|
||||
return { ...state, volume: event };
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
map(({ volume }) => volume),
|
||||
),
|
||||
);
|
||||
|
||||
// This private field is used to override the value from the superclass
|
||||
private __videoEnabled$: Behavior<boolean>;
|
||||
public get videoEnabled$(): Behavior<boolean> {
|
||||
return this.__videoEnabled$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this participant's audio is disabled.
|
||||
*/
|
||||
public readonly locallyMuted$ = this.scope.behavior<boolean>(
|
||||
this.locallyMuted$ = this.scope.behavior<boolean>(
|
||||
this.localVolume$.pipe(map((volume) => volume === 0)),
|
||||
);
|
||||
|
||||
public constructor(
|
||||
scope: ObservableScope,
|
||||
id: string,
|
||||
userId: string,
|
||||
participant$: Observable<RemoteParticipant | null>,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
livekitRoom$: Behavior<LivekitRoom | undefined>,
|
||||
focusUrl$: Behavior<string | undefined>,
|
||||
private readonly pretendToBeDisconnected$: Behavior<boolean>,
|
||||
displayName$: Behavior<string>,
|
||||
mxcAvatarUrl$: Behavior<string | undefined>,
|
||||
handRaised$: Behavior<Date | null>,
|
||||
reaction$: Behavior<ReactionOption | null>,
|
||||
) {
|
||||
super(
|
||||
scope,
|
||||
id,
|
||||
userId,
|
||||
participant$,
|
||||
encryptionSystem,
|
||||
livekitRoom$,
|
||||
focusUrl$,
|
||||
displayName$,
|
||||
mxcAvatarUrl$,
|
||||
handRaised$,
|
||||
reaction$,
|
||||
);
|
||||
|
||||
this.__speaking$ = this.scope.behavior(
|
||||
pretendToBeDisconnected$.pipe(
|
||||
switchMap((disconnected) =>
|
||||
disconnected ? of(false) : super.speaking$,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
this.__videoEnabled$ = this.scope.behavior(
|
||||
pretendToBeDisconnected$.pipe(
|
||||
switchMap((disconnected) =>
|
||||
@@ -708,7 +723,10 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
switchMap((disconnected) => (disconnected ? of(0) : this.localVolume$)),
|
||||
this.scope.bind(),
|
||||
),
|
||||
]).subscribe(([p, volume]) => p?.setVolume(volume));
|
||||
]).subscribe(([p, volume]) => {
|
||||
p?.setVolume(volume);
|
||||
this.remoteUserSetting.volume = volume;
|
||||
});
|
||||
}
|
||||
|
||||
public toggleLocallyMuted(): void {
|
||||
@@ -723,6 +741,11 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
||||
this.localVolumeCommit$.next();
|
||||
}
|
||||
|
||||
public toggleFitContain(): void {
|
||||
super.toggleFitContain();
|
||||
this.remoteUserSetting.cropVideo = this._cropVideo$.value;
|
||||
}
|
||||
|
||||
public audioStreamStats$ = combineLatest([
|
||||
this.participant$,
|
||||
showConnectionStats.value$,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Setting } from "../settings/settings";
|
||||
|
||||
export interface RemoteUserSettingData {
|
||||
volume: number;
|
||||
cropVideo: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of local modifications for a remote user's media that should persist
|
||||
* across calls.
|
||||
*/
|
||||
export class RemoteUserSetting extends Setting<RemoteUserSettingData> {
|
||||
constructor(userId: string) {
|
||||
super(`remoteusersettings-${userId}`, { volume: 1, cropVideo: true });
|
||||
}
|
||||
|
||||
public set volume(volume: number) {
|
||||
this.setValue({
|
||||
...this.getValue(),
|
||||
volume,
|
||||
});
|
||||
}
|
||||
|
||||
public set cropVideo(cropVideo: boolean) {
|
||||
this.setValue({
|
||||
...this.getValue(),
|
||||
cropVideo,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user