ov-components: enhance screenshare button functionality for Firefox

- Updated the screenshare button in the toolbar to behave differently based on the browser type (Firefox vs others).
- Added an input property `isFirefoxBrowser` to the `ToolbarMediaButtonsComponent` to determine the browser type.
- Modified the HTML template to conditionally render the screenshare button and its menu based on the browser.
- Improved the `switchScreenshare` method in the `ParticipantService` to handle errors more gracefully and ensure the local participant is defined before proceeding.
- Refactored the `switchScreenshare` method in the `ParticipantModel` to reject promises with meaningful messages when no active screen share track is found.
master
CSantosM 2026-03-16 13:15:13 +01:00
parent 87b3ac3716
commit 34e34ee078
7 changed files with 2678 additions and 1541 deletions

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,19 @@
<!-- Screenshare button - Only visible on tablet+ or when active --> <!-- Screenshare button - Only visible on tablet+ or when active -->
@if (showScreenshareButtonDirect()) { @if (showScreenshareButtonDirect()) {
@if (isFirefoxBrowser) {
<button
mat-icon-button
id="screenshare-btn"
[disabled]="isConnectionLost"
[class.active-btn]="isScreenShareEnabled"
[class.mobile-btn]="isMobileView()"
matTooltip="{{ isScreenShareEnabled ? ('TOOLBAR.DISABLE_SCREEN' | translate) : ('TOOLBAR.ENABLE_SCREEN' | translate) }}"
(click)="onScreenShareToggle()"
>
<mat-icon>screen_share</mat-icon>
</button>
} @else {
<button <button
mat-icon-button mat-icon-button
id="screenshare-btn" id="screenshare-btn"
@ -62,6 +75,7 @@
</button> </button>
</mat-menu> </mat-menu>
} }
}
<!-- Additional buttons injection from parent component (desktop/tablet only) --> <!-- Additional buttons injection from parent component (desktop/tablet only) -->
@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'beforeMenu') { @if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'beforeMenu') {

View File

@ -28,6 +28,7 @@ export class ToolbarMediaButtonsComponent {
// Screenshare related inputs // Screenshare related inputs
@Input() showScreenshareButton: boolean = true; @Input() showScreenshareButton: boolean = true;
@Input() isScreenShareEnabled: boolean = false; @Input() isScreenShareEnabled: boolean = false;
@Input() isFirefoxBrowser: boolean = false;
// Device availability inputs // Device availability inputs
@Input() hasVideoDevices: boolean = true; @Input() hasVideoDevices: boolean = true;

View File

@ -49,6 +49,7 @@
[microphoneMuteChanging]="microphoneMuteChanging" [microphoneMuteChanging]="microphoneMuteChanging"
[showScreenshareButton]="showScreenshareButton" [showScreenshareButton]="showScreenshareButton"
[isScreenShareEnabled]="isScreenShareEnabled" [isScreenShareEnabled]="isScreenShareEnabled"
[isFirefoxBrowser]="isFirefoxBrowser"
[hasVideoDevices]="hasVideoDevices" [hasVideoDevices]="hasVideoDevices"
[hasAudioDevices]="hasAudioDevices" [hasAudioDevices]="hasAudioDevices"
[isConnectionLost]="isConnectionLost" [isConnectionLost]="isConnectionLost"

View File

@ -344,6 +344,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/ */
showCaptionsButton: boolean = true; showCaptionsButton: boolean = true;
/**
* @internal
*/
isFirefoxBrowser: boolean = false;
/** /**
* @ignore * @ignore
*/ */
@ -450,6 +455,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private templateManagerService: TemplateManagerService private templateManagerService: TemplateManagerService
) { ) {
this.log = this.loggerSrv.get('ToolbarComponent'); this.log = this.loggerSrv.get('ToolbarComponent');
this.isFirefoxBrowser = this.platformService.isFirefox();
// Effect to react to local participant changes // Effect to react to local participant changes
effect(() => { effect(() => {

View File

@ -436,14 +436,19 @@ export class ParticipantModel {
* @internal * @internal
*/ */
async switchScreenshare(newTrack: LocalTrack): Promise<void> { async switchScreenshare(newTrack: LocalTrack): Promise<void> {
if (this.participant instanceof LocalParticipant) { if (!(this.participant instanceof LocalParticipant)) {
const screenTrack = this.tracks.find((track) => track.source === Track.Source.ScreenShare);
if (screenTrack) {
await (screenTrack.videoTrack as LocalTrack).replaceTrack(newTrack.mediaStreamTrack);
return Promise.resolve();
}
return Promise.reject("Remote participant can't switch screen share"); return Promise.reject("Remote participant can't switch screen share");
} }
const screenTrack = this.tracks.find((track) => track.source === Track.Source.ScreenShare);
if (!screenTrack || !screenTrack.videoTrack) {
return Promise.reject('No active screen share track to switch');
}
const currentTrack = screenTrack.videoTrack as LocalTrack;
await currentTrack.replaceTrack(newTrack.mediaStreamTrack);
return Promise.resolve();
} }
/** /**

View File

@ -189,19 +189,26 @@ export class ParticipantService {
* Switches the active screen share track showing a native browser dialog to select a screen or window. * Switches the active screen share track showing a native browser dialog to select a screen or window.
*/ */
async switchScreenShare(): Promise<void> { async switchScreenShare(): Promise<void> {
if (this.localParticipant) { if (!this.localParticipant) {
this.log.e('Local participant is undefined when switching screenshare');
return;
}
// Chrome / Safari: seamless replaceTrack keeps the same publication SID.
const options = this.getScreenCaptureOptions(); const options = this.getScreenCaptureOptions();
const [newTrack] = await this.localParticipant.createScreenTracks(options); const [newTrack] = await this.localParticipant.createScreenTracks(options);
if (newTrack) { if (newTrack) {
newTrack?.addListener('ended', async () => { newTrack.addListener('ended', async () => {
this.log.d('Clicked native stop button. Stopping screen sharing'); this.log.d('Clicked native stop button. Stopping screen sharing');
await this.setScreenShareEnabled(false); await this.setScreenShareEnabled(false);
}); });
try {
await this.localParticipant.switchScreenshare(newTrack); await this.localParticipant.switchScreenshare(newTrack);
} catch (error) {
newTrack.stop();
throw error;
} }
} else {
this.log.e('Local participant is undefined when switching screenshare');
} }
// this.updateLocalParticipant(); // this.updateLocalParticipant();
@ -459,7 +466,6 @@ export class ParticipantService {
} }
} }
/** /**
* Returns the participant with the given identity. * Returns the participant with the given identity.
* @param identity * @param identity