Livestreams and ended broadcasts accumulated in every user's resume row
because Jellyfin saves a playback position for channel items regardless
of whether the position means anything. A livestream has nothing to
return to, so the entry stayed pinned forever.
Two parts:
- PlaybackResumeGuard, an IHostedService on ISessionManager.PlaybackStopped.
Jellyfin saves the resume point before raising the event, so the guard
zeroes it afterwards for livestreams. Stops new entries at the source.
- ResumeCleanupService plus a daily 4 AM task (after the 3 AM expiration
check) for the existing backlog. Clears livestreams, resume points older
than N days, positions under N seconds and playback past N% of runtime,
each threshold configurable.
Only plugin-owned items are touched, matched by the SRF provider ID with a
fallback to the owning channel ID so recordings are covered too. Clearing
sets PlaybackPositionTicks to 0 and leaves Played false: nothing is deleted
and the item stays unwatched.
Config page gains the thresholds plus "Clean Up Stale Entries Now" and
"Clear All SRF Play Entries" buttons, backed by MaintenanceController
under RequiresElevation.
Also folds the duplicated urn.Contains("livestream") check in
MediaSourceFactory and RecordingService into UrnHelper.IsLivestreamUrn.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 lines
1.5 KiB
C#
41 lines
1.5 KiB
C#
using System;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace Jellyfin.Plugin.SRFPlay.Utilities;
|
|
|
|
/// <summary>
|
|
/// Helper class for URN-related operations.
|
|
/// </summary>
|
|
public static class UrnHelper
|
|
{
|
|
/// <summary>
|
|
/// Generates a deterministic GUID from a URN.
|
|
/// This ensures the same URN always produces the same GUID.
|
|
/// MD5 is used for non-cryptographic purposes only (generating stable IDs).
|
|
/// </summary>
|
|
/// <param name="urn">The URN to convert.</param>
|
|
/// <returns>A deterministic GUID string.</returns>
|
|
#pragma warning disable CA5351 // MD5 is used for non-cryptographic purposes (ID generation)
|
|
public static string ToGuid(string urn)
|
|
{
|
|
var hash = MD5.HashData(Encoding.UTF8.GetBytes(urn));
|
|
var guid = new Guid(hash);
|
|
return guid.ToString();
|
|
}
|
|
#pragma warning restore CA5351
|
|
|
|
/// <summary>
|
|
/// Determines whether a URN refers to a livestream (scheduled or continuous).
|
|
/// This is a URN-only heuristic that needs no API call, so it stays usable long after
|
|
/// the broadcast has ended and the media composition is no longer worth fetching.
|
|
/// </summary>
|
|
/// <param name="urn">The URN to inspect. May be null or empty.</param>
|
|
/// <returns>True if the URN identifies livestream content.</returns>
|
|
public static bool IsLivestreamUrn(string? urn)
|
|
{
|
|
return !string.IsNullOrEmpty(urn)
|
|
&& urn.Contains("livestream", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|