Make credit skipping functionality available
This commit is contained in:
parent
04903541e9
commit
8a9b630e68
@ -163,9 +163,14 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
// ===== Localization support =====
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to display in the Skip Intro button.
|
||||
/// Gets or sets the text to display in the skip button in introduction mode.
|
||||
/// </summary>
|
||||
public string SkipButtonText { get; set; } = "Skip Intro";
|
||||
public string SkipButtonIntroText { get; set; } = "Skip Intro";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to display in the skip button in end credits mode.
|
||||
/// </summary>
|
||||
public string SkipButtonEndCreditsText { get; set; } = "Next";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the notification text sent after automatically skipping an introduction.
|
||||
|
@ -9,11 +9,13 @@ public class UserInterfaceConfiguration
|
||||
/// Initializes a new instance of the <see cref="UserInterfaceConfiguration"/> class.
|
||||
/// </summary>
|
||||
/// <param name="visible">Skip button visibility.</param>
|
||||
/// <param name="text">Skip button text.</param>
|
||||
public UserInterfaceConfiguration(bool visible, string text)
|
||||
/// <param name="introText">Skip button intro text.</param>
|
||||
/// <param name="creditsText">Skip button end credits text.</param>
|
||||
public UserInterfaceConfiguration(bool visible, string introText, string creditsText)
|
||||
{
|
||||
SkipButtonVisible = visible;
|
||||
SkipButtonText = text;
|
||||
SkipButtonIntroText = introText;
|
||||
SkipButtonEndCreditsText = creditsText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -22,7 +24,12 @@ public class UserInterfaceConfiguration
|
||||
public bool SkipButtonVisible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to display in the skip intro button.
|
||||
/// Gets or sets the text to display in the skip intro button in introduction mode.
|
||||
/// </summary>
|
||||
public string SkipButtonText { get; set; }
|
||||
public string SkipButtonIntroText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text to display in the skip intro button in end credits mode.
|
||||
/// </summary>
|
||||
public string SkipButtonEndCreditsText { get; set; }
|
||||
}
|
||||
|
@ -265,15 +265,25 @@
|
||||
<summary>User Interface Customization</summary>
|
||||
|
||||
<div class="inputContainer">
|
||||
<label class="inputLabel" for="SkipButtonText">
|
||||
<label class="inputLabel" for="SkipButtonIntroText">
|
||||
Skip intro button text
|
||||
</label>
|
||||
<input id="SkipButtonText" type="text" is="emby-input" />
|
||||
<input id="SkipButtonIntroText" type="text" is="emby-input" />
|
||||
<div class="fieldDescription">
|
||||
Text to display in the skip intro button.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<label class="inputLabel" for="SkipButtonEndCreditsText">
|
||||
Skip end credits button text
|
||||
</label>
|
||||
<input id="SkipButtonEndCreditsText" type="text" is="emby-input" />
|
||||
<div class="fieldDescription">
|
||||
Text to display in the skip end credits button.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<label class="inputLabel" for="AutoSkipNotificationText">
|
||||
Automatic skip notification message
|
||||
@ -440,7 +450,9 @@
|
||||
// internals
|
||||
"SilenceDetectionMaximumNoise",
|
||||
"SilenceDetectionMinimumDuration",
|
||||
"SkipButtonText",
|
||||
// UI customization
|
||||
"SkipButtonIntroText",
|
||||
"SkipButtonEndCreditsText",
|
||||
"AutoSkipNotificationText"
|
||||
]
|
||||
|
||||
|
@ -34,7 +34,7 @@ introSkipper.fetchWrapper = async function (...args) {
|
||||
introSkipper.d(path);
|
||||
|
||||
let id = path.split("/")[2];
|
||||
introSkipper.skipSegments = await introSkipper.secureFetch(`Episode/${id}/IntroTimestamps/v1`);
|
||||
introSkipper.skipSegments = await introSkipper.secureFetch(`Episode/${id}/IntroSkipperSegments`);
|
||||
|
||||
introSkipper.d("successfully retrieved skip segments");
|
||||
introSkipper.d(introSkipper.skipSegments);
|
||||
@ -151,10 +151,12 @@ introSkipper.injectButton = async function () {
|
||||
button.addEventListener("click", introSkipper.doSkip);
|
||||
button.innerHTML = `
|
||||
<button is="paper-icon-button-light" class="btnSkipIntro paper-icon-button-light">
|
||||
<span id="btnSkipIntroText"></span>
|
||||
<span id="btnSkipSegmentText"></span>
|
||||
<span class="material-icons skip_next"></span>
|
||||
</button>
|
||||
`;
|
||||
button.dataset["intro_text"] = config.SkipButtonIntroText;
|
||||
button.dataset["credits_text"] = config.SkipButtonEndCreditsText;
|
||||
|
||||
/*
|
||||
* Alternative workaround for #44. Jellyfin's video component registers a global click handler
|
||||
@ -166,37 +168,60 @@ introSkipper.injectButton = async function () {
|
||||
// Append the button to the video OSD
|
||||
let controls = document.querySelector("div#videoOsdPage");
|
||||
controls.appendChild(button);
|
||||
}
|
||||
|
||||
document.querySelector("#btnSkipIntroText").textContent = config.SkipButtonText;
|
||||
/** Get the currently playing skippable segment. */
|
||||
introSkipper.getCurrentSegment = function (position) {
|
||||
for (let key in introSkipper.skipSegments) {
|
||||
const segment = introSkipper.skipSegments[key];
|
||||
if (position >= segment.ShowSkipPromptAt && position < segment.HideSkipPromptAt) {
|
||||
segment["SegmentType"] = key;
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
||||
return { "SegmentType": "None" };
|
||||
}
|
||||
|
||||
/** Playback position changed, check if the skip button needs to be displayed. */
|
||||
introSkipper.videoPositionChanged = function () {
|
||||
// Ensure a skip segment was found.
|
||||
if (!introSkipper.skipSegments || !introSkipper.skipSegments.Valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const skipButton = document.querySelector("#skipIntro");
|
||||
if (!skipButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = introSkipper.videoPlayer.currentTime;
|
||||
if (position >= introSkipper.skipSegments.ShowSkipPromptAt &&
|
||||
position < introSkipper.skipSegments.HideSkipPromptAt) {
|
||||
skipButton.classList.remove("hide");
|
||||
return;
|
||||
const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime);
|
||||
switch (segment["SegmentType"]) {
|
||||
case "None":
|
||||
skipButton.classList.add("hide");
|
||||
return;
|
||||
|
||||
case "Introduction":
|
||||
skipButton.querySelector("#btnSkipSegmentText").textContent =
|
||||
skipButton.dataset["intro_text"];
|
||||
break;
|
||||
|
||||
case "Credits":
|
||||
skipButton.querySelector("#btnSkipSegmentText").textContent =
|
||||
skipButton.dataset["credits_text"];
|
||||
break;
|
||||
}
|
||||
|
||||
skipButton.classList.add("hide");
|
||||
skipButton.classList.remove("hide");
|
||||
}
|
||||
|
||||
/** Seeks to the end of the intro. */
|
||||
introSkipper.doSkip = function (e) {
|
||||
introSkipper.d("Skipping intro");
|
||||
introSkipper.d(introSkipper.skipSegments);
|
||||
introSkipper.videoPlayer.currentTime = introSkipper.skipSegments.IntroEnd;
|
||||
|
||||
const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime);
|
||||
if (segment["SegmentType"] === "None") {
|
||||
console.warn("[intro skipper] doSkip() called without an active segment");
|
||||
return;
|
||||
}
|
||||
|
||||
introSkipper.videoPlayer.currentTime = segment["IntroEnd"];
|
||||
}
|
||||
|
||||
/** Tests if an element with the provided selector exists. */
|
||||
|
@ -44,15 +44,33 @@ public class SkipIntroController : ControllerBase
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Populate the prompt show/hide times.
|
||||
var config = Plugin.Instance!.Configuration;
|
||||
intro.ShowSkipPromptAt = Math.Max(0, intro.IntroStart - config.ShowPromptAdjustment);
|
||||
intro.HideSkipPromptAt = intro.IntroStart + config.HidePromptAdjustment;
|
||||
intro.IntroEnd -= config.SecondsOfIntroToPlay;
|
||||
|
||||
return intro;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary of all skippable segments.
|
||||
/// </summary>
|
||||
/// <param name="id">Media ID.</param>
|
||||
/// <response code="200">Skippable segments dictionary.</response>
|
||||
/// <returns>Dictionary of skippable segments.</returns>
|
||||
[HttpGet("Episode/{id}/IntroSkipperSegments")]
|
||||
public ActionResult<Dictionary<AnalysisMode, Intro>> GetSkippableSegments([FromRoute] Guid id)
|
||||
{
|
||||
var segments = new Dictionary<AnalysisMode, Intro>();
|
||||
|
||||
if (GetIntro(id, AnalysisMode.Introduction) is Intro intro)
|
||||
{
|
||||
segments[AnalysisMode.Introduction] = intro;
|
||||
}
|
||||
|
||||
if (GetIntro(id, AnalysisMode.Credits) is Intro credits)
|
||||
{
|
||||
segments[AnalysisMode.Credits] = credits;
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
/// <summary>Lookup and return the skippable timestamps for the provided item.</summary>
|
||||
/// <param name="id">Unique identifier of this episode.</param>
|
||||
/// <param name="mode">Mode.</param>
|
||||
@ -65,8 +83,15 @@ public class SkipIntroController : ControllerBase
|
||||
Plugin.Instance!.Intros[id] :
|
||||
Plugin.Instance!.Credits[id];
|
||||
|
||||
// A copy is returned to avoid mutating the original Intro object stored in the dictionary.
|
||||
return new(timestamp);
|
||||
// Operate on a copy to avoid mutating the original Intro object stored in the dictionary.
|
||||
var segment = new Intro(timestamp);
|
||||
|
||||
var config = Plugin.Instance!.Configuration;
|
||||
segment.ShowSkipPromptAt = Math.Max(0, segment.IntroStart - config.ShowPromptAdjustment);
|
||||
segment.HideSkipPromptAt = segment.IntroStart + config.HidePromptAdjustment;
|
||||
segment.IntroEnd -= config.SecondsOfIntroToPlay;
|
||||
|
||||
return segment;
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
@ -145,6 +170,9 @@ public class SkipIntroController : ControllerBase
|
||||
public ActionResult<UserInterfaceConfiguration> GetUserInterfaceConfiguration()
|
||||
{
|
||||
var config = Plugin.Instance!.Configuration;
|
||||
return new UserInterfaceConfiguration(config.SkipButtonVisible, config.SkipButtonText);
|
||||
return new UserInterfaceConfiguration(
|
||||
config.SkipButtonVisible,
|
||||
config.SkipButtonIntroText,
|
||||
config.SkipButtonEndCreditsText);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user