frontend: refactor meeting components and services for improved readability and performance
This commit is contained in:
parent
4c864b193f
commit
599a744302
@ -1,6 +1,6 @@
|
||||
<div class="captions-container">
|
||||
<div class="captions-wrapper" [class.single-caption]="captions().length === 1">
|
||||
@for (caption of captions(); track trackByCaption($index, caption)) {
|
||||
@for (caption of captions(); track caption.id) {
|
||||
<div
|
||||
[ngClass]="getCaptionClasses(caption)"
|
||||
[attr.data-caption-id]="caption.id"
|
||||
|
||||
@ -13,7 +13,7 @@ export class MeetingCaptionsComponent {
|
||||
captions = input<Caption[]>([]);
|
||||
|
||||
// Track animation state for each caption
|
||||
protected readonly captionAnimationState = signal<Map<string, 'entering' | 'active' | 'leaving'>>(new Map());
|
||||
captionAnimationState = signal<Map<string, 'entering' | 'active' | 'leaving'>>(new Map());
|
||||
|
||||
// ViewChildren to access caption text containers for auto-scroll
|
||||
@ViewChildren('captionTextContainer')
|
||||
@ -77,17 +77,6 @@ export class MeetingCaptionsComponent {
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks captions by their ID for optimal Angular rendering.
|
||||
*
|
||||
* @param index Item index
|
||||
* @param caption Caption item
|
||||
* @returns Unique identifier
|
||||
*/
|
||||
protected trackByCaption(index: number, caption: Caption): string {
|
||||
return caption.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls all caption text containers to the bottom to show the most recent text.
|
||||
* Called automatically when captions are updated.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@if (meetingContextService.lkRoom()) {
|
||||
@if (lkRoom()) {
|
||||
<div class="main-container" [ngClass]="{ withFooter: areCaptionsEnabledByUser() }">
|
||||
<ov-layout
|
||||
[ovRemoteParticipants]="visibleRemoteParticipants()"
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, effect, inject, signal, untracked } from '@angular/core';
|
||||
import {
|
||||
ILogger,
|
||||
LoggerService,
|
||||
OpenViduComponentsUiModule,
|
||||
PanelService,
|
||||
@ -30,46 +29,42 @@ import { MeetingCaptionsComponent } from '../meeting-captions/meeting-captions.c
|
||||
styleUrl: './meeting-custom-layout.component.scss'
|
||||
})
|
||||
export class MeetingCustomLayoutComponent {
|
||||
private readonly logger: ILogger = inject(LoggerService).get('MeetingCustomLayoutComponent');
|
||||
protected readonly layoutService = inject(MeetingLayoutService);
|
||||
protected readonly meetingContextService = inject(MeetingContextService);
|
||||
protected readonly meetingService = inject(MeetingService);
|
||||
protected readonly panelService = inject(PanelService);
|
||||
protected readonly captionsService = inject(MeetingCaptionsService);
|
||||
protected readonly linkOverlayConfig = {
|
||||
protected meetingContextService = inject(MeetingContextService);
|
||||
protected meetingService = inject(MeetingService);
|
||||
protected layoutService = inject(MeetingLayoutService);
|
||||
protected captionsService = inject(MeetingCaptionsService);
|
||||
protected panelService = inject(PanelService);
|
||||
protected logger = inject(LoggerService).get('MeetingCustomLayoutComponent');
|
||||
|
||||
lkRoom = this.meetingContextService.lkRoom;
|
||||
|
||||
meetingUrl = this.meetingContextService.meetingUrl;
|
||||
shouldShowLinkOverlay = computed(() => {
|
||||
const hasNoRemotes = this.remoteParticipants().length === 0;
|
||||
return this.meetingContextService.canModerateRoom() && hasNoRemotes;
|
||||
});
|
||||
linkOverlayConfig = {
|
||||
title: 'Start collaborating',
|
||||
subtitle: 'Share this link to bring others into the meeting',
|
||||
titleSize: 'xl' as const,
|
||||
titleWeight: 'bold' as const
|
||||
};
|
||||
|
||||
protected readonly meetingUrl = computed(() => this.meetingContextService.meetingUrl());
|
||||
protected readonly remoteParticipants = computed(() => this.meetingContextService.remoteParticipants());
|
||||
protected readonly shouldShowLinkOverlay = computed(() => {
|
||||
const hasNoRemotes = this.meetingContextService.remoteParticipants().length === 0;
|
||||
return this.meetingContextService.canModerateRoom() && hasNoRemotes;
|
||||
});
|
||||
|
||||
protected readonly areCaptionsEnabledByUser = computed(() => this.captionsService.areCaptionsEnabledByUser());
|
||||
|
||||
protected readonly captions = computed(() => this.captionsService.captions());
|
||||
|
||||
protected readonly isLayoutSwitchingAllowed = this.meetingContextService.allowLayoutSwitching;
|
||||
|
||||
private displayedParticipantIds: string[] = [];
|
||||
private audioElements = new Map<string, HTMLMediaElement>();
|
||||
private proxyCache = new WeakMap<ParticipantModel, { proxy: ParticipantModel; showCamera: boolean }>();
|
||||
areCaptionsEnabledByUser = this.captionsService.areCaptionsEnabledByUser;
|
||||
isLayoutSwitchingAllowed = this.meetingContextService.allowLayoutSwitching;
|
||||
isSmartMosaicActive = computed(() => this.isLayoutSwitchingAllowed() && this.layoutService.isSmartMosaicEnabled());
|
||||
captions = this.captionsService.captions;
|
||||
|
||||
remoteParticipants = this.meetingContextService.remoteParticipants;
|
||||
private _visibleRemoteParticipants = signal<ParticipantModel[]>([]);
|
||||
readonly visibleRemoteParticipants = this._visibleRemoteParticipants.asReadonly();
|
||||
visibleRemoteParticipants = this._visibleRemoteParticipants.asReadonly();
|
||||
|
||||
protected readonly hiddenParticipantsCount = computed(() => {
|
||||
hiddenParticipantsCount = computed(() => {
|
||||
const total = this.remoteParticipants().length;
|
||||
const visible = this.visibleRemoteParticipants().length;
|
||||
return Math.max(0, total - visible);
|
||||
});
|
||||
|
||||
protected readonly hiddenParticipantNames = computed(() => {
|
||||
hiddenParticipantNames = computed(() => {
|
||||
const visibleIds = new Set(this.visibleRemoteParticipants().map((p) => p.identity));
|
||||
return this.remoteParticipants()
|
||||
.filter((p) => !visibleIds.has(p.identity))
|
||||
@ -80,7 +75,7 @@ export class MeetingCustomLayoutComponent {
|
||||
* Indicates whether to show the hidden participants indicator in the top bar
|
||||
* when in smart mosaic mode.
|
||||
*/
|
||||
protected readonly showTopBarHiddenParticipantsIndicator = computed(() => {
|
||||
showTopBarHiddenParticipantsIndicator = computed(() => {
|
||||
const localParticipant = this.meetingContextService.localParticipant()!;
|
||||
const hasPinnedParticipant =
|
||||
localParticipant.isPinned || this.remoteParticipants().some((p) => (p as CustomParticipantModel).isPinned);
|
||||
@ -90,6 +85,10 @@ export class MeetingCustomLayoutComponent {
|
||||
return showTopBar;
|
||||
});
|
||||
|
||||
private displayedParticipantIds: string[] = [];
|
||||
private audioElements = new Map<string, HTMLMediaElement>();
|
||||
private proxyCache = new WeakMap<ParticipantModel, { proxy: ParticipantModel; showCamera: boolean }>();
|
||||
|
||||
constructor() {
|
||||
this.setupSpeakerTrackingEffect();
|
||||
this.setupParticipantCleanupEffect();
|
||||
@ -106,17 +105,13 @@ export class MeetingCustomLayoutComponent {
|
||||
this.meetingService.copyMeetingSpeakerLink(room);
|
||||
}
|
||||
|
||||
protected isSmartMosaicActive(): boolean {
|
||||
return this.isLayoutSwitchingAllowed() && this.layoutService.isSmartMosaicEnabled();
|
||||
}
|
||||
|
||||
protected toggleParticipantsPanel(): void {
|
||||
this.panelService.togglePanel(PanelType.PARTICIPANTS);
|
||||
}
|
||||
|
||||
private setupVisibleParticipantsUpdate(): void {
|
||||
effect(() => {
|
||||
const allRemotes = this.meetingContextService.remoteParticipants();
|
||||
const allRemotes = this.remoteParticipants();
|
||||
|
||||
if (!this.isSmartMosaicActive()) {
|
||||
this._visibleRemoteParticipants.set(allRemotes);
|
||||
@ -164,7 +159,6 @@ export class MeetingCustomLayoutComponent {
|
||||
* @param targetIds Set of participant IDs that should be displayed.
|
||||
* @param availableIds Set of participant IDs that are currently available for display.
|
||||
*/
|
||||
|
||||
private syncDisplayedParticipantsWithTarget(targetIds: Set<string>, availableIds: Set<string>): void {
|
||||
this.displayedParticipantIds = this.displayedParticipantIds.filter((id) => availableIds.has(id));
|
||||
|
||||
@ -193,7 +187,7 @@ export class MeetingCustomLayoutComponent {
|
||||
|
||||
private setupSpeakerTrackingEffect(): void {
|
||||
effect(() => {
|
||||
const room = this.meetingContextService.lkRoom();
|
||||
const room = this.lkRoom();
|
||||
if (this.isLayoutSwitchingAllowed() && room) {
|
||||
this.layoutService.initializeSpeakerTracking(room);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { LoggerService } from 'openvidu-components-angular';
|
||||
import { ShareMeetingLinkComponent } from '../../components/share-meeting-link/share-meeting-link.component';
|
||||
import { MeetingContextService } from '../../services/meeting-context.service';
|
||||
@ -21,19 +21,8 @@ export class MeetingInvitePanelComponent {
|
||||
protected loggerService = inject(LoggerService);
|
||||
protected log = this.loggerService.get('OpenVidu Meet - MeetingInvitePanel');
|
||||
|
||||
/**
|
||||
* Computed signal to determine if the share link should be shown
|
||||
*/
|
||||
protected showShareLink = computed(() => {
|
||||
return this.meetingContextService.canModerateRoom();
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed signal for the meeting URL from context
|
||||
*/
|
||||
protected meetingUrl = computed(() => {
|
||||
return this.meetingContextService.meetingUrl();
|
||||
});
|
||||
showShareLink = this.meetingContextService.canModerateRoom;
|
||||
meetingUrl = this.meetingContextService.meetingUrl;
|
||||
|
||||
onCopyClicked(): void {
|
||||
const room = this.meetingContextService.meetRoom();
|
||||
|
||||
@ -64,4 +64,3 @@
|
||||
</ov-participant-panel-item>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@ -22,14 +22,14 @@ export class MeetingParticipantItemComponent {
|
||||
// Template reference for the component's template
|
||||
@ViewChild('template', { static: true }) template!: TemplateRef<any>;
|
||||
|
||||
protected meetingService: MeetingService = inject(MeetingService);
|
||||
protected meetingService = inject(MeetingService);
|
||||
protected loggerService = inject(LoggerService);
|
||||
protected log = this.loggerService.get('OpenVidu Meet - MeetingParticipantItem');
|
||||
|
||||
/**
|
||||
* Get or compute display properties for a participant
|
||||
*/
|
||||
protected getDisplayProperties(
|
||||
getDisplayProperties(
|
||||
participant: CustomParticipantModel,
|
||||
localParticipant: CustomParticipantModel
|
||||
): ParticipantDisplayProperties {
|
||||
@ -56,7 +56,6 @@ export class MeetingParticipantItemComponent {
|
||||
if (!localParticipant.isModerator()) return;
|
||||
|
||||
const roomId = localParticipant.roomName;
|
||||
|
||||
if (!roomId) {
|
||||
this.log.e('Cannot change participant role: local participant room name is undefined');
|
||||
return;
|
||||
@ -81,7 +80,6 @@ export class MeetingParticipantItemComponent {
|
||||
if (!localParticipant.isModerator()) return;
|
||||
|
||||
const roomId = localParticipant.roomName;
|
||||
|
||||
if (!roomId) {
|
||||
this.log.e('Cannot change participant role: local participant room name is undefined');
|
||||
return;
|
||||
@ -106,9 +104,8 @@ export class MeetingParticipantItemComponent {
|
||||
if (!localParticipant.isModerator()) return;
|
||||
|
||||
const roomId = localParticipant.roomName;
|
||||
|
||||
if (!roomId) {
|
||||
this.log.e('Cannot change participant role: local participant room name is undefined');
|
||||
this.log.e('Cannot kick participant: local participant room name is undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@ -30,43 +30,24 @@ import { MeetingLayoutService } from '../../services/meeting-layout.service';
|
||||
styleUrl: './meeting-settings-extensions.component.scss'
|
||||
})
|
||||
export class MeetingSettingsExtensionsComponent {
|
||||
private readonly layoutService = inject(MeetingLayoutService);
|
||||
protected readonly meetingContextService = inject(MeetingContextService);
|
||||
private readonly layoutService = inject(MeetingLayoutService);
|
||||
|
||||
/**
|
||||
* Expose LayoutMode enum to template
|
||||
*/
|
||||
readonly LayoutMode = MeetLayoutMode;
|
||||
/** Whether the layout switching feature is allowed */
|
||||
isLayoutSwitchingAllowed = this.meetingContextService.allowLayoutSwitching;
|
||||
/** Expose LayoutMode enum to template */
|
||||
LayoutMode = MeetLayoutMode;
|
||||
/** Current layout mode */
|
||||
layoutMode = this.layoutService.layoutMode;
|
||||
/** Whether Smart Mosaic layout is enabled */
|
||||
isSmartMode = this.layoutService.isSmartMosaicEnabled;
|
||||
|
||||
/**
|
||||
* Whether the layout switching feature is allowed
|
||||
*/
|
||||
protected readonly isLayoutSwitchingAllowed = this.meetingContextService.allowLayoutSwitching;
|
||||
|
||||
/**
|
||||
* Current layout mode
|
||||
*/
|
||||
protected readonly layoutMode = computed(() => this.layoutService.layoutMode());
|
||||
|
||||
/**
|
||||
* Current participant count
|
||||
*/
|
||||
protected readonly participantCount = computed(() => this.layoutService.maxRemoteSpeakers());
|
||||
|
||||
/**
|
||||
* Minimum number of participants that can be shown when Smart Mosaic layout is enabled
|
||||
*/
|
||||
protected readonly minParticipants = this.layoutService.MIN_REMOTE_SPEAKERS;
|
||||
|
||||
/**
|
||||
* Maximum number of participants that can be shown
|
||||
*/
|
||||
protected readonly maxParticipants = this.layoutService.MAX_REMOTE_SPEAKERS_LIMIT;
|
||||
|
||||
/**
|
||||
* Computed property to check if Smart Mosaic mode is active
|
||||
*/
|
||||
readonly isSmartMode = this.layoutService.isSmartMosaicEnabled;
|
||||
/** Minimum number of participants that can be shown when Smart Mosaic layout is enabled */
|
||||
minParticipants = this.layoutService.MIN_REMOTE_SPEAKERS;
|
||||
/** Maximum number of participants that can be shown */
|
||||
maxParticipants = this.layoutService.MAX_REMOTE_SPEAKERS_LIMIT;
|
||||
/** Current participant count */
|
||||
participantCount = this.layoutService.maxRemoteSpeakers;
|
||||
|
||||
/**
|
||||
* Handler for layout mode change
|
||||
|
||||
@ -22,38 +22,26 @@ import { MeetingService } from '../../services/meeting.service';
|
||||
export class MeetingToolbarExtraButtonsComponent {
|
||||
protected meetingContextService = inject(MeetingContextService);
|
||||
protected meetingService = inject(MeetingService);
|
||||
protected loggerService = inject(LoggerService);
|
||||
protected captionService = inject(MeetingCaptionsService);
|
||||
protected loggerService = inject(LoggerService);
|
||||
protected log = this.loggerService.get('OpenVidu Meet - MeetingToolbarExtraButtons');
|
||||
protected readonly copyLinkTooltip = 'Copy the meeting link';
|
||||
protected readonly copyLinkText = 'Copy meeting link';
|
||||
|
||||
/**
|
||||
* Whether to show the copy link button
|
||||
*/
|
||||
protected showCopyLinkButton = computed(() => this.meetingContextService.canModerateRoom());
|
||||
/** Whether to show the copy link button (only for moderators) */
|
||||
showCopyLinkButton = this.meetingContextService.canModerateRoom;
|
||||
copyLinkTooltip = 'Copy the meeting link';
|
||||
copyLinkText = 'Copy meeting link';
|
||||
|
||||
/**
|
||||
* Captions status based on room and global configuration
|
||||
*/
|
||||
protected captionsStatus = computed(() => this.meetingContextService.getCaptionsStatus());
|
||||
/** Captions status based on room and global configuration */
|
||||
captionsStatus = this.meetingContextService.getCaptionsStatus;
|
||||
/** Whether to show the captions button (visible when not HIDDEN) */
|
||||
showCaptionsButton = computed(() => this.captionsStatus() !== 'HIDDEN');
|
||||
/** Whether captions button is disabled (true when DISABLED_WITH_WARNING) */
|
||||
isCaptionsButtonDisabled = computed(() => this.captionsStatus() === 'DISABLED_WITH_WARNING');
|
||||
/** Whether captions are currently enabled by the user */
|
||||
areCaptionsEnabledByUser = this.captionService.areCaptionsEnabledByUser;
|
||||
|
||||
/**
|
||||
* Whether to show the captions button (visible when not HIDDEN)
|
||||
*/
|
||||
protected showCaptionsButton = computed(() => this.captionsStatus() !== 'HIDDEN');
|
||||
|
||||
/**
|
||||
* Whether captions button is disabled (true when DISABLED_WITH_WARNING)
|
||||
*/
|
||||
protected isCaptionsButtonDisabled = computed(() => this.captionsStatus() === 'DISABLED_WITH_WARNING');
|
||||
|
||||
/**
|
||||
* Whether the device is mobile (affects button style)
|
||||
*/
|
||||
protected isMobile = computed(() => this.meetingContextService.isMobile());
|
||||
|
||||
protected areCaptionsEnabledByUser = computed(() => this.captionService.areCaptionsEnabledByUser());
|
||||
/** Whether the device is mobile (affects button style) */
|
||||
isMobile = this.meetingContextService.isMobile;
|
||||
|
||||
onCopyLinkClick(): void {
|
||||
const room = this.meetingContextService.meetRoom();
|
||||
@ -71,6 +59,6 @@ export class MeetingToolbarExtraButtonsComponent {
|
||||
this.log.w('Captions are disabled at system level (MEET_CAPTIONS_ENABLED=false)');
|
||||
return;
|
||||
}
|
||||
this.captionService.areCaptionsEnabledByUser() ? this.captionService.disable() : this.captionService.enable();
|
||||
this.areCaptionsEnabledByUser() ? this.captionService.disable() : this.captionService.enable();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@ -21,26 +21,16 @@ import { MeetingService } from '../../services/meeting.service';
|
||||
export class MeetingToolbarLeaveButtonComponent {
|
||||
protected meetingContextService = inject(MeetingContextService);
|
||||
protected meetingService = inject(MeetingService);
|
||||
protected openviduService = inject(OpenViduService);
|
||||
protected loggerService = inject(LoggerService);
|
||||
protected log = this.loggerService.get('OpenVidu Meet - MeetingToolbarLeaveButtons');
|
||||
protected openviduService = inject(OpenViduService);
|
||||
protected readonly leaveMenuTooltip = 'Leave options';
|
||||
protected readonly leaveOptionText = 'Leave meeting';
|
||||
protected readonly endMeetingOptionText = 'End meeting for all';
|
||||
|
||||
/**
|
||||
* Whether to show the leave menu with options
|
||||
*/
|
||||
protected showLeaveMenu = computed(() => {
|
||||
return this.meetingContextService.canModerateRoom();
|
||||
});
|
||||
showLeaveMenu = this.meetingContextService.canModerateRoom;
|
||||
isMobile = this.meetingContextService.isMobile;
|
||||
|
||||
/**
|
||||
* Whether the device is mobile (affects button style)
|
||||
*/
|
||||
protected isMobile = computed(() => {
|
||||
return this.meetingContextService.isMobile();
|
||||
});
|
||||
leaveMenuTooltip = 'Leave options';
|
||||
leaveOptionText = 'Leave meeting';
|
||||
endMeetingOptionText = 'End meeting for all';
|
||||
|
||||
async onLeaveMeetingClick(): Promise<void> {
|
||||
await this.openviduService.disconnectRoom();
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MeetingToolbarMoreOptionsMenuComponent } from './meeting-toolbar-more-options-menu.component';
|
||||
|
||||
describe('MeetingToolbarMoreOptionsButtonsComponent', () => {
|
||||
let component: MeetingToolbarMoreOptionsMenuComponent;
|
||||
let fixture: ComponentFixture<MeetingToolbarMoreOptionsMenuComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MeetingToolbarMoreOptionsMenuComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MeetingToolbarMoreOptionsMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
@ -12,52 +12,23 @@ import { MeetingContextService } from '../../services/meeting-context.service';
|
||||
* This component handles custom actions like opening the settings panel for grid layout changes.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ov-meeting-toolbar-more-options-menu',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatMenuModule,
|
||||
MatTooltipModule
|
||||
],
|
||||
templateUrl: './meeting-toolbar-more-options-menu.component.html',
|
||||
styleUrl: './meeting-toolbar-more-options-menu.component.scss'
|
||||
selector: 'ov-meeting-toolbar-more-options-menu',
|
||||
imports: [CommonModule, MatIconModule, MatButtonModule, MatMenuModule, MatTooltipModule],
|
||||
templateUrl: './meeting-toolbar-more-options-menu.component.html',
|
||||
styleUrl: './meeting-toolbar-more-options-menu.component.scss'
|
||||
})
|
||||
export class MeetingToolbarMoreOptionsMenuComponent {
|
||||
/**
|
||||
* Viewport service for responsive behavior detection
|
||||
* Injected from openvidu-components-angular
|
||||
*/
|
||||
private viewportService = inject(ViewportService);
|
||||
private meetingContextService = inject(MeetingContextService);
|
||||
private viewportService = inject(ViewportService);
|
||||
private panelService = inject(PanelService);
|
||||
|
||||
/**
|
||||
* Panel service for opening/closing panels
|
||||
* Injected from openvidu-components-angular
|
||||
*/
|
||||
private panelService = inject(PanelService);
|
||||
isMobileView = this.viewportService.isMobile;
|
||||
isLayoutSwitchingAllowed = this.meetingContextService.allowLayoutSwitching;
|
||||
|
||||
/**
|
||||
* Meeting context service for feature flags
|
||||
*/
|
||||
private meetingContextService = inject(MeetingContextService);
|
||||
|
||||
/**
|
||||
* Computed properties for responsive button behavior
|
||||
* These follow the same pattern as toolbar-media-buttons component
|
||||
*/
|
||||
readonly isMobileView = computed(() => this.viewportService.isMobile());
|
||||
readonly isTabletView = computed(() => this.viewportService.isTablet());
|
||||
readonly isDesktopView = computed(() => this.viewportService.isDesktop());
|
||||
|
||||
/**
|
||||
* Whether the layout switching feature is allowed
|
||||
*/
|
||||
readonly isLayoutSwitchingAllowed = computed(() => this.meetingContextService.allowLayoutSwitching());
|
||||
|
||||
/**
|
||||
* Opens the settings panel to allow users to change grid layout
|
||||
*/
|
||||
onOpenSettings(): void {
|
||||
this.panelService.togglePanel(PanelType.SETTINGS);
|
||||
}
|
||||
/**
|
||||
* Opens the settings panel to allow users to change grid layout
|
||||
*/
|
||||
onOpenSettings(): void {
|
||||
this.panelService.togglePanel(PanelType.SETTINGS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,15 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, computed, ContentChild, effect, inject, OnInit, signal, Signal } from '@angular/core';
|
||||
import { Component, computed, ContentChild, effect, inject, OnInit, signal } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import {
|
||||
OpenViduComponentsUiModule,
|
||||
OpenViduThemeMode,
|
||||
OpenViduThemeService,
|
||||
Room,
|
||||
Track
|
||||
} from 'openvidu-components-angular';
|
||||
import { OpenViduComponentsUiModule, OpenViduThemeMode, OpenViduThemeService, Room } from 'openvidu-components-angular';
|
||||
import { Subject } from 'rxjs';
|
||||
import { RoomFeatures } from '../../../../shared/models/app.model';
|
||||
import { GlobalConfigService } from '../../../../shared/services/global-config.service';
|
||||
import { NotificationService } from '../../../../shared/services/notification.service';
|
||||
import { RoomFeatureService } from '../../../../shared/services/room-feature.service';
|
||||
import { RuntimeConfigService } from '../../../../shared/services/runtime-config.service';
|
||||
import { SoundService } from '../../../../shared/services/sound.service';
|
||||
import { RoomMemberContextService } from '../../../room-members/services/room-member-context.service';
|
||||
import { MeetingLobbyComponent } from '../../components/meeting-lobby/meeting-lobby.component';
|
||||
import { MeetingParticipantItemComponent } from '../../customization/meeting-participant-item/meeting-participant-item.component';
|
||||
import { MeetingCaptionsService } from '../../services/meeting-captions.service';
|
||||
@ -41,7 +33,16 @@ import { MeetingLobbyService } from '../../services/meeting-lobby.service';
|
||||
providers: [MeetingLobbyService, MeetingEventHandlerService, SoundService]
|
||||
})
|
||||
export class MeetingComponent implements OnInit {
|
||||
protected _participantItem?: MeetingParticipantItemComponent;
|
||||
protected meetingContextService = inject(MeetingContextService);
|
||||
protected lobbyService = inject(MeetingLobbyService);
|
||||
protected eventHandlerService = inject(MeetingEventHandlerService);
|
||||
protected captionsService = inject(MeetingCaptionsService);
|
||||
protected configService = inject(GlobalConfigService);
|
||||
protected roomFeatureService = inject(RoomFeatureService);
|
||||
protected ovThemeService = inject(OpenViduThemeService);
|
||||
protected notificationService = inject(NotificationService);
|
||||
protected soundService = inject(SoundService);
|
||||
protected runtimeConfigService = inject(RuntimeConfigService);
|
||||
|
||||
// Template reference for custom participant panel item
|
||||
@ContentChild(MeetingParticipantItemComponent)
|
||||
@ -49,55 +50,28 @@ export class MeetingComponent implements OnInit {
|
||||
// Store the reference to the custom participant panel item component
|
||||
this._participantItem = value;
|
||||
}
|
||||
protected _participantItem?: MeetingParticipantItemComponent;
|
||||
protected participantItemTemplate = computed(() => this._participantItem?.template);
|
||||
|
||||
/**
|
||||
* Controls whether to show lobby (true) or meeting view (false)
|
||||
*/
|
||||
/** Controls whether to show lobby (true) or meeting view (false) */
|
||||
showLobby = true;
|
||||
isLobbyReady = false;
|
||||
|
||||
/**
|
||||
* Controls whether to show the videoconference component
|
||||
*/
|
||||
protected isMeetingLeft = signal(false);
|
||||
/** Controls whether to show the videoconference component */
|
||||
isMeetingLeft = signal(false);
|
||||
|
||||
// Signals for meeting context data
|
||||
roomName = this.lobbyService.roomName;
|
||||
roomMemberToken = this.lobbyService.roomMemberToken;
|
||||
e2eeKey = this.lobbyService.e2eeKeyValue;
|
||||
localParticipant = this.meetingContextService.localParticipant;
|
||||
|
||||
features = this.roomFeatureService.features;
|
||||
hasRecordings = this.meetingContextService.hasRecordings;
|
||||
|
||||
protected features: Signal<RoomFeatures>;
|
||||
protected roomMemberContextService = inject(RoomMemberContextService);
|
||||
protected roomFeatureService = inject(RoomFeatureService);
|
||||
protected ovThemeService = inject(OpenViduThemeService);
|
||||
protected configService = inject(GlobalConfigService);
|
||||
protected notificationService = inject(NotificationService);
|
||||
protected lobbyService = inject(MeetingLobbyService);
|
||||
protected meetingContextService = inject(MeetingContextService);
|
||||
protected eventHandlerService = inject(MeetingEventHandlerService);
|
||||
protected captionsService = inject(MeetingCaptionsService);
|
||||
protected soundService = inject(SoundService);
|
||||
protected runtimeConfigService = inject(RuntimeConfigService);
|
||||
protected destroy$ = new Subject<void>();
|
||||
|
||||
// === LOBBY PHASE COMPUTED SIGNALS (when showLobby = true) ===
|
||||
protected participantName = computed(() => this.lobbyService.participantName());
|
||||
protected e2eeKey = computed(() => this.lobbyService.e2eeKeyValue());
|
||||
protected roomName = computed(() => this.lobbyService.roomName());
|
||||
protected roomMemberToken = computed(() => this.lobbyService.roomMemberToken());
|
||||
|
||||
// === MEETING PHASE COMPUTED SIGNALS (when showLobby = false) ===
|
||||
// These read from MeetingContextService (Single Source of Truth during meeting)
|
||||
protected localParticipant = computed(() => this.meetingContextService.localParticipant());
|
||||
protected remoteParticipants = computed(() => this.meetingContextService.remoteParticipants());
|
||||
protected hasRemoteParticipants = computed(() => this.remoteParticipants().length > 0);
|
||||
protected participantsVersion = computed(() => this.meetingContextService.participantsVersion());
|
||||
|
||||
// === SHARED COMPUTED SIGNALS (used in both phases) ===
|
||||
// Both lobby and meeting need these, so we read from MeetingContextService (Single Source of Truth)
|
||||
protected roomId = computed(() => this.meetingContextService.roomId());
|
||||
protected roomSecret = computed(() => this.meetingContextService.roomSecret());
|
||||
protected hasRecordings = computed(() => this.meetingContextService.hasRecordings());
|
||||
|
||||
constructor() {
|
||||
this.features = this.roomFeatureService.features;
|
||||
|
||||
// Change theme variables when custom theme is enabled
|
||||
effect(() => {
|
||||
if (this.features().hasCustomTheme) {
|
||||
@ -156,28 +130,8 @@ export class MeetingComponent implements OnInit {
|
||||
this.captionsService.destroy();
|
||||
}
|
||||
|
||||
// async onRoomConnected() {
|
||||
// try {
|
||||
// // Suscribirse solo para actualizar el estado de video pin
|
||||
// // Los participantes se actualizan automáticamente en MeetingContextService
|
||||
// combineLatest([
|
||||
// this.ovComponentsParticipantService.remoteParticipants$,
|
||||
// this.ovComponentsParticipantService.localParticipant$
|
||||
// ])
|
||||
// .pipe(takeUntil(this.destroy$))
|
||||
// .subscribe(() => {
|
||||
// this.updateVideoPinState();
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error('Error accessing meeting:', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
onRoomCreated(lkRoom: Room) {
|
||||
// At this point, user has joined the meeting and MeetingContextService becomes the Single Source of Truth
|
||||
// MeetingContextService has been updated during lobby initialization with roomId, roomSecret, hasRecordings
|
||||
// All subsequent updates (hasRecordings, roomSecret, participants) go to MeetingContextService
|
||||
|
||||
// Store LiveKit room in context
|
||||
this.meetingContextService.setLkRoom(lkRoom);
|
||||
|
||||
@ -193,29 +147,15 @@ export class MeetingComponent implements OnInit {
|
||||
this.eventHandlerService.setupRoomListeners(lkRoom);
|
||||
}
|
||||
|
||||
// async leaveMeeting() {
|
||||
// await this.openviduService.disconnectRoom();
|
||||
// }
|
||||
|
||||
// async endMeeting() {
|
||||
// if (!this.participantService.isModerator()) return;
|
||||
|
||||
// this.meetingContextService.setMeetingEndedBy('self');
|
||||
|
||||
// try {
|
||||
// await this.meetingService.endMeeting(this.roomId()!);
|
||||
// } catch (error) {
|
||||
// console.error('Error ending meeting:', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
async onViewRecordingsClicked() {
|
||||
const basePath = this.runtimeConfigService.basePath;
|
||||
const basePathForUrl = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
|
||||
let recordingsUrl = `${basePathForUrl}/room/${this.roomId()}/recordings`;
|
||||
|
||||
const roomId = this.meetingContextService.roomId();
|
||||
let recordingsUrl = `${basePathForUrl}/room/${roomId}/recordings`;
|
||||
|
||||
// Append room secret as query param if it exists
|
||||
const secret = this.roomSecret();
|
||||
const secret = this.meetingContextService.roomSecret();
|
||||
if (secret) {
|
||||
recordingsUrl += `?secret=${secret}`;
|
||||
}
|
||||
@ -232,27 +172,8 @@ export class MeetingComponent implements OnInit {
|
||||
/**
|
||||
* Handles the participant left event and hides the videoconference component
|
||||
*/
|
||||
protected onParticipantLeft(event: any): void {
|
||||
onParticipantLeft(event: any): void {
|
||||
this.isMeetingLeft.set(true);
|
||||
this.eventHandlerService.onParticipantLeft(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized logic for managing video pinning based on
|
||||
* remote participants and local screen sharing state.
|
||||
*/
|
||||
protected updateVideoPinState(): void {
|
||||
const localParticipant = this.localParticipant();
|
||||
if (!localParticipant) return;
|
||||
|
||||
const isSharing = localParticipant.isScreenShareEnabled;
|
||||
|
||||
if (this.hasRemoteParticipants() && isSharing) {
|
||||
// Pin the local screen share to appear bigger
|
||||
localParticipant.setVideoPinnedBySource(Track.Source.ScreenShare, true);
|
||||
} else {
|
||||
// Unpin everything if no remote participants or not sharing
|
||||
localParticipant.setAllVideoPinned(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,9 +18,9 @@ import { CustomParticipantModel } from '../models/custom-participant.model';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MeetingCaptionsService {
|
||||
private readonly participantService = inject(ParticipantService);
|
||||
private readonly loggerService = inject(LoggerService);
|
||||
private readonly logger: ILogger;
|
||||
private readonly participantService = inject(ParticipantService);
|
||||
|
||||
// Configuration with defaults
|
||||
private readonly defaultConfig: Required<CaptionsConfig> = {
|
||||
|
||||
@ -28,7 +28,6 @@ export class MeetingContextService {
|
||||
private readonly _hasRecordings = signal<boolean>(false);
|
||||
private readonly _meetingEndedBy = signal<'self' | 'other' | null>(null);
|
||||
private readonly _lkRoom = signal<Room | undefined>(undefined);
|
||||
private readonly _participantsVersion = signal<number>(0);
|
||||
private readonly _localParticipant = signal<CustomParticipantModel | undefined>(undefined);
|
||||
private readonly _remoteParticipants = signal<CustomParticipantModel[]>([]);
|
||||
|
||||
@ -53,11 +52,6 @@ export class MeetingContextService {
|
||||
|
||||
/** Readonly signal for the current LiveKit room */
|
||||
readonly lkRoom = this._lkRoom.asReadonly();
|
||||
/**
|
||||
* Readonly signal for participants version (increments on role changes)
|
||||
* Used to trigger reactivity when participant properties change without array reference changes
|
||||
*/
|
||||
readonly participantsVersion = this._participantsVersion.asReadonly();
|
||||
/** Readonly signal for the local participant */
|
||||
readonly localParticipant = this._localParticipant.asReadonly();
|
||||
/** Readonly signal for the remote participants */
|
||||
@ -172,14 +166,6 @@ export class MeetingContextService {
|
||||
this._lkRoom.set(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the participants version counter
|
||||
* Used to trigger reactivity when participant properties (like role) change
|
||||
*/
|
||||
incrementParticipantsVersion(): void {
|
||||
this._participantsVersion.update((v) => v + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes participants from OpenVidu Components ParticipantService using signals.
|
||||
* Effects are automatically cleaned up when the service is destroyed.
|
||||
@ -211,7 +197,6 @@ export class MeetingContextService {
|
||||
this._roomSecret.set(undefined);
|
||||
this._hasRecordings.set(false);
|
||||
this._meetingEndedBy.set(null);
|
||||
this._participantsVersion.set(0);
|
||||
this._localParticipant.set(undefined);
|
||||
this._remoteParticipants.set([]);
|
||||
}
|
||||
|
||||
@ -59,23 +59,11 @@ export class MeetingEventHandlerService {
|
||||
* @param room The LiveKit Room instance
|
||||
*/
|
||||
setupRoomListeners(room: Room): void {
|
||||
this.setupDataReceivedListener(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the DataReceived event listener for handling room signals
|
||||
* @param room The LiveKit Room instance
|
||||
*/
|
||||
private setupDataReceivedListener(room: Room): void {
|
||||
room.on(
|
||||
RoomEvent.DataReceived,
|
||||
async (payload: Uint8Array, _participant?: RemoteParticipant, _kind?: DataPacket_Kind, topic?: string) => {
|
||||
// Only process topics that this handler is responsible for
|
||||
const relevantTopics = [
|
||||
'recordingStopped',
|
||||
MeetSignalType.MEET_ROOM_CONFIG_UPDATED,
|
||||
MeetSignalType.MEET_PARTICIPANT_ROLE_UPDATED
|
||||
];
|
||||
const relevantTopics = ['recordingStopped', MeetSignalType.MEET_PARTICIPANT_ROLE_UPDATED];
|
||||
|
||||
if (!topic || !relevantTopics.includes(topic)) {
|
||||
return;
|
||||
@ -90,14 +78,10 @@ export class MeetingEventHandlerService {
|
||||
this.meetingContext.setHasRecordings(true);
|
||||
break;
|
||||
|
||||
case MeetSignalType.MEET_ROOM_CONFIG_UPDATED:
|
||||
// Room cannot be updated if a meeting is ongoing
|
||||
// await this.handleRoomConfigUpdated(event);
|
||||
break;
|
||||
|
||||
case MeetSignalType.MEET_PARTICIPANT_ROLE_UPDATED:
|
||||
await this.handleParticipantRoleUpdated(event);
|
||||
this.showParticipantRoleUpdatedNotification(event);
|
||||
const roleUpdateEvent = event as MeetParticipantRoleUpdatedPayload;
|
||||
await this.handleParticipantRoleUpdated(roleUpdateEvent);
|
||||
this.showParticipantRoleUpdatedNotification(roleUpdateEvent.newRole);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
@ -111,8 +95,6 @@ export class MeetingEventHandlerService {
|
||||
* Handles participant connected event.
|
||||
* Sends JOINED event to parent window (for web component integration).
|
||||
*
|
||||
* Arrow function ensures correct 'this' binding when called from template.
|
||||
*
|
||||
* @param event Participant model from OpenVidu
|
||||
*/
|
||||
onParticipantConnected = (event: ParticipantModel): void => {
|
||||
@ -130,19 +112,17 @@ export class MeetingEventHandlerService {
|
||||
* Handles participant left event.
|
||||
* - Maps technical reason to user-friendly reason
|
||||
* - Sends LEFT event to parent window
|
||||
* - Cleans up session storage (secrets, tokens)
|
||||
* - Clears participant identity and token from RoomMemberContextService
|
||||
* - Navigates to disconnected page
|
||||
*
|
||||
* Arrow function ensures correct 'this' binding when called from template.
|
||||
*
|
||||
* @param event Participant left event from OpenVidu
|
||||
*/
|
||||
onParticipantLeft = async (event: ParticipantLeftEvent): Promise<void> => {
|
||||
let leftReason = this.mapLeftReason(event.reason);
|
||||
|
||||
// If meeting was ended by this user, update reason
|
||||
const meetingEndedBy = this.meetingContext.meetingEndedBy();
|
||||
if (leftReason === LeftEventReason.MEETING_ENDED && meetingEndedBy === 'self') {
|
||||
// If meeting was ended by local user, update reason
|
||||
const meetingEndedBySelf = this.meetingContext.meetingEndedBy() === 'self';
|
||||
if (leftReason === LeftEventReason.MEETING_ENDED && meetingEndedBySelf) {
|
||||
leftReason = LeftEventReason.MEETING_ENDED_BY_SELF;
|
||||
}
|
||||
|
||||
@ -167,8 +147,6 @@ export class MeetingEventHandlerService {
|
||||
/**
|
||||
* Handles recording start request event.
|
||||
*
|
||||
* Arrow function ensures correct 'this' binding when called from template.
|
||||
*
|
||||
* @param event Recording start requested event from OpenVidu
|
||||
*/
|
||||
onRecordingStartRequested = async (event: RecordingStartRequestedEvent): Promise<void> => {
|
||||
@ -189,8 +167,6 @@ export class MeetingEventHandlerService {
|
||||
/**
|
||||
* Handles recording stop request event.
|
||||
*
|
||||
* Arrow function ensures correct 'this' binding when called from template.
|
||||
*
|
||||
* @param event Recording stop requested event from OpenVidu
|
||||
*/
|
||||
onRecordingStopRequested = async (event: RecordingStopRequestedEvent): Promise<void> => {
|
||||
@ -205,42 +181,6 @@ export class MeetingEventHandlerService {
|
||||
// PRIVATE METHODS - Event Handlers
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Handles room config updated event.
|
||||
* Updates feature config and refreshes room member token if needed.
|
||||
* Obtains roomId and roomSecret from MeetingContextService.
|
||||
*/
|
||||
// private async handleRoomConfigUpdated(event: MeetRoomConfigUpdatedPayload): Promise<void> {
|
||||
// const { config } = event;
|
||||
|
||||
// // Update feature configuration
|
||||
// this.featureConfService.setRoomConfig(config);
|
||||
|
||||
// // Refresh room member token if recording is enabled
|
||||
// if (config.recording.enabled) {
|
||||
// try {
|
||||
// const roomId = this.meetingContext.roomId();
|
||||
// const roomSecret = this.meetingContext.roomSecret();
|
||||
// const participantName = this.roomMemberService.getParticipantName();
|
||||
// const participantIdentity = this.roomMemberService.getParticipantIdentity();
|
||||
|
||||
// if (!roomId || !roomSecret) {
|
||||
// console.error('Room ID or secret not available for token refresh');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// await this.roomMemberService.generateToken(roomId, {
|
||||
// secret: roomSecret,
|
||||
// grantJoinMeetingPermission: true,
|
||||
// participantName,
|
||||
// participantIdentity
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error('Error refreshing room member token:', error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Handles participant role updated event.
|
||||
* Updates local or remote participant role and refreshes room member token if needed.
|
||||
@ -256,9 +196,8 @@ export class MeetingEventHandlerService {
|
||||
if (local && participantIdentity === local.identity) {
|
||||
if (!secret || !roomId) return;
|
||||
|
||||
// Update room secret in context
|
||||
// Update room secret in context (without updating session storage)
|
||||
this.meetingContext.setRoomSecret(secret);
|
||||
this.sessionStorageService.setRoomSecret(secret);
|
||||
|
||||
try {
|
||||
// Refresh participant token with new role
|
||||
@ -272,9 +211,6 @@ export class MeetingEventHandlerService {
|
||||
// Update local participant role
|
||||
local.meetRole = newRole;
|
||||
console.log(`You have been assigned the role of ${newRole}`);
|
||||
|
||||
// Increment version to trigger reactivity
|
||||
this.meetingContext.incrementParticipantsVersion();
|
||||
} catch (error) {
|
||||
console.error('Error refreshing room member token:', error);
|
||||
}
|
||||
@ -284,15 +220,11 @@ export class MeetingEventHandlerService {
|
||||
const participant = remoteParticipants.find((p) => p.identity === participantIdentity);
|
||||
if (participant) {
|
||||
participant.meetRole = newRole;
|
||||
|
||||
// Increment version to trigger reactivity
|
||||
this.meetingContext.incrementParticipantsVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private showParticipantRoleUpdatedNotification(event: MeetParticipantRoleUpdatedPayload): void {
|
||||
const { newRole } = event as MeetParticipantRoleUpdatedPayload;
|
||||
private showParticipantRoleUpdatedNotification(newRole: MeetRoomMemberRole): void {
|
||||
this.notificationService.showSnackbar(`You have been assigned the role of ${newRole.toUpperCase()}`);
|
||||
newRole === MeetRoomMemberRole.MODERATOR
|
||||
? this.soundService.playParticipantRoleUpgradedSound()
|
||||
|
||||
@ -20,20 +20,19 @@ import { MeetingService } from './meeting.service';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MeetingWebComponentManagerService {
|
||||
protected meetingService = inject(MeetingService);
|
||||
protected meetingContextService = inject(MeetingContextService);
|
||||
protected roomMemberContextService = inject(RoomMemberContextService);
|
||||
protected appCtxService = inject(AppContextService);
|
||||
protected openviduService = inject(OpenViduService);
|
||||
protected loggerService = inject(LoggerService);
|
||||
protected log = this.loggerService.get('OpenVidu Meet - WebComponentManagerService');
|
||||
|
||||
protected isInitialized = false;
|
||||
protected parentDomain: string = '';
|
||||
protected boundHandleMessage: (event: MessageEvent) => Promise<void>;
|
||||
|
||||
protected log;
|
||||
protected readonly meetingContextService = inject(MeetingContextService);
|
||||
protected readonly roomMemberContextService = inject(RoomMemberContextService);
|
||||
protected readonly openviduService = inject(OpenViduService);
|
||||
protected readonly meetingService = inject(MeetingService);
|
||||
protected readonly loggerService = inject(LoggerService);
|
||||
protected readonly appCtxService = inject(AppContextService);
|
||||
|
||||
constructor() {
|
||||
this.log = this.loggerService.get('OpenVidu Meet - WebComponentManagerService');
|
||||
this.boundHandleMessage = this.handleMessage.bind(this);
|
||||
effect(() => {
|
||||
if (this.appCtxService.isEmbeddedMode()) {
|
||||
@ -95,6 +94,7 @@ export class MeetingWebComponentManagerService {
|
||||
const message: WebComponentInboundCommandMessage = event.data;
|
||||
const { command, payload } = message;
|
||||
|
||||
// If parent domain is not set, only accept INITIALIZE command to set the parent domain
|
||||
if (!this.parentDomain) {
|
||||
if (command === WebComponentCommand.INITIALIZE) {
|
||||
if (!payload || !('domain' in payload)) {
|
||||
@ -107,6 +107,7 @@ export class MeetingWebComponentManagerService {
|
||||
return;
|
||||
}
|
||||
|
||||
// For security, only accept messages from the parent domain
|
||||
if (event.origin !== this.parentDomain) {
|
||||
console.warn(`Untrusted origin: ${event.origin}`);
|
||||
return;
|
||||
|
||||
@ -13,16 +13,12 @@ export class AppContextService {
|
||||
private readonly _edition: WritableSignal<Edition> = signal(Edition.CE);
|
||||
private readonly _version: WritableSignal<string> = signal('');
|
||||
|
||||
readonly mode = computed(() => this._mode());
|
||||
readonly edition = computed(() => this._edition());
|
||||
readonly version = computed(() => this._version());
|
||||
readonly mode = this._mode.asReadonly();
|
||||
readonly edition = this._edition.asReadonly();
|
||||
readonly version = this._version.asReadonly();
|
||||
|
||||
readonly isEmbeddedMode = computed(() => this._mode() === ApplicationMode.EMBEDDED);
|
||||
readonly isStandaloneMode = computed(() => this._mode() === ApplicationMode.STANDALONE);
|
||||
readonly appData = computed(() => ({
|
||||
mode: this._mode(),
|
||||
edition: this._edition(),
|
||||
version: this._version()
|
||||
}));
|
||||
|
||||
constructor() {
|
||||
this.detectMode();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user