Duncan Tourolle 2f5a182afd
Some checks failed
🏗️ Build Plugin / call (push) Failing after 0s
📝 Create/Update Release Draft & Release Bump PR / call (push) Failing after 0s
🔬 Run CodeQL / call (push) Failing after 0s
🧪 Test Plugin / call (push) Failing after 0s
First POC with working playback
2025-12-13 23:54:33 +01:00

164 lines
5.2 KiB
C#

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;
/// <summary>
/// Manages LMS player discovery and state tracking.
/// </summary>
public class LmsPlayerManager
{
private readonly ILogger<LmsPlayerManager> _logger;
private readonly ILmsApiClient _lmsClient;
private readonly ConcurrentDictionary<string, LmsPlayer> _players = new();
private DateTime _lastRefresh = DateTime.MinValue;
private readonly TimeSpan _cacheExpiry = TimeSpan.FromSeconds(30);
/// <summary>
/// Initializes a new instance of the <see cref="LmsPlayerManager"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <param name="lmsClient">The LMS API client.</param>
public LmsPlayerManager(ILogger<LmsPlayerManager> logger, ILmsApiClient lmsClient)
{
_logger = logger;
_lmsClient = lmsClient;
}
/// <summary>
/// Gets all known LMS players, refreshing if cache is stale.
/// </summary>
/// <param name="forceRefresh">Force a refresh from LMS.</param>
/// <returns>List of LMS players.</returns>
public async Task<List<LmsPlayer>> 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();
}
/// <summary>
/// Gets a specific player by MAC address.
/// </summary>
/// <param name="macAddress">The player's MAC address.</param>
/// <returns>The player, or null if not found.</returns>
public async Task<LmsPlayer?> GetPlayerAsync(string macAddress)
{
if (_players.TryGetValue(macAddress, out var player))
{
return player;
}
await RefreshPlayersAsync().ConfigureAwait(false);
return _players.GetValueOrDefault(macAddress);
}
/// <summary>
/// Refreshes the player list from LMS.
/// </summary>
/// <returns>A task representing the operation.</returns>
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");
}
}
/// <summary>
/// Gets all current sync groups.
/// </summary>
/// <returns>List of sync groups.</returns>
public async Task<List<SyncGroup>> GetSyncGroupsAsync()
{
return await _lmsClient.GetSyncGroupsAsync().ConfigureAwait(false);
}
/// <summary>
/// Creates a sync group with the specified players.
/// </summary>
/// <param name="masterMac">The master player's MAC address.</param>
/// <param name="slaveMacs">The slave players' MAC addresses.</param>
/// <returns>True if successful.</returns>
public async Task<bool> CreateSyncGroupAsync(string masterMac, IEnumerable<string> 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;
}
/// <summary>
/// Removes a player from its sync group.
/// </summary>
/// <param name="playerMac">The player's MAC address.</param>
/// <returns>True if successful.</returns>
public async Task<bool> UnsyncPlayerAsync(string playerMac)
{
var result = await _lmsClient.UnsyncPlayerAsync(playerMac).ConfigureAwait(false);
await RefreshPlayersAsync().ConfigureAwait(false);
return result;
}
/// <summary>
/// Dissolves an entire sync group (unsyncs all members).
/// </summary>
/// <param name="masterMac">The master player's MAC address.</param>
/// <returns>True if successful.</returns>
public async Task<bool> 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;
}
}