马黑黑 发表于 2025-2-23 10:15

集成快捷键的多曲音频播放器

<h3>效果</h3>
<div id="showRes" style="margin: 30px; text-align: center;"></div>
<h3>代码</h3>
<div id="hEdiv"><pre id="hEpre">
&lt;style&gt;
        #mboard { margin-bottom: 170px; width: 500px; height: 40px; background: beige; border-radius: 8px; box-shadow: 2px 2px 4px rgba(0,0,0,.5); display: flex; justify-content: center; align-items: center; gap: 8px; position: relative; }
        #mboard:hover #mlist { opacity: 1; }
        #mboard img { width: 26px; cursor: pointer; }
        #mboard img:hover { filter: drop-shadow(1px 1px 1px rgba(0,0,0,.5)); }
        #tMsg1, #tMsg2 { width: 45px; font-size: 13px; text-align: center; }
        #volwrap { position: absolute; width: 60px; height: 40px; right: 75px; display: grid; place-items: center; background: none; }
        #volwrap:hover #volume { display: inline; }
        #btnMute:hover ~ #volwrap &gt; #volume { display: inline; }
        #volume { position: absolute; width: 50px; height: 4px; opacity: .75; display: none; }
        #prog { --track: gray; --prog: red; --prg: 0%; width: 200px; height: 20px; cursor: pointer; background: linear-gradient(to right, var(--prog) var(--prg), var(--track) 0) no-repeat 0% 50%/100% 2px; }
        #mlist { position: absolute; top: 45px; width: 100%; height: 160px; padding: 12px; border-radius: 0px; overflow: auto; scrollbar-width: thin; background: inherit; column-count: 2; column-rule: 2px solid #eee; column-fill: auto; box-sizing: border-box; border: 1px solid gray; opacity: 0; transition: .7s; }
        .list { margin: 0 8px; padding: 0 0 0 30px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; line-height: 20px; text-align: left; position: relative; }
        .list::before { position: absolute; content: attr(data-idx); width: 30px; left: 0; }
        .list1 { color: black; cursor: pointer; }
        .list2 { color: red; cursor: default; }
        .list1:hover { color: red; }
&lt;/style&gt;

&lt;div id="mboard"&gt;
        &lt;div id="mlist"&gt;&lt;/div&gt;
        &lt;img id="btnPrev" src="https://638183.freep.cn/638183/web/icon/prev.svg" title="前一曲 (Alt+↑)" alt="" /&gt;
        &lt;img id="btnPlay" src="https://638183.freep.cn/638183/web/icon/play.svg" title="播放/暂停 (Alt+X)" alt="" /&gt;
        &lt;img id="btnNext" src="https://638183.freep.cn/638183/web/icon/next.svg" title="下一曲 (Alt+↓)" alt="" /&gt;
        &lt;span id="tMsg1"&gt;00:00&lt;/span&gt;
        &lt;span id="prog"&gt;&lt;/span&gt;
        &lt;span id="tMsg2"&gt;00:00&lt;/span&gt;
        &lt;img id="btnMute" src="https://638183.freep.cn/638183/web/icon/unmuted.svg" title="静音 (Alt+J)" alt="" /&gt;
        &lt;img id="btnCirc" src="https://638183.freep.cn/638183/web/icon/circs.svg" title="随机循环 (Alt+S)" alt="" /&gt;
        &lt;div id="volwrap"&gt;&lt;input id="volume" type="range" min="0" max="1" step="0.1" value="1" /&gt;&lt;/div&gt;
        &lt;audio id="aud"&gt;&lt;/audio&gt;
&lt;/div&gt;

&lt;script&gt;
       
let lastVolume = 1, currentId = null, circ = true, muted = false, playAr = [], lists = [];

const musics = [
        ['https://music.163.com/song/media/outer/url?id=1321297488','跟太阳系说再见'],
        ['https://music.163.com/song/media/outer/url?id=1380590400','优雅的邂逅-致丁仪'],
        ['https://music.163.com/song/media/outer/url?id=1406003054','逝去的时间(time is over)'],
        ['https://music.163.com/song/media/outer/url?id=1421093308','人列计算机'],
        ['https://music.163.com/song/media/outer/url?id=1482053654','地球反抗军'],
        ['https://music.163.com/song/media/outer/url?id=1488362358','黑暗战役A'],
        ['https://music.163.com/song/media/outer/url?id=1488360994','黑暗战役B'],
        ['https://music.163.com/song/media/outer/url?id=1322066478','歌行者'],
        ['https://music.163.com/song/media/outer/url?id=524406882','地下世界-Without Spiccato'],
        ['https://music.163.com/song/media/outer/url?id=534254637','水滴-摧毁舰队+metal drums'],
        ['https://music.163.com/song/media/outer/url?id=1384983167','为了生存-末日方舟开场曲'],
        ['https://music.163.com/song/media/outer/url?id=1331220129','四维空间深不见底'],
        ['https://music.163.com/song/media/outer/url?id=1363443031','章北海登上自然选择号'],
        ['https://music.163.com/song/media/outer/url?id=2015042301','人生的旅途'],
        ['https://music.163.com/song/media/outer/url?id=520502976','面壁者的沉思+CL'],
];

