Skip to content

Commit

Permalink
Make credit skipping functionality available
Browse files Browse the repository at this point in the history
  • Loading branch information
ConfusedPolarBear committed Mar 4, 2023
1 parent 0490354 commit 8a9b630
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,14 @@ public PluginConfiguration()
// ===== 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -22,7 +24,12 @@ public UserInterfaceConfiguration(bool visible, string text)
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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -440,7 +450,9 @@ <h3>Fingerprint Visualizer</h3>
// internals
"SilenceDetectionMaximumNoise",
"SilenceDetectionMinimumDuration",
"SkipButtonText",
// UI customization
"SkipButtonIntroText",
"SkipButtonEndCreditsText",
"AutoSkipNotificationText"
]

Expand Down
55 changes: 40 additions & 15 deletions ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -166,37 +168,60 @@ introSkipper.injectButton = async function () {
// Append the button to the video OSD
let controls = document.querySelector("div#videoOsdPage");
controls.appendChild(button);
}

/** 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;
}
}

document.querySelector("#btnSkipIntroText").textContent = config.SkipButtonText;
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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,33 @@ public ActionResult<Intro> GetIntroTimestamps(
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>
Expand All @@ -65,8 +83,15 @@ public ActionResult<Intro> GetIntroTimestamps(
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)
{
Expand Down Expand Up @@ -145,6 +170,9 @@ public ActionResult<List<IntroWithMetadata>> GetAllTimestamps(
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);
}
}

0 comments on commit 8a9b630

Please sign in to comment.