MediaWiki:Gadget-floatingYouTubePlayer.js
From Angelina Jordan Wiki
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...
})();