const btnImg = {
        play: 'https://638183.freep.cn/638183/web/icon/play.svg',
        pause: 'https://638183.freep.cn/638183/web/icon/pause.svg',
        unmute: 'https://638183.freep.cn/638183/web/icon/unmuted.svg',
        mute: 'https://638183.freep.cn/638183/web/icon/muted.svg',
        prev: 'https://638183.freep.cn/638183/web/icon/prev.svg',
        next: 'https://638183.freep.cn/638183/web/icon/next.svg',
        circs: 'https://638183.freep.cn/638183/web/icon/circs.svg',
        circ1: 'https://638183.freep.cn/638183/web/icon/circ1.svg',
};

const setVolume = (val) =&gt; Math.min(1, Math.max(0, val));
const scrollX = (elm, x) =&gt; {
        if (elm.scrollWidth &lt; elm.offsetWidth) return;
        elm.scroll({left: x, top: 0, hehavior: 'smooth'});
};

const ranAr = (total) =&gt; {
        let ar = Array(total).fill().map((_,key) =&gt; key);
        ar.sort(() =&gt; 0.5 - Math.random());
        return ar;
};

playAr = ranAr(musics.length);

musics.forEach((item,key) =&gt; {
        let p = document.createElement('p');
        p.className = 'list list1';
        p.dataset.idx = key + 1;
        p.innerText = p.title = item;
        p.onclick = () =&gt; mplay(key);
        mlist.appendChild(p);
        lists.push(p);
});

const mplay = (idx = null) =&gt; {
        if(musics.length === 0 || (idx !== null && idx === currentId)) return;
        if(idx === null) {
                if(playAr.length === 0) playAr = ranAr(musics.length);
                let tmpIdx = Math.floor(Math.random() * playAr.length);
                idx = playAr;
                playAr.splice(tmpIdx, 1);
        }
        aud.src = musics;
        if(idx !== null && idx !== currentId) aud.play();
        lists.className = 'list list2';
        if(currentId !== null) lists.className = 'list list1';
        currentId = idx;
        scrollX(mlist, lists.offsetLeft - 22);
};

const setMute = () =&gt; {
        if(lastVolume === 0) return;
        muted = !muted;
        muted ? aud.volume = 0 : aud.volume = lastVolume;
};

const s2m = (seconds) =&gt; {
    const secs = Math.floor(seconds || 0);
    return `${String(secs/60|0).padStart(2,'0')}:${String(secs%60).padStart(2,'0')}`;
};

const mState = () =&gt; {
        btnPlay.src = aud.paused ? btnImg.play : btnImg.pause;
        btnPlay.title = (aud.paused ? '播放' : '暂停') + ' (Alt+X)';
};

document.addEventListener('keydown', e =&gt; {
        if(!e.altKey) return;
        switch (e.keyCode) {
                case 38:
                        btnPrev.click();
                        break;
                case 40:
                        btnNext.click();
                        break;
                case 74:
                        setMute();
                        break;
                case 83:
                        btnCirc.click();
                        break;
                case 88:
                        btnPlay.click();
                        break;
                case 187: case 107:
                        aud.volume = setVolume(aud.volume + 0.1);
                        lastVolume = aud.volume;
                        break;
                case 189: case 109:
                        aud.volume = setVolume(aud.volume - 0.1);
                        lastVolume = aud.volume;
                        break;
                default:
                        return;
        }
});

aud.onplaying = aud.onpause = () =&gt; mState();
aud.onended = () =&gt; { circ ? mplay() : aud.play(); };

aud.ontimeupdate = () =&gt; {
        prog.style.setProperty('--prg', aud.currentTime/aud.duration*100 +'%');
        tMsg1.innerText = s2m(aud.currentTime);
        tMsg2.innerText = s2m(aud.duration);
};

aud.onvolumechange = () =&gt; {
        btnMute.src = aud.volume === 0 ? btnImg.mute : btnImg.unmute;
        volume.value = aud.volume;
}

btnPlay.onclick = () =&gt; aud.paused ? aud.play() : aud.pause();
btnPrev.onclick = () =&gt; { if(currentId &gt; 0) mplay(currentId - 1); };
btnNext.onclick = () =&gt; { if(currentId &lt; musics.length - 1) mplay(currentId + 1); };
btnMute.onclick = () =&gt; setMute();

btnCirc.onclick = () =&gt; {
        circ = !circ;
        btnCirc.src = circ ? btnImg.circs : btnImg.circ1;
        btnCirc.title = (circ ? '随机循环' : '单曲循环') + ' (Alt+S)';
};

volume.onchange = () =&gt; aud.volume = lastVolume = volume.value;
prog.onclick = (e) =&gt; aud.currentTime = e.offsetX * aud.duration / prog.offsetWidth;
prog.onmousemove = (e) =&gt; prog.title = s2m(e.offsetX * aud.duration / prog.offsetWidth);
volwrap.onmousemove = () =&gt; volwrap.title = '音量 : ' + volume.value + ' (Alt++/-)';
mboard.onmouseenter = () =&gt; scrollX(mlist, lists.offsetLeft - 22);

