Add own control buttons for element-call (#2744)

* add mutation observer hok

* add hook to read speaking member by observing iframe content

* display speaking member name in call status bar and improve layout

* fix shrining

* add joined call control bar

* remove chat toggle from room header

* change member speaking icon to mic

* fix joined call control appear in other

* show spinner on end call button

* hide call statusbar for mobile view when room is selected

* make call statusbar more mobile friendly

* fix call status bar item align
This commit is contained in:
Ajay Bura
2026-03-09 14:04:48 +11:00
committed by GitHub
parent 55e8306576
commit bc6caddcc8
17 changed files with 521 additions and 100 deletions

View File

@@ -14,12 +14,58 @@ export class CallControl extends EventEmitter implements CallControlState {
private iframe: HTMLIFrameElement;
private controlMutationObserver: MutationObserver;
private get document(): Document | undefined {
return this.iframe.contentDocument ?? this.iframe.contentWindow?.document;
}
private get screenshareButton(): HTMLElement | undefined {
const screenshareBtn = this.document?.querySelector(
'[data-testid="incall_screenshare"]'
) as HTMLElement | null;
return screenshareBtn ?? undefined;
}
private get settingsButton(): HTMLElement | undefined {
const leaveBtn = this.document?.querySelector('[data-testid="incall_leave"]');
const settingsButton = leaveBtn?.previousElementSibling as HTMLElement | null;
return settingsButton ?? undefined;
}
private get reactionsButton(): HTMLElement | undefined {
const reactionsButton = this.settingsButton?.previousElementSibling as HTMLElement | null;
return reactionsButton ?? undefined;
}
private get spotlightButton(): HTMLInputElement | undefined {
const spotlightButton = this.document?.querySelector(
'input[value="spotlight"]'
) as HTMLInputElement | null;
return spotlightButton ?? undefined;
}
private get gridButton(): HTMLInputElement | undefined {
const gridButton = this.document?.querySelector(
'input[value="grid"]'
) as HTMLInputElement | null;
return gridButton ?? undefined;
}
constructor(state: CallControlState, call: ClientWidgetApi, iframe: HTMLIFrameElement) {
super();
this.state = state;
this.call = call;
this.iframe = iframe;
this.controlMutationObserver = new MutationObserver(this.onControlMutation.bind(this));
}
public getState(): CallControlState {
@@ -38,6 +84,14 @@ export class CallControl extends EventEmitter implements CallControlState {
return this.state.sound;
}
public get screenshare(): boolean {
return this.state.screenshare;
}
public get spotlight(): boolean {
return this.state.spotlight;
}
public async applyState() {
await this.setMediaState({
audio_enabled: this.microphone,
@@ -47,6 +101,26 @@ export class CallControl extends EventEmitter implements CallControlState {
this.emitStateUpdate();
}
public startObserving() {
this.controlMutationObserver.disconnect();
const screenshareBtn = this.screenshareButton;
if (screenshareBtn) {
this.controlMutationObserver.observe(screenshareBtn, {
attributes: true,
attributeFilter: ['data-kind'],
});
}
const spotlightBtn = this.spotlightButton;
if (spotlightBtn) {
this.controlMutationObserver.observe(spotlightBtn, {
attributes: true,
});
}
this.onControlMutation();
}
public applySound() {
this.setSound(this.sound);
}
@@ -72,7 +146,9 @@ export class CallControl extends EventEmitter implements CallControlState {
const state = new CallControlState(
data.audio_enabled ?? this.microphone,
data.video_enabled ?? this.video,
this.sound
this.sound,
this.screenshare,
this.spotlight
);
this.state = state;
@@ -83,6 +159,20 @@ export class CallControl extends EventEmitter implements CallControlState {
}
}
public onControlMutation() {
const screenshare: boolean = this.screenshareButton?.getAttribute('data-kind') === 'primary';
const spotlight: boolean = this.spotlightButton?.checked ?? false;
this.state = new CallControlState(
this.microphone,
this.video,
this.sound,
screenshare,
spotlight
);
this.emitStateUpdate();
}
public toggleMicrophone() {
const payload: ElementMediaStatePayload = {
audio_enabled: !this.microphone,
@@ -104,7 +194,13 @@ export class CallControl extends EventEmitter implements CallControlState {
this.setSound(sound);
const state = new CallControlState(this.microphone, this.video, sound);
const state = new CallControlState(
this.microphone,
this.video,
sound,
this.screenshare,
this.spotlight
);
this.state = state;
this.emitStateUpdate();
@@ -113,6 +209,30 @@ export class CallControl extends EventEmitter implements CallControlState {
}
}
public toggleScreenshare() {
this.screenshareButton?.click();
}
public toggleSpotlight() {
if (this.spotlight) {
this.gridButton?.click();
return;
}
this.spotlightButton?.click();
}
public toggleReactions() {
this.reactionsButton?.click();
}
public toggleSettings() {
this.settingsButton?.click();
}
public dispose() {
this.controlMutationObserver.disconnect();
}
private emitStateUpdate() {
this.emit(CallControlEvent.StateUpdate);
}