红影 发表于 2025-2-2 11:11

马黑黑 发表于 2025-2-1 22:21
那是自然

结果又忍不住去听歌了{:4_173:}

马黑黑 发表于 2025-2-2 12:36

红影 发表于 2025-2-2 11:11
结果又忍不住去听歌了

听歌有益健康

马黑黑 发表于 2025-2-2 12:38

花飞飞 发表于 2025-2-2 10:58
这个好喝。。热腾腾的奶茶来一杯

热热身子

马黑黑 发表于 2025-2-2 12:39

花飞飞 发表于 2025-2-2 10:58
这几天天天用,完全顺手。。
突然想到卸了的火狐里不知道有没有存一些偏门的网址。。卸快了

火绒安全的软件管理里面有卸载残余的

马黑黑 发表于 2025-2-2 12:39

花飞飞 发表于 2025-2-2 10:57
那敢情好,天荒地老

白的可能是银子,值钱

红影 发表于 2025-2-2 13:21

马黑黑 发表于 2025-2-2 12:36
听歌有益健康

是啊,尤其喜欢的歌,听了总是那么开心呢{:4_187:}

花飞飞 发表于 2025-2-2 19:01

马黑黑 发表于 2025-2-2 12:38
热热身子

阴天有零星小雨{:4_173:}

花飞飞 发表于 2025-2-2 19:03

马黑黑 发表于 2025-2-2 12:39
火绒安全的软件管理里面有卸载残余的

火狐么,没准哪天又装回来了。这个暂时先不清。。

花飞飞 发表于 2025-2-2 19:04

马黑黑 发表于 2025-2-2 12:39
白的可能是银子,值钱

看到银子两眼放光,提神{:4_170:}

马黑黑 发表于 2025-2-2 20:16

花飞飞 发表于 2025-2-2 19:04
看到银子两眼放光,提神

{:4_189:}

马黑黑 发表于 2025-2-2 20:18

花飞飞 发表于 2025-2-2 19:01
阴天有零星小雨

雨伞俺有的

花飞飞 发表于 2025-2-2 20:45

马黑黑 发表于 2025-2-2 20:16


{:4_208:}比咖啡效果好

花飞飞 发表于 2025-2-2 20:45

马黑黑 发表于 2025-2-2 20:18
雨伞俺有的

毛毛雨。。{:4_173:}

马黑黑 发表于 2025-2-2 22:06

花飞飞 发表于 2025-2-2 20:45
毛毛雨。。

毛毛雨也是雨

马黑黑 发表于 2025-2-2 22:06

花飞飞 发表于 2025-2-2 20:45
比咖啡效果好

那不错

马黑黑 发表于 2025-2-4 08:51

2月4日更新:


(一)鼠标指针移动到背景图片地址栏自动获焦并选中地址(若有);
(二)音乐列表:
      一是设置一定透明度,鼠标指针移动到其上时恢复不透明度为0即没有透明度;
      二是滚动条自动隐藏,仅在鼠标指针经过其上时出现(若有)。

完整代码:
<style>
/* 父元素 */
#papa {
        margin: 30px auto 0;
        padding: 10px;
        width: 800px;
        height: 400px;
        font-size: 16px;
        background: linear-gradient(to bottom right, #000, #ffc);
        box-shadow: 3px 6px 20px #000;
        border-radius: 6px;
        display: grid;
        place-items: center;
        position: relative;
}

/* 选择本地音乐父元素 */
#openFile {
        position: absolute;
        left: 15px;
        top: 15px;
        width: calc(100% - 30px);
}

/* 选择音乐控件 */
#mfile { display: none; }

/* 选择音乐按钮 */
#selectSong {
        margin-right: 8px;
        border: 2px solid #ccc;
        border-radius: 6px;
        outline: none;
        background: none;
        color: snow;
        cursor: pointer;
}

/* 背景图片地址栏 */
#bgurl {
        position: absolute;
        right: 0;
        display: none;
}


#openFile:hover #bgurl {
        display: inline;
}

/* 选择音乐按钮指针经过 */
#selectSong:hover { background: darkred; }

/* 当前播放曲目 */
#curSong { color: #eee; }

/* 播放器 */
#mplayer {
        --bg1: teal;
        --bg2: snow;
        --ppLen: 4px;
        --prog: white;
        --track: silver;
        --prg: 0%;
        --ppCap: white;
        position: absolute;
        right: 180px;
        width: 100px;
        height: 100px;
        border-radius: 50%;
        background: linear-gradient(
                to right,
                var(--prog) var(--prg),
                var(--track) var(--prg),
                var(--track) 0
        ) no-repeat 0 50% / 100% 2px;
        cursor: pointer;
        display: grid;
        place-items: center;
}

