import { PlayerAPI } from 'bitmovin-player'; import { i18n } from '../../localization/i18n'; import { version as UI_VERSION } from '../../version'; import { Label, LabelConfig } from '../labels/Label'; import { UIInstanceManager } from '../../UIManager'; import { PlayerUtils } from '../../utils/PlayerUtils'; import { TimestampLinkUtils } from '../../utils/TimestampLinkUtils'; import { ContextMenu, ContextMenuConfig } from './ContextMenu'; import { PlayerInsightsContextMenuItem } from '../panels/player-insights/PlayerInsightsContextMenuItem'; import type { PlayerInsightsPanel } from '../panels/player-insights/PlayerInsightsPanel'; import { InteractiveContextMenuItem, InteractiveContextMenuItemConfig } from './InteractiveContextMenuItem'; import { SettingsPanelItem, SettingsPanelItemConfig } from '../settings/SettingsPanelItem'; import { SettingsPanelPage } from '../settings/SettingsPanelPage'; import { SettingsPanelSeparator } from '../settings/SettingsPanelSeparator'; /** * Configuration interface for the {@link PlayerContextMenu}. * * @category Configs */ export interface PlayerContextMenuConfig extends ContextMenuConfig { /** * The player insights panel to expose as a default context menu action. */ playerInsightsPanel: PlayerInsightsPanel; } /** * A player-specific context menu with Bitmovin info and Player/UI versions. * * @category Components */ export class PlayerContextMenu extends ContextMenu { private readonly copyTimestampLinkItem: InteractiveContextMenuItem; constructor(config: PlayerContextMenuConfig) { super(config); this.config = this.mergeConfig( config, { cssClasses: ['ui-player-context-menu'], }, this.config, ); const copyTimestampLinkItem = new InteractiveContextMenuItem({ label: new Label({ text: i18n.getLocalizer('contextMenu.copyTimestampLink'), }), ariaLabel: i18n.getLocalizer('contextMenu.copyTimestampLink'), closeContextMenuOnAction: true, }); this.copyTimestampLinkItem = copyTimestampLinkItem; this.addComponent( new SettingsPanelPage({ components: [ new PlayerInfoContextMenuItem(), new SettingsPanelSeparator(), new PlayerInsightsContextMenuItem({ playerInsightsPanel: config.playerInsightsPanel, }), copyTimestampLinkItem, ], }), ); } configure(player: PlayerAPI, uimanager: UIInstanceManager): void { super.configure(player, uimanager); this.copyTimestampLinkItem.onClick.subscribe(() => { const timestampLink = TimestampLinkUtils.buildTimestampLink(player.getCurrentTime()); if (!navigator.clipboard?.writeText) { console.warn('Clipboard API is not available. Timestamp link was not copied.'); return; } navigator.clipboard .writeText(timestampLink) .catch(() => console.warn('Failed to copy timestamp link to clipboard.')); }); const liveStreamDetector = new PlayerUtils.LiveStreamDetector(player, uimanager); liveStreamDetector.onLiveChanged.subscribe((sender, args: PlayerUtils.LiveStreamDetectorEventArgs) => { if (!uimanager.getConfig().enableTimestampDeepLink || args.live) { this.copyTimestampLinkItem.hide(); } else { this.copyTimestampLinkItem.show(); } }); liveStreamDetector.detect(); } } class PlayerInfoContextMenuItem extends SettingsPanelItem { private readonly playerVersionLabel: Label; constructor(config: SettingsPanelItemConfig = {}) { super(config); const playerVersionLabel = new Label({ text: 'Player: -', cssClasses: ['ui-player-context-menu-info'], }); this.config = this.mergeConfig( config, { label: null, cssClasses: ['ui-player-context-menu-info-item'], isSetting: false, role: 'group', tabIndex: -1, }, this.config, ); this.playerVersionLabel = playerVersionLabel; this.addComponent( new Label({ text: i18n.getLocalizer('contextMenu.title'), cssClasses: ['ui-player-context-menu-header'], }), ); this.addComponent( new Label({ text: i18n.getLocalizer('contextMenu.subtitle'), cssClasses: ['ui-player-context-menu-subtitle'], }), ); this.addComponent(playerVersionLabel); this.addComponent( new Label({ text: `UI: ${UI_VERSION}`, cssClasses: ['ui-player-context-menu-info'], }), ); } configure(player: PlayerAPI, uimanager: UIInstanceManager): void { super.configure(player, uimanager); this.playerVersionLabel.setText(`Player: ${player.version}`); } }