Update MediaSegments directly (#350)
Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> Co-authored-by: Kilian von Pflugk <github@jumoog.io>
This commit is contained in:
parent
8f7c63172f
commit
1a731e3acc
@ -68,6 +68,20 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
/// </summary>
|
||||
public bool WithChromaprint { get; set; } = true;
|
||||
|
||||
// ===== Media Segment handling =====
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to update Media Segments.
|
||||
/// </summary>
|
||||
public bool UpdateMediaSegments { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to regenerate all EDL files during the next scan.
|
||||
/// By default, EDL files are only written for a season if the season had at least one newly analyzed episode.
|
||||
/// If this is set, all EDL files will be regenerated and overwrite any existing EDL file.
|
||||
/// </summary>
|
||||
public bool RegenerateMediaSegments { get; set; } = true;
|
||||
|
||||
// ===== EDL handling =====
|
||||
|
||||
/// <summary>
|
||||
|
@ -153,6 +153,29 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details id="mediasegment">
|
||||
<summary>Jellyfin Mediasegment Generation</summary>
|
||||
|
||||
<br />
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="UpdateMediaSegments" type="checkbox" is="emby-checkbox" />
|
||||
<span>Update Media Segments for Newly Added Files During Scan</span>
|
||||
</label>
|
||||
|
||||
<div class="fieldDescription">Enable this option to update media segments for newly added files during a scan. <strong>Warning:</strong> This should be disabled if you're using media segment providers other than Intro Skipper.</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="RegenerateMediaSegments" type="checkbox" is="emby-checkbox" />
|
||||
<span>Regenerate All Media Segments on Next Scan</span>
|
||||
</label>
|
||||
|
||||
<div class="fieldDescription">When enabled, this option will <strong>overwrite all existing media segments</strong> for your episodes with currently detected introduction and credit timestamps during the next scan.</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details id="edl">
|
||||
<summary>EDL File Generation</summary>
|
||||
|
||||
@ -671,7 +694,7 @@
|
||||
"AutoSkipCreditsNotificationText",
|
||||
];
|
||||
|
||||
var booleanConfigurationFields = ["AutoDetectIntros", "AutoDetectCredits", "AnalyzeMovies", "AnalyzeSeasonZero", "SelectAllLibraries", "RegenerateEdlFiles", "CacheFingerprints", "AutoSkip", "AutoSkipCredits", "SkipFirstEpisode", "PersistSkipButton", "SkipButtonVisible"];
|
||||
var booleanConfigurationFields = ["AutoDetectIntros", "AutoDetectCredits", "AnalyzeMovies", "AnalyzeSeasonZero", "SelectAllLibraries", "UpdateMediaSegments", "RegenerateMediaSegments", "RegenerateEdlFiles", "CacheFingerprints", "AutoSkip", "AutoSkipCredits", "SkipFirstEpisode", "PersistSkipButton", "SkipButtonVisible"];
|
||||
|
||||
// visualizer elements
|
||||
var ignorelistSection = document.querySelector("div#ignorelistSection");
|
||||
|
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ConfusedPolarBear.Plugin.IntroSkipper.Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaSegmentUpdateManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="mediaSegmentManager">MediaSegmentManager.</param>
|
||||
/// <param name="logger">logger.</param>
|
||||
/// <param name="segmentProvider">segmentProvider.</param>
|
||||
public class MediaSegmentUpdateManager(IMediaSegmentManager mediaSegmentManager, ILogger<MediaSegmentUpdateManager> logger, IMediaSegmentProvider segmentProvider)
|
||||
{
|
||||
private readonly IMediaSegmentManager _mediaSegmentManager = mediaSegmentManager;
|
||||
private readonly ILogger<MediaSegmentUpdateManager> _logger = logger;
|
||||
private readonly IMediaSegmentProvider _segmentProvider = segmentProvider;
|
||||
private readonly string _name = Plugin.Instance!.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Updates all media items in a List.
|
||||
/// </summary>
|
||||
/// <param name="episodes">Queued media items.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task UpdateMediaSegmentsAsync(IReadOnlyList<QueuedEpisode> episodes, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var episode in episodes)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var existingSegments = await _mediaSegmentManager.GetSegmentsAsync(episode.EpisodeId, null).ConfigureAwait(false);
|
||||
var deleteTasks = existingSegments.Select(s => _mediaSegmentManager.DeleteSegmentAsync(s.Id));
|
||||
await Task.WhenAll(deleteTasks).ConfigureAwait(false);
|
||||
|
||||
var newSegments = await _segmentProvider.GetMediaSegments(new MediaSegmentGenerationRequest { ItemId = episode.EpisodeId }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (newSegments.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("No segments found for episode {EpisodeId}", episode.EpisodeId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var createTasks = newSegments.Select(s => _mediaSegmentManager.CreateSegmentAsync(s, _name));
|
||||
await Task.WhenAll(createTasks).ConfigureAwait(false);
|
||||
|
||||
_logger.LogDebug("Updated {SegmentCount} segments for episode {EpisodeId}", newSegments.Count, episode.EpisodeId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing episode {EpisodeId}", episode.EpisodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -416,7 +416,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
List<string> oldRepos =
|
||||
[
|
||||
"https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json",
|
||||
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json"
|
||||
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json"
|
||||
];
|
||||
// Access the current server configuration
|
||||
var config = serverConfiguration.Configuration;
|
||||
|
@ -1,3 +1,4 @@
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Providers;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||
using MediaBrowser.Controller;
|
||||
@ -18,6 +19,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper
|
||||
serviceCollection.AddHostedService<AutoSkipCredits>();
|
||||
serviceCollection.AddHostedService<Entrypoint>();
|
||||
serviceCollection.AddSingleton<IMediaSegmentProvider, SegmentProvider>();
|
||||
serviceCollection.AddSingleton<MediaSegmentUpdateManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Providers
|
||||
/// </summary>
|
||||
public class SegmentProvider : IMediaSegmentProvider
|
||||
{
|
||||
private readonly long _remainingTicks;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SegmentProvider"/> class.
|
||||
/// </summary>
|
||||
public SegmentProvider()
|
||||
{
|
||||
_remainingTicks = TimeSpan.FromSeconds(Plugin.Instance?.Configuration.RemainingSecondsOfIntro ?? 2).Ticks;
|
||||
}
|
||||
private static long RemainingTicks => TimeSpan.FromSeconds(Plugin.Instance?.Configuration.RemainingSecondsOfIntro ?? 2).Ticks;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => Plugin.Instance!.Name;
|
||||
@ -38,7 +30,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Providers
|
||||
segments.Add(new MediaSegmentDto
|
||||
{
|
||||
StartTicks = TimeSpan.FromSeconds(introValue.Start).Ticks,
|
||||
EndTicks = TimeSpan.FromSeconds(introValue.End).Ticks - _remainingTicks,
|
||||
EndTicks = TimeSpan.FromSeconds(introValue.End).Ticks - RemainingTicks,
|
||||
ItemId = request.ItemId,
|
||||
Type = MediaSegmentType.Intro
|
||||
});
|
||||
@ -61,7 +53,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Providers
|
||||
}
|
||||
else
|
||||
{
|
||||
outroSegment.EndTicks = creditEndTicks - _remainingTicks;
|
||||
outroSegment.EndTicks = creditEndTicks - RemainingTicks;
|
||||
}
|
||||
|
||||
segments.Add(outroSegment);
|
||||
|
@ -18,12 +18,10 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||
public class BaseItemAnalyzerTask
|
||||
{
|
||||
private readonly IReadOnlyCollection<AnalysisMode> _analysisModes;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseItemAnalyzerTask"/> class.
|
||||
@ -32,16 +30,19 @@ public class BaseItemAnalyzerTask
|
||||
/// <param name="logger">Task logger.</param>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="mediaSegmentUpdateManager">MediaSegmentUpdateManager.</param>
|
||||
public BaseItemAnalyzerTask(
|
||||
IReadOnlyCollection<AnalysisMode> modes,
|
||||
ILogger logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager)
|
||||
ILibraryManager libraryManager,
|
||||
MediaSegmentUpdateManager mediaSegmentUpdateManager)
|
||||
{
|
||||
_analysisModes = modes;
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_libraryManager = libraryManager;
|
||||
_mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||
|
||||
if (Plugin.Instance!.Configuration.EdlAction != EdlAction.None)
|
||||
{
|
||||
@ -55,7 +56,8 @@ public class BaseItemAnalyzerTask
|
||||
/// <param name="progress">Progress.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <param name="seasonsToAnalyze">Season Ids to analyze.</param>
|
||||
public void AnalyzeItems(
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task AnalyzeItems(
|
||||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken,
|
||||
IReadOnlyCollection<Guid>? seasonsToAnalyze = null)
|
||||
@ -95,12 +97,13 @@ public class BaseItemAnalyzerTask
|
||||
var totalProcessed = 0;
|
||||
var options = new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism
|
||||
MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism,
|
||||
CancellationToken = cancellationToken
|
||||
};
|
||||
|
||||
Parallel.ForEach(queue, options, season =>
|
||||
await Parallel.ForEachAsync(queue, options, async (season, ct) =>
|
||||
{
|
||||
var writeEdl = false;
|
||||
var updateManagers = false;
|
||||
|
||||
// Since the first run of the task can run for multiple hours, ensure that none
|
||||
// of the current media items were deleted from Jellyfin since the task was started.
|
||||
@ -132,17 +135,17 @@ public class BaseItemAnalyzerTask
|
||||
|
||||
try
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (AnalysisMode mode in requiredModes)
|
||||
{
|
||||
var analyzed = AnalyzeItems(episodes, mode, cancellationToken);
|
||||
var analyzed = AnalyzeItems(episodes, mode, ct);
|
||||
Interlocked.Add(ref totalProcessed, analyzed);
|
||||
|
||||
writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles;
|
||||
updateManagers = analyzed > 0 || updateManagers;
|
||||
|
||||
progress.Report(totalProcessed * 100 / totalQueued);
|
||||
}
|
||||
@ -156,15 +159,21 @@ public class BaseItemAnalyzerTask
|
||||
ex);
|
||||
}
|
||||
|
||||
if (writeEdl && Plugin.Instance.Configuration.EdlAction != EdlAction.None)
|
||||
if (Plugin.Instance.Configuration.RegenerateMediaSegments || (updateManagers && Plugin.Instance.Configuration.UpdateMediaSegments))
|
||||
{
|
||||
await _mediaSegmentUpdateManager.UpdateMediaSegmentsAsync(episodes, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (Plugin.Instance.Configuration.RegenerateEdlFiles || (updateManagers && Plugin.Instance.Configuration.EdlAction != EdlAction.None))
|
||||
{
|
||||
EdlManager.UpdateEDLFiles(episodes);
|
||||
}
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (Plugin.Instance.Configuration.RegenerateEdlFiles)
|
||||
if (Plugin.Instance.Configuration.RegenerateMediaSegments || Plugin.Instance.Configuration.RegenerateEdlFiles)
|
||||
{
|
||||
_logger.LogInformation("Turning EDL file regeneration flag off");
|
||||
_logger.LogInformation("Turning Mediasegment/EDL file regeneration flag off");
|
||||
Plugin.Instance.Configuration.RegenerateMediaSegments = false;
|
||||
Plugin.Instance.Configuration.RegenerateEdlFiles = false;
|
||||
Plugin.Instance.SaveConfiguration();
|
||||
}
|
||||
@ -182,7 +191,7 @@ public class BaseItemAnalyzerTask
|
||||
AnalysisMode mode,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var totalItems = items.Count;
|
||||
var totalItems = items.Count(e => !e.State.IsAnalyzed(mode));
|
||||
|
||||
// Only analyze specials (season 0) if the user has opted in.
|
||||
var first = items[0];
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
@ -14,29 +15,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||
/// Analyze all television episodes for credits.
|
||||
/// TODO: analyze all media files.
|
||||
/// </summary>
|
||||
public class DetectCreditsTask : IScheduledTask
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="DetectCreditsTask"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||
public class DetectCreditsTask(
|
||||
ILogger<DetectCreditsTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager,
|
||||
MediaSegmentUpdateManager mediaSegmentUpdateManager) : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<DetectCreditsTask> _logger;
|
||||
private readonly ILogger<DetectCreditsTask> _logger = logger;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILoggerFactory _loggerFactory = loggerFactory;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DetectCreditsTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
public DetectCreditsTask(
|
||||
ILogger<DetectCreditsTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task name.
|
||||
@ -64,7 +62,7 @@ public class DetectCreditsTask : IScheduledTask
|
||||
/// <param name="progress">Task progress.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_libraryManager is null)
|
||||
{
|
||||
@ -75,10 +73,10 @@ public class DetectCreditsTask : IScheduledTask
|
||||
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
||||
{
|
||||
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
||||
Entrypoint.CancelAutomaticTask(cancellationToken);
|
||||
await Entrypoint.CancelAutomaticTaskAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (ScheduledTaskSemaphore.Acquire(cancellationToken))
|
||||
using (await ScheduledTaskSemaphore.AcquireAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
_logger.LogInformation("Scheduled Task is starting");
|
||||
|
||||
@ -88,11 +86,10 @@ public class DetectCreditsTask : IScheduledTask
|
||||
modes,
|
||||
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
||||
_loggerFactory,
|
||||
_libraryManager);
|
||||
_libraryManager,
|
||||
_mediaSegmentUpdateManager);
|
||||
|
||||
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
return Task.CompletedTask;
|
||||
await baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
@ -13,29 +14,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||
/// <summary>
|
||||
/// Analyze all television episodes for introduction sequences.
|
||||
/// </summary>
|
||||
public class DetectIntrosCreditsTask : IScheduledTask
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="DetectIntrosCreditsTask"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||
public class DetectIntrosCreditsTask(
|
||||
ILogger<DetectIntrosCreditsTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager,
|
||||
MediaSegmentUpdateManager mediaSegmentUpdateManager) : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<DetectIntrosCreditsTask> _logger;
|
||||
private readonly ILogger<DetectIntrosCreditsTask> _logger = logger;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILoggerFactory _loggerFactory = loggerFactory;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DetectIntrosCreditsTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
public DetectIntrosCreditsTask(
|
||||
ILogger<DetectIntrosCreditsTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task name.
|
||||
@ -63,7 +61,7 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
||||
/// <param name="progress">Task progress.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_libraryManager is null)
|
||||
{
|
||||
@ -74,10 +72,10 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
||||
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
||||
{
|
||||
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
||||
Entrypoint.CancelAutomaticTask(cancellationToken);
|
||||
await Entrypoint.CancelAutomaticTaskAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (ScheduledTaskSemaphore.Acquire(cancellationToken))
|
||||
using (await ScheduledTaskSemaphore.AcquireAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
_logger.LogInformation("Scheduled Task is starting");
|
||||
|
||||
@ -87,11 +85,10 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
||||
modes,
|
||||
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
|
||||
_loggerFactory,
|
||||
_libraryManager);
|
||||
_libraryManager,
|
||||
_mediaSegmentUpdateManager);
|
||||
|
||||
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
return Task.CompletedTask;
|
||||
await baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
@ -13,29 +14,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||
/// <summary>
|
||||
/// Analyze all television episodes for introduction sequences.
|
||||
/// </summary>
|
||||
public class DetectIntrosTask : IScheduledTask
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="DetectIntrosTask"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||
public class DetectIntrosTask(
|
||||
ILogger<DetectIntrosTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager,
|
||||
MediaSegmentUpdateManager mediaSegmentUpdateManager) : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<DetectIntrosTask> _logger;
|
||||
private readonly ILogger<DetectIntrosTask> _logger = logger;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILoggerFactory _loggerFactory = loggerFactory;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DetectIntrosTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
public DetectIntrosTask(
|
||||
ILogger<DetectIntrosTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the task name.
|
||||
@ -63,7 +61,7 @@ public class DetectIntrosTask : IScheduledTask
|
||||
/// <param name="progress">Task progress.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_libraryManager is null)
|
||||
{
|
||||
@ -74,10 +72,10 @@ public class DetectIntrosTask : IScheduledTask
|
||||
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
||||
{
|
||||
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
||||
Entrypoint.CancelAutomaticTask(cancellationToken);
|
||||
await Entrypoint.CancelAutomaticTaskAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (ScheduledTaskSemaphore.Acquire(cancellationToken))
|
||||
using (await ScheduledTaskSemaphore.AcquireAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
_logger.LogInformation("Scheduled Task is starting");
|
||||
|
||||
@ -87,11 +85,10 @@ public class DetectIntrosTask : IScheduledTask
|
||||
modes,
|
||||
_loggerFactory.CreateLogger<DetectIntrosTask>(),
|
||||
_loggerFactory,
|
||||
_libraryManager);
|
||||
_libraryManager,
|
||||
_mediaSegmentUpdateManager);
|
||||
|
||||
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
return Task.CompletedTask;
|
||||
await baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||
|
||||
@ -11,9 +12,9 @@ internal sealed class ScheduledTaskSemaphore : IDisposable
|
||||
{
|
||||
}
|
||||
|
||||
public static IDisposable Acquire(CancellationToken cancellationToken)
|
||||
public static async Task<IDisposable> AcquireAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_semaphore.Wait(cancellationToken);
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
return new ScheduledTaskSemaphore();
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@ -25,9 +26,10 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILogger<Entrypoint> _logger;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager;
|
||||
private readonly HashSet<Guid> _seasonsToAnalyze = [];
|
||||
private readonly Timer _queueTimer;
|
||||
private static readonly ManualResetEventSlim _autoTaskCompletEvent = new(false);
|
||||
private static readonly SemaphoreSlim _analysisSemaphore = new(1, 1);
|
||||
private PluginConfiguration _config;
|
||||
private bool _analyzeAgain;
|
||||
private static CancellationTokenSource? _cancellationTokenSource;
|
||||
@ -39,16 +41,19 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
/// <param name="taskManager">Task manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||
public Entrypoint(
|
||||
ILibraryManager libraryManager,
|
||||
ITaskManager taskManager,
|
||||
ILogger<Entrypoint> logger,
|
||||
ILoggerFactory loggerFactory)
|
||||
ILoggerFactory loggerFactory,
|
||||
MediaSegmentUpdateManager mediaSegmentUpdateManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_taskManager = taskManager;
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||
|
||||
_config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||
_queueTimer = new Timer(
|
||||
@ -61,42 +66,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
/// <summary>
|
||||
/// Gets State of the automatic task.
|
||||
/// </summary>
|
||||
public static TaskState AutomaticTaskState
|
||||
public static TaskState AutomaticTaskState => _cancellationTokenSource switch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cancellationTokenSource is not null)
|
||||
{
|
||||
return _cancellationTokenSource.IsCancellationRequested
|
||||
? TaskState.Cancelling
|
||||
: TaskState.Running;
|
||||
}
|
||||
|
||||
return TaskState.Idle;
|
||||
}
|
||||
}
|
||||
null => TaskState.Idle,
|
||||
{ IsCancellationRequested: true } => TaskState.Cancelling,
|
||||
_ => TaskState.Running
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_libraryManager.ItemAdded += OnItemAdded;
|
||||
_libraryManager.ItemUpdated += OnItemModified;
|
||||
_libraryManager.ItemAdded += OnItemChanged;
|
||||
_libraryManager.ItemUpdated += OnItemChanged;
|
||||
_taskManager.TaskCompleted += OnLibraryRefresh;
|
||||
Plugin.Instance!.ConfigurationChanged += OnSettingsChanged;
|
||||
|
||||
FFmpegWrapper.Logger = _logger;
|
||||
|
||||
try
|
||||
{
|
||||
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
|
||||
_logger.LogInformation("Running startup enqueue");
|
||||
var queueManager = new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager);
|
||||
queueManager?.GetMediaItems();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Unable to run startup enqueue: {Exception}", ex);
|
||||
}
|
||||
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
|
||||
_logger.LogInformation("Running startup enqueue");
|
||||
new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager).GetMediaItems();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -104,75 +93,33 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_libraryManager.ItemAdded -= OnItemAdded;
|
||||
_libraryManager.ItemUpdated -= OnItemModified;
|
||||
_libraryManager.ItemAdded -= OnItemChanged;
|
||||
_libraryManager.ItemUpdated -= OnItemChanged;
|
||||
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
||||
Plugin.Instance!.ConfigurationChanged -= OnSettingsChanged;
|
||||
|
||||
// Stop the timer
|
||||
_queueTimer.Change(Timeout.Infinite, 0);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Disclose source for inspiration
|
||||
// Implementation based on the principles of jellyfin-plugin-media-analyzer:
|
||||
// https://github.com/endrl/jellyfin-plugin-media-analyzer
|
||||
|
||||
/// <summary>
|
||||
/// Library item was added.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sending entity.</param>
|
||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||
private void OnItemAdded(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
private void OnItemChanged(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
// Don't do anything if auto detection is disabled
|
||||
if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
|
||||
if ((_config.AutoDetectIntros || _config.AutoDetectCredits) &&
|
||||
itemChangeEventArgs.Item is { LocationType: not LocationType.Virtual } item)
|
||||
{
|
||||
return;
|
||||
Guid? id = item is Episode episode ? episode.SeasonId : (item is Movie movie ? movie.Id : null);
|
||||
|
||||
if (id.HasValue)
|
||||
{
|
||||
_seasonsToAnalyze.Add(id.Value);
|
||||
StartTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (itemChangeEventArgs.Item is not Episode episode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemChangeEventArgs.Item.LocationType == LocationType.Virtual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_seasonsToAnalyze.Add(episode.SeasonId);
|
||||
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Library item was modified.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sending entity.</param>
|
||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||
private void OnItemModified(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
// Don't do anything if auto detection is disabled
|
||||
if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (itemChangeEventArgs.Item is not Episode episode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemChangeEventArgs.Item.LocationType == LocationType.Virtual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_seasonsToAnalyze.Add(episode.SeasonId);
|
||||
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -182,31 +129,12 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
/// <param name="eventArgs">The <see cref="TaskCompletionEventArgs"/>.</param>
|
||||
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
|
||||
{
|
||||
// Don't do anything if auto detection is disabled
|
||||
if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
|
||||
if ((_config.AutoDetectIntros || _config.AutoDetectCredits) &&
|
||||
eventArgs.Result is { Key: "RefreshLibrary", Status: TaskCompletionStatus.Completed } &&
|
||||
AutomaticTaskState != TaskState.Running)
|
||||
{
|
||||
return;
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
var result = eventArgs.Result;
|
||||
|
||||
if (result.Key != "RefreshLibrary")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Status != TaskCompletionStatus.Completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Unless user initiated, this is likely an overlap
|
||||
if (AutomaticTaskState == TaskState.Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(object? sender, BasePluginConfiguration e) => _config = (PluginConfiguration)e;
|
||||
@ -223,90 +151,80 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
else if (AutomaticTaskState == TaskState.Idle)
|
||||
{
|
||||
_logger.LogDebug("Media Library changed, analyzis will start soon!");
|
||||
_queueTimer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan);
|
||||
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for timer callback to be completed.
|
||||
/// </summary>
|
||||
private void OnTimerCallback(object? state)
|
||||
private void OnTimerCallback(object? state) =>
|
||||
_ = RunAnalysisAsync();
|
||||
|
||||
private async Task RunAnalysisAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformAnalysis();
|
||||
await PerformAnalysisAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in PerformAnalysis");
|
||||
_logger.LogError(ex, "Error in RunAnalysisAsync");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
_cancellationTokenSource = null;
|
||||
_autoTaskCompletEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for timer to be completed.
|
||||
/// </summary>
|
||||
private void PerformAnalysis()
|
||||
private async Task PerformAnalysisAsync()
|
||||
{
|
||||
_logger.LogInformation("Initiate automatic analysis task.");
|
||||
_autoTaskCompletEvent.Reset();
|
||||
|
||||
using (_cancellationTokenSource = new CancellationTokenSource())
|
||||
using (ScheduledTaskSemaphore.Acquire(_cancellationTokenSource.Token))
|
||||
await _analysisSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var seasonIds = new HashSet<Guid>(_seasonsToAnalyze);
|
||||
_seasonsToAnalyze.Clear();
|
||||
|
||||
_analyzeAgain = false;
|
||||
var progress = new Progress<double>();
|
||||
var modes = new List<AnalysisMode>();
|
||||
var tasklogger = _loggerFactory.CreateLogger("DefaultLogger");
|
||||
|
||||
if (_config.AutoDetectIntros)
|
||||
using (_cancellationTokenSource = new CancellationTokenSource())
|
||||
using (await ScheduledTaskSemaphore.AcquireAsync(_cancellationTokenSource.Token).ConfigureAwait(false))
|
||||
{
|
||||
modes.Add(AnalysisMode.Introduction);
|
||||
tasklogger = _loggerFactory.CreateLogger<DetectIntrosTask>();
|
||||
}
|
||||
|
||||
if (_config.AutoDetectCredits)
|
||||
{
|
||||
modes.Add(AnalysisMode.Credits);
|
||||
tasklogger = modes.Count == 2
|
||||
? _loggerFactory.CreateLogger<DetectIntrosCreditsTask>()
|
||||
: _loggerFactory.CreateLogger<DetectCreditsTask>();
|
||||
}
|
||||
|
||||
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
|
||||
modes,
|
||||
tasklogger,
|
||||
_loggerFactory,
|
||||
_libraryManager);
|
||||
|
||||
baseCreditAnalyzer.AnalyzeItems(progress, _cancellationTokenSource.Token, seasonIds);
|
||||
|
||||
// New item detected, start timer again
|
||||
if (_analyzeAgain && !_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
|
||||
StartTimer();
|
||||
_logger.LogInformation("Initiating automatic analysis task");
|
||||
var seasonIds = new HashSet<Guid>(_seasonsToAnalyze);
|
||||
_seasonsToAnalyze.Clear();
|
||||
_analyzeAgain = false;
|
||||
|
||||
var modes = new List<AnalysisMode>();
|
||||
|
||||
if (_config.AutoDetectIntros)
|
||||
{
|
||||
modes.Add(AnalysisMode.Introduction);
|
||||
}
|
||||
|
||||
if (_config.AutoDetectCredits)
|
||||
{
|
||||
modes.Add(AnalysisMode.Credits);
|
||||
}
|
||||
|
||||
var analyzer = new BaseItemAnalyzerTask(modes, _loggerFactory.CreateLogger<Entrypoint>(), _loggerFactory, _libraryManager, _mediaSegmentUpdateManager);
|
||||
await analyzer.AnalyzeItems(new Progress<double>(), _cancellationTokenSource.Token, seasonIds).ConfigureAwait(false);
|
||||
|
||||
if (_analyzeAgain && !_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
|
||||
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_analysisSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to cancel the automatic task.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static void CancelAutomaticTask(CancellationToken cancellationToken)
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public static async Task CancelAutomaticTaskAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
|
||||
if (_cancellationTokenSource is { IsCancellationRequested: false })
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
@ -314,7 +232,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
}
|
||||
}
|
||||
|
||||
_autoTaskCompletEvent.Wait(TimeSpan.FromSeconds(60), cancellationToken); // Wait for the signal
|
||||
await _analysisSemaphore.WaitAsync(TimeSpan.FromSeconds(60), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -322,7 +240,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
||||
{
|
||||
_queueTimer.Dispose();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_autoTaskCompletEvent.Dispose();
|
||||
_analysisSemaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user