/* 播放器指针经过 */
#mplayer:hover {
        filter: hue-rotate(90deg) drop-shadow(0 0 26px black);
}

/* 播放器伪元素 :音频时间信息 */
#mplayer::before, #mplayer::after {
        position: absolute;
        color: snow;
}

/* 播放器伪元素一 :当前播放时间 */
#mplayer::before {
        content: attr(data-cu);
        top: 20%;
}

/* 播放器伪元素二 :音频时长 */
#mplayer::after {
        content: attr(data-du);
        top: 56%;
}

/* 频谱条 */
.pp {
        position: absolute;
        left: calc(50% - 2px);
        bottom: 50%;
        width: var(--ppLen);
        height: 20px;
        background: linear-gradient(to top, var(--bg1), var(--bg2));
        transform-origin: 50% 100%;
        transform: rotate(var(--deg)) translate(-50px, 0);
        display: grid;
        place-items: center;
}

/* 频谱条伪元素 :频谱帽 */
.pp::after {
        position: absolute;
        content: '';
        width: calc(var(--ppLen) + 4px);
        height: calc(var(--ppLen) + 4px);
        top: 0px;
        background: var(--bg2);
        border-radius: 50%;
}

/* 音乐列表 */
#mlist {
        position: absolute;
        left: 20px;
        top: 60px;
        min-width: 40%;
        max-width: 50%;
        min-height: 20%;
        max-height: calc(100% - 80px);
        color: silver;
        font-size: 14px;
        line-height: 20px;
        overflow: hidden;
        opacity: .4;
}

/* 音乐列表鼠标经过 */
#mlist:hover {
        overflow: auto;
        opacity: 1;
}

/* 音乐列表项目一 :待播放曲目 */
.list1 { cursor: pointer; }

/* 音乐列表项目二 :正在播放曲目 */
.list2 {
        color: cyan;
        cursor:default;
}

/* 音乐列表项目一指针经过 */
.list1:hover { color: white; }
</style>

<div id="papa">
        <div id="openFile">
                <input id="selectSong" type="button" value="选择音乐" />
                <input type="file" id="mfile" accept=".mp3,.ogg,.wav,.acc,.webm,.flac" multiple />
                <span id="curSong"></span>
                <input id="bgurl" type="text" placeholder="背景 : 网络图片地址" value="" />
        </div>
        <div id="mplayer" class="mplayer"></div>
        <div id="mlist"></div>
        <audio id="aud"></audio>
</div>
<p style="text-align:right"><br>更新 :2025年2月4日 &nbsp;&nbsp;&nbsp;<br></p>

<script>

//选择的文件, 播放列表, 波形数据, 频谱
let files=[], playAr = [], output = [], pps = [];
//打开文件次数, 频谱条总数
let openIdx = 0, total = 30;

//获取波形数据
const getDatas = () => {
        if(openIdx > 0) return;
        openIdx ++;
        Ac = new AudioContext;
        source = Ac.createMediaElementSource(aud);
        analyser = Ac.createAnalyser();
        source.connect(analyser);
        analyser.connect(Ac.destination);
        output = new Uint8Array(total);
};

//生成频谱条
Array(total).fill(0).forEach((_, k) => {
        let pp = document.createElement('span');
        pp.className = 'pp';
        pp.style.cssText += `--deg: ${360 / total * k}deg`;
        mplayer.appendChild(pp);
        pps.push(pp);
});

//波形数据刷新
(function update() {
        if(aud.src) analyser.getByteFrequencyData(output);
        for(let j = 0; j < total ; j++) {
                pps.style.height = output / 2 + 'px';
        }
        window.requestAnimationFrame(update);
})();

//播放 :idx为空时随机播放
const mplay = (idx = null) => {
        if(files.length === 0) return;
        let isScrolling = false;
        if(idx === null) {
                if(playAr.length === 0) playAr = ranNum(files.length);
                let tmpIdx = Math.floor(Math.random() * playAr.length);
                idx = playAr;
                playAr.splice(tmpIdx, 1);
                isScrolling = true;
        }
        aud.src = URL.createObjectURL(files);
        let name = files.name;
        curSong.innerText = name.substring(0, name.lastIndexOf('.')) + `(${files.length}/${idx+1})`;
        aud.play();
        mlist.innerHTML = showList(files, idx);
        if(isScrolling) mlist.scrollTop = 20 * idx; //scrolling(20, idx);
};