mlist.onwheel = (e) =&gt; {
        const delta = Math.abs(e.deltaX) &gt; 0 ? e.deltaX : e.deltaY;
        mlist.scrollLeft += delta * 1.5;
        e.preventDefault();
};

mplay();

&lt;/script&gt;
</pre></div>

<script type="module">
import hlight from 'https://638183.freep.cn/638183/web/mod/helight.js';
hlight.hl(hEdiv, hEpre);

var runCodes = (str,target) => {
        let reg = /(<script(.*?)>)(.|\n)*?(<\/script>)/g;
        let js_str, html_str;
        if(str.match(reg) !== null) {
                js_str = str.match(reg);
                html_str = str.replace(js_str, '').trim();
                js_str = js_str.replace(/<[\/]{0,1}script[^>]*>/g,'').trim();
        } else {
                js_str = '';
                html_str = str.trim();
        }
        target.innerHTML = html_str;
        let myfunc = new Function(js_str);
        myfunc();
};

runCodes(hEpre.innerText, showRes);
</script>

马黑黑 发表于 2025-2-23 10:16

歌曲曲目列表支持鼠标滑轮翻页

近朱者赤 发表于 2025-2-23 10:19

谢谢老师分享{:5_116:}{:5_116:}{:5_116:}

梦江南 发表于 2025-2-23 10:21

谢谢老师辛苦!{:4_190:}

马黑黑 发表于 2025-2-23 11:04

梦江南 发表于 2025-2-23 10:21
谢谢老师辛苦!

{:4_191:}

马黑黑 发表于 2025-2-23 11:04

近朱者赤 发表于 2025-2-23 10:19
谢谢老师分享

{:4_191:}

花飞飞 发表于 2025-2-23 12:50

昨天完成的大工程,超强制作~~
多曲比想象中的更复杂,不单是多加几首歌那么简单。。
还增加了前一曲,后一曲,循环播放~~

花飞飞 发表于 2025-2-23 12:51

马黑黑 发表于 2025-2-23 10:16
歌曲曲目列表支持鼠标滑轮翻页

试了滚轮翻页,相当流畅,应响极快。。

花飞飞 发表于 2025-2-23 12:55

这个更接近于平时用的播放器的效果。。
想到之前的出的多曲播放本地音乐,天天用的那个。。
键盘控制与否差别,还有曲目本地和网络的差别。。。哈哈,好象差别很大。。

马黑黑 发表于 2025-2-23 12:55

花飞飞 发表于 2025-2-23 12:51
试了滚轮翻页,相当流畅,应响极快。。

这组滚轮代码参考了DeepSeek给的建议

马黑黑 发表于 2025-2-23 12:56

花飞飞 发表于 2025-2-23 12:50
昨天完成的大工程,超强制作~~
多曲比想象中的更复杂,不单是多加几首歌那么简单。。
还增加了前一曲,后 ...

以及歌曲列表的设计和实现

花飞飞 发表于 2025-2-23 12:57

这个可以做专辑贴。。占地更小一点。。功能更强大控制更方便一些。{:4_173:}

马黑黑 发表于 2025-2-23 12:57

花飞飞 发表于 2025-2-23 12:55
这个更接近于平时用的播放器的效果。。
想到之前的出的多曲播放本地音乐,天天用的那个。。
键盘控制与否 ...

彼此间的设计和实现没有互相参考

花飞飞 发表于 2025-2-23 12:57

马黑黑 发表于 2025-2-23 12:55
这组滚轮代码参考了DeepSeek给的建议

DS马上用到实例中了,真是造福代码人

马黑黑 发表于 2025-2-23 12:58

花飞飞 发表于 2025-2-23 12:57
DS马上用到实例中了,真是造福代码人

它给的代码基本也不能照搬,参考是可以的

马黑黑 发表于 2025-2-23 12:59

花飞飞 发表于 2025-2-23 12:57
这个可以做专辑贴。。占地更小一点。。功能更强大控制更方便一些。

歌曲列表的位置需要考量

花飞飞 发表于 2025-2-23 12:59

马黑黑 发表于 2025-2-23 12:56
以及歌曲列表的设计和实现

{:4_173:}嗯哪,多曲需要曲目列表。。感觉工程量翻倍了

马黑黑 发表于 2025-2-23 13:00

花飞飞 发表于 2025-2-23 12:59
嗯哪,多曲需要曲目列表。。感觉工程量翻倍了

代码量也翻番了

花飞飞 发表于 2025-2-23 13:00

马黑黑 发表于 2025-2-23 12:57
彼此间的设计和实现没有互相参考

我瞅了好多眼,完全没有重叠 的地方。。
就是一个管本地多曲,一个管网络多曲。。全乎了

花飞飞 发表于 2025-2-23 13:02

马黑黑 发表于 2025-2-23 12:58
它给的代码基本也不能照搬,参考是可以的

嗯嗯,它给的都是个大轮廓,要修修剪剪的。。。除了诗歌,基本都要改改{:4_173:}
页: [1] 2 3 4 5 6 7
查看完整版本: 集成快捷键的多曲音频播放器