Add onPipMediaOrientationUpdate to controls api.

This commit is contained in:
Timo K
2026-06-01 16:12:57 +02:00
parent 72b0d12d7c
commit a6fc710d8c
4 changed files with 38 additions and 5 deletions

View File

@@ -12,6 +12,7 @@ export interface Controls {
canEnterPip(): boolean; canEnterPip(): boolean;
enablePip(): void; enablePip(): void;
disablePip(): void; disablePip(): void;
onPipMediaOrientationUpdate?: (orientation: "landscape" | "portrait") => void;
setAvailableAudioDevices(devices: OutputDevice[]): void; setAvailableAudioDevices(devices: OutputDevice[]): void;
setAudioDevice(id: string): void; setAudioDevice(id: string): void;

View File

@@ -1191,6 +1191,31 @@ export function createCallViewModel$(
})), })),
); );
spotlight$
.pipe(
switchMap((media) => {
let layout;
switch (media[0].type) {
case "user":
layout = media[0].videoOrientation$;
break;
case "ringing":
layout = of("landscape" as const);
break;
case "screen share":
layout = of("landscape" as const);
break;
}
return layout;
}),
tap((orientation) => {
logger.info("controls api pip orientation updated:", orientation);
window.controls.onPipMediaOrientationUpdate?.(orientation);
}),
scope.bind(),
)
.subscribe();
/** /**
* The media to be used to produce a layout. * The media to be used to produce a layout.
*/ */

View File

@@ -48,6 +48,7 @@ export interface BaseUserMediaViewModel extends BaseMemberMediaViewModel {
audioEnabled$: Behavior<boolean>; audioEnabled$: Behavior<boolean>;
videoEnabled$: Behavior<boolean>; videoEnabled$: Behavior<boolean>;
videoFit$: Behavior<"cover" | "contain">; videoFit$: Behavior<"cover" | "contain">;
videoOrientation$: Behavior<"landscape" | "portrait">;
toggleCropVideo: () => void; toggleCropVideo: () => void;
/** /**
* The expected identity of the LiveKit participant. Exposed for debugging. * The expected identity of the LiveKit participant. Exposed for debugging.
@@ -104,6 +105,7 @@ export function createBaseUserMedia(
{ width: number; height: number } | undefined { width: number; height: number } | undefined
>(undefined); >(undefined);
const videoSize$ = videoSizeFromParticipant$(participant$);
return { return {
...createMemberMedia(scope, { ...createMemberMedia(scope, {
...inputs, ...inputs,
@@ -129,11 +131,14 @@ export function createBaseUserMedia(
videoEnabled$: scope.behavior( videoEnabled$: scope.behavior(
media$.pipe(map((m) => m?.cameraTrack?.isMuted === false)), media$.pipe(map((m) => m?.cameraTrack?.isMuted === false)),
), ),
videoFit$: videoFit$( videoOrientation$: scope.behavior(
scope, videoSize$.pipe(
videoSizeFromParticipant$(participant$), map((s) => (s ? s.width / s.height : 1)),
targetSize$, map((aspect) => (aspect > 1 ? "landscape" : "portrait")),
),
"portrait",
), ),
videoFit$: videoFit$(scope, videoSize$, targetSize$),
toggleCropVideo: () => toggleCropVideo$.next(), toggleCropVideo: () => toggleCropVideo$.next(),
rtcBackendIdentity, rtcBackendIdentity,
handRaised$, handRaised$,

View File

@@ -32,7 +32,9 @@ export function observeRtpStreamStats$(
> { > {
return combineLatest([ return combineLatest([
observeTrackReference$(participant, source), observeTrackReference$(participant, source),
interval(1000).pipe(startWith(0)), // The update frequency is high because we use this value to update the PiP oreintation and the fit/fill video tile props based on that
// We want it to be responsive. For just the debug tools 1s would be sufficient.
interval(350).pipe(startWith(0)),
]).pipe( ]).pipe(
switchMap(async ([trackReference]) => { switchMap(async ([trackReference]) => {
const track = trackReference?.publication?.track; const track = trackReference?.publication?.track;