//显示音乐列表
const showList = (ar, idx) => {
        let res = '';
        for(let j = 0; j < ar.length; j ++) {
                let item = (j + 1) + '. ';
                item += j === idx ?
                        `<span class="list2">${ar.name}</span>` :
                        `<span class="list1" onclick="mplay(${j})">${ar.name}</span>`;
                res += item + '<br>';
        }
        return res;
};

//生成不重复随机数组
const ranNum = (total) => {
        let ar = Array(total).fill().map((_,key) => key);
        ar.sort(() => 0.5 - Math.random());
        return ar;
};

//秒转分
const s2m = (seconds) => {
        if (!seconds) return '00:00';
        let min = parseInt(seconds / 60), sec = parseFloat(Math.floor(seconds) % 60);
        if(min < 10) min = '0' + min;
        if(sec < 10) sec = '0' + sec;
        return min + ':' + sec;
};

//判断进度条区域
const innerH = (e, h) => e.offsetY > h / 2 - 5 && e.offsetY < h / 2 + 5;

//audio timeupdate监听事件
aud.addEventListener('timeupdate', () => {
        mplayer.style.setProperty('--prg', aud.currentTime / aud.duration * 100 + '%');
        mplayer.dataset.cu = s2m(aud.currentTime);
        mplayer.dataset.du = s2m(aud.duration);
});

//单曲播放结束
aud.addEventListener('ended',() => mplay());

//选择歌曲
selectSong.onclick = () => mfile.click();

//文件选择器改变
mfile.onchange = () => {
        let filelist = mfile.files;
        if(filelist.length === 0) return;
        files.length = 0;
        for(let j = 0; j < filelist.length; j ++) {
                files.push(filelist);
        }
        playAr = ranNum(files.length);
        mplay();
        getDatas();
}

//播放器点击
mplayer.onclick = (e) => {
        if(files.length < 1) return;
        if(innerH(e,mplayer.clientHeight)) {
                aud.currentTime = aud.duration * e.offsetX / mplayer.offsetWidth;
        }else{
                aud.paused ? aud.play() : aud.pause();
        }
};

//播放器鼠标移过
mplayer.onmousemove = (e) => {
        mplayer.title = innerH(e,mplayer.clientHeight) ?
                s2m(aud.duration * e.offsetX / mplayer.offsetWidth) :
                (aud.paused ? '点击播放' : '点击暂停');
};

//背景图地址栏输入事件
bgurl.oninput = () => {
        let img = new Image(), src = bgurl.value.trim();
        img.src = src;
        img.onload = () => {
                papa.style.cssText += `background: url(${src}) no-repeat center/cover;`;
                localStorage.setItem('player_url', src)
        };
};

//背景图地址栏鼠标经过事件
bgurl.onmouseover = () => {
        bgurl.focus();
        bgurl.setSelectionRange(0,bgurl.value.length);
};

//加载背景图
const player_url = bgurl.value = localStorage.getItem('player_url');
if(player_url) papa.style.cssText += `background: url(${player_url}) no-repeat center/cover;`;

</script>

马黑黑 发表于 2025-2-5 10:35

本帖最后由 马黑黑 于 2025-2-5 12:17 编辑

2月5日更新内容:改进音乐列表滚动条样式(支持chrome和Firefox)

<style>
/* 父元素 */
#papa {
      margin: 30px auto 0;
      padding: 10px;
      width: 800px;
      height: 400px;
      font-size: 16px;
      background: linear-gradient(to bottom right, #000, #ffc);
      box-shadow: 3px 6px 20px #000;
      border-radius: 6px;
      display: grid;
      place-items: center;
      position: relative;
}

/* 选择本地音乐父元素 */
#openFile {
      position: absolute;
      left: 15px;
      top: 15px;
      width: calc(100% - 30px);
}

/* 选择音乐控件 */
#mfile { display: none; }

/* 选择音乐按钮 */
#selectSong {
      margin-right: 8px;
      border: 2px solid #ccc;
      border-radius: 6px;
      outline: none;
      background: none;
      color: snow;
      cursor: pointer;
}

/* 背景图片地址栏 */
#bgurl {
      position: absolute;
      right: 0;
      display: none;
}


#openFile:hover #bgurl {
      display: inline;
}

