|
|
/** audioplayer.js
1. AudPlayer 类配置: let option = { pa: '.pa'; // 或者 '#pa' | pa urls: [ ['歌曲地址1', '曲名1'], ['歌曲地址2', '曲名2'], ], fs: false, // 启用全屏按钮,缺省值 true }
2. 实例化举例:const aud = new AudPlayer(option);
3. 前台CSS: ① 播 放 器: .player { width: 420px; bottom: 10px; right: 20px; color: gold; } ② 全屏按钮: .btnFs { top: 20px; right: 20px; color: gold; } */ class AudPlayer { constructor(config = {}) { // 基础配置 this.config = { pa: config.pa || document.body, urls: config.urls || [], fs: true, };
// 关键DOM+核心状态 this.pa = this.getParentElement(); this.aud = new Audio(); this.fs_btn = null; this.playList = [...this.config.urls]; // 原始歌单 this.randomQueue = []; // 随机播放队列 this.currentIndex = 0; // 当前播放索引 this.isPlaying = false; this.isSingle = this.playList.length === 1;
// 初始化 this.generateUI(); this.initRandomQueue(); this.bindAudEvents(); this.placeMList(); this.displayPlayer(); this.playFirst(); }
// 加载+播放曲目 loadTrack(index) { if (index < 0 || index >= this.playList.length) return; this.currentIndex = index; const [url, title] = this.playList[index]; this.aud.src = url; this.aud.play().catch(err => this.showError('自动播放被浏览器限制,请手动点击播放'));
// 更新歌单高亮/翻页 if (!this.isSingle) { this.mlist.dataset.currentsong = '正在播放 :' + title; const lists = this.mlist.querySelectorAll('li'); const curList = this.mlist.querySelector(`li[data-idx="${index}"]`); lists.forEach(li => li.classList.remove('list-highlight')); curList.classList.add('list-highlight'); curList.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }
// 首次播放 playFirst() { const idx = Math.floor(Math.random() * this.playList.length); this.loadTrack(idx); }
// 手动选曲(不影响随机队列) selectTrack(index) { this.loadTrack(index); }
// 上一首 playPrev() { if (this.isSingle) return; let prevIndex = this.currentIndex - 1; if (prevIndex < 0) prevIndex = this.playList.length - 1; this.loadTrack(prevIndex); }
// 下一首 playNext() { if (this.isSingle) return; let nextIndex = this.currentIndex + 1; if (nextIndex >= this.playList.length) nextIndex = 0; this.loadTrack(nextIndex); }
// 切换播放/暂停+按钮状态 togglePlay() { const vids = this.pa.querySelectorAll('video'); if (this.isPlaying) { this.aud.pause(); this.playbtn.classList.remove('clip-pause'); this.playbtn.classList.add('clip-play'); this.pa.style.setProperty('--state', 'paused'); if (vids) vids.forEach(vid => vid.pause()); } else { this.aud.play().catch(err => this.showError('播放失败,请检查音频链接')); this.playbtn.classList.remove('clip-play'); this.playbtn.classList.add('clip-pause'); this.pa.style.setProperty('--state', 'running'); if (vids) vids.forEach(vid => vid.play()); } }
// 播放结束处理 handlePlayEnd() { if (this.isSingle) { // 单曲循环 this.aud.currentTime = 0; this.aud.play(); } else { // 多曲:从随机队列取歌 if (this.randomQueue.length === 0) { this.resetRandomQueue(); // 周期结束,重置随机队列 } const nextTrack = this.randomQueue.shift(); const nextIndex = this.playList.findIndex(item => item[0] === nextTrack[0]); this.loadTrack(nextIndex); } }
// 初始化随机播放队列 initRandomQueue() { if (this.isSingle) return; this.randomQueue = [...this.playList].sort(() => Math.random() - 0.5); }
// 重置随机队列(一个周期结束后) resetRandomQueue() { this.initRandomQueue(); }
// 音频事件绑定 bindAudEvents() { // 时间更新 this.aud.addEventListener('timeupdate', () => { const { currentTime, duration } = this.aud; this.prog.style.setProperty('--prog', `${currentTime / duration * 100}%`); this.tmsg.textContent = `${this.s2m(currentTime)} / ${this.s2m(duration)}`; });
// 播放结束 this.aud.addEventListener('ended', () => { this.handlePlayEnd(); });
// 播放/暂停状态同步 this.aud.addEventListener('play', () => this.isPlaying = true);
// 暂停 this.aud.addEventListener('pause', () => this.isPlaying = false);
// 错误处理 this.aud.addEventListener('error', (e) => { this.showError(`播放失败:${this.playList[this.currentIndex][1]}`); this.handlePlayEnd(); }); }
// 创建UI generateUI() { if (document.querySelector('#audio-player-style')) return; const style = document.createElement('style'); style.id = 'audio-player-style'; style.textContent = [ `.player { position: absolute; padding: 6px; width: 460px; height: 40px; line-height: 40px; display: flex; align-items: center; gap: 10px; transition: .75s; opacity: var(--opacity); }`, `.player * { box-sizing: border-box; }`, `.aud-btn { width: 35px; height: 35px; border: 1px solid currentColor; border-radius: 50%; cursor: pointer; position: relative; display: grid; place-items: center; }`, `.aud-btn:hover { background: rgba(0,0,0,.25); }`, `.aud-btn::before { content: ''; position: absolute; width: 50%; height: 50%; background: currentColor; clip-path: var(--clip-path); }`, `.aud-prog { flex-grow: 1; height: 12px; background: linear-gradient(to right, currentColor var(--prog), transparent var(--prog), transparent 0); border: 1px solid currentColor; border-radius: 12px; cursor: pointer; --prog: 0%; }`, `.common-btn { width: 26px; height: 26px; border: 1px solid currentColor; border-radius: 6px; padding: 0; font: normal 16px/26px sans-serif; text-align: center; user-select: none; cursor: pointer; }`, `.common-btn:hover { background: rgba(0,0,0,.25); }`, `.music-list { position: absolute; left: 50%; transform: translateX(-50%); width: 100%; max-width: 460px; min-height: 100%; height: 232px; border-radius: 6px; background: rgba(0,0,0,.25); box-shadow: 3px 3px 6px gray; display: none; }`, `.music-list::before { position: sticky; content: attr(data-currentsong); font-weight: bold; padding: 0 10px;}`, `.music-list ol { height: 160px; overflow: auto; scrollbar-width: thin; scrollbar-color: currentColor transparent; }`, `.music-list ol li span { cursor: pointer; }`, `.music-list ol li span:hover { opacity: .75; }`, `.aud-tmsg { user-select: none; cursor: default; }`, `.clip-play { --clip-path: polygon(0 0, 0 100%, 100% 50%);}`, `.clip-pause { --clip-path: polygon(45% 0, 45% 100%, 10% 100%, 10% 0, 90% 0, 90% 100%, 55% 100%, 55% 0); }`, `.list-highlight { color: red; }`, `.btnFs { position: absolute; padding: 6px 12px; border: 3px solid currentColor; border-radius: 12px; font-size: 1.2em; color: currentColor; background: rgba(0,0,0,.25); transition: .75s; opacity: var(--opacity); user-select: none; cursor: pointer; }`, `.btnFs:hover { font-weight: bold; }`, ].join(''); document.head.appendChild(style); // 播放器容器 this.player = document.createElement('div'); this.player.classList.add('player');
// 前一首按钮 if (!this.isSingle) { const btnPrev = document.createElement('div'); btnPrev.classList.add('common-btn'); btnPrev.textContent = '←'; btnPrev.title = '前一首'; btnPrev.addEventListener('click', () => this.playPrev()); this.player.appendChild(btnPrev); }
// 播放|暂停按钮 this.playbtn = document.createElement('div'); this.playbtn.classList.add('aud-btn', 'clip-pause'); this.playbtn.title = '播放/暂停'; this.playbtn.addEventListener('click', () => this.togglePlay()); this.player.appendChild(this.playbtn);
// 下一首按钮 if (!this.isSingle) { const btnNext = document.createElement('div'); btnNext.classList.add('common-btn'); btnNext.textContent = '→'; btnNext.title = '下一首'; btnNext.addEventListener('click', () => this.playNext()); this.player.appendChild(btnNext); }
// 进度条 this.prog = document.createElement('div'); this.prog.classList.add('aud-prog'); this.prog.addEventListener('click', (e) => { const duration = this.aud.duration; if (isNaN(duration)) return; this.aud.currentTime = duration * e.offsetX / this.prog.offsetWidth; }); this.prog.addEventListener('mousemove', (e) => { const duration = this.aud.duration; this.prog.title = this.s2m(duration * e.offsetX / this.prog.offsetWidth); }); this.player.appendChild(this.prog);
// 数字时间 this.tmsg = document.createElement('div'); this.tmsg.classList.add('aud-tmsg'); this.tmsg.textContent = '00:00 / 00:00'; this.player.appendChild(this.tmsg);
// 列表控制按钮 if (!this.isSingle) { this.listControl = document.createElement('div'); this.listControl.classList.add('common-btn'); this.listControl.textContent = '▼'; this.listControl.title = '音乐列表';
// 列表弹出/收起+按钮箭头变换 this.listControl.addEventListener('click', () => { let hide = this.mlist.style.display === 'block'; this.mlist.style.display = hide ? 'none' : 'block'; this.listControl.textContent = this.listControl.textContent === '▲' ? '▼' : '▲'; if (!hide) { this.mlist.querySelector(`li[data-idx="${this.currentIndex}"]`).scrollIntoView({behavior: 'smooth', block: 'center'}); } }); this.player.appendChild(this.listControl); // 音乐列表 this.mlist = document.createElement('div'); this.mlist.classList.add('music-list'); this.generateMusicList(); this.player.appendChild(this.mlist); }
// 全屏按钮 if (this.config.fs) { this.fs_btn = document.createElement('div'); this.fs_btn.classList.add('btnFs'); this.fullScreen(this.fs_btn); this.pa.appendChild(this.fs_btn); }
this.pa.appendChild(this.player); }
// 列表定位+控制按钮状态 placeMList() { if (this.isSingle) return; const style = window.getComputedStyle(this.player); const up = parseInt(style.getPropertyValue('bottom')) >= parseInt(style.getPropertyValue('top')); const ar = ['▲','▼' ]; this.listControl.textContent = ar[+up]; this.mlist.style.setProperty(`${up ? 'top' : 'bottom'}`, '100%'); }
// 生成列表 generateMusicList() { const ol = document.createElement('ol'); this.playList.forEach((list, idx) => { const li = document.createElement('li'); li.innerHTML = `<span>${list[1]}</span>`; li.dataset.idx = idx; li.onclick = () => this.selectTrack(idx); ol.appendChild(li); }); this.mlist.appendChild(ol); }
// 全屏 fullScreen = (btn) => { let isFullscreen = false; btn.textContent = '进入全屏'; btn.title = 'F11'; btn.addEventListener('click', () => { isFullscreen ? document.exitFullscreen() : this.pa.requestFullscreen(); });
document.addEventListener('fullscreenchange', () => { if (document.fullscreenElement !== null) { isFullscreen = true; btn.textContent = '退出全屏'; } else { isFullscreen = false; btn.textContent = '进入全屏'; } });
document.addEventListener('keydown', (e) => { if (e.key === 'F11') { e.preventDefault(); isFullscreen ? document.exitFullscreen() : this.pa.requestFullscreen(); } }); };
// 播放器+全屏隐身现身 displayPlayer() { let timerId; this.pa.addEventListener('mousemove', () => { clearTimeout(timerId); this.pa.style.setProperty('--opacity', '1'); timerId = setTimeout(() => this.pa.style.setProperty('--opacity', '0'), 3000); }); }
// 获取父元素(支持 id/class/元素实体) getParentElement() { const pa = this.config.pa; if (pa instanceof HTMLElement) return pa; return document.querySelector(pa) || document.body; } // 错误处理 showError(msg) { console.log(msg); }
// 时间格式化 s2m(seconds) { const min = Math.floor(seconds / 60).toString().padStart(2, '0'); const sec = Math.floor(seconds % 60).toString().padStart(2, '0'); return `${min}:${sec}`; } }
|
|