using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Jellyfin.Plugin.JellyLMS.Models; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.JellyLMS.Services; /// /// Manages LMS player discovery and state tracking. /// public class LmsPlayerManager { private readonly ILogger _logger; private readonly ILmsApiClient _lmsClient; private readonly ConcurrentDictionary _players = new(); private DateTime _lastRefresh = DateTime.MinValue; private readonly TimeSpan _cacheExpiry = TimeSpan.FromSeconds(30); /// /// Initializes a new instance of the class. /// /// The logger instance. /// The LMS API client. public LmsPlayerManager(ILogger logger, ILmsApiClient lmsClient) { _logger = logger; _lmsClient = lmsClient; } /// /// Gets all known LMS players, refreshing if cache is stale. /// /// Force a refresh from LMS. /// List of LMS players. public async Task> GetPlayersAsync(bool forceRefresh = false) { if (!forceRefresh && DateTime.UtcNow - _lastRefresh < _cacheExpiry && _players.Count > 0) { return _players.Values.ToList(); } await RefreshPlayersAsync().ConfigureAwait(false); return _players.Values.ToList(); } /// /// Gets a specific player by MAC address. /// /// The player's MAC address. /// The player, or null if not found. public async Task GetPlayerAsync(string macAddress) { if (_players.TryGetValue(macAddress, out var player)) { return player; } await RefreshPlayersAsync().ConfigureAwait(false); return _players.GetValueOrDefault(macAddress); } /// /// Refreshes the player list from LMS. /// /// A task representing the operation. public async Task RefreshPlayersAsync() { try { var players = await _lmsClient.GetPlayersAsync().ConfigureAwait(false); _players.Clear(); foreach (var player in players) { _players[player.MacAddress] = player; } _lastRefresh = DateTime.UtcNow; _logger.LogDebug("Refreshed {Count} players from LMS", players.Count); } catch (Exception ex) { _logger.LogError(ex, "Failed to refresh players from LMS"); } } /// /// Gets all current sync groups. /// /// List of sync groups. public async Task> GetSyncGroupsAsync() { return await _lmsClient.GetSyncGroupsAsync().ConfigureAwait(false); } /// /// Creates a sync group with the specified players. /// /// The master player's MAC address. /// The slave players' MAC addresses. /// True if successful. public async Task CreateSyncGroupAsync(string masterMac, IEnumerable slaveMacs) { var success = true; foreach (var slaveMac in slaveMacs) { if (!await _lmsClient.SyncPlayerAsync(masterMac, slaveMac).ConfigureAwait(false)) { _logger.LogWarning("Failed to sync player {Slave} to master {Master}", slaveMac, masterMac); success = false; } } // Refresh player state to update sync info await RefreshPlayersAsync().ConfigureAwait(false); return success; } /// /// Removes a player from its sync group. /// /// The player's MAC address. /// True if successful. public async Task UnsyncPlayerAsync(string playerMac) { var result = await _lmsClient.UnsyncPlayerAsync(playerMac).ConfigureAwait(false); await RefreshPlayersAsync().ConfigureAwait(false); return result; } /// /// Dissolves an entire sync group (unsyncs all members). /// /// The master player's MAC address. /// True if successful. public async Task DissolveSyncGroupAsync(string masterMac) { var player = await GetPlayerAsync(masterMac).ConfigureAwait(false); if (player == null) { return false; } var success = true; // Unsync all slaves foreach (var slaveMac in player.SyncSlaves) { if (!await _lmsClient.UnsyncPlayerAsync(slaveMac).ConfigureAwait(false)) { success = false; } } await RefreshPlayersAsync().ConfigureAwait(false); return success; } }