/* 选择音乐按钮指针经过 */
#selectSong:hover { background: darkred; }

/* 当前播放曲目 */
#curSong { color: #eee; }

/* 播放器 */
#mplayer {
      --bg1: teal;
      --bg2: snow;
      --ppLen: 4px;
      --prog: white;
      --track: silver;
      --prg: 0%;
      --ppCap: white;
      position: absolute;
      right: 180px;
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: linear-gradient(
                to right,
                var(--prog) var(--prg),
                var(--track) var(--prg),
                var(--track) 0
      ) no-repeat 0 50% / 100% 2px;
      cursor: pointer;
      display: grid;
      place-items: center;
}

/* 播放器指针经过 */
#mplayer:hover {
      filter: hue-rotate(90deg) drop-shadow(0 0 26px black);
}

/* 播放器伪元素 :音频时间信息 */
#mplayer::before, #mplayer::after {
      position: absolute;
      color: snow;
}

/* 播放器伪元素一 :当前播放时间 */
#mplayer::before {
      content: attr(data-cu);
      top: 20%;
}

/* 播放器伪元素二 :音频时长 */
#mplayer::after {
      content: attr(data-du);
      top: 56%;
}

/* 频谱条 */
.pp {
      position: absolute;
      left: calc(50% - 2px);
      bottom: 50%;
      width: var(--ppLen);
      height: 20px;
      background: linear-gradient(to top, var(--bg1), var(--bg2));
      transform-origin: 50% 100%;
      transform: rotate(var(--deg)) translate(-50px, 0);
      display: grid;
      place-items: center;
}

/* 频谱条伪元素 :频谱帽 */
.pp::after {
      position: absolute;
      content: '';
      width: calc(var(--ppLen) + 4px);
      height: calc(var(--ppLen) + 4px);
      top: 0px;
      background: var(--bg2);
      border-radius: 50%;
}

/* 音乐列表 */
#mlist {
      position: absolute;
      left: 20px;
      top: 60px;
      min-width: 40%;
      max-width: 50%;
      min-height: 20%;
      max-height: calc(100% - 80px);
      color: silver;
      font-size: 14px;
      line-height: 20px;
      opacity: .4;
      /* 滚动条 */
      overflow: hidden;
      scrollbar-width: thin;
      scrollbar-color: teal rgba(255,255,255,.2);
}

/* 音乐列表鼠标经过 */
#mlist:hover {
      overflow: auto;
      opacity: 1;
}

/* 音乐列表项目一 :待播放曲目 */
.list1 { cursor: pointer; }

/* 音乐列表项目二 :正在播放曲目 */
.list2 {
      color: cyan;
      cursor:default;
}

/* 音乐列表项目一指针经过 */
.list1:hover { color: white; }
</style>

<div id="papa">
      <div id="openFile">
                <input id="selectSong" type="button" value="选择音乐" />
                <input type="file" id="mfile" accept=".mp3,.ogg,.wav,.acc,.webm,.flac" multiple />
                <span id="curSong"></span>
                <input id="bgurl" type="text" placeholder="背景 : 网络图片地址" value="" />
      </div>
      <div id="mplayer" class="mplayer"></div>
      <div id="mlist"></div>
      <audio id="aud"></audio>
</div>
<p style="text-align:right"><br>更新 :2025年2月5日 &nbsp;&nbsp;&nbsp;<br></p>

<script>

//选择的文件, 播放列表, 波形数据, 频谱
let files=[], playAr = [], output = [], pps = [];
//打开文件次数, 频谱条总数
let openIdx = 0, total = 30;

//获取波形数据
const getDatas = () => {
      if(openIdx > 0) return;
      openIdx ++;
      Ac = new AudioContext;
      source = Ac.createMediaElementSource(aud);
      analyser = Ac.createAnalyser();
      source.connect(analyser);
      analyser.connect(Ac.destination);
      output = new Uint8Array(total);
};

//生成频谱条
Array(total).fill(0).forEach((_, k) => {
      let pp = document.createElement('span');
      pp.className = 'pp';
      pp.style.cssText += `--deg: ${360 / total * k}deg`;
      mplayer.appendChild(pp);
      pps.push(pp);
});

//波形数据刷新
(function update() {
      if(aud.src) analyser.getByteFrequencyData(output);
      for(let j = 0; j < total ; j++) {
                pps.style.height = output / 2 + 'px';
      }
      window.requestAnimationFrame(update);
})();

