MediaWiki:Gadget-floatingYouTubePlayer.js

From Angelina Jordan Wiki
Revision as of 20:49, 28 September 2025 by Most2dot0 (talk | contribs) (another rewrite by ChatGPT)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
(function () {
    'use strict';

    // ===== CONFIG =====
    var CONTENT_SELECTOR = '#mw-content-text';
    var HEADER_SELECTOR = '#firstHeading';
    var CORE_PLAYER_ID = 'core-youtube-player';
    var YT_SCRIPT_ID = 'youtube-iframe-api';
    var API_PROP = 'text|displaytitle|title';

    function stripTags(s) {
        var d = document.createElement('div');
        d.innerHTML = s || '';
        return d.textContent || d.innerText || '';
    }

    function parseTimeParam(val) {
        if (!val) return 0;
        val = String(val);
        if (/^\d+$/.test(val)) return parseInt(val, 10);
        var m = val.match(/(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/);
        if (m) return (parseInt(m[1] || 0, 10) * 3600) + (parseInt(m[2] || 0, 10) * 60) + (parseInt(m[3] || 0, 10));
        var digits = val.replace(/\D/g, '');
        return digits ? parseInt(digits, 10) : 0;
    }

    function extractTitleFromUrl(href) {
        try {
            var url = new URL(href, location.href);
            if (url.origin !== location.origin) return null;
            var path = url.pathname;
            var m = path.match(/^\/wiki\/(.+)/);
            if (m) return decodeURIComponent(m[1]).replace(/_/g, ' ').split(/[?#]/)[0];
            if (path.indexOf('/index.php') !== -1 || path.indexOf('/w/index.php') !== -1) {
                var t = url.searchParams.get('title');
                if (t) return decodeURIComponent(t).replace(/_/g, ' ').split(/[?#]/)[0];
            }
            return null;
        } catch (e) { return null; }
    }

    function parseYouTubeFromHref(href) {
        if (!href) return null;
        var idMatch = href.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([A-Za-z0-9_-]{5,})/);
        if (!idMatch) return null;
        var videoId = idMatch[1];
        var q = '';
        try { q = (new URL(href, location.href)).search; } catch (e) { var parts = href.split('?'); q = parts[1] ? '?' + parts[1] : ''; }
        var params = new URLSearchParams(q);
        var start = parseTimeParam(params.get('t') || params.get('start'));
        var end = params.get('end') ? parseTimeParam(params.get('end')) : null;
        return { videoId: videoId, start: start, end: end };
    }

    // ===== CorePlayer =====
    var CorePlayer = (function () {
        var playlists = [], player = null, ytReady = false, activePlaylist = null, activeCheckInterval = null;

        function ensureContainer() {
            var c = document.getElementById(CORE_PLAYER_ID);
            if (!c) {
                c = document.createElement('div');
                c.id = CORE_PLAYER_ID;
                c.style.position = 'fixed';
                c.style.bottom = '12px';
                c.style.right = '12px';
                c.style.zIndex = 9999;
                c.style.background = '#000';
                c.style.padding = '6px';
                c.style.borderRadius = '6px';
                c.style.boxShadow = '0 2px 8px rgba(0,0,0,0.35)';
                document.body.appendChild(c);
            }
            return c;
        }

        function createPlayer(initialVideo) {
            var container = ensureContainer();
            if (!ytReady) return;
            if (player) return;
            container.innerHTML = '<div id="core-youtube-player-frame"></div>';
            var opts = {
                height: '270',
                width: '480',
                playerVars: { autoplay: 0, rel: 0 },
                events: {
                    onReady: function () { },
                    onStateChange: onPlayerStateChange
                }
            };
            if (initialVideo) opts.videoId = initialVideo.videoId;
            player = new YT.Player('core-youtube-player-frame', opts);
        }

        function onPlayerStateChange(ev) {
            if (activePlaylist === null) return;
            clearActiveInterval();
            if (ev.data === YT.PlayerState.PLAYING) {
                var cur = playlists[activePlaylist];
                var curVideo = cur && cur.videos[cur.currentIndex];
                if (curVideo && curVideo.end != null) {
                    activeCheckInterval = setInterval(function () {
                        try {
                            var ct = player.getCurrentTime();
                            if (ct >= curVideo.end) { clearActiveInterval(); playNext(activePlaylist); }
                        } catch (e) { clearActiveInterval(); }
                    }, 500);
                }
            } else if (ev.data === YT.PlayerState.ENDED) {
                playNext(activePlaylist);
            }
        }

        function clearActiveInterval() { if (activeCheckInterval) { clearInterval(activeCheckInterval); activeCheckInterval = null; } }

        function registerPlaylist(placeholderEl, videoArray) {
            var pid = playlists.length;
            playlists.push({ placeholderEl: placeholderEl, videos: videoArray.slice(), currentIndex: 0 });
            placeholderEl.setAttribute('data-core-playlist-id', pid);
            if (ytReady && !player && videoArray.length) createPlayer(videoArray[0]);
            return pid;
        }

        function cleanup() {
            for (var i = playlists.length - 1; i >= 0; i--) {
                if (!playlists[i].placeholderEl || !document.body.contains(playlists[i].placeholderEl)) {
                    if (i === activePlaylist) { activePlaylist = null; clearActiveInterval(); }
                    playlists.splice(i, 1);
                }
            }
            playlists.forEach(function (pl, idx) {
                if (pl.placeholderEl) pl.placeholderEl.setAttribute('data-core-playlist-id', idx);
            });
        }

        function gotoVideo(playlistId, idx) {
            playlistId = Number(playlistId); if (!playlists[playlistId]) return;
            var pl = playlists[playlistId]; if (idx < 0 || idx >= pl.videos.length) return;
            pl.currentIndex = idx; var v = pl.videos[idx]; activePlaylist = playlistId; clearActiveInterval();
            if (!ytReady) { loadYouTubeApi(); setTimeout(function () { if (!player && ytReady) createPlayer(v); try { player.loadVideoById({ videoId: v.videoId, startSeconds: v.start }); } catch (e) { } }, 300); return; }
            if (!player) { createPlayer(v); setTimeout(function () { try { player.loadVideoById({ videoId: v.videoId, startSeconds: v.start }); } catch (e) { } }, 200); return; }
            try { player.loadVideoById({ videoId: v.videoId, startSeconds: v.start }); } catch (e) { try { player.cueVideoById({ videoId: v.videoId, startSeconds: v.start }); } catch (e2) { } }
        }

        function playNext(playlistId) { playlistId = Number(playlistId); var pl = playlists[playlistId]; if (!pl) return; pl.currentIndex++; if (pl.currentIndex >= pl.videos.length) pl.currentIndex = 0; gotoVideo(playlistId, pl.currentIndex); }
        function playPrev(playlistId) { playlistId = Number(playlistId); var pl = playlists[playlistId]; if (!pl) return; pl.currentIndex--; if (pl.currentIndex < 0) pl.currentIndex = pl.videos.length - 1; gotoVideo(playlistId, pl.currentIndex); }
        function hasAnyPlaylist() { return playlists.length > 0; }

        return {
            setYTReady: function () { ytReady = true; },
            registerPlaylist: registerPlaylist,
            gotoVideo: gotoVideo,
            next: playNext,
            prev: playPrev,
            cleanup: cleanup,
            hasAnyPlaylist: hasAnyPlaylist,
            _debug: function () { return { playlists: playlists, active: activePlaylist, player: !!player }; }
        };
    })();

    function loadYouTubeApi() { if (document.getElementById(YT_SCRIPT_ID)) return; var s = document.createElement('script'); s.id = YT_SCRIPT_ID; s.src = 'https://www.youtube.com/iframe_api'; document.head.appendChild(s); }
    window.onYouTubeIframeAPIReady = function () { CorePlayer.setYTReady(); };

    // The rest of the script (attachPlaceholders, AJAX navigation, popstate, event listeners) continues here...
})();