diff --git a/webui.patch b/webui.patch deleted file mode 100644 index c2b1662..0000000 --- a/webui.patch +++ /dev/null @@ -1,226 +0,0 @@ -diff --git a/src/controllers/playback/video/index.html b/src/controllers/playback/video/index.html -index a460ee8f6a3..d7b344d4b1b 100644 ---- a/src/controllers/playback/video/index.html -+++ b/src/controllers/playback/video/index.html -@@ -6,6 +6,12 @@ - - -
-+
-+ -+
-
-
-
-diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js -index 2adad5708c3..5b81eebc7f1 100644 ---- a/src/controllers/playback/video/index.js -+++ b/src/controllers/playback/video/index.js -@@ -365,7 +365,7 @@ export default function (view) { - toggleSubtitleSync('hide'); - - // Firefox does not blur by itself -- if (document.activeElement) { -+ if (document.activeElement && !skipButton.contains(document.activeElement)) { - document.activeElement.blur(); - } - } -@@ -517,9 +517,95 @@ export default function (view) { - updatePlaylist(); - enableStopOnBack(true); - updatePlaybackRate(player); -+ getIntroTimestamps(state.NowPlayingItem); - } - } - -+ function secureFetch(url) { -+ const apiClient = ServerConnections.currentApiClient(); -+ const address = apiClient.serverAddress(); -+ const reqInit = { -+ headers: { -+ "Authorization": `MediaBrowser Token=${apiClient.accessToken()}` -+ } -+ }; -+ return fetch(`${address}${url}`, reqInit).then(r => { -+ return r.ok ? r.json() : null; -+ }); -+ } -+ -+ function getIntroTimestamps(item) { -+ secureFetch(`/Episode/${item.Id}/IntroSkipperSegments`).then(segments => { -+ skipSegments = segments; -+ hasCreditsSegment = Object.keys(segments).some(key => key === "Credits"); -+ }).catch(err => { -+ skipSegments = {}; -+ hasCreditsSegment = false; }); -+ secureFetch(`/Intros/UserInterfaceConfiguration`).then(config => { -+ skipButton.dataset.Introduction = config.SkipButtonIntroText; -+ skipButton.dataset.Credits = config.SkipButtonEndCreditsText; -+ }).catch(err => { -+ skipButton.dataset.Introduction = 'Skip Intro'; -+ skipButton.dataset.Credits = 'Next'; }); -+ } -+ -+ function getCurrentSegment(position) { -+ for (const [key, segment] of Object.entries(skipSegments)) { -+ if ((position > segment.ShowSkipPromptAt && position < segment.HideSkipPromptAt - 1) || -+ (currentVisibleMenu === 'osd' && position > segment.IntroStart && position < segment.IntroEnd - 1)) { -+ segment.SegmentType = key; -+ return segment; -+ } -+ } -+ return { SegmentType: "None" }; -+ } -+ -+ function videoPositionChanged(currentTime) { -+ const embyButton = skipButton.querySelector(".emby-button"); -+ const segmentType = getCurrentSegment(currentTime / TICKS_PER_SECOND).SegmentType; -+ if (segmentType === "None") { -+ if (!skipButton.classList.contains('show')) return; -+ skipButton.classList.remove('show'); -+ embyButton.addEventListener("transitionend", () => { -+ skipButton.classList.add("hide"); -+ if (!currentVisibleMenu) { -+ embyButton.blur(); -+ } else { -+ _focus(osdBottomElement.querySelector('.btnPause')); -+ } -+ }, { once: true }); -+ return; -+ } -+ skipButton.querySelector("#btnSkipSegmentText").textContent = skipButton.dataset[segmentType]; -+ if (!skipButton.classList.contains("hide")) { -+ if (!currentVisibleMenu && !embyButton.contains(document.activeElement)) _focus(embyButton); -+ return; -+ } -+ requestAnimationFrame(() => { -+ skipButton.classList.remove("hide"); -+ requestAnimationFrame(() => { -+ skipButton.classList.add('show'); -+ _focus(embyButton); -+ }); -+ }); -+ } -+ -+ function doSkip() { -+ const segment = getCurrentSegment(playbackManager.currentTime(currentPlayer) / 1000); -+ if (segment.SegmentType === "None") { -+ console.warn("[intro skipper] doSkip() called without an active segment"); -+ return; -+ } -+ playbackManager.seek(segment.IntroEnd * TICKS_PER_SECOND, currentPlayer); -+ } -+ -+ function eventHandler(e) { -+ if (e.key !== "Enter") return; -+ e.stopPropagation(); -+ e.preventDefault(); -+ doSkip(); -+ } -+ - function onPlayPauseStateChanged() { - if (isEnabled) { - updatePlayPauseState(this.paused()); -@@ -637,12 +723,13 @@ export default function (view) { - const item = currentItem; - refreshProgramInfoIfNeeded(player, item); - showComingUpNextIfNeeded(player, item, currentTime, currentRuntimeTicks); -+ videoPositionChanged(currentTime); - } - } - } - - function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) { -- if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) { -+ if (!hasCreditsSegment && runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) { - let showAtSecondsLeft = 30; - if (runtimeTicks >= 50 * TICKS_PER_MINUTE) { - showAtSecondsLeft = 40; -@@ -1543,7 +1630,10 @@ export default function (view) { - let programEndDateMs = 0; - let playbackStartTimeTicks = 0; - let subtitleSyncOverlay; -+ let skipSegments = {}; -+ let hasCreditsSegment; - let trickplayResolution = null; -+ const skipButton = document.querySelector(".skipIntro"); - const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider'); - const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer'); - const nowPlayingPositionSlider = view.querySelector('.osdPositionSlider'); -@@ -1699,6 +1789,10 @@ export default function (view) { - let lastPointerDown = 0; - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(view, window.PointerEvent ? 'pointerdown' : 'click', function (e) { -+ if (dom.parentWithClass(e.target, ['btnSkipIntro'])) { -+ return; -+ } -+ - if (dom.parentWithClass(e.target, ['videoOsdBottom', 'upNextContainer'])) { - showOsd(); - return; -@@ -1854,6 +1948,8 @@ export default function (view) { - }); - view.querySelector('.btnAudio').addEventListener('click', showAudioTrackSelection); - view.querySelector('.btnSubtitles').addEventListener('click', showSubtitleTrackSelection); -+ skipButton.addEventListener('click', doSkip); -+ skipButton.addEventListener("keydown", eventHandler); - - // HACK: Remove `emby-button` from the rating button to make it look like the other buttons - view.querySelector('.btnUserRating').classList.remove('emby-button'); -@@ -1964,4 +2060,3 @@ export default function (view) { - }); - } - } -- -diff --git a/src/styles/videoosd.scss b/src/styles/videoosd.scss -index 2c8c00e2601..336b2bacad3 100644 ---- a/src/styles/videoosd.scss -+++ b/src/styles/videoosd.scss -@@ -346,3 +346,44 @@ - transform: rotate(-360deg); - } - } -+ -+:root { -+ --rounding: 4px; -+ --accent: 0, 164, 220; -+} -+.skipIntro { -+ position: absolute; -+ bottom: 7.5em; -+ right: 5em; -+ background-color: transparent; -+} -+.skipIntro .emby-button { -+ color: #ffffff; -+ font-size: 110%; -+ background: rgba(0, 0, 0, 0.7); -+ border-radius: var(--rounding); -+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.6); -+ transition: opacity 0.3s cubic-bezier(0.4,0,0.2,1), -+ transform 0.3s cubic-bezier(0.4,0,0.2,1), -+ background-color 0.2s ease-out, -+ box-shadow 0.2s ease-out; -+ opacity: 0; -+ transform: translateY(50%); -+} -+.skipIntro.show .emby-button { -+ opacity: 1; -+ transform: translateY(0); -+} -+.skipIntro .emby-button:hover { -+ background: rgb(var(--accent)); -+ box-shadow: 0 0 8px rgba(var(--accent), 0.6); -+ filter: brightness(1.2); -+} -+.skipIntro .emby-button:focus { -+ background: rgb(var(--accent)); -+ box-shadow: 0 0 8px rgba(var(--accent), 0.6); -+} -+.btnSkipSegmentText { -+ letter-spacing: 0.5px; -+ padding: 0 5px 0 5px; -+}