mirror of
https://github.com/vector-im/element-call.git
synced 2026-02-08 04:19:11 +00:00
* Refactor media devices to live outside React as Observables This moves the media devices state out of React to further our transition to a MVVM architecture in which we can more easily model and store complex application state. I have created an AppViewModel to act as the overarching state holder for any future non-React state we end up creating, and the MediaDevices reside within this. We should move more application logic (including the CallViewModel itself) there in the future. * Address review feedback * Fixes from ios debugging session: (#3342) - dont use preferred vs selected concept in controlled media. Its not needed since we dont use the id for actual browser media devices (the id's are not even actual browser media devices) - add more logging - add more conditions to not accidently set a deviceId that is not a browser deviceId but one provided via controlled. --------- Co-authored-by: Timo <16718859+toger5@users.noreply.github.com>
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import {
|
|
fromEvent,
|
|
map,
|
|
merge,
|
|
type Observable,
|
|
of,
|
|
startWith,
|
|
switchMap,
|
|
} from "rxjs";
|
|
import {
|
|
facingModeFromLocalTrack,
|
|
type LocalVideoTrack,
|
|
TrackEvent,
|
|
} from "livekit-client";
|
|
import { useObservable, useObservableEagerState } from "observable-hooks";
|
|
import { logger } from "matrix-js-sdk/lib/logger";
|
|
|
|
import { useMediaDevices } from "../MediaDevicesContext";
|
|
import { platform } from "../Platform";
|
|
import { useLatest } from "../useLatest";
|
|
|
|
/**
|
|
* Determines whether the user should be shown a button to switch their camera,
|
|
* producing a callback if so.
|
|
*/
|
|
export function useSwitchCamera(
|
|
video$: Observable<LocalVideoTrack | null>,
|
|
): (() => void) | null {
|
|
const mediaDevices = useMediaDevices();
|
|
const setVideoInput = useLatest(mediaDevices.videoInput.select);
|
|
|
|
// Produce an observable like the input 'video' observable, except make it
|
|
// emit whenever the track is muted or the device changes
|
|
const videoTrack$: Observable<LocalVideoTrack | null> = useObservable(
|
|
(inputs$) =>
|
|
inputs$.pipe(
|
|
switchMap(([video$]) => video$),
|
|
switchMap((video) => {
|
|
if (video === null) return of(null);
|
|
return merge(
|
|
fromEvent(video, TrackEvent.Restarted).pipe(
|
|
startWith(null),
|
|
map(() => video),
|
|
),
|
|
fromEvent(video, TrackEvent.Muted).pipe(map(() => null)),
|
|
);
|
|
}),
|
|
),
|
|
[video$],
|
|
);
|
|
|
|
const switchCamera$: Observable<(() => void) | null> = useObservable(
|
|
(inputs$) =>
|
|
platform === "desktop"
|
|
? of(null)
|
|
: inputs$.pipe(
|
|
switchMap(([track$]) => track$),
|
|
map((track) => {
|
|
if (track === null) return null;
|
|
const facingMode = facingModeFromLocalTrack(track).facingMode;
|
|
// If the camera isn't front or back-facing, don't provide a switch
|
|
// camera shortcut at all
|
|
if (facingMode !== "user" && facingMode !== "environment")
|
|
return null;
|
|
// Restart the track with a camera facing the opposite direction
|
|
return (): void =>
|
|
void track
|
|
.restartTrack({
|
|
facingMode: facingMode === "user" ? "environment" : "user",
|
|
})
|
|
.then(() => {
|
|
// Inform the MediaDeviceContext which camera was chosen
|
|
const deviceId =
|
|
track.mediaStreamTrack.getSettings().deviceId;
|
|
if (deviceId !== undefined) setVideoInput.current(deviceId);
|
|
})
|
|
.catch((e) =>
|
|
logger.error("Failed to switch camera", facingMode, e),
|
|
);
|
|
}),
|
|
),
|
|
[videoTrack$],
|
|
);
|
|
|
|
return useObservableEagerState(switchCamera$);
|
|
}
|