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>
88 lines
2.8 KiB
C#
88 lines
2.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
|
using MediaBrowser.Model.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.SRFPlay.ScheduledTasks;
|
|
|
|
/// <summary>
|
|
/// Scheduled task that clears stale SRF Play resume points from "Continue Watching".
|
|
/// </summary>
|
|
public class ResumeCleanupTask : IScheduledTask
|
|
{
|
|
private readonly ILogger<ResumeCleanupTask> _logger;
|
|
private readonly IResumeCleanupService _resumeCleanupService;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ResumeCleanupTask"/> class.
|
|
/// </summary>
|
|
/// <param name="logger">The logger.</param>
|
|
/// <param name="resumeCleanupService">The resume cleanup service.</param>
|
|
public ResumeCleanupTask(
|
|
ILogger<ResumeCleanupTask> logger,
|
|
IResumeCleanupService resumeCleanupService)
|
|
{
|
|
_logger = logger;
|
|
_resumeCleanupService = resumeCleanupService;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "Clean Up SRF Play Continue Watching";
|
|
|
|
/// <inheritdoc />
|
|
public string Description => "Removes stale SRF Play resume points - ended livestreams, abandoned playback and finished programmes - from every user's Continue Watching row";
|
|
|
|
/// <inheritdoc />
|
|
public string Category => "SRF Play";
|
|
|
|
/// <inheritdoc />
|
|
public string Key => "SRFPlayResumeCleanup";
|
|
|
|
/// <inheritdoc />
|
|
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogInformation("Starting SRF Play Continue Watching cleanup");
|
|
progress?.Report(0);
|
|
|
|
try
|
|
{
|
|
var result = _resumeCleanupService.Cleanup(false, cancellationToken);
|
|
|
|
progress?.Report(100);
|
|
_logger.LogInformation(
|
|
"SRF Play Continue Watching cleanup completed. Cleared {Cleared} of {Inspected} resume points",
|
|
result.Cleared,
|
|
result.Inspected);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
_logger.LogInformation("SRF Play Continue Watching cleanup was cancelled");
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error during SRF Play Continue Watching cleanup");
|
|
throw;
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
|
{
|
|
// Runs after the 3 AM expiration check so items deleted there are already gone.
|
|
return new[]
|
|
{
|
|
new TaskTriggerInfo
|
|
{
|
|
Type = TaskTriggerInfo.TriggerDaily,
|
|
TimeOfDayTicks = TimeSpan.FromHours(4).Ticks
|
|
}
|
|
};
|
|
}
|
|
}
|