diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index e8054e5fb3..7ce9448306 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -4,7 +4,8 @@ import Events from '../../utils/events.ts'; import layoutManager from '../layoutManager'; import { playbackManager } from '../playback/playbackmanager'; import playMethodHelper from '../playback/playmethodhelper'; -import SyncPlay from '../../plugins/syncPlay/core'; +import { pluginManager } from '../pluginManager'; +import { PluginType } from '../../types/plugin.ts'; import './playerstats.scss'; import ServerConnections from '../ServerConnections'; @@ -325,6 +326,12 @@ import ServerConnections from '../ServerConnections'; } function getSyncPlayStats() { + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + + if (!SyncPlay?.Manager.isSyncPlayEnabled()) { + return []; + } + const syncStats = []; const stats = SyncPlay.Manager.getStats(); @@ -422,10 +429,10 @@ import ServerConnections from '../ServerConnections'; name: globalize.translate('LabelOriginalMediaInfo') }); - const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); - if (SyncPlay.Manager.isSyncPlayEnabled() && apiClient.isMinServerVersion('10.6.0')) { + const syncPlayStats = getSyncPlayStats(); + if (syncPlayStats.length > 0) { categories.push({ - stats: getSyncPlayStats(), + stats: syncPlayStats, name: globalize.translate('LabelSyncPlayInfo') }); } diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index 70ae0644d1..90d835e1c5 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -4,10 +4,12 @@ import dialogHelper from '../dialogHelper/dialogHelper'; import loading from '../loading/loading'; import layoutManager from '../layoutManager'; import { playbackManager } from '../playback/playbackmanager'; -import SyncPlay from '../../plugins/syncPlay/core'; +import { pluginManager } from '../pluginManager'; import * as userSettings from '../../scripts/settings/userSettings'; import { appRouter } from '../appRouter'; import globalize from '../../scripts/globalize'; +import { PluginType } from '../../types/plugin.ts'; + import '../../elements/emby-button/emby-button'; import '../../elements/emby-input/emby-input'; import '../../elements/emby-button/paper-icon-button-light'; @@ -117,10 +119,12 @@ import ServerConnections from '../ServerConnections'; }; const apiClient = ServerConnections.getApiClient(currentServerId); + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; - if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay.Manager.isSyncPlayEnabled()) { + if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) { html += ``; } diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index 763f0eb2d1..c2b67e4213 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -119,9 +119,14 @@ class PluginManager { } ofType(type) { - return this.pluginsList.filter((o) => { - return o.type === type; - }); + return this.pluginsList.filter(plugin => plugin.type === type); + } + + firstOfType(type) { + // Get all plugins of the specified type + return this.ofType(type) + // Return the plugin with the "highest" (lowest numeric value) priority + .sort((p1, p2) => (p1.priority || 0) - (p2.priority || 0))[0]; } #mapRoute(plugin, route) { diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index d7bbab581d..0d3637615d 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1,6 +1,5 @@ import escapeHtml from 'escape-html'; import { playbackManager } from '../../../components/playback/playbackmanager'; -import SyncPlay from '../../../plugins/syncPlay/core'; import browser from '../../../scripts/browser'; import dom from '../../../scripts/dom'; import inputManager from '../../../scripts/inputManager'; @@ -25,6 +24,8 @@ import SubtitleSync from '../../../components/subtitlesync/subtitlesync'; import { appRouter } from '../../../components/appRouter'; import LibraryMenu from '../../../scripts/libraryMenu'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop'; +import { pluginManager } from '../../../components/pluginManager'; +import { PluginType } from '../../../types/plugin.ts'; /* eslint-disable indent */ const TICKS_PER_MINUTE = 600000000; @@ -1774,38 +1775,39 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components }, iconVisibilityTime); }; - Events.on(SyncPlay.Manager, 'enabled', (event, enabled) => { - if (enabled) { - // SyncPlay enabled - } else { - const syncPlayIcon = view.querySelector('#syncPlayIcon'); - syncPlayIcon.style.visibility = 'hidden'; - } - }); + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + if (SyncPlay) { + Events.on(SyncPlay.Manager, 'enabled', (_event, enabled) => { + if (!enabled) { + const syncPlayIcon = view.querySelector('#syncPlayIcon'); + syncPlayIcon.style.visibility = 'hidden'; + } + }); - Events.on(SyncPlay.Manager, 'notify-osd', (event, action) => { - showIcon(action); - }); + Events.on(SyncPlay.Manager, 'notify-osd', (_event, action) => { + showIcon(action); + }); - Events.on(SyncPlay.Manager, 'group-state-update', (event, state, reason) => { - if (state === 'Playing' && reason === 'Unpause') { - showIcon('schedule-play'); - } else if (state === 'Playing' && reason === 'Ready') { - showIcon('schedule-play'); - } else if (state === 'Paused' && reason === 'Pause') { - showIcon('pause'); - } else if (state === 'Paused' && reason === 'Ready') { - showIcon('clear'); - } else if (state === 'Waiting' && reason === 'Seek') { - showIcon('seek'); - } else if (state === 'Waiting' && reason === 'Buffer') { - showIcon('buffering'); - } else if (state === 'Waiting' && reason === 'Pause') { - showIcon('wait-pause'); - } else if (state === 'Waiting' && reason === 'Unpause') { - showIcon('wait-unpause'); - } - }); + Events.on(SyncPlay.Manager, 'group-state-update', (_event, state, reason) => { + if (state === 'Playing' && reason === 'Unpause') { + showIcon('schedule-play'); + } else if (state === 'Playing' && reason === 'Ready') { + showIcon('schedule-play'); + } else if (state === 'Paused' && reason === 'Pause') { + showIcon('pause'); + } else if (state === 'Paused' && reason === 'Ready') { + showIcon('clear'); + } else if (state === 'Waiting' && reason === 'Seek') { + showIcon('seek'); + } else if (state === 'Waiting' && reason === 'Buffer') { + showIcon('buffering'); + } else if (state === 'Waiting' && reason === 'Pause') { + showIcon('wait-pause'); + } else if (state === 'Waiting' && reason === 'Unpause') { + showIcon('wait-unpause'); + } + }); + } } /* eslint-enable indent */ diff --git a/src/plugins/syncPlay/plugin.ts b/src/plugins/syncPlay/plugin.ts index 59c066aaf7..33d99aa757 100644 --- a/src/plugins/syncPlay/plugin.ts +++ b/src/plugins/syncPlay/plugin.ts @@ -12,6 +12,7 @@ class SyncPlayPlugin implements Plugin { id: string; type: string; priority: number; + instance: typeof SyncPlay; constructor() { this.name = 'SyncPlay Plugin'; @@ -21,6 +22,8 @@ class SyncPlayPlugin implements Plugin { this.type = PluginType.SyncPlay; this.priority = 1; + this.instance = SyncPlay; + this.init(); } diff --git a/src/plugins/syncPlay/ui/groupSelectionMenu.js b/src/plugins/syncPlay/ui/groupSelectionMenu.js index 383d893782..36589dfcb4 100644 --- a/src/plugins/syncPlay/ui/groupSelectionMenu.js +++ b/src/plugins/syncPlay/ui/groupSelectionMenu.js @@ -1,11 +1,12 @@ -import SyncPlay from '../core'; import SyncPlaySettingsEditor from './settings/SettingsEditor'; import loading from '../../../components/loading/loading'; import toast from '../../../components/toast/toast'; import actionsheet from '../../../components/actionSheet/actionSheet'; import globalize from '../../../scripts/globalize'; import playbackPermissionManager from './playbackPermissionManager'; +import { pluginManager } from '../../../components/pluginManager'; import ServerConnections from '../../../components/ServerConnections'; +import { PluginType } from '../../../types/plugin.ts'; import Events from '../../../utils/events.ts'; import './groupSelectionMenu.scss'; @@ -17,8 +18,22 @@ class GroupSelectionMenu { constructor() { // Register to SyncPlay events. this.syncPlayEnabled = false; - Events.on(SyncPlay.Manager, 'enabled', (e, enabled) => { - this.syncPlayEnabled = enabled; + this.SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + + if (this.SyncPlay) { + Events.on(this.SyncPlay.Manager, 'enabled', (_event, enabled) => { + this.syncPlayEnabled = enabled; + }); + } + + Events.on(pluginManager, 'registered', (_event0, plugin) => { + if (plugin.type === PluginType.SyncPlay) { + this.SyncPlay = plugin.instance; + + Events.on(plugin.instance.Manager, 'enabled', (_event1, enabled) => { + this.syncPlayEnabled = enabled; + }); + } }); } @@ -103,10 +118,11 @@ class GroupSelectionMenu { * @param {Object} apiClient - ApiClient. */ showLeaveGroupSelection(button, user, apiClient) { - const groupInfo = SyncPlay.Manager.getGroupInfo(); + const groupInfo = this.SyncPlay?.Manager.getGroupInfo(); const menuItems = []; - if (!SyncPlay.Manager.isPlaylistEmpty() && !SyncPlay.Manager.isPlaybackActive()) { + if (!this.SyncPlay?.Manager.isPlaylistEmpty() + && !this.SyncPlay?.Manager.isPlaybackActive()) { menuItems.push({ name: globalize.translate('LabelSyncPlayResumePlayback'), icon: 'play_circle_filled', @@ -114,7 +130,7 @@ class GroupSelectionMenu { selected: false, secondaryText: globalize.translate('LabelSyncPlayResumePlaybackDescription') }); - } else if (SyncPlay.Manager.isPlaybackActive()) { + } else if (this.SyncPlay?.Manager.isPlaybackActive()) { menuItems.push({ name: globalize.translate('LabelSyncPlayHaltPlayback'), icon: 'pause_circle_filled', @@ -149,15 +165,15 @@ class GroupSelectionMenu { border: true }; - actionsheet.show(menuOptions).then(function (id) { + actionsheet.show(menuOptions).then((id) => { if (id == 'resume-playback') { - SyncPlay.Manager.resumeGroupPlayback(apiClient); + this.SyncPlay?.Manager.resumeGroupPlayback(apiClient); } else if (id == 'halt-playback') { - SyncPlay.Manager.haltGroupPlayback(apiClient); + this.SyncPlay?.Manager.haltGroupPlayback(apiClient); } else if (id == 'leave-group') { apiClient.leaveSyncPlayGroup(); } else if (id == 'settings') { - new SyncPlaySettingsEditor(apiClient, SyncPlay.Manager.getTimeSyncCore(), { groupInfo: groupInfo }) + new SyncPlaySettingsEditor(apiClient, this.SyncPlay?.Manager.getTimeSyncCore(), { groupInfo: groupInfo }) .embed() .catch(error => { if (error) { diff --git a/src/plugins/syncPlay/ui/settings/SettingsEditor.js b/src/plugins/syncPlay/ui/settings/SettingsEditor.js index f782d0b91e..0377feab65 100644 --- a/src/plugins/syncPlay/ui/settings/SettingsEditor.js +++ b/src/plugins/syncPlay/ui/settings/SettingsEditor.js @@ -3,13 +3,14 @@ * @module components/syncPlay/settings/SettingsEditor */ -import SyncPlay from '../../core'; import { setSetting } from '../../core/Settings'; import dialogHelper from '../../../../components/dialogHelper/dialogHelper'; import layoutManager from '../../../../components/layoutManager'; +import { pluginManager } from '../../../../components/pluginManager'; import loading from '../../../../components/loading/loading'; import toast from '../../../../components/toast/toast'; import globalize from '../../../../scripts/globalize'; +import { PluginType } from '../../../../types/plugin.ts'; import Events from '../../../../utils/events.ts'; import 'material-design-icons-iconfont'; @@ -36,6 +37,7 @@ class SettingsEditor { this.apiClient = apiClient; this.timeSyncCore = timeSyncCore; this.options = options; + this.SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; } async embed() { @@ -95,14 +97,14 @@ class SettingsEditor { async initEditor() { const { context } = this; - context.querySelector('#txtExtraTimeOffset').value = SyncPlay.Manager.timeSyncCore.extraTimeOffset; - context.querySelector('#chkSyncCorrection').checked = SyncPlay.Manager.playbackCore.enableSyncCorrection; - context.querySelector('#txtMinDelaySpeedToSync').value = SyncPlay.Manager.playbackCore.minDelaySpeedToSync; - context.querySelector('#txtMaxDelaySpeedToSync').value = SyncPlay.Manager.playbackCore.maxDelaySpeedToSync; - context.querySelector('#txtSpeedToSyncDuration').value = SyncPlay.Manager.playbackCore.speedToSyncDuration; - context.querySelector('#txtMinDelaySkipToSync').value = SyncPlay.Manager.playbackCore.minDelaySkipToSync; - context.querySelector('#chkSpeedToSync').checked = SyncPlay.Manager.playbackCore.useSpeedToSync; - context.querySelector('#chkSkipToSync').checked = SyncPlay.Manager.playbackCore.useSkipToSync; + context.querySelector('#txtExtraTimeOffset').value = this.SyncPlay?.Manager.timeSyncCore.extraTimeOffset; + context.querySelector('#chkSyncCorrection').checked = this.SyncPlay?.Manager.playbackCore.enableSyncCorrection; + context.querySelector('#txtMinDelaySpeedToSync').value = this.SyncPlay?.Manager.playbackCore.minDelaySpeedToSync; + context.querySelector('#txtMaxDelaySpeedToSync').value = this.SyncPlay?.Manager.playbackCore.maxDelaySpeedToSync; + context.querySelector('#txtSpeedToSyncDuration').value = this.SyncPlay?.Manager.playbackCore.speedToSyncDuration; + context.querySelector('#txtMinDelaySkipToSync').value = this.SyncPlay?.Manager.playbackCore.minDelaySkipToSync; + context.querySelector('#chkSpeedToSync').checked = this.SyncPlay?.Manager.playbackCore.useSpeedToSync; + context.querySelector('#chkSkipToSync').checked = this.SyncPlay?.Manager.playbackCore.useSkipToSync; } onSubmit() { @@ -139,7 +141,7 @@ class SettingsEditor { setSetting('useSpeedToSync', useSpeedToSync); setSetting('useSkipToSync', useSkipToSync); - Events.trigger(SyncPlay.Manager, 'settings-update'); + Events.trigger(this.SyncPlay?.Manager, 'settings-update'); } } diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index de8c0c483c..7863cafe46 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -1,5 +1,5 @@ import { playbackManager } from '../components/playback/playbackmanager'; -import SyncPlay from '../plugins/syncPlay/core'; +import { pluginManager } from '../components/pluginManager'; import inputManager from '../scripts/inputManager'; import focusManager from '../components/focusManager'; import { appRouter } from '../components/appRouter'; @@ -7,6 +7,7 @@ import ServerConnections from '../components/ServerConnections'; import toast from '../components/toast/toast'; import alert from '../components/alert'; import Events from '../utils/events.ts'; +import { PluginType } from '../types/plugin.ts'; const serverNotifications = {}; @@ -140,6 +141,8 @@ function processGeneralCommand(cmd, apiClient) { function onMessageReceived(e, msg) { const apiClient = this; + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + if (msg.MessageType === 'Play') { notifyApp(); const serverId = apiClient.serverInfo().Id; @@ -186,9 +189,9 @@ function onMessageReceived(e, msg) { } } } else if (msg.MessageType === 'SyncPlayCommand') { - SyncPlay.Manager.processCommand(msg.Data, apiClient); + SyncPlay?.Manager.processCommand(msg.Data, apiClient); } else if (msg.MessageType === 'SyncPlayGroupUpdate') { - SyncPlay.Manager.processGroupUpdate(msg.Data, apiClient); + SyncPlay?.Manager.processGroupUpdate(msg.Data, apiClient); } else { Events.trigger(serverNotifications, msg.MessageType, [apiClient, msg.Data]); } diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 4b4b946e90..b2896012df 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -9,5 +9,5 @@ export interface Plugin { name: string id: string type: PluginType | string - priority: number + priority?: number }