mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-18 18:59:23 +00:00
quick experimental kiosk mode which hides local tiles.
src/UrlParams.ts — added hideLocalTiles: boolean to UrlConfiguration, parsed it via parser.getFlag(hideLocalTiles), and defaulted it to false in both the SPA fallback and the widget intent preset. src/state/CallViewModel/CallViewModel.ts — in the userMedia$ generator: - Added a module-level PUBLISH_SUFFIX = +publish constant. - Added base-device-ID and +publish-twin helpers. - Always: when a +publish membership exists for (userId, baseDeviceId), the non-suffixed sibling is dropped (it would just be an empty receive-only tile). - When hideLocalTiles is set: every membership whose (userId, baseDeviceId) matches the local user/device is dropped — covering both the local camera tile and any +publish twin from this device.
This commit is contained in:
@@ -179,6 +179,14 @@ export interface UrlConfiguration {
|
|||||||
* Whether to hide the screen-sharing button.
|
* Whether to hide the screen-sharing button.
|
||||||
*/
|
*/
|
||||||
hideScreensharing: boolean;
|
hideScreensharing: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to hide tiles representing the local device. This includes the
|
||||||
|
* local camera tile and any other MatrixRTC memberships originating from
|
||||||
|
* the same device (e.g. a `+publish` twin used to publish media). Useful
|
||||||
|
* for kiosk-style embeddings where the embedder does not want the local
|
||||||
|
* user to see themselves.
|
||||||
|
*/
|
||||||
|
hideLocalTiles: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the app is allowed to use fallback STUN servers for ICE in case the
|
* Whether the app is allowed to use fallback STUN servers for ICE in case the
|
||||||
@@ -369,6 +377,7 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => {
|
|||||||
header: platform === "desktop" ? HeaderStyle.None : HeaderStyle.AppBar,
|
header: platform === "desktop" ? HeaderStyle.None : HeaderStyle.AppBar,
|
||||||
showControls: true,
|
showControls: true,
|
||||||
hideScreensharing: false,
|
hideScreensharing: false,
|
||||||
|
hideLocalTiles: false,
|
||||||
allowIceFallback: true,
|
allowIceFallback: true,
|
||||||
perParticipantE2EE: true,
|
perParticipantE2EE: true,
|
||||||
controlledAudioDevices: platform === "desktop" ? false : true,
|
controlledAudioDevices: platform === "desktop" ? false : true,
|
||||||
@@ -424,6 +433,7 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => {
|
|||||||
header: HeaderStyle.Standard,
|
header: HeaderStyle.Standard,
|
||||||
showControls: true,
|
showControls: true,
|
||||||
hideScreensharing: false,
|
hideScreensharing: false,
|
||||||
|
hideLocalTiles: false,
|
||||||
allowIceFallback: false,
|
allowIceFallback: false,
|
||||||
perParticipantE2EE: false,
|
perParticipantE2EE: false,
|
||||||
controlledAudioDevices: false,
|
controlledAudioDevices: false,
|
||||||
@@ -470,6 +480,7 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => {
|
|||||||
header: parser.getEnumParam("header", HeaderStyle),
|
header: parser.getEnumParam("header", HeaderStyle),
|
||||||
showControls: parser.getFlag("showControls"),
|
showControls: parser.getFlag("showControls"),
|
||||||
hideScreensharing: parser.getFlag("hideScreensharing"),
|
hideScreensharing: parser.getFlag("hideScreensharing"),
|
||||||
|
hideLocalTiles: parser.getFlag("hideLocalTiles"),
|
||||||
allowIceFallback: parser.getFlag("allowIceFallback"),
|
allowIceFallback: parser.getFlag("allowIceFallback"),
|
||||||
perParticipantE2EE: parser.getFlag("perParticipantE2EE"),
|
perParticipantE2EE: parser.getFlag("perParticipantE2EE"),
|
||||||
controlledAudioDevices: parser.getFlag("controlledAudioDevices"),
|
controlledAudioDevices: parser.getFlag("controlledAudioDevices"),
|
||||||
|
|||||||
@@ -197,6 +197,11 @@ const smallMobileCallThreshold = 3;
|
|||||||
// with the interface
|
// with the interface
|
||||||
const showFooterMs = 4000;
|
const showFooterMs = 4000;
|
||||||
|
|
||||||
|
// Suffix used by MatrixRTC members who participate twice from the same
|
||||||
|
// physical device — once with the bare device ID (typically receive-only)
|
||||||
|
// and once with this suffix appended (the publishing twin).
|
||||||
|
const PUBLISH_SUFFIX = "+publish";
|
||||||
|
|
||||||
export type GridMode = "grid" | "spotlight";
|
export type GridMode = "grid" | "spotlight";
|
||||||
|
|
||||||
export type WindowMode = "normal" | "narrow" | "flat" | "pip";
|
export type WindowMode = "normal" | "narrow" | "flat" | "pip";
|
||||||
@@ -704,6 +709,18 @@ export function createCallViewModel$(
|
|||||||
]) {
|
]) {
|
||||||
const computeMediaId = (m: MatrixLivekitMember): string =>
|
const computeMediaId = (m: MatrixLivekitMember): string =>
|
||||||
`${m.userId}:${m.membership$.value.deviceId}`;
|
`${m.userId}:${m.membership$.value.deviceId}`;
|
||||||
|
// A member may participate twice from the same physical device by
|
||||||
|
// suffixing their device ID with "+publish" — one membership for
|
||||||
|
// receiving and one for publishing media. We treat both as the same
|
||||||
|
// logical device for de-duplication and local-tile filtering.
|
||||||
|
const baseDeviceId = (deviceId: string): string =>
|
||||||
|
deviceId.endsWith(PUBLISH_SUFFIX)
|
||||||
|
? deviceId.slice(0, -PUBLISH_SUFFIX.length)
|
||||||
|
: deviceId;
|
||||||
|
const isPublishTwin = (m: MatrixLivekitMember): boolean =>
|
||||||
|
m.membership$.value.deviceId.endsWith(PUBLISH_SUFFIX);
|
||||||
|
const computeBaseId = (m: MatrixLivekitMember): string =>
|
||||||
|
`${m.userId}:${baseDeviceId(m.membership$.value.deviceId)}`;
|
||||||
|
|
||||||
const localUserMediaId = localMatrixLivekitMember
|
const localUserMediaId = localMatrixLivekitMember
|
||||||
? computeMediaId(localMatrixLivekitMember)
|
? computeMediaId(localMatrixLivekitMember)
|
||||||
@@ -715,10 +732,28 @@ export function createCallViewModel$(
|
|||||||
const remoteWithoutLocal = matrixLivekitMembers.value.filter(
|
const remoteWithoutLocal = matrixLivekitMembers.value.filter(
|
||||||
(m) => computeMediaId(m) !== localUserMediaId,
|
(m) => computeMediaId(m) !== localUserMediaId,
|
||||||
);
|
);
|
||||||
const allMatrixLivekitMembers = [
|
const candidates = [...localAsArray, ...remoteWithoutLocal];
|
||||||
...localAsArray,
|
|
||||||
...remoteWithoutLocal,
|
// For any (user, device) that has a +publish membership, hide its
|
||||||
];
|
// non-suffixed sibling — the publishing twin is the one carrying
|
||||||
|
// the media, so the receive-only twin's tile would just be empty.
|
||||||
|
const publishTwinBaseIds = new Set(
|
||||||
|
candidates.filter(isPublishTwin).map(computeBaseId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { hideLocalTiles } = getUrlParams();
|
||||||
|
const localBaseId = `${userId}:${deviceId}`;
|
||||||
|
|
||||||
|
const allMatrixLivekitMembers = candidates.filter((m) => {
|
||||||
|
// Hide all tiles originating from the local device, including
|
||||||
|
// the +publish twin.
|
||||||
|
if (hideLocalTiles && computeBaseId(m) === localBaseId)
|
||||||
|
return false;
|
||||||
|
// Drop the receive-only sibling when a +publish twin is present.
|
||||||
|
if (!isPublishTwin(m) && publishTwinBaseIds.has(computeBaseId(m)))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
for (const matrixLivekitMember of allMatrixLivekitMembers) {
|
for (const matrixLivekitMember of allMatrixLivekitMembers) {
|
||||||
const { userId, participant, connection$, membership$ } =
|
const { userId, participant, connection$, membership$ } =
|
||||||
|
|||||||
Reference in New Issue
Block a user