/** * AKSRT Video Player * * Lightweight HTML5 video player wrapper with support for: * - Standard MP4/WebM playback * - HLS streaming (lazy-loaded) * - FLV streaming (lazy-loaded) * - Auto-quality selection * - Keyboard shortcuts * * Usage: *
*
* * @package AKSRT */ (function () { 'use strict'; var STYLE_ID = 'aksrt-video-player-style'; var HLS_SRC = 'https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js'; var FLV_SRC = 'https://cdn.jsdelivr.net/npm/flv.js@1/dist/flv.min.js'; function injectStyle() { if (document.getElementById(STYLE_ID)) return; var style = document.createElement('style'); style.id = STYLE_ID; style.textContent = '.aksrt-video{position:relative;background:#000;border-radius:12px;overflow:hidden;cursor:pointer;}' + '.aksrt-video video{display:block;width:100%;height:auto;max-height:70vh;}' + '.aksrt-video__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.3);transition:opacity 0.3s;pointer-events:none;}' + '.aksrt-video.is-playing .aksrt-video__overlay{opacity:0;}' + '.aksrt-video__play{width:60px;height:60px;border-radius:50%;background:rgba(255,255,255,0.9);display:flex;align-items:center;justify-content:center;font-size:24px;color:#1a1a2e;box-shadow:0 4px 20px rgba(0,0,0,0.3);transition:transform 0.2s;}' + '.aksrt-video:hover .aksrt-video__play{transform:scale(1.1);}' + '.aksrt-video__badge{position:absolute;top:8px;right:8px;background:rgba(0,0,0,0.6);color:#fff;font-size:11px;padding:2px 8px;border-radius:4px;backdrop-filter:blur(4px);}' + '.aksrt-video.is-playing .aksrt-video__badge{display:none;}'; document.head.appendChild(style); } function detectType(src) { if (!src) return 'mp4'; if (/\.m3u8(\?|$)/i.test(src)) return 'hls'; if (/\.flv(\?|$)/i.test(src)) return 'flv'; return 'mp4'; } function loadScript(src) { return new Promise(function (resolve, reject) { var script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } function AksrtVideoPlayer(element) { this.el = typeof element === 'string' ? document.querySelector(element) : element; if (!this.el || this.el.dataset.aksrtVideoBound) return; this.el.dataset.aksrtVideoBound = '1'; this.el._aksrtPlayer = this; this.src = this.el.dataset.src || ''; this.poster = this.el.dataset.poster || ''; this.type = this.el.dataset.type || detectType(this.src); this.autoplay = this.el.dataset.autoplay === 'true'; this.loop = this.el.dataset.loop === 'true'; this.muted = this.el.dataset.muted === 'true' || this.autoplay; this.init(); } AksrtVideoPlayer.prototype.init = function () { var self = this; // Create video element this.video = document.createElement('video'); this.video.controls = true; this.video.playsInline = true; this.video.preload = this.autoplay ? 'auto' : 'metadata'; if (this.loop) this.video.loop = true; if (this.muted) this.video.muted = true; if (this.poster) this.video.poster = this.poster; this.video.style.cssText = 'display:block;width:100%;height:100%;object-fit:contain;max-height:none;'; // Play overlay this.overlay = document.createElement('div'); this.overlay.className = 'aksrt-video__overlay'; this.overlay.innerHTML = '
'; // Badge if (this.type === 'hls') { var badge = document.createElement('div'); badge.className = 'aksrt-video__badge'; badge.textContent = 'HLS'; this.el.appendChild(badge); } this.el.appendChild(this.video); this.el.appendChild(this.overlay); // Events this.el.addEventListener('click', function (e) { if (e.target.closest('video') && self.video.controls) return; self.toggle(); }); this.video.addEventListener('play', function () { self.el.classList.add('is-playing'); }); this.video.addEventListener('pause', function () { self.el.classList.remove('is-playing'); }); this.video.addEventListener('ended', function () { self.el.classList.remove('is-playing'); }); // Keyboard shortcuts this.video.addEventListener('keydown', function (e) { switch (e.key) { case ' ': e.preventDefault(); self.toggle(); break; case 'ArrowLeft': self.video.currentTime -= 5; break; case 'ArrowRight': self.video.currentTime += 5; break; case 'f': self.toggleFullscreen(); break; case 'm': self.video.muted = !self.video.muted; break; } }); // Load source this.loadSource(); }; AksrtVideoPlayer.prototype.loadSource = function () { var self = this; if (this.type === 'hls') { if (window.Hls && Hls.isSupported()) { this.hls = new Hls(); this.hls.loadSource(this.src); this.hls.attachMedia(this.video); } else if (this.video.canPlayType('application/vnd.apple.mpegurl')) { // Safari native HLS this.video.src = this.src; } else { // Load hls.js dynamically loadScript(HLS_SRC).then(function () { if (window.Hls && Hls.isSupported()) { self.hls = new Hls(); self.hls.loadSource(self.src); self.hls.attachMedia(self.video); } }); } } else if (this.type === 'flv') { loadScript(FLV_SRC).then(function () { if (window.flvjs && flvjs.isSupported()) { self.flvPlayer = flvjs.createPlayer({ type: 'flv', url: self.src }); self.flvPlayer.attachMediaElement(self.video); self.flvPlayer.load(); } }); } else { this.video.src = this.src; } if (this.autoplay) { this.video.play().catch(function () { /* autoplay blocked */ }); } }; AksrtVideoPlayer.prototype.toggle = function () { if (this.video.paused) { this.video.play(); } else { this.video.pause(); } }; AksrtVideoPlayer.prototype.toggleFullscreen = function () { if (document.fullscreenElement) { document.exitFullscreen(); } else if (this.el.requestFullscreen) { this.el.requestFullscreen(); } }; AksrtVideoPlayer.prototype.destroy = function () { if (this.hls) { this.hls.destroy(); this.hls = null; } if (this.flvPlayer) { this.flvPlayer.destroy(); this.flvPlayer = null; } this.video.src = ''; this.el.innerHTML = ''; delete this.el.dataset.aksrtVideoBound; }; /** * 切换视频源(剧集切换) * @param {string} src - 新视频地址 * @param {string} [type] - 可选类型 (mp4/hls/flv) * @param {string} [poster] - 可选封面 */ AksrtVideoPlayer.prototype.switchSource = function (src, type, poster) { if (!src) return; // 清理旧播放器 if (this.hls) { this.hls.destroy(); this.hls = null; } if (this.flvPlayer) { this.flvPlayer.destroy(); this.flvPlayer = null; } this.src = src; this.type = type || detectType(src); if (poster) { this.poster = poster; this.video.poster = poster; } this.video.src = ''; this.loadSource(); }; // ── 剧集切换按钮事件委托 ── document.addEventListener('click', function (e) { var switchBtn = e.target.closest('.switch-video, .switch-iframe'); if (!switchBtn) return; var container = switchBtn.closest('.aksrt-video') || switchBtn.closest('[data-aksrt-video]'); // Single cover: episodes sit in .single-video-footer; player is sibling under .single-video if (!container) { var singleVideo = switchBtn.closest('.single-video, .single-head-cover'); if (singleVideo) { container = singleVideo.querySelector('.aksrt-video, [data-aksrt-video]'); } } if (!container) return; var src = switchBtn.dataset.src || switchBtn.getAttribute('video-url') || switchBtn.getAttribute('href'); if (!src) return; e.preventDefault(); // 检查是否已有播放器实例 if (container._aksrtPlayer) { container._aksrtPlayer.switchSource(src, switchBtn.dataset.type); } else { container.dataset.src = src; if (switchBtn.dataset.type) container.dataset.type = switchBtn.dataset.type; new AksrtVideoPlayer(container); } // 高亮当前集 var scope = switchBtn.closest('.featured-video-episode') || (container.parentElement || document); var siblings = scope.querySelectorAll('.switch-video, .switch-iframe'); siblings.forEach(function (btn) { btn.classList.remove('is-active'); btn.classList.remove('active'); }); switchBtn.classList.add('is-active'); switchBtn.classList.add('active'); }); // ── Public API ── window.AksrtVideoPlayer = AksrtVideoPlayer; // ── Auto-init ── function init() { injectStyle(); document.querySelectorAll('[data-aksrt-video]').forEach(function (el) { new AksrtVideoPlayer(el); }); } jQuery(document).off('auto_fun.aksrt-video-player').on('auto_fun.aksrt-video-player', function () { init(document); }); })();