using Jellyfin.Data; using Jellyfin.Database.Implementations.Enums; using Jellyfin.Plugin.Vanguard.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.Vanguard.Tasks; /// /// Enforces a direct-play-only policy on every non-administrator user (this box has no hardware encoder). /// Runs at startup and every 30 minutes so users created by the SSO plugin are covered automatically. /// Turn off in the plugin config when the server gets Quick Sync. /// public sealed class PlaybackPolicyTask : IScheduledTask { private readonly IUserManager _users; private readonly ILogger _log; public PlaybackPolicyTask(IUserManager users, ILogger log) { _users = users; _log = log; } public string Name => "Vanguard: enforce playback policy"; public string Key => "VanguardPlaybackPolicy"; public string Description => "Sets direct-play-only (no video transcoding) on all non-admin users, including new SSO users."; public string Category => "Vanguard"; public async Task ExecuteAsync(IProgress progress, CancellationToken ct) { var cfg = Plugin.Instance?.Configuration ?? new PluginConfiguration(); if (!cfg.EnforceDirectPlayOnly) { _log.LogDebug("playback policy enforcement is off"); return; } int changed = 0; var all = _users.GetUsers().ToList(); foreach (var u in all) { ct.ThrowIfCancellationRequested(); if (u.HasPermission(PermissionKind.IsAdministrator) && !cfg.EnforceOnAdminsToo) continue; bool dirty = false; dirty |= Set(u, PermissionKind.EnableVideoPlaybackTranscoding, false); dirty |= Set(u, PermissionKind.EnableAudioPlaybackTranscoding, false); dirty |= Set(u, PermissionKind.EnablePlaybackRemuxing, true); // container remux is cheap and keeps odd clients working if (dirty) { await _users.UpdateUserAsync(u).ConfigureAwait(false); changed++; } } _log.LogInformation("playback policy: {Changed} of {Total} users updated", changed, all.Count); progress.Report(100); } private static bool Set(Jellyfin.Database.Implementations.Entities.User u, PermissionKind kind, bool value) { if (u.HasPermission(kind) == value) return false; u.SetPermission(kind, value); return true; } public IEnumerable GetDefaultTriggers() => [ new TaskTriggerInfo { Type = TaskTriggerInfoType.StartupTrigger }, new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromMinutes(30).Ticks }, ]; }