马黑黑AudioPlayer插件源码
<span class="atips_close" onclick="this.parentNode.style.display='none'"></span></div>
<div class="blockcode"><div id="code_DzS"><ol><li>/** audioplayer.js(2026年5月5日更新)<br />
<li><br />
<li> 在指定父元素生成播放器+全屏按钮,<br />
<li> 支持添加多个自定义音频控制按钮,<br />
<li> 支持热键操作(F11、Alt+X/N/P/L)<br />
<li><br />
<li> 1. 前台配置:<br />
<li> let option = {<br />
<li> pa: '.pa'; // 或者 '#pa' | pa<br />
<li> urls: [<br />
<li> ['歌曲地址1', '曲名1'],<br />
<li> ['歌曲地址2', '曲名2'],<br />
<li> ],<br />
<li> fs: false, // 禁用全屏按钮,缺省值 true(启用)<br />
<li> btns: , // 自定义播放控制器(若需要)<br />
<li> }<br />
<li><br />
<li> 2. 实例化举例:const aud = new AudPlayer(option);<br />
<li><br />
<li> 3. 前台CSS:<br />
<li> ① 播 放 器: .player { width: 420px; bottom: 10px; right: 20px; color: gold; }<br />
<li> ② 全屏按钮: .btnFs { top: 20px; right: 20px; color: gold; }<br />
<li>*/<br />
<li>class AudPlayer {<br />
<li> constructor(config = {}) {<br />
<li> // 基础配置<br />
<li> this.config = {<br />
<li> pa: config.pa || document.body,<br />
<li> urls: config.urls || [],<br />
<li> fs: true,<br />
<li> btns: config.btns,<br />
<li> };<br />
<li><br />
<li> // 关键DOM+核心状态<br />
<li> this.pa = this.getParentElement();<br />
<li> this.aud = new Audio();<br />
<li> this.fs_btn = null;<br />
<li> this.playList = [...this.config.urls]; // 原始歌单<br />
<li> this.randomQueue = []; // 随机播放队列<br />
<li> this.currentIndex = 0; // 当前播放索引<br />
<li> this.isPlaying = false;<br />
<li> this.isSingle = this.playList.length === 1;<br />
<li><br />
<li> // 初始化<br />
<li> this.generateUI();<br />
<li> this.initRandomQueue();<br />
<li> this.placeMList();<br />
<li> this.displayPlayer();<br />
<li> this.bindAudEvents();<br />
<li> this.playFirst();<br />
<li> }<br />
<li><br />
<li> // 加载+播放曲目<br />
<li> loadTrack(index) {<br />
<li> if (index < 0 || index >= this.playList.length) return;<br />
<li> this.currentIndex = index;<br />
<li> const = this.playList;<br />
<li><br />
<li> this.aud.src = url;<br />
<li> this.aud.play().catch(err => this.showError('自动播放受限,请点击播放按钮'));<br />
<li><br />
<li> // 歌单高亮+翻页<br />
<li> if (!this.isSingle) {<br />
<li> this.mlist.dataset.currentsong = '正在播放 :' + title;<br />
<li> const lists = this.mlist.querySelectorAll('li');<br />
<li> const curList = this.mlist.querySelector(`li`);<br />
<li> lists.forEach(li => li.classList.remove('list-highlight'));<br />
<li> curList.classList.add('list-highlight');<br />
<li> curList.scrollIntoView({ behavior: 'smooth'});<br />
<li> console.log(this.mlist.scrollHeight, curList.offsetTop)<br />
<li> }<br />
<li> this.mState();<br />
<li> }<br />
<li><br />
<li> // 首次播放<br />
<li> playFirst() {<br />
<li> const idx = Math.floor(Math.random() * this.playList.length);<br />
<li> this.loadTrack(idx);<br />
<li> }<br />
<li><br />
<li> // 手动选曲(不影响随机队列)<br />
<li> selectTrack(index) {<br />
<li> this.loadTrack(index);<br />
<li> }<br />
<li><br />
<li> // 上一首<br />
<li> playPrev() {<br />
<li> if (this.isSingle) return;<br />
<li> let prevIndex = this.currentIndex - 1;<br />
<li> if (prevIndex < 0) prevIndex = this.playList.length - 1;<br />
<li> this.loadTrack(prevIndex);<br />
<li> }<br />
<li><br />
<li> // 下一首<br />
<li> playNext() {<br />
<li> if (this.isSingle) return;<br />
<li> let nextIndex = this.currentIndex + 1;<br />
<li> if (nextIndex >= this.playList.length) nextIndex = 0;<br />
<li> this.loadTrack(nextIndex);<br />
<li> }<br />
<li><br />
<li> // 切换播放/暂停<br />
<li> togglePlay() {<br />
<li> if (this.isPlaying) {<br />
<li> this.aud.pause();<br />
<li> } else {<br />
<li> this.aud.play().catch(err => this.showError('播放失败,请检查音频链接'));<br />
<li> }<br />
<li> }<br />
<li><br />
<li> // 按钮、视频等状态维护<br />
<li> mState() {<br />
<li> const vids = this.pa.querySelectorAll('video');<br />
<li> if (this.aud.paused) {<br />
<li> this.playbtn.classList.remove('clip-pause');<br />
<li> this.playbtn.classList.add('clip-play');<br />
<li> this.pa.style.setProperty('--state', 'paused');<br />
<li> if (vids) vids.forEach(vid => vid.pause());<br />
<li> } else {<br />
<li> this.playbtn.classList.remove('clip-play');<br />
<li> this.playbtn.classList.add('clip-pause');<br />
<li> this.pa.style.setProperty('--state', 'running');<br />
<li> if (vids) vids.forEach(vid => vid.play());<br />
<li> }<br />
<li> }<br />
<li><br />
<li> // 播放结束处理<br />
<li> handlePlayEnd() {<br />
<li> if (this.isSingle) {<br />
<li> // 单曲循环<br />
<li> this.aud.currentTime = 0;<br />
<li> this.aud.play();<br />
<li> } else {<br />
<li> // 多曲:从随机队列取歌<br />
<li> if (this.randomQueue.length === 0) {<br />
<li> this.resetRandomQueue(); // 周期结束,重置随机队列<br />
<li> }<br />
<li> const nextTrack = this.randomQueue.shift();<br />
<li> const nextIndex = this.playList.findIndex(item => item === nextTrack);<br />
<li> this.loadTrack(nextIndex);<br />
<li> }<br />
<li> }<br />
<li><br />
<li> // 初始化随机播放队列<br />
<li> initRandomQueue() {<br />
<li> if (this.isSingle) return;<br />
<li> this.randomQueue = [...this.playList].sort(() => Math.random() - 0.5);<br />
<li> }<br />
<li><br />
<li> // 重置随机队列(一个周期结束后)<br />
<li> resetRandomQueue() {<br />
<li> this.initRandomQueue();<br />
<li> }<br />
<li><br />
<li> // 音频事件绑定<br />
<li> bindAudEvents() {<br />
<li> // 时间更新<br />
<li> this.aud.addEventListener('timeupdate', () => {<br />
<li> const { currentTime, duration } = this.aud;<br />
<li> this.prog.style.setProperty('--prog', `${currentTime / duration * 100}%`);<br />
<li> this.tmsg.textContent = `${this.s2m(currentTime)} / ${this.s2m(duration)}`;<br />
<li> });<br />
<li><br />
<li> // 播放结束<br />
<li> this.aud.addEventListener('ended', () => {<br />
<li> this.handlePlayEnd();<br />
<li> });<br />
<li><br />
<li> // 播放/暂停状态同步<br />
<li> this.aud.addEventListener('play', () => {<br />
<li> this.isPlaying = true;<br />
<li> this.mState();<br />
<li> });<br />
<li><br />
<li> // 暂停<br />
<li> this.aud.addEventListener('pause', () => {<br />
<li> this.isPlaying = false;<br />
<li> this.mState();<br />
<li> });<br />
<li><br />
<li> // 出错<br />
<li> this.aud.addEventListener('error', (e) => {<br />
<li> this.showError(`播放失败:${this.playList}`);<br />
<li> this.handlePlayEnd();<br />
<li> });<br />
<li> }<br />
<li><br />
<li> // 创建UI<br />
<li> generateUI() {<br />
<li> if (document.querySelector('#audio-player-style')) return;<br />
<li> const style = document.createElement('style');<br />
<li> style.id = 'audio-player-style';<br />
<li> style.textContent = [<br />
<li> `.player { position: absolute; padding: 6px; width: 460px; height: 40px; line-height: 40px; display: flex; align-items: center; gap: 10px; transition: .75s; opacity: var(--opacity); }`,<br />
<li> `.player * { box-sizing: border-box; }`,<br />
<li> `.aud-btn { width: 35px; height: 35px; border: 1px solid currentColor; border-radius: 50%; cursor: pointer; position: relative; display: grid; place-items: center; }`,<br />
<li> `.aud-btn:hover { background: rgba(0,0,0,.25); }`,<br />
<li> `.aud-btn::before { content: ''; position: absolute; width: 50%; height: 50%; background: currentColor; clip-path: var(--clip-path); }`,<br />
<li> `.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%; }`,<br />
<li> `.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; }`,<br />
<li> `.common-btn:hover { background: rgba(0,0,0,.25); }`,<br />
<li> `.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; }`,<br />
<li> `.music-list::before { position: sticky; content: attr(data-currentsong); font-weight: bold; padding: 5px 15px;}`,<br />
<li> `.music-list ol { height: 160px; overflow: auto; scrollbar-width: thin; scrollbar-color: currentColor transparent; }`,<br />
<li> `.music-list ol li span { cursor: pointer; }`,<br />
<li> `.music-list ol li span:hover { opacity: .75; }`,<br />
<li> `.aud-tmsg { user-select: none; cursor: default; }`,<br />
<li> `.clip-play { --clip-path: polygon(0 0, 0 100%, 100% 50%);}`,<br />
<li> `.clip-pause { --clip-path: polygon(45% 0, 45% 100%, 10% 100%, 10% 0, 90% 0, 90% 100%, 55% 100%, 55% 0); }`,<br />
<li> `.list-highlight { color: red; }`,<br />
<li> `.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; }`,<br />
<li> `.btnFs:hover { font-weight: bold; }`,<br />
<li> ].join('');<br />
<li> document.head.appendChild(style);<br />
<li><br />
<li> // 播放器容器<br />
<li> this.player = document.createElement('div');<br />
<li> this.player.classList.add('player');<br />
<li><br />
<li> // 前一首按钮<br />
<li> if (!this.isSingle) {<br />
<li> const btnPrev = document.createElement('div');<br />
<li> btnPrev.classList.add('common-btn');<br />
<li> btnPrev.textContent = '←';<br />
<li> btnPrev.title = '前一首(Alt+P)';<br />
<li> btnPrev.addEventListener('click', () => this.playPrev());<br />
<li> this.player.appendChild(btnPrev);<br />
<li> }<br />
<li><br />
<li> // 播放|暂停按钮<br />
<li> this.playbtn = document.createElement('div');<br />
<li> this.playbtn.classList.add('aud-btn', 'clip-pause');<br />
<li> this.playbtn.title = '播放/暂停(Alt+X)';<br />
<li> this.playbtn.addEventListener('click', () => this.togglePlay());<br />
<li> this.player.appendChild(this.playbtn);<br />
<li><br />
<li> // 下一首按钮<br />
<li> if (!this.isSingle) {<br />
<li> const btnNext = document.createElement('div');<br />
<li> btnNext.classList.add('common-btn');<br />
<li> btnNext.textContent = '→';<br />
<li> btnNext.title = '下一首(Alt+N)';<br />
<li> btnNext.addEventListener('click', () => this.playNext());<br />
<li> this.player.appendChild(btnNext);<br />
<li> }<br />
<li><br />
<li> // 进度条<br />
<li> this.prog = document.createElement('div');<br />
<li> this.prog.classList.add('aud-prog');<br />
<li> this.prog.addEventListener('click', (e) => {<br />
<li> const duration = this.aud.duration;<br />
<li> if (isNaN(duration)) return;<br />
<li> this.aud.currentTime = duration * e.offsetX / this.prog.offsetWidth;<br />
<li> });<br />
<li> this.prog.addEventListener('mousemove', (e) => {<br />
<li> const duration = this.aud.duration;<br />
<li> this.prog.title = this.s2m(duration * e.offsetX / this.prog.offsetWidth);<br />
<li> });<br />
<li> this.player.appendChild(this.prog);<br />
<li><br />
<li> // 数字时间<br />
<li> this.tmsg = document.createElement('div');<br />
<li> this.tmsg.classList.add('aud-tmsg');<br />
<li> this.tmsg.textContent = '00:00 / 00:00';<br />
<li> this.player.appendChild(this.tmsg);<br />
<li><br />
<li> // 列表控制按钮<br />
<li> if (!this.isSingle) {<br />
<li> this.listControl = document.createElement('div');<br />
<li> this.listControl.classList.add('common-btn');<br />
<li> this.listControl.textContent = '▼';<br />
<li> this.listControl.title = '音乐列表(Alt+L)';<br />
<li><br />
<li> // 列表弹出/收起+按钮箭头变换<br />
<li> this.listControl.addEventListener('click', () => {<br />
<li> let hide = this.mlist.style.display === 'block';<br />
<li> this.mlist.style.display = hide ? 'none' : 'block';<br />
<li> this.listControl.textContent = this.listControl.textContent === '▲' ? '▼' : '▲';<br />
<li> if (!hide) {<br />
<li> this.mlist.querySelector(`li`).scrollIntoView({behavior: 'smooth'});<br />
<li> }<br />
<li> });<br />
<li> this.player.appendChild(this.listControl);<br />
<li> // 音乐列表<br />
<li> this.mlist = document.createElement('div');<br />
<li> this.mlist.classList.add('music-list');<br />
<li> this.generateMusicList();<br />
<li> this.player.appendChild(this.mlist);<br />
<li> }<br />
<li><br />
<li> // 全屏按钮<br />
<li> if (this.config.fs) {<br />
<li> this.fs_btn = document.createElement('div');<br />
<li> this.fs_btn.classList.add('btnFs');<br />
<li> this.fullScreen(this.fs_btn);<br />
<li> this.pa.appendChild(this.fs_btn);<br />
<li> }<br />
<li><br />
<li> this.pa.appendChild(this.player);<br />
<li><br />
<li> // 自定义添加的播放按钮(数组doms传参)<br />
<li> if (this.config.btns) {<br />
<li> this.config.btns.forEach(btn => {<br />
<li> btn.title = '播放/暂停(Alt+X)';<br />
<li> btn.addEventListener('click', () => {<br />
<li> this.togglePlay();<br />
<li> });<br />
<li> });<br />
<li> }<br />
<li><br />
<li> // 热键<br />
<li> document.addEventListener('keydown', (e) => {<br />
<li> if(e.altKey) {<br />
<li> if (e.key === 'x') this.togglePlay();<br />
<li> if (e.key === 'p') this.playPrev();<br />
<li> if (e.key === 'n') this.playNext();<br />
<li> if (e.key === 'l') this.listControl.click();<br />
<li> }<br />
<li> });<br />
<li> }<br />
<li><br />
<li> // 列表定位+控制按钮状态<br />
<li> placeMList() {<br />
<li> if (this.isSingle) return;<br />
<li> const style = window.getComputedStyle(this.player);<br />
<li> const up = parseInt(style.getPropertyValue('bottom')) >= parseInt(style.getPropertyValue('top'));<br />
<li> const ar = ['▲','▼' ];<br />
<li> this.listControl.textContent = ar[+up];<br />
<li> this.mlist.style.setProperty(`${up ? 'top' : 'bottom'}`, '100%');<br />
<li> }<br />
<li><br />
<li> // 生成列表<br />
<li> generateMusicList() {<br />
<li> const ol = document.createElement('ol');<br />
<li> this.playList.forEach((list, idx) => {<br />
<li> const li = document.createElement('li');<br />
<li> li.innerHTML = `<span>${list}</span>`;<br />
<li> li.dataset.idx = idx;<br />
<li> li.onclick = () => {<br />
<li> this.selectTrack(idx);<br />
<li> }<br />
<li> ol.appendChild(li);<br />
<li> });<br />
<li> this.mlist.appendChild(ol);<br />
<li> }<br />
<li><br />
<li> // 全屏<br />
<li> fullScreen = (btn) => {<br />
<li> let isFullscreen = false;<br />
<li> btn.textContent = '进入全屏';<br />
<li> btn.title = 'F11';<br />
<li> btn.addEventListener('click', () => {<br />
<li> isFullscreen ? document.exitFullscreen() : this.pa.requestFullscreen();<br />
<li> });<br />
<li><br />
<li> document.addEventListener('fullscreenchange', () => {<br />
<li> if (document.fullscreenElement !== null) {<br />
<li> isFullscreen = true;<br />
<li> btn.textContent = '退出全屏';<br />
<li> } else {<br />
<li> isFullscreen = false;<br />
<li> btn.textContent = '进入全屏';<br />
<li> }<br />
<li> });<br />
<li><br />
<li> document.addEventListener('keydown', (e) => {<br />
<li> if (e.key === 'F11') {<br />
<li> e.preventDefault();<br />
<li> isFullscreen ? document.exitFullscreen() : this.pa.requestFullscreen();<br />
<li> }<br />
<li> });<br />
<li> };<br />
<li><br />
<li> // 播放器+全屏隐身现身<br />
<li> displayPlayer() {<br />
<li> let timerId;<br />
<li> this.pa.addEventListener('mousemove', () => {<br />
<li> clearTimeout(timerId);<br />
<li> this.pa.style.setProperty('--opacity', '1');<br />
<li> timerId = setTimeout(() => this.pa.style.setProperty('--opacity', '0'), 3000);<br />
<li> });<br />
<li> }<br />
<li><br />
<li> // 获取元素(支持 id/class/元素实体)<br />
<li> getParentElement() {<br />
<li> const pa = this.config.pa;<br />
<li> if (pa instanceof HTMLElement) return pa;<br />
<li> return document.querySelector(pa) || document.body;<br />
<li> }<br />
<li><br />
<li> // 错误处理<br />
<li> showError(msg) {<br />
<li> this.mlist.dataset.currentsong = msg;<br />
<li> }<br />
<li><br />
<li> // 时间格式化<br />
<li> s2m(seconds) {<br />
<li> const min = Math.floor(seconds / 60).toString().padStart(2, '0');<br />
<li> const sec = Math.floor(seconds % 60).toString().padStart(2, '0');<br />
<li> return `${min}:${sec}`;<br />
<li> }<br />
<li>}</ol></div><em onclick="copycode($('code_DzS'));">复制代码</em></div>
5月5日更新的 AudioPlayer 插件实现在指定的元素上生成播放器+全屏机制。主要更新内容:
(一)支持热键交互
Alt + X :播放/暂停
Alt + L :呼出/关闭音乐列表
Alt + P :前一首
Alt + N :下一首
F11 :全屏/常规模式切换
Esc :关闭全屏
(二)支持添加多个自定义播放控制元素
使用方法:在配置中加入 btns 键,键值为数组,数组元素为 dom 实体。
【例一】假如帖子中有两个拥有 id 的元素,id="myplayer1" 和 id="myplayer2",想让它们也能通过点击控制音频的播放、暂停,则:
var setting = {
// 其它配置
btns: ,
};
【例二】假设帖子中有一组 class="mypic" 的图片,现在想让它们能成为音频控制器,则:
var setting = {
// 其它配置
btns: document.querySelectorAll('.mypic'),
};
注意:请不要将帖子主元素加入 btns 键值中,因为这会影响其下子元素的所有点击操作。
页:
[1]