let tvIntro; class skipIntroPlugin { constructor({ events, playbackManager, ServerConnections }) { this.name = 'Skip Intro Plugin'; this.type = 'input'; this.id = 'skipIntroPlugin'; (async() => { const api = await window.apiPromise; const enabled = await new Promise(resolve => { api.settings.value('plugins', 'skipintro', resolve); }); console.log("Skip Intro Plugin enabled: " + enabled); if (!enabled) return; // Based on https://github.com/jellyfin/jellyfin-web/compare/release-10.8.z...ConfusedPolarBear:jellyfin-web:intros // Adapted for use in JMP const stylesheet = ` `; document.head.insertAdjacentHTML('beforeend', stylesheet); const skipIntroHtml = `
`; function waitForElement(element, maxWait = 10000) { return new Promise((resolve, reject) => { const interval = setInterval(() => { const result = document.querySelector(element); if (result) { clearInterval(interval); resolve(result); } }, 100); setTimeout(() => { clearInterval(interval); reject(); }, maxWait); }); } async function injectSkipIntroHtml() { const playerContainer = await waitForElement('.upNextContainer', 5000); // inject only if it doesn't exist if (!document.querySelector('.skipIntro .btnSkipIntro')) { playerContainer.insertAdjacentHTML('afterend', skipIntroHtml); } document.querySelector('.skipIntro .btnSkipIntro').addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); skipIntro(); }, { useCapture: true }); if (window.PointerEvent) { document.querySelector('.skipIntro .btnSkipIntro').addEventListener('pointerdown', (e) => { e.preventDefault(); e.stopPropagation(); }, { useCapture: true }); } } function onPlayback(e, player, state) { if (state.NowPlayingItem) { getIntroTimestamps(state.NowPlayingItem); const onTimeUpdate = async () => { // Check if an introduction sequence was detected for this item. if (!tvIntro?.Valid) { return; } const seconds = playbackManager.currentTime(player) / 1000; await injectSkipIntroHtml(); // I have trust issues const skipIntro = document.querySelector(".skipIntro"); // If the skip prompt should be shown, show it. if (seconds >= tvIntro.ShowSkipPromptAt && seconds < tvIntro.HideSkipPromptAt) { skipIntro.classList.remove("hide"); return; } skipIntro.classList.add("hide"); }; events.on(player, 'timeupdate', onTimeUpdate); const onPlaybackStop = () => { events.off(player, 'timeupdate', onTimeUpdate); events.off(player, 'playbackstop', onPlaybackStop); }; events.on(player, 'playbackstop', onPlaybackStop); } }; events.on(playbackManager, 'playbackstart', onPlayback); function getIntroTimestamps(item) { const apiClient = ServerConnections ? ServerConnections.currentApiClient() : window.ApiClient; const address = apiClient.serverAddress(); const url = `${address}/Episode/${item.Id}/IntroTimestamps`; const reqInit = { headers: { "Authorization": `MediaBrowser Token=${apiClient.accessToken()}` } }; fetch(url, reqInit).then(r => { if (!r.ok) { tvIntro = null; return; } return r.json(); }).then(intro => { tvIntro = intro; }).catch(err => { tvIntro = null; }); } function skipIntro() { playbackManager.seekMs(tvIntro.IntroEnd * 1000); } })(); } } window._skipIntroPlugin = skipIntroPlugin;