feat(call): full-screen camera broadcast parity
Four changes to match screenshare full-screen UX for camera feeds:
1. Fullscreen button always visible
CallControls.tsx: remove `screenshare &&` gate — the ⛶ fullscreen
button now appears in camera-only calls, not just during screenshare.
2. Per-participant camera focus (CallControl.focusCameraParticipant)
Finds the target's video tile in the EC iframe DOM via:
[data-testid="videoTile"] / [data-video-fit]
closest ancestor of [aria-label="${userId}"]
Enables spotlight mode if not already active, then clicks the tile
so EC's internal focus handler runs. Falls back gracefully if the
tile is not in the DOM (camera off).
3. MemberGlance participant popup
Clicking a participant avatar in the call status bar now shows a
small menu: "Focus camera" (calls focusCameraParticipant) and
"View profile" (existing behaviour). Previously it opened the
profile immediately with no way to focus the camera.
4. PiP fullscreen button
A ⛶/⊡ icon button appears in the PiP overlay top-right area,
letting users go fullscreen directly from PiP mode without
navigating back to the call room first.
UNTESTED — requires a real multi-participant call to verify tile
clicking behaviour and fullscreen transitions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -288,6 +288,38 @@ export class CallControl extends EventEmitter implements CallControlState {
|
||||
this.settingsButton?.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus a specific participant's camera tile in Element Call.
|
||||
*
|
||||
* EC renders video tiles as `[data-testid="videoTile"]`. Each tile wraps a
|
||||
* mute-status indicator with `aria-label` set to the participant's Matrix
|
||||
* user ID. We find the tile containing that user, switch to spotlight mode
|
||||
* if needed, then click the tile so EC's internal focus handler runs.
|
||||
*
|
||||
* Falls back to a plain spotlight toggle if the tile is not found (e.g. the
|
||||
* participant has their camera off and EC didn't render a video tile for
|
||||
* them yet).
|
||||
*/
|
||||
public focusCameraParticipant(userId: string): void {
|
||||
const doc = this.document;
|
||||
if (!doc) return;
|
||||
|
||||
// Find the mute icon / aria-label element that identifies this participant
|
||||
const userEl = doc.querySelector<HTMLElement>(`[aria-label="${CSS.escape(userId)}"]`);
|
||||
// Walk up to the nearest video tile container
|
||||
const tile =
|
||||
userEl?.closest<HTMLElement>('[data-testid="videoTile"]') ??
|
||||
userEl?.closest<HTMLElement>('[data-video-fit]');
|
||||
|
||||
if (!this.spotlight) {
|
||||
this.spotlightButton?.click();
|
||||
}
|
||||
|
||||
if (tile) {
|
||||
tile.click();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.controlMutationObserver.disconnect();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user