From 9723c13bd3e57266bd2d92560fe8ce5e9cce1542 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 14 Dec 2025 11:25:58 +0100 Subject: [PATCH] Added sync group page and settings --- .../Configuration/configPage.html | 13 + .../Configuration/syncPage.html | 490 ++++++++++++++++++ .../Jellyfin.Plugin.JellyLMS.csproj | 2 + Jellyfin.Plugin.JellyLMS/Plugin.cs | 5 + .../Services/LmsDeviceDiscoveryService.cs | 5 +- .../Services/LmsSessionController.cs | 17 +- 6 files changed, 529 insertions(+), 3 deletions(-) create mode 100644 Jellyfin.Plugin.JellyLMS/Configuration/syncPage.html diff --git a/Jellyfin.Plugin.JellyLMS/Configuration/configPage.html b/Jellyfin.Plugin.JellyLMS/Configuration/configPage.html index f7f93d3..12e3659 100644 --- a/Jellyfin.Plugin.JellyLMS/Configuration/configPage.html +++ b/Jellyfin.Plugin.JellyLMS/Configuration/configPage.html @@ -95,6 +95,19 @@ +
+

Player Sync Management

+

Manage synchronized playback groups for multi-room audio.

+
+ + Open Sync Manager + +
+
+ Users can access this page directly at: /web/configurationpage?name=JellyLMS%20Sync +
+
+
+
+ +
+

Current Sync Groups

+
+

Loading sync groups...

+
+
+ +
+

Available Players

+
+

Loading players...

+
+
+ +
+

Create Sync Group

+

Select players to sync together. The first selected player will be the master (controls playback).

+ +
+ +
+ +
+ + +
+
+
+ + + + + + diff --git a/Jellyfin.Plugin.JellyLMS/Jellyfin.Plugin.JellyLMS.csproj b/Jellyfin.Plugin.JellyLMS/Jellyfin.Plugin.JellyLMS.csproj index 30496d5..7ab2623 100644 --- a/Jellyfin.Plugin.JellyLMS/Jellyfin.Plugin.JellyLMS.csproj +++ b/Jellyfin.Plugin.JellyLMS/Jellyfin.Plugin.JellyLMS.csproj @@ -27,7 +27,9 @@ + + diff --git a/Jellyfin.Plugin.JellyLMS/Plugin.cs b/Jellyfin.Plugin.JellyLMS/Plugin.cs index 36aa6a1..b065b4d 100644 --- a/Jellyfin.Plugin.JellyLMS/Plugin.cs +++ b/Jellyfin.Plugin.JellyLMS/Plugin.cs @@ -49,6 +49,11 @@ public class Plugin : BasePlugin, IHasWebPages { Name = Name, EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.configPage.html", GetType().Namespace) + }, + new PluginPageInfo + { + Name = "JellyLMS Sync", + EmbeddedResourcePath = string.Format(CultureInfo.InvariantCulture, "{0}.Configuration.syncPage.html", GetType().Namespace) } ]; } diff --git a/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs b/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs index 78d6b58..8ba0193 100644 --- a/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs +++ b/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Plugin.JellyLMS.Models; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Session; using Microsoft.Extensions.DependencyInjection; @@ -177,13 +178,15 @@ public class LmsDeviceDiscoveryService : IHostedService, IDisposable var isNew = !_registeredDeviceIds.ContainsKey(player.MacAddress); // Add our controller to the session using EnsureController pattern + var libraryManager = _serviceProvider.GetRequiredService(); var (controller, created) = session.EnsureController( s => new LmsSessionController( _logger, _lmsClient, player, s, - sessionManager)); + sessionManager, + libraryManager)); if (created) { diff --git a/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs b/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs index 738ef21..4a1d3ec 100644 --- a/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs +++ b/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs @@ -3,6 +3,8 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.JellyLMS.Models; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -20,8 +22,10 @@ public class LmsSessionController : ISessionController, IDisposable private readonly LmsPlayer _player; private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; + private readonly ILibraryManager _libraryManager; private Timer? _progressTimer; private bool _disposed; + private BaseItem? _currentItem; /// /// Initializes a new instance of the class. @@ -31,18 +35,21 @@ public class LmsSessionController : ISessionController, IDisposable /// The LMS player this controller manages. /// The Jellyfin session associated with this controller. /// The session manager for reporting playback events. + /// The library manager for item lookups. public LmsSessionController( ILogger logger, ILmsApiClient lmsClient, LmsPlayer player, SessionInfo session, - ISessionManager sessionManager) + ISessionManager sessionManager, + ILibraryManager libraryManager) { _logger = logger; _lmsClient = lmsClient; _player = player; _session = session; _sessionManager = sessionManager; + _libraryManager = libraryManager; } /// @@ -145,6 +152,9 @@ public class LmsSessionController : ISessionController, IDisposable IsPlaying = true; IsPaused = false; + // Look up the item for duration info + _currentItem = _libraryManager.GetItemById(itemId); + // Seek to start position if specified var startPositionTicks = 0L; if (playRequest.StartPositionTicks.HasValue && playRequest.StartPositionTicks.Value > 0) @@ -177,7 +187,10 @@ public class LmsSessionController : ISessionController, IDisposable IsMuted = false }; - _logger.LogInformation("Reporting playback start for item {ItemId}", itemId); + _logger.LogInformation( + "Reporting playback start for item {ItemId}, duration: {Duration}", + itemId, + _currentItem?.RunTimeTicks); await _sessionManager.OnPlaybackStart(startInfo).ConfigureAwait(false); } catch (Exception ex)