From 52120529ff59e9f2cc25727d4bfdd966d6db91cc Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 14 Dec 2025 11:15:04 +0100 Subject: [PATCH] controls and position synced --- .../Services/LmsDeviceDiscoveryService.cs | 70 +------ .../Services/LmsSessionController.cs | 172 +++++++++++++++++- 2 files changed, 168 insertions(+), 74 deletions(-) diff --git a/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs b/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs index 9ea4131..78d6b58 100644 --- a/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs +++ b/Jellyfin.Plugin.JellyLMS/Services/LmsDeviceDiscoveryService.cs @@ -5,7 +5,6 @@ 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; @@ -183,7 +182,8 @@ public class LmsDeviceDiscoveryService : IHostedService, IDisposable _logger, _lmsClient, player, - s)); + s, + sessionManager)); if (created) { @@ -221,72 +221,6 @@ public class LmsDeviceDiscoveryService : IHostedService, IDisposable player.MacAddress, session.Id); } - - // Report playback progress if the controller has an active item - if (controller is LmsSessionController lmsController) - { - await ReportPlaybackProgressAsync(sessionManager, session, lmsController).ConfigureAwait(false); - } - } - - private async Task ReportPlaybackProgressAsync( - ISessionManager sessionManager, - SessionInfo session, - LmsSessionController controller) - { - if (!controller.IsPlaying || !controller.CurrentItemId.HasValue) - { - return; - } - - try - { - // Get current playback status from LMS - var status = await _lmsClient.GetPlayerStatusAsync(controller.PlayerMac).ConfigureAwait(false); - if (status == null) - { - return; - } - - // Get the item from Jellyfin library - var libraryManager = _serviceProvider.GetService(); - if (libraryManager == null) - { - return; - } - - var item = libraryManager.GetItemById(controller.CurrentItemId.Value); - if (item == null) - { - return; - } - - // Calculate position in ticks - var positionTicks = (long)(status.Time * TimeSpan.TicksPerSecond); - - // Determine if paused - var isPaused = status.Mode == "pause"; - - // Create playback progress info - var progressInfo = new PlaybackProgressInfo - { - ItemId = controller.CurrentItemId.Value, - SessionId = session.Id, - IsPaused = isPaused, - PositionTicks = positionTicks, - PlayMethod = PlayMethod.DirectStream, - CanSeek = true, - IsMuted = status.Volume == 0, - VolumeLevel = status.Volume - }; - - // Report progress to session manager - await sessionManager.OnPlaybackProgress(progressInfo).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "Error reporting playback progress for player {Name}", controller.PlayerMac); - } } /// diff --git a/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs b/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs index 21fa6e3..738ef21 100644 --- a/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs +++ b/Jellyfin.Plugin.JellyLMS/Services/LmsSessionController.cs @@ -13,12 +13,15 @@ namespace Jellyfin.Plugin.JellyLMS.Services; /// Session controller for LMS player devices. /// Enables Jellyfin to send playback commands to LMS players via the cast interface. /// -public class LmsSessionController : ISessionController +public class LmsSessionController : ISessionController, IDisposable { private readonly ILogger _logger; private readonly ILmsApiClient _lmsClient; private readonly LmsPlayer _player; private readonly SessionInfo _session; + private readonly ISessionManager _sessionManager; + private Timer? _progressTimer; + private bool _disposed; /// /// Initializes a new instance of the class. @@ -27,16 +30,19 @@ public class LmsSessionController : ISessionController /// The LMS API client. /// The LMS player this controller manages. /// The Jellyfin session associated with this controller. + /// The session manager for reporting playback events. public LmsSessionController( ILogger logger, ILmsApiClient lmsClient, LmsPlayer player, - SessionInfo session) + SessionInfo session, + ISessionManager sessionManager) { _logger = logger; _lmsClient = lmsClient; _player = player; _session = session; + _sessionManager = sessionManager; } /// @@ -140,11 +146,141 @@ public class LmsSessionController : ISessionController IsPaused = false; // Seek to start position if specified + var startPositionTicks = 0L; if (playRequest.StartPositionTicks.HasValue && playRequest.StartPositionTicks.Value > 0) { - var positionSeconds = playRequest.StartPositionTicks.Value / TimeSpan.TicksPerSecond; + startPositionTicks = playRequest.StartPositionTicks.Value; + var positionSeconds = startPositionTicks / TimeSpan.TicksPerSecond; await _lmsClient.SeekAsync(_player.MacAddress, positionSeconds).ConfigureAwait(false); } + + // Report playback start to Jellyfin + await ReportPlaybackStartAsync(itemId, startPositionTicks).ConfigureAwait(false); + + // Start progress reporting timer (every 2 seconds) + StartProgressTimer(); + } + } + + private async Task ReportPlaybackStartAsync(Guid itemId, long positionTicks) + { + try + { + var startInfo = new PlaybackStartInfo + { + ItemId = itemId, + SessionId = _session.Id, + PositionTicks = positionTicks, + PlayMethod = PlayMethod.DirectStream, + CanSeek = true, + IsPaused = false, + IsMuted = false + }; + + _logger.LogInformation("Reporting playback start for item {ItemId}", itemId); + await _sessionManager.OnPlaybackStart(startInfo).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to report playback start"); + } + } + + private void StartProgressTimer() + { + // Stop any existing timer + _progressTimer?.Dispose(); + + // Report progress every 2 seconds + _progressTimer = new Timer( + async _ => await ReportPlaybackProgressAsync().ConfigureAwait(false), + null, + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(2)); + } + + private void StopProgressTimer() + { + _progressTimer?.Dispose(); + _progressTimer = null; + } + + private async Task ReportPlaybackProgressAsync() + { + if (!IsPlaying || !CurrentItemId.HasValue) + { + return; + } + + try + { + var status = await _lmsClient.GetPlayerStatusAsync(_player.MacAddress).ConfigureAwait(false); + if (status == null) + { + return; + } + + var positionTicks = (long)(status.Time * TimeSpan.TicksPerSecond); + var isPaused = status.Mode == "pause"; + + // Update our local state from LMS + IsPaused = isPaused; + + // Check if playback has stopped on LMS side + if (status.Mode == "stop") + { + _logger.LogInformation("LMS playback stopped, reporting to Jellyfin"); + await ReportPlaybackStoppedAsync().ConfigureAwait(false); + return; + } + + var progressInfo = new PlaybackProgressInfo + { + ItemId = CurrentItemId.Value, + SessionId = _session.Id, + IsPaused = isPaused, + PositionTicks = positionTicks, + PlayMethod = PlayMethod.DirectStream, + CanSeek = true, + IsMuted = status.Volume == 0, + VolumeLevel = status.Volume + }; + + await _sessionManager.OnPlaybackProgress(progressInfo).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error reporting playback progress"); + } + } + + private async Task ReportPlaybackStoppedAsync() + { + if (!CurrentItemId.HasValue) + { + return; + } + + try + { + StopProgressTimer(); + + var stopInfo = new PlaybackStopInfo + { + ItemId = CurrentItemId.Value, + SessionId = _session.Id + }; + + _logger.LogInformation("Reporting playback stopped for item {ItemId}", CurrentItemId.Value); + await _sessionManager.OnPlaybackStopped(stopInfo).ConfigureAwait(false); + + IsPlaying = false; + IsPaused = false; + CurrentItemId = null; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to report playback stopped"); } } @@ -167,9 +303,7 @@ public class LmsSessionController : ISessionController case PlaystateCommand.Stop: var stopResult = await _lmsClient.StopAsync(_player.MacAddress).ConfigureAwait(false); _logger.LogInformation("Stop command result: {Result}", stopResult); - IsPlaying = false; - IsPaused = false; - CurrentItemId = null; + await ReportPlaybackStoppedAsync().ConfigureAwait(false); break; case PlaystateCommand.Pause: @@ -302,4 +436,30 @@ public class LmsSessionController : ISessionController return url; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes managed resources. + /// + /// Whether to dispose managed resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + StopProgressTimer(); + } + + _disposed = true; + } }