Always deal with MediaItem at the LayoutMedia layer

That way we only convert to the view model when we do LayoutMedia => Layout
This commit is contained in:
Hugh Nimmo-Smith 2024-11-14 12:08:17 +00:00
parent 84e172a2ba
commit cfea4bd50c
5 changed files with 51 additions and 44 deletions

View File

@ -91,37 +91,37 @@ const showFooterMs = 4000;
export interface GridLayoutMedia { export interface GridLayoutMedia {
type: "grid"; type: "grid";
spotlight?: MediaViewModel[]; spotlight?: MediaItem[];
grid: UserMediaViewModel[]; grid: UserMedia[];
} }
export interface SpotlightLandscapeLayoutMedia { export interface SpotlightLandscapeLayoutMedia {
type: "spotlight-landscape"; type: "spotlight-landscape";
spotlight: MediaViewModel[]; spotlight: MediaItem[];
grid: UserMediaViewModel[]; grid: UserMedia[];
} }
export interface SpotlightPortraitLayoutMedia { export interface SpotlightPortraitLayoutMedia {
type: "spotlight-portrait"; type: "spotlight-portrait";
spotlight: MediaViewModel[]; spotlight: MediaItem[];
grid: UserMediaViewModel[]; grid: UserMedia[];
} }
export interface SpotlightExpandedLayoutMedia { export interface SpotlightExpandedLayoutMedia {
type: "spotlight-expanded"; type: "spotlight-expanded";
spotlight: MediaViewModel[]; spotlight: MediaItem[];
pip?: UserMediaViewModel; pip?: UserMedia;
} }
export interface OneOnOneLayoutMedia { export interface OneOnOneLayoutMedia {
type: "one-on-one"; type: "one-on-one";
local: UserMediaViewModel; local: UserMedia;
remote: UserMediaViewModel; remote: UserMedia;
} }
export interface PipLayoutMedia { export interface PipLayoutMedia {
type: "pip"; type: "pip";
spotlight: MediaViewModel[]; spotlight: MediaItem[];
} }
export type LayoutMedia = export type LayoutMedia =
@ -478,10 +478,9 @@ export class CallViewModel extends ViewModel {
), ),
); );
private readonly localUserMedia: Observable<LocalUserMediaViewModel> = private readonly localUserMedia: Observable<UserMedia> = this.userMedia.pipe(
this.mediaItems.pipe( map((ms) => ms.find((m) => m.vm.local) as UserMedia),
map((ms) => ms.find((m) => m.vm.local)!.vm as LocalUserMediaViewModel), );
);
private readonly screenShares: Observable<ScreenShare[]> = private readonly screenShares: Observable<ScreenShare[]> =
this.mediaItems.pipe( this.mediaItems.pipe(
@ -491,7 +490,7 @@ export class CallViewModel extends ViewModel {
this.scope.state(), this.scope.state(),
); );
private readonly spotlightSpeaker: Observable<UserMediaViewModel> = private readonly spotlightSpeaker: Observable<UserMedia> =
this.userMedia.pipe( this.userMedia.pipe(
switchMap((mediaItems) => switchMap((mediaItems) =>
mediaItems.length === 0 mediaItems.length === 0
@ -523,11 +522,10 @@ export class CallViewModel extends ViewModel {
}, },
null, null,
), ),
map((speaker) => speaker.vm),
this.scope.state(), this.scope.state(),
); );
private readonly grid: Observable<UserMediaViewModel[]> = this.userMedia.pipe( private readonly grid: Observable<UserMedia[]> = this.userMedia.pipe(
switchMap((mediaItems) => { switchMap((mediaItems) => {
const bins = mediaItems.map((m) => const bins = mediaItems.map((m) =>
combineLatest( combineLatest(
@ -558,27 +556,27 @@ export class CallViewModel extends ViewModel {
return bins.length === 0 return bins.length === 0
? of([]) ? of([])
: combineLatest(bins, (...bins) => : combineLatest(bins, (...bins) =>
bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m.vm), bins.sort(([, bin1], [, bin2]) => bin1 - bin2).map(([m]) => m),
); );
}), }),
); );
private readonly spotlightAndPip: Observable< private readonly spotlightAndPip: Observable<
[Observable<MediaViewModel[]>, Observable<UserMediaViewModel | null>] [Observable<MediaItem[]>, Observable<UserMedia | null>]
> = this.screenShares.pipe( > = this.screenShares.pipe(
map((screenShares) => map((screenShares) =>
screenShares.length > 0 screenShares.length > 0
? ([of(screenShares.map((m) => m.vm)), this.spotlightSpeaker] as const) ? ([of(screenShares.map((m) => m)), this.spotlightSpeaker] as const)
: ([ : ([
this.spotlightSpeaker.pipe(map((speaker) => [speaker!])), this.spotlightSpeaker.pipe(map((speaker) => [speaker!])),
this.spotlightSpeaker.pipe( this.spotlightSpeaker.pipe(
switchMap((speaker) => switchMap((speaker) =>
speaker.local speaker.vm.local
? of(null) ? of(null)
: this.localUserMedia.pipe( : this.localUserMedia.pipe(
switchMap((vm) => switchMap((lm) =>
vm.alwaysShow.pipe( (lm.vm as LocalUserMediaViewModel).alwaysShow.pipe(
map((alwaysShow) => (alwaysShow ? vm : null)), map((alwaysShow) => (alwaysShow ? lm : null)),
), ),
), ),
), ),
@ -588,7 +586,7 @@ export class CallViewModel extends ViewModel {
), ),
); );
private readonly spotlight: Observable<MediaViewModel[]> = private readonly spotlight: Observable<MediaItem[]> =
this.spotlightAndPip.pipe( this.spotlightAndPip.pipe(
switchMap(([spotlight]) => spotlight), switchMap(([spotlight]) => spotlight),
this.scope.state(), this.scope.state(),
@ -597,12 +595,14 @@ export class CallViewModel extends ViewModel {
private readonly hasRemoteScreenShares: Observable<boolean> = private readonly hasRemoteScreenShares: Observable<boolean> =
this.spotlight.pipe( this.spotlight.pipe(
map((spotlight) => map((spotlight) =>
spotlight.some((vm) => !vm.local && vm instanceof ScreenShareViewModel), spotlight.some(
(m) => !m.vm.local && m.vm instanceof ScreenShareViewModel,
),
), ),
distinctUntilChanged(), distinctUntilChanged(),
); );
private readonly pip: Observable<UserMediaViewModel | null> = private readonly pip: Observable<UserMedia | null> =
this.spotlightAndPip.pipe(switchMap(([, pip]) => pip)); this.spotlightAndPip.pipe(switchMap(([, pip]) => pip));
private readonly pipEnabled: Observable<boolean> = setPipEnabled.pipe( private readonly pipEnabled: Observable<boolean> = setPipEnabled.pipe(
@ -676,7 +676,7 @@ export class CallViewModel extends ViewModel {
[this.grid, this.spotlight], [this.grid, this.spotlight],
(grid, spotlight) => ({ (grid, spotlight) => ({
type: "grid", type: "grid",
spotlight: spotlight.some((vm) => vm instanceof ScreenShareViewModel) spotlight: spotlight.some((m) => m.vm instanceof ScreenShareViewModel)
? spotlight ? spotlight
: undefined, : undefined,
grid, grid,
@ -708,15 +708,16 @@ export class CallViewModel extends ViewModel {
this.mediaItems.pipe( this.mediaItems.pipe(
map((mediaItems) => { map((mediaItems) => {
if (mediaItems.length !== 2) return null; if (mediaItems.length !== 2) return null;
const local = mediaItems.find((vm) => vm.vm.local)! const local = mediaItems.find(
.vm as LocalUserMediaViewModel; (m) => m instanceof UserMedia && m.vm.local,
const remote = mediaItems.find((vm) => !vm.vm.local)?.vm as ) as UserMedia | undefined;
| RemoteUserMediaViewModel const remote = mediaItems.find(
| undefined; (m) => m instanceof UserMedia && !m.vm.local,
) as UserMedia | undefined;
// There might not be a remote tile if there are screen shares, or if // There might not be a remote tile if there are screen shares, or if
// only the local user is in the call and they're using the duplicate // only the local user is in the call and they're using the duplicate
// tiles option // tiles option
if (remote === undefined) return null; if (!local || !remote) return null;
return { type: "one-on-one", local, remote }; return { type: "one-on-one", local, remote };
}), }),

View File

@ -26,10 +26,10 @@ export function gridLikeLayout(
const update = prevTiles.from(visibleTiles); const update = prevTiles.from(visibleTiles);
if (media.spotlight !== undefined) if (media.spotlight !== undefined)
update.registerSpotlight( update.registerSpotlight(
media.spotlight, media.spotlight.map((m) => m.vm),
media.type === "spotlight-portrait", media.type === "spotlight-portrait",
); );
for (const mediaVm of media.grid) update.registerGridTile(mediaVm); for (const mediaVm of media.grid) update.registerGridTile(mediaVm.vm);
const tiles = update.build(); const tiles = update.build();
return [ return [

View File

@ -18,14 +18,14 @@ export function oneOnOneLayout(
prevTiles: TileStore, prevTiles: TileStore,
): [OneOnOneLayout, TileStore] { ): [OneOnOneLayout, TileStore] {
const update = prevTiles.from(visibleTiles); const update = prevTiles.from(visibleTiles);
update.registerGridTile(media.local); update.registerGridTile(media.local.vm);
update.registerGridTile(media.remote); update.registerGridTile(media.remote.vm);
const tiles = update.build(); const tiles = update.build();
return [ return [
{ {
type: media.type, type: media.type,
local: tiles.gridTilesByMedia.get(media.local)!, local: tiles.gridTilesByMedia.get(media.local.vm)!,
remote: tiles.gridTilesByMedia.get(media.remote)!, remote: tiles.gridTilesByMedia.get(media.remote.vm)!,
}, },
tiles, tiles,
]; ];

View File

@ -18,7 +18,10 @@ export function pipLayout(
prevTiles: TileStore, prevTiles: TileStore,
): [PipLayout, TileStore] { ): [PipLayout, TileStore] {
const update = prevTiles.from(visibleTiles); const update = prevTiles.from(visibleTiles);
update.registerSpotlight(media.spotlight, true); update.registerSpotlight(
media.spotlight.map((m) => m.vm),
true,
);
const tiles = update.build(); const tiles = update.build();
return [ return [
{ {

View File

@ -21,8 +21,11 @@ export function spotlightExpandedLayout(
prevTiles: TileStore, prevTiles: TileStore,
): [SpotlightExpandedLayout, TileStore] { ): [SpotlightExpandedLayout, TileStore] {
const update = prevTiles.from(visibleTiles); const update = prevTiles.from(visibleTiles);
update.registerSpotlight(media.spotlight, true); update.registerSpotlight(
if (media.pip !== undefined) update.registerGridTile(media.pip); media.spotlight.map((m) => m.vm),
true,
);
if (media.pip !== undefined) update.registerGridTile(media.pip.vm);
const tiles = update.build(); const tiles = update.build();
return [ return [