select autoskip clients (#277)
Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com>
This commit is contained in:
parent
899d5e1914
commit
d428efb1f2
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
@ -21,32 +21,25 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// Automatically skip past introduction sequences.
|
/// Automatically skip past introduction sequences.
|
||||||
/// Commands clients to seek to the end of the intro as soon as they start playing it.
|
/// Commands clients to seek to the end of the intro as soon as they start playing it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AutoSkip : IHostedService, IDisposable
|
/// <remarks>
|
||||||
{
|
|
||||||
private readonly object _sentSeekCommandLock = new();
|
|
||||||
|
|
||||||
private ILogger<AutoSkip> _logger;
|
|
||||||
private IUserDataManager _userDataManager;
|
|
||||||
private ISessionManager _sessionManager;
|
|
||||||
private Timer _playbackTimer = new(1000);
|
|
||||||
private Dictionary<string, bool> _sentSeekCommand;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AutoSkip"/> class.
|
/// Initializes a new instance of the <see cref="AutoSkip"/> class.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="userDataManager">User data manager.</param>
|
/// <param name="userDataManager">User data manager.</param>
|
||||||
/// <param name="sessionManager">Session manager.</param>
|
/// <param name="sessionManager">Session manager.</param>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
public AutoSkip(
|
public class AutoSkip(
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
ILogger<AutoSkip> logger)
|
ILogger<AutoSkip> logger) : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
_userDataManager = userDataManager;
|
private readonly object _sentSeekCommandLock = new();
|
||||||
_sessionManager = sessionManager;
|
|
||||||
_logger = logger;
|
private ILogger<AutoSkip> _logger = logger;
|
||||||
_sentSeekCommand = new Dictionary<string, bool>();
|
private IUserDataManager _userDataManager = userDataManager;
|
||||||
}
|
private ISessionManager _sessionManager = sessionManager;
|
||||||
|
private Timer _playbackTimer = new(1000);
|
||||||
|
private Dictionary<string, bool> _sentSeekCommand = [];
|
||||||
|
private HashSet<string> _clientList = [];
|
||||||
|
|
||||||
private void AutoSkipChanged(object? sender, BasePluginConfiguration e)
|
private void AutoSkipChanged(object? sender, BasePluginConfiguration e)
|
||||||
{
|
{
|
||||||
@ -54,6 +47,7 @@ public class AutoSkip : IHostedService, IDisposable
|
|||||||
var newState = configuration.AutoSkip;
|
var newState = configuration.AutoSkip;
|
||||||
_logger.LogDebug("Setting playback timer enabled to {NewState}", newState);
|
_logger.LogDebug("Setting playback timer enabled to {NewState}", newState);
|
||||||
_playbackTimer.Enabled = newState;
|
_playbackTimer.Enabled = newState;
|
||||||
|
_clientList = [.. configuration.ClientList.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserDataManager_UserDataSaved(object? sender, UserDataSaveEventArgs e)
|
private void UserDataManager_UserDataSaved(object? sender, UserDataSaveEventArgs e)
|
||||||
@ -111,19 +105,8 @@ public class AutoSkip : IHostedService, IDisposable
|
|||||||
|
|
||||||
private void PlaybackTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
private void PlaybackTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
foreach (var session in _sessionManager.Sessions)
|
foreach (var session in _sessionManager.Sessions.Where(s => _clientList.Contains(s.Client, StringComparer.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
if (WarningManager.HasFlag(PluginWarning.UnableToAddSkipButton))
|
|
||||||
{
|
|
||||||
_logger.LogTrace("using autoskip to skip the intro because the injection of the skip button failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// only need for official Android TV App and jellyfin-kodi
|
|
||||||
else if (session.Client != "Android TV" && session.Client != "Kodi")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var deviceId = session.DeviceId;
|
var deviceId = session.DeviceId;
|
||||||
var itemId = session.NowPlayingItem.Id;
|
var itemId = session.NowPlayingItem.Id;
|
||||||
var position = session.PlayState.PositionTicks / TimeSpan.TicksPerSecond;
|
var position = session.PlayState.PositionTicks / TimeSpan.TicksPerSecond;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
@ -21,32 +21,25 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// Automatically skip past credit sequences.
|
/// Automatically skip past credit sequences.
|
||||||
/// Commands clients to seek to the end of the credits as soon as they start playing it.
|
/// Commands clients to seek to the end of the credits as soon as they start playing it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AutoSkipCredits : IHostedService, IDisposable
|
/// <remarks>
|
||||||
{
|
|
||||||
private readonly object _sentSeekCommandLock = new();
|
|
||||||
|
|
||||||
private ILogger<AutoSkipCredits> _logger;
|
|
||||||
private IUserDataManager _userDataManager;
|
|
||||||
private ISessionManager _sessionManager;
|
|
||||||
private Timer _playbackTimer = new(1000);
|
|
||||||
private Dictionary<string, bool> _sentSeekCommand;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="AutoSkipCredits"/> class.
|
/// Initializes a new instance of the <see cref="AutoSkipCredits"/> class.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="userDataManager">User data manager.</param>
|
/// <param name="userDataManager">User data manager.</param>
|
||||||
/// <param name="sessionManager">Session manager.</param>
|
/// <param name="sessionManager">Session manager.</param>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
public AutoSkipCredits(
|
public class AutoSkipCredits(
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
ILogger<AutoSkipCredits> logger)
|
ILogger<AutoSkipCredits> logger) : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
_userDataManager = userDataManager;
|
private readonly object _sentSeekCommandLock = new();
|
||||||
_sessionManager = sessionManager;
|
|
||||||
_logger = logger;
|
private ILogger<AutoSkipCredits> _logger = logger;
|
||||||
_sentSeekCommand = new Dictionary<string, bool>();
|
private IUserDataManager _userDataManager = userDataManager;
|
||||||
}
|
private ISessionManager _sessionManager = sessionManager;
|
||||||
|
private Timer _playbackTimer = new(1000);
|
||||||
|
private Dictionary<string, bool> _sentSeekCommand = [];
|
||||||
|
private HashSet<string> _clientList = [];
|
||||||
|
|
||||||
private void AutoSkipCreditChanged(object? sender, BasePluginConfiguration e)
|
private void AutoSkipCreditChanged(object? sender, BasePluginConfiguration e)
|
||||||
{
|
{
|
||||||
@ -54,6 +47,7 @@ public class AutoSkipCredits : IHostedService, IDisposable
|
|||||||
var newState = configuration.AutoSkipCredits;
|
var newState = configuration.AutoSkipCredits;
|
||||||
_logger.LogDebug("Setting playback timer enabled to {NewState}", newState);
|
_logger.LogDebug("Setting playback timer enabled to {NewState}", newState);
|
||||||
_playbackTimer.Enabled = newState;
|
_playbackTimer.Enabled = newState;
|
||||||
|
_clientList = [.. configuration.ClientList.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserDataManager_UserDataSaved(object? sender, UserDataSaveEventArgs e)
|
private void UserDataManager_UserDataSaved(object? sender, UserDataSaveEventArgs e)
|
||||||
@ -111,19 +105,8 @@ public class AutoSkipCredits : IHostedService, IDisposable
|
|||||||
|
|
||||||
private void PlaybackTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
private void PlaybackTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
foreach (var session in _sessionManager.Sessions)
|
foreach (var session in _sessionManager.Sessions.Where(s => _clientList.Contains(s.Client, StringComparer.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
if (WarningManager.HasFlag(PluginWarning.UnableToAddSkipButton))
|
|
||||||
{
|
|
||||||
_logger.LogTrace("using autoskip to skip the credits because the injection of the skip button failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// only need for official Android TV App and jellyfin-kodi
|
|
||||||
else if (session.Client != "Android TV" && session.Client != "Kodi")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var deviceId = session.DeviceId;
|
var deviceId = session.DeviceId;
|
||||||
var itemId = session.NowPlayingItem.Id;
|
var itemId = session.NowPlayingItem.Id;
|
||||||
var position = session.PlayState.PositionTicks / TimeSpan.TicksPerSecond;
|
var position = session.PlayState.PositionTicks / TimeSpan.TicksPerSecond;
|
||||||
|
@ -29,6 +29,11 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string SelectedLibraries { get; set; } = string.Empty;
|
public string SelectedLibraries { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of client to auto skip for.
|
||||||
|
/// </summary>
|
||||||
|
public string ClientList { get; set; } = "Android TV, Kodi";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to scan for intros during a scheduled task.
|
/// Gets or sets a value indicating whether to scan for intros during a scheduled task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -398,6 +398,14 @@
|
|||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<details id="AutoSkipClientList" style="padding-bottom: 1em;">
|
||||||
|
<summary>Auto Skip Client List</summary>
|
||||||
|
<br />
|
||||||
|
<div class="checkboxList paperList" style="padding:.5em 1em"></div>
|
||||||
|
<label class="inputLabel" for="ClientList"></label>
|
||||||
|
<input id="ClientList" type="hidden" is="emby-input" />
|
||||||
|
</details>
|
||||||
|
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
<label class="emby-checkbox-label">
|
<label class="emby-checkbox-label">
|
||||||
<input id="SkipButtonVisible" type="checkbox" is="emby-checkbox" />
|
<input id="SkipButtonVisible" type="checkbox" is="emby-checkbox" />
|
||||||
@ -699,6 +707,7 @@
|
|||||||
// analysis
|
// analysis
|
||||||
"MaxParallelism",
|
"MaxParallelism",
|
||||||
"SelectedLibraries",
|
"SelectedLibraries",
|
||||||
|
"ClientList",
|
||||||
"AnalysisPercent",
|
"AnalysisPercent",
|
||||||
"AnalysisLengthLimit",
|
"AnalysisLengthLimit",
|
||||||
"MinimumIntroDuration",
|
"MinimumIntroDuration",
|
||||||
@ -758,12 +767,13 @@
|
|||||||
var autoSkip = document.querySelector("input#AutoSkip");
|
var autoSkip = document.querySelector("input#AutoSkip");
|
||||||
var skipFirstEpisode = document.querySelector("div#divSkipFirstEpisode");
|
var skipFirstEpisode = document.querySelector("div#divSkipFirstEpisode");
|
||||||
var secondsOfIntroStartToPlay = document.querySelector("div#divSecondsOfIntroStartToPlay");
|
var secondsOfIntroStartToPlay = document.querySelector("div#divSecondsOfIntroStartToPlay");
|
||||||
|
var autoSkipClientList = document.getElementById("AutoSkipClientList");
|
||||||
var secondsOfCreditsStartToPlay = document.querySelector("div#divSecondsOfCreditsStartToPlay");
|
var secondsOfCreditsStartToPlay = document.querySelector("div#divSecondsOfCreditsStartToPlay");
|
||||||
var autoSkipNotificationText = document.querySelector("div#divAutoSkipNotificationText");
|
var autoSkipNotificationText = document.querySelector("div#divAutoSkipNotificationText");
|
||||||
var autoSkipCredits = document.querySelector("input#AutoSkipCredits");
|
var autoSkipCredits = document.querySelector("input#AutoSkipCredits");
|
||||||
var autoSkipCreditsNotificationText = document.querySelector("div#divAutoSkipCreditsNotificationText");
|
var autoSkipCreditsNotificationText = document.querySelector("div#divAutoSkipCreditsNotificationText");
|
||||||
|
|
||||||
async function autoSkipChanged() {
|
function autoSkipChanged() {
|
||||||
if (autoSkip.checked) {
|
if (autoSkip.checked) {
|
||||||
skipFirstEpisode.style.display = 'unset';
|
skipFirstEpisode.style.display = 'unset';
|
||||||
autoSkipNotificationText.style.display = 'unset';
|
autoSkipNotificationText.style.display = 'unset';
|
||||||
@ -773,11 +783,12 @@
|
|||||||
autoSkipNotificationText.style.display = 'none';
|
autoSkipNotificationText.style.display = 'none';
|
||||||
secondsOfIntroStartToPlay.style.display = 'none';
|
secondsOfIntroStartToPlay.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
clientListVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
autoSkip.addEventListener("change", autoSkipChanged);
|
autoSkip.addEventListener("change", autoSkipChanged);
|
||||||
|
|
||||||
async function autoSkipCreditsChanged() {
|
function autoSkipCreditsChanged() {
|
||||||
if (autoSkipCredits.checked) {
|
if (autoSkipCredits.checked) {
|
||||||
autoSkipCreditsNotificationText.style.display = 'unset';
|
autoSkipCreditsNotificationText.style.display = 'unset';
|
||||||
secondsOfCreditsStartToPlay.style.display = 'unset';
|
secondsOfCreditsStartToPlay.style.display = 'unset';
|
||||||
@ -785,10 +796,57 @@
|
|||||||
autoSkipCreditsNotificationText.style.display = 'none';
|
autoSkipCreditsNotificationText.style.display = 'none';
|
||||||
secondsOfCreditsStartToPlay.style.display = 'none';
|
secondsOfCreditsStartToPlay.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
clientListVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
autoSkipCredits.addEventListener("change", autoSkipCreditsChanged);
|
autoSkipCredits.addEventListener("change", autoSkipCreditsChanged);
|
||||||
|
|
||||||
|
function clientListVisible() {
|
||||||
|
if (autoSkip.checked || autoSkipCredits.checked) {
|
||||||
|
autoSkipClientList.style.display = 'unset';
|
||||||
|
autoSkipClientList.style.width = '100%';
|
||||||
|
} else {
|
||||||
|
autoSkipClientList.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDeviceList() {
|
||||||
|
const response = await getJson("Devices");
|
||||||
|
const devices = [...new Set(response.Items.map(item => item.AppName))];
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateClientList() {
|
||||||
|
document.getElementById('ClientList').value = Array.from(
|
||||||
|
autoSkipClientList.querySelectorAll('input[type="checkbox"]:checked')
|
||||||
|
).map(checkbox => checkbox.nextElementSibling.textContent).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateAutoSkipClientList() {
|
||||||
|
var devices = await getDeviceList();
|
||||||
|
var deviceList = document.getElementById('ClientList').value;
|
||||||
|
var checkedDevices = deviceList ? deviceList.split(', ') : [];
|
||||||
|
|
||||||
|
var checkboxListHtml = devices.map(function(device) {
|
||||||
|
var id = 'chk' + device.replace(/\s+/g, '');
|
||||||
|
var isChecked = checkedDevices.includes(device) ? 'checked' : '';
|
||||||
|
return '<label class="emby-checkbox-label">' +
|
||||||
|
'<input type="checkbox" is="emby-checkbox" id="' + id + '" ' + isChecked + '>' +
|
||||||
|
'<span class="checkboxLabel">' + device + '</span>' +
|
||||||
|
'</label>';
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
var checkboxList = autoSkipClientList.querySelector('.checkboxList.paperList');
|
||||||
|
checkboxList.innerHTML = checkboxListHtml;
|
||||||
|
|
||||||
|
var checkboxes = checkboxList.querySelectorAll('input[type="checkbox"]');
|
||||||
|
for (var i = 0; i < checkboxes.length; i++) {
|
||||||
|
checkboxes[i].addEventListener('change', function() {
|
||||||
|
updateClientList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var persistSkip = document.querySelector("input#PersistSkipButton");
|
var persistSkip = document.querySelector("input#PersistSkipButton");
|
||||||
var showAdjustment = document.querySelector("div#divShowPromptAdjustment");
|
var showAdjustment = document.querySelector("div#divShowPromptAdjustment");
|
||||||
var hideAdjustment = document.querySelector("div#divHidePromptAdjustment");
|
var hideAdjustment = document.querySelector("div#divHidePromptAdjustment");
|
||||||
@ -976,7 +1034,6 @@
|
|||||||
document.querySelector("#editRightIntroEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Introduction.IntroEnd));
|
document.querySelector("#editRightIntroEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Introduction.IntroEnd));
|
||||||
document.querySelector("#editRightCreditEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Credits.IntroStart));
|
document.querySelector("#editRightCreditEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Credits.IntroStart));
|
||||||
document.querySelector("#editRightCreditEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Credits.IntroEnd));
|
document.querySelector("#editRightCreditEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Credits.IntroEnd));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds an item to a dropdown
|
// adds an item to a dropdown
|
||||||
@ -1143,6 +1200,7 @@
|
|||||||
autoSkipChanged();
|
autoSkipChanged();
|
||||||
autoSkipCreditsChanged();
|
autoSkipCreditsChanged();
|
||||||
persistSkipChanged();
|
persistSkipChanged();
|
||||||
|
generateAutoSkipClientList();
|
||||||
|
|
||||||
Dashboard.hideLoadingMsg();
|
Dashboard.hideLoadingMsg();
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user