花潮论坛

搜索
热搜: 活动 交友 discuz
查看: 8|回复: 3

AudioPlayer插件源码

[复制链接]
  • TA的每日心情
    奋斗
    2026-5-5 08:50
  • 签到天数: 1808 天

    [LV.Master]伴坛终老

    3208

    主题

    13万

    回帖

    28万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9

    花潮帅哥鼠牛虎兔龙蛇马羊猴鸡狗猪多彩人生星月交辉奔放热烈海样胸怀春风拂面火热情怀优雅迷人神秘浪漫相遇之美鹰傲苍穹花好月圆紫色情节飞龙在天王者至尊大将风范音画大师天籁妙音共看流星风雨同行我心永远幸福快乐喜乐安康侠骨柔肠心想事成开朗大方花潮管理

    发表于 2026-5-5 09:22 | 显示全部楼层 |阅读模式

    请马上登录,朋友们都在花潮里等着你哦:)

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    1. /** audioplayer.js(2026年5月5日更新)

    2.     在指定父元素生成播放器+全屏按钮,
    3.     支持添加多个自定义音频控制按钮,
    4.     支持热键操作(F11、Alt+X/N/P/L)

    5.     1. 前台配置:
    6.     let option = {
    7.         pa: '.pa'; // 或者 '#pa' | pa
    8.         urls: [
    9.             ['歌曲地址1', '曲名1'],
    10.             ['歌曲地址2', '曲名2'],
    11.         ],
    12.         fs: false, // 禁用全屏按钮,缺省值 true(启用)
    13.         btns: [dom1, dom2, dom2], // 自定义播放控制器(若需要)
    14.     }

    15.     2. 实例化举例:const aud = new AudPlayer(option);

    16.     3. 前台CSS:
    17.     ① 播 放 器: .player { width: 420px; bottom: 10px; right: 20px; color: gold; }
    18.     ② 全屏按钮: .btnFs { top: 20px; right: 20px; color: gold; }
    19. */
    20. class AudPlayer {
    21.     constructor(config = {}) {
    22.         // 基础配置
    23.         this.config = {
    24.             pa: config.pa || document.body,
    25.             urls: config.urls || [],
    26.             fs: true,
    27.             btns: config.btns,
    28.         };

    29.         // 关键DOM+核心状态
    30.         this.pa = this.getParentElement();
    31.         this.aud = new Audio();
    32.         this.fs_btn = null;
    33.         this.playList = [...this.config.urls]; // 原始歌单
    34.         this.randomQueue = []; // 随机播放队列
    35.         this.currentIndex = 0; // 当前播放索引
    36.         this.isPlaying = false;
    37.         this.isSingle = this.playList.length === 1;

    38.         // 初始化
    39.         this.generateUI();
    40.         this.initRandomQueue();
    41.         this.placeMList();
    42.         this.displayPlayer();
    43.         this.bindAudEvents();
    44.         this.playFirst();
    45.     }

    46.     // 加载+播放曲目
    47.     loadTrack(index) {
    48.         if (index < 0 || index >= this.playList.length) return;
    49.         this.currentIndex = index;
    50.         const [url, title] = this.playList[index];

    51.         this.aud.src = url;
    52.         this.aud.play().catch(err => this.showError('自动播放受限,请点击播放按钮'));

    53.         // 歌单高亮+翻页
    54.         if (!this.isSingle) {
    55.             this.mlist.dataset.currentsong = '正在播放 :' + title;
    56.             const lists = this.mlist.querySelectorAll('li');
    57.             const curList = this.mlist.querySelector(`li[data-idx="${index}"]`);
    58.             lists.forEach(li => li.classList.remove('list-highlight'));
    59.             curList.classList.add('list-highlight');
    60.             curList.scrollIntoView({ behavior: 'smooth'});
    61.             console.log(this.mlist.scrollHeight, curList.offsetTop)
    62.         }
    63.         this.mState();
    64.     }

    65.     // 首次播放
    66.     playFirst() {
    67.         const idx = Math.floor(Math.random() * this.playList.length);
    68.         this.loadTrack(idx);
    69.     }

    70.     // 手动选曲(不影响随机队列)
    71.     selectTrack(index) {
    72.         this.loadTrack(index);
    73.     }

    74.     // 上一首
    75.     playPrev() {
    76.         if (this.isSingle) return;
    77.         let prevIndex = this.currentIndex - 1;
    78.         if (prevIndex < 0) prevIndex = this.playList.length - 1;
    79.         this.loadTrack(prevIndex);
    80.     }

    81.     // 下一首
    82.     playNext() {
    83.         if (this.isSingle) return;
    84.         let nextIndex = this.currentIndex + 1;
    85.         if (nextIndex >= this.playList.length) nextIndex = 0;
    86.         this.loadTrack(nextIndex);
    87.     }

    88.     // 切换播放/暂停
    89.     togglePlay() {
    90.         if (this.isPlaying) {
    91.               this.aud.pause();
    92.         } else {
    93.             this.aud.play().catch(err => this.showError('播放失败,请检查音频链接'));
    94.         }
    95.     }

    96.     // 按钮、视频等状态维护
    97.     mState() {
    98.         const vids = this.pa.querySelectorAll('video');
    99.         if (this.aud.paused) {
    100.             this.playbtn.classList.remove('clip-pause');
    101.             this.playbtn.classList.add('clip-play');
    102.             this.pa.style.setProperty('--state', 'paused');
    103.             if (vids) vids.forEach(vid => vid.pause());
    104.         } else {
    105.             this.playbtn.classList.remove('clip-play');
    106.               this.playbtn.classList.add('clip-pause');
    107.               this.pa.style.setProperty('--state', 'running');
    108.               if (vids) vids.forEach(vid => vid.play());
    109.         }
    110.     }

    111.     // 播放结束处理
    112.     handlePlayEnd() {
    113.         if (this.isSingle) {
    114.             // 单曲循环
    115.             this.aud.currentTime = 0;
    116.             this.aud.play();
    117.         } else {
    118.             // 多曲:从随机队列取歌
    119.             if (this.randomQueue.length === 0) {
    120.                 this.resetRandomQueue(); // 周期结束,重置随机队列
    121.             }
    122.             const nextTrack = this.randomQueue.shift();
    123.             const nextIndex = this.playList.findIndex(item => item[0] === nextTrack[0]);
    124.             this.loadTrack(nextIndex);
    125.         }
    126.     }

    127.     // 初始化随机播放队列
    128.     initRandomQueue() {
    129.         if (this.isSingle) return;
    130.         this.randomQueue = [...this.playList].sort(() => Math.random() - 0.5);
    131.     }

    132.     // 重置随机队列(一个周期结束后)
    133.     resetRandomQueue() {
    134.         this.initRandomQueue();
    135.     }

    136.     // 音频事件绑定
    137.     bindAudEvents() {
    138.         // 时间更新
    139.         this.aud.addEventListener('timeupdate', () => {
    140.             const { currentTime, duration } = this.aud;
    141.             this.prog.style.setProperty('--prog',  `${currentTime / duration * 100}%`);
    142.             this.tmsg.textContent = `${this.s2m(currentTime)} / ${this.s2m(duration)}`;
    143.         });

    144.         // 播放结束
    145.         this.aud.addEventListener('ended', () => {
    146.             this.handlePlayEnd();
    147.         });

    148.         // 播放/暂停状态同步
    149.         this.aud.addEventListener('play', () => {
    150.             this.isPlaying = true;
    151.             this.mState();
    152.         });

    153.         // 暂停
    154.         this.aud.addEventListener('pause', () => {
    155.             this.isPlaying = false;
    156.             this.mState();
    157.         });

    158.         // 出错
    159.         this.aud.addEventListener('error', (e) => {
    160.             this.showError(`播放失败:${this.playList[this.currentIndex][1]}`);
    161.             this.handlePlayEnd();
    162.         });
    163.     }

    164.     // 创建UI
    165.     generateUI() {
    166.         if (document.querySelector('#audio-player-style')) return;
    167.         const style = document.createElement('style');
    168.         style.id = 'audio-player-style';
    169.         style.textContent = [
    170.             `.player { position: absolute; padding: 6px; width: 460px; height: 40px; line-height: 40px; display: flex; align-items: center; gap: 10px; transition: .75s; opacity: var(--opacity); }`,
    171.             `.player * { box-sizing: border-box; }`,
    172.             `.aud-btn { width: 35px; height: 35px; border: 1px solid currentColor; border-radius: 50%; cursor: pointer; position: relative; display: grid; place-items: center; }`,
    173.             `.aud-btn:hover { background: rgba(0,0,0,.25); }`,
    174.             `.aud-btn::before { content: ''; position: absolute; width: 50%; height: 50%; background: currentColor; clip-path: var(--clip-path); }`,
    175.             `.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%; }`,
    176.             `.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; }`,
    177.             `.common-btn:hover { background: rgba(0,0,0,.25); }`,
    178.             `.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; }`,
    179.             `.music-list::before { position: sticky; content: attr(data-currentsong); font-weight: bold; padding: 5px 15px;}`,
    180.             `.music-list ol { height: 160px; overflow: auto; scrollbar-width: thin; scrollbar-color: currentColor transparent; }`,
    181.             `.music-list ol li span { cursor: pointer; }`,
    182.             `.music-list ol li span:hover { opacity: .75; }`,
    183.             `.aud-tmsg { user-select: none; cursor: default; }`,
    184.             `.clip-play { --clip-path: polygon(0 0, 0 100%, 100% 50%);}`,
    185.             `.clip-pause { --clip-path: polygon(45% 0, 45% 100%, 10% 100%, 10% 0, 90% 0, 90% 100%, 55% 100%, 55% 0); }`,
    186.             `.list-highlight { color: red; }`,
    187.             `.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; }`,
    188.             `.btnFs:hover { font-weight: bold; }`,
    189.         ].join('');
    190.         document.head.appendChild(style);

    191.         // 播放器容器
    192.         this.player = document.createElement('div');
    193.         this.player.classList.add('player');

    194.         // 前一首按钮
    195.         if (!this.isSingle) {
    196.             const btnPrev = document.createElement('div');
    197.             btnPrev.classList.add('common-btn');
    198.             btnPrev.textContent = '←';
    199.             btnPrev.title = '前一首(Alt+P)';
    200.             btnPrev.addEventListener('click', () => this.playPrev());
    201.             this.player.appendChild(btnPrev);
    202.         }

    203.         // 播放|暂停按钮
    204.         this.playbtn = document.createElement('div');
    205.         this.playbtn.classList.add('aud-btn', 'clip-pause');
    206.         this.playbtn.title = '播放/暂停(Alt+X)';
    207.         this.playbtn.addEventListener('click', () => this.togglePlay());
    208.         this.player.appendChild(this.playbtn);

    209.         // 下一首按钮
    210.         if (!this.isSingle) {
    211.             const btnNext = document.createElement('div');
    212.             btnNext.classList.add('common-btn');
    213.             btnNext.textContent = '→';
    214.             btnNext.title = '下一首(Alt+N)';
    215.             btnNext.addEventListener('click', () => this.playNext());
    216.             this.player.appendChild(btnNext);
    217.         }

    218.         // 进度条
    219.         this.prog = document.createElement('div');
    220.         this.prog.classList.add('aud-prog');
    221.         this.prog.addEventListener('click', (e) => {
    222.             const duration = this.aud.duration;
    223.             if (isNaN(duration)) return;
    224.             this.aud.currentTime = duration * e.offsetX / this.prog.offsetWidth;
    225.         });
    226.         this.prog.addEventListener('mousemove', (e) => {
    227.             const duration = this.aud.duration;
    228.             this.prog.title = this.s2m(duration * e.offsetX / this.prog.offsetWidth);
    229.         });
    230.         this.player.appendChild(this.prog);

    231.         // 数字时间
    232.         this.tmsg = document.createElement('div');
    233.         this.tmsg.classList.add('aud-tmsg');
    234.         this.tmsg.textContent = '00:00 / 00:00';
    235.         this.player.appendChild(this.tmsg);

    236.         // 列表控制按钮
    237.         if (!this.isSingle) {
    238.         this.listControl = document.createElement('div');
    239.         this.listControl.classList.add('common-btn');
    240.         this.listControl.textContent = '▼';
    241.         this.listControl.title = '音乐列表(Alt+L)';

    242.         // 列表弹出/收起+按钮箭头变换
    243.         this.listControl.addEventListener('click', () => {
    244.             let hide = this.mlist.style.display === 'block';
    245.             this.mlist.style.display = hide ? 'none' : 'block';
    246.             this.listControl.textContent = this.listControl.textContent === '▲' ? '▼' : '▲';
    247.             if (!hide) {
    248.                 this.mlist.querySelector(`li[data-idx="${this.currentIndex}"]`).scrollIntoView({behavior: 'smooth'});
    249.             }
    250.         });
    251.         this.player.appendChild(this.listControl);
    252.         // 音乐列表
    253.         this.mlist = document.createElement('div');
    254.         this.mlist.classList.add('music-list');
    255.         this.generateMusicList();
    256.         this.player.appendChild(this.mlist);
    257.         }

    258.         // 全屏按钮
    259.         if (this.config.fs) {
    260.             this.fs_btn = document.createElement('div');
    261.             this.fs_btn.classList.add('btnFs');
    262.             this.fullScreen(this.fs_btn);
    263.             this.pa.appendChild(this.fs_btn);
    264.         }

    265.         this.pa.appendChild(this.player);

    266.         // 自定义添加的播放按钮(数组doms传参)
    267.         if (this.config.btns) {
    268.             this.config.btns.forEach(btn => {
    269.                 btn.title = '播放/暂停(Alt+X)';
    270.                 btn.addEventListener('click', () => {
    271.                     this.togglePlay();
    272.                 });
    273.             });
    274.         }

    275.         // 热键
    276.         document.addEventListener('keydown', (e) => {
    277.             if(e.altKey) {
    278.                 if (e.key === 'x') this.togglePlay();
    279.                 if (e.key === 'p') this.playPrev();
    280.                 if (e.key === 'n') this.playNext();
    281.                 if (e.key === 'l') this.listControl.click();
    282.             }
    283.         });
    284.     }

    285.     // 列表定位+控制按钮状态
    286.     placeMList() {
    287.         if (this.isSingle) return;
    288.         const style = window.getComputedStyle(this.player);
    289.         const up = parseInt(style.getPropertyValue('bottom')) >= parseInt(style.getPropertyValue('top'));
    290.         const ar = ['▲','▼' ];
    291.         this.listControl.textContent = ar[+up];
    292.         this.mlist.style.setProperty(`${up ? 'top' : 'bottom'}`, '100%');
    293.     }

    294.     // 生成列表
    295.     generateMusicList() {
    296.         const ol = document.createElement('ol');
    297.         this.playList.forEach((list, idx) => {
    298.             const li = document.createElement('li');
    299.             li.innerHTML = `<span>${list[1]}</span>`;
    300.             li.dataset.idx = idx;
    301.             li.onclick = () => {
    302.                 this.selectTrack(idx);
    303.             }
    304.             ol.appendChild(li);
    305.         });
    306.         this.mlist.appendChild(ol);
    307.     }

    308.     // 全屏
    309.     fullScreen = (btn) => {
    310.         let isFullscreen = false;
    311.         btn.textContent = '进入全屏';
    312.         btn.title = 'F11';
    313.         btn.addEventListener('click', () => {
    314.             isFullscreen ? document.exitFullscreen() : this.pa.requestFullscreen();
    315.         });

    316.         document.addEventListener('fullscreenchange', () => {
    317.             if (document.fullscreenElement !== null) {
    318.                 isFullscreen = true;
    319.                 btn.textContent = '退出全屏';
    320.             } else {
    321.                 isFullscreen = false;
    322.                 btn.textContent = '进入全屏';
    323.             }
    324.         });

    325.         document.addEventListener('keydown', (e) => {
    326.             if (e.key === 'F11') {
    327.                 e.preventDefault();
    328.                 isFullscreen ? document.exitFullscreen() : this.pa.requestFullscreen();
    329.             }
    330.         });
    331.     };

    332.     // 播放器+全屏隐身现身
    333.     displayPlayer() {
    334.         let timerId;
    335.         this.pa.addEventListener('mousemove', () => {
    336.         clearTimeout(timerId);
    337.             this.pa.style.setProperty('--opacity', '1');
    338.             timerId = setTimeout(() => this.pa.style.setProperty('--opacity', '0'), 3000);
    339.         });
    340.     }

    341.       // 获取元素(支持 id/class/元素实体)
    342.       getParentElement() {
    343.           const pa = this.config.pa;
    344.           if (pa instanceof HTMLElement) return pa;
    345.           return document.querySelector(pa) || document.body;
    346.       }

    347.     // 错误处理
    348.       showError(msg) {
    349.           this.mlist.dataset.currentsong = msg;
    350.       }

    351.     // 时间格式化
    352.     s2m(seconds) {
    353.         const min = Math.floor(seconds / 60).toString().padStart(2, '0');
    354.         const sec = Math.floor(seconds % 60).toString().padStart(2, '0');
    355.         return `${min}:${sec}`;
    356.     }
    357. }
    复制代码

    评分

    参与人数 1威望 +50 金钱 +100 经验 +50 收起 理由
    红影 + 50 + 100 + 50 匠心独运,细节精致入微!

    查看全部评分

  • TA的每日心情
    奋斗
    2026-5-5 08:50
  • 签到天数: 1808 天

    [LV.Master]伴坛终老

    3208

    主题

    13万

    回帖

    28万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9

    花潮帅哥鼠牛虎兔龙蛇马羊猴鸡狗猪多彩人生星月交辉奔放热烈海样胸怀春风拂面火热情怀优雅迷人神秘浪漫相遇之美鹰傲苍穹花好月圆紫色情节飞龙在天王者至尊大将风范音画大师天籁妙音共看流星风雨同行我心永远幸福快乐喜乐安康侠骨柔肠心想事成开朗大方花潮管理

     楼主| 发表于 2026-5-5 09:22 | 显示全部楼层
    5月5日更新的 AudioPlayer 插件实现在指定的元素上生成播放器+全屏机制。主要更新内容:

    (一)支持热键交互

    Alt + X :播放/暂停
    Alt + L :呼出/关闭音乐列表
    Alt + P :前一首
    Alt + N :下一首

    F11 :全屏/常规模式切换
    Esc :关闭全屏

    (二)支持添加多个自定义播放控制元素

    使用方法:在配置中加入 btns 键,键值为数组,数组元素为 dom 实体。

    【例一】假如帖子中有两个拥有 id 的元素,id="myplayer1" 和 id="myplayer2",想让它们也能通过点击控制音频的播放、暂停,则:

    var setting = {
        // 其它配置
        btns: [myplayer1, myplayer2],
    };

    【例二】假设帖子中有一组 class="mypic" 的图片,现在想让它们能成为音频控制器,则:

    var setting = {
        // 其它配置
        btns: document.querySelectorAll('.mypic'),
    };

    注意:请不要将帖子主元素加入 btns 键值中,因为这会影响其下子元素的所有点击操作。

    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2025-12-1 20:32
  • 签到天数: 1052 天

    [LV.10]以坛为家III

    1897

    主题

    33万

    回帖

    39万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9

    花潮美女虎龙狗猪多彩人生星月交辉海样胸怀火热情怀优雅迷人神秘浪漫缤纷心情草莓情怀蝴蝶情怀心曲飞扬星星情怀七彩绚丽活泼开朗女儿情怀相遇之美一往情深花好月圆心香一瓣紫色情节飞龙在天金剪刀天籁妙音妙笔生花风雨同行我心永远天长地久幸福快乐绚丽缤纷喜乐安康中秋征文周年庆指尖上的流年舞会之星分析(喊冤)章总结章杀人王小强章最佳杀刺临屏写诗七夕诗钟活动第五届风云第六届风云情人节花潮管理

    发表于 2026-5-5 10:02 | 显示全部楼层
    还增加了键盘控制前后歌曲的切换以及播放暂停和显示列表等功能,更方便了。还能添加多个小播呢。
    黑黑的插件真是越弄越完美了
     
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2026-5-5 08:50
  • 签到天数: 1808 天

    [LV.Master]伴坛终老

    3208

    主题

    13万

    回帖

    28万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9Rank: 9

    花潮帅哥鼠牛虎兔龙蛇马羊猴鸡狗猪多彩人生星月交辉奔放热烈海样胸怀春风拂面火热情怀优雅迷人神秘浪漫相遇之美鹰傲苍穹花好月圆紫色情节飞龙在天王者至尊大将风范音画大师天籁妙音共看流星风雨同行我心永远幸福快乐喜乐安康侠骨柔肠心想事成开朗大方花潮管理

     楼主| 发表于 2026-5-5 10:54 | 显示全部楼层
    红影 发表于 2026-5-5 10:02
    还增加了键盘控制前后歌曲的切换以及播放暂停和显示列表等功能,更方便了。还能添加多个小播呢。
    黑黑的插 ...

    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|手机版|Archiver|服务支持:DZ动力|huachaowang.com Inc. ( 蜀ICP备17032287号-1 )

    GMT+8, 2026-5-5 11:45 , Processed in 0.065541 second(s), 26 queries .

    Powered by Discuz! X3.4

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表