mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-08 20:09:19 +00:00
Retain remoteusermedia modifications across sessions
This commit is contained in:
@@ -56,6 +56,7 @@ import { platform } from "../Platform";
|
|||||||
import { type MediaDevices } from "./MediaDevices";
|
import { type MediaDevices } from "./MediaDevices";
|
||||||
import { type Behavior } from "./Behavior";
|
import { type Behavior } from "./Behavior";
|
||||||
import { type ObservableScope } from "./ObservableScope";
|
import { type ObservableScope } from "./ObservableScope";
|
||||||
|
import { RemoteUserSetting } from "./RemoteUserSettings";
|
||||||
|
|
||||||
export function observeTrackReference$(
|
export function observeTrackReference$(
|
||||||
participant: Participant,
|
participant: Participant,
|
||||||
@@ -398,7 +399,6 @@ abstract class BaseUserMediaViewModel extends BaseMediaViewModel {
|
|||||||
return this._videoEnabled$;
|
return this._videoEnabled$;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _cropVideo$ = new BehaviorSubject(true);
|
|
||||||
/**
|
/**
|
||||||
* Whether the tile video should be contained inside the tile or be cropped to fit.
|
* 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>,
|
mxcAvatarUrl$: Behavior<string | undefined>,
|
||||||
public readonly handRaised$: Behavior<Date | null>,
|
public readonly handRaised$: Behavior<Date | null>,
|
||||||
public readonly reaction$: Behavior<ReactionOption | null>,
|
public readonly reaction$: Behavior<ReactionOption | null>,
|
||||||
|
public readonly _cropVideo$ = new BehaviorSubject(true),
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
scope,
|
scope,
|
||||||
@@ -610,13 +611,71 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
|||||||
* The volume to which this participant's audio is set, as a scalar
|
* The volume to which this participant's audio is set, as a scalar
|
||||||
* multiplier.
|
* 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(
|
merge(
|
||||||
this.locallyMutedToggle$.pipe(map(() => "toggle mute" as const)),
|
this.locallyMutedToggle$.pipe(map(() => "toggle mute" as const)),
|
||||||
this.localVolumeAdjustment$,
|
this.localVolumeAdjustment$,
|
||||||
this.localVolumeCommit$.pipe(map(() => "commit" as const)),
|
this.localVolumeCommit$.pipe(map(() => "commit" as const)),
|
||||||
).pipe(
|
).pipe(
|
||||||
accumulate({ volume: 1, committedVolume: 1 }, (state, event) => {
|
accumulate(
|
||||||
|
{ volume: storedVolume, committedVolume: storedVolume },
|
||||||
|
(state, event) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "toggle mute":
|
case "toggle mute":
|
||||||
return {
|
return {
|
||||||
@@ -636,60 +695,16 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
|||||||
// Volume adjustment
|
// Volume adjustment
|
||||||
return { ...state, volume: event };
|
return { ...state, volume: event };
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
map(({ volume }) => volume),
|
map(({ volume }) => volume),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// This private field is used to override the value from the superclass
|
this.locallyMuted$ = this.scope.behavior<boolean>(
|
||||||
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.localVolume$.pipe(map((volume) => volume === 0)),
|
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(
|
this.__videoEnabled$ = this.scope.behavior(
|
||||||
pretendToBeDisconnected$.pipe(
|
pretendToBeDisconnected$.pipe(
|
||||||
switchMap((disconnected) =>
|
switchMap((disconnected) =>
|
||||||
@@ -708,7 +723,10 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
|||||||
switchMap((disconnected) => (disconnected ? of(0) : this.localVolume$)),
|
switchMap((disconnected) => (disconnected ? of(0) : this.localVolume$)),
|
||||||
this.scope.bind(),
|
this.scope.bind(),
|
||||||
),
|
),
|
||||||
]).subscribe(([p, volume]) => p?.setVolume(volume));
|
]).subscribe(([p, volume]) => {
|
||||||
|
p?.setVolume(volume);
|
||||||
|
this.remoteUserSetting.volume = volume;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleLocallyMuted(): void {
|
public toggleLocallyMuted(): void {
|
||||||
@@ -723,6 +741,11 @@ export class RemoteUserMediaViewModel extends BaseUserMediaViewModel {
|
|||||||
this.localVolumeCommit$.next();
|
this.localVolumeCommit$.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleFitContain(): void {
|
||||||
|
super.toggleFitContain();
|
||||||
|
this.remoteUserSetting.cropVideo = this._cropVideo$.value;
|
||||||
|
}
|
||||||
|
|
||||||
public audioStreamStats$ = combineLatest([
|
public audioStreamStats$ = combineLatest([
|
||||||
this.participant$,
|
this.participant$,
|
||||||
showConnectionStats.value$,
|
showConnectionStats.value$,
|
||||||
|
|||||||
30
src/state/RemoteUserSettings.ts
Normal file
30
src/state/RemoteUserSettings.ts
Normal file
@@ -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