//播放 :idx为空时随机播放
const mplay = (idx = null) => {
      if(files.length === 0) return;
      let isScrolling = false;
      if(idx === null) {
                if(playAr.length === 0) playAr = ranNum(files.length);
                let tmpIdx = Math.floor(Math.random() * playAr.length);
                idx = playAr;
                playAr.splice(tmpIdx, 1);
                isScrolling = true;
      }
      aud.src = URL.createObjectURL(files);
      let name = files.name;
      curSong.innerText = name.substring(0, name.lastIndexOf('.')) + `(${files.length}/${idx+1})`;
      aud.play();
      mlist.innerHTML = showList(files, idx);
      if(isScrolling) mlist.scrollTop = 20 * idx; //scrolling(20, idx);
};

//显示音乐列表
const showList = (ar, idx) => {
      let res = '';
      for(let j = 0; j < ar.length; j ++) {
                let item = (j + 1) + '. ';
                item += j === idx ?
                        `<span class="list2">${ar.name}</span>` :
                        `<span class="list1" onclick="mplay(${j})">${ar.name}</span>`;
                res += item + '<br>';
      }
      return res;
};

//生成不重复随机数组
const ranNum = (total) => {
      let ar = Array(total).fill().map((_,key) => key);
      ar.sort(() => 0.5 - Math.random());
      return ar;
};

//秒转分
const s2m = (seconds) => {
      if (!seconds) return '00:00';
      let min = parseInt(seconds / 60), sec = parseFloat(Math.floor(seconds) % 60);
      if(min < 10) min = '0' + min;
      if(sec < 10) sec = '0' + sec;
      return min + ':' + sec;
};

//判断进度条区域
const innerH = (e, h) => e.offsetY > h / 2 - 5 && e.offsetY < h / 2 + 5;

//audio timeupdate监听事件
aud.addEventListener('timeupdate', () => {
      mplayer.style.setProperty('--prg', aud.currentTime / aud.duration * 100 + '%');
      mplayer.dataset.cu = s2m(aud.currentTime);
      mplayer.dataset.du = s2m(aud.duration);
});

//单曲播放结束
aud.addEventListener('ended',() => mplay());

//选择歌曲
selectSong.onclick = () => mfile.click();

//文件选择器改变
mfile.onchange = () => {
      let filelist = mfile.files;
      if(filelist.length === 0) return;
      files.length = 0;
      for(let j = 0; j < filelist.length; j ++) {
                files.push(filelist);
      }
      playAr = ranNum(files.length);
      mplay();
      getDatas();
}

//播放器点击
mplayer.onclick = (e) => {
      if(files.length < 1) return;
      if(innerH(e,mplayer.clientHeight)) {
                aud.currentTime = aud.duration * e.offsetX / mplayer.offsetWidth;
      }else{
                aud.paused ? aud.play() : aud.pause();
      }
};

//播放器鼠标移过
mplayer.onmousemove = (e) => {
      mplayer.title = innerH(e,mplayer.clientHeight) ?
                s2m(aud.duration * e.offsetX / mplayer.offsetWidth) :
                (aud.paused ? '点击播放' : '点击暂停');
};

//背景图地址栏输入事件
bgurl.oninput = () => {
      let img = new Image(), src = bgurl.value.trim();
      img.src = src;
      img.onload = () => {
                papa.style.cssText += `background: url(${src}) no-repeat center/cover;`;
                localStorage.setItem('player_url', src)
      };
};

//背景图地址栏鼠标经过事件
bgurl.onmouseover = () => {
      bgurl.focus();
      bgurl.setSelectionRange(0,bgurl.value.length);
};

//加载背景图
const player_url = bgurl.value = localStorage.getItem('player_url');
if(player_url) papa.style.cssText += `background: url(${player_url}) no-repeat center/cover;`;

</script>

马黑黑 发表于 2025-2-5 10:38

这个图片做做背景不错:

https://iphoto.mac89.com/photo/2020/01/10/EPS-200110_5/eTtFKMeFeQ_small.jpg

花飞飞 发表于 2025-2-5 13:10

马黑黑 发表于 2025-2-2 22:06
毛毛雨也是雨

事实证明今年是个暖冬,几乎没冷过

花飞飞 发表于 2025-2-5 13:12

马黑黑 发表于 2025-2-2 22:06
那不错

一杯解万愁
页: 2 3 4 5 6 7 8 9 10 11 [12] 13 14 15 16
查看完整版本: 播放多个本地音频文件的响应式频谱播放器(2.5更新)