请马上登录,朋友们都在花潮里等着你哦:)
您需要 登录 才可以下载或查看,没有账号?立即注册
x
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天籁之音</title>
</head>
<body>
<style>
body { background: #666; }
h1 { font-size: 2.5em; text-align: center; color: white; text-shadow: -2px -2px tan; }
.papa { margin: auto; padding: 20px; width: clamp(600px, 60vw, 1000px); height: 80vh; font-size: 16px; background: linear-gradient(to bottom right, #000, #ffc); box-shadow: 3px 6px 20px #000; border-radius: 10px; display: flex; flex-direction: column; flex-wrap: wrap; gap: 10px; align-content: space-between; justify-content: space-between; position: relative; }
.papa * { box-sizing: border-box; }
.son { box-sizing: border-box; width: calc(50% - 5px); height: calc(50% - 5px); display: grid; place-items: center; position: relative; }
#openFile { position: absolute; left: 15px; top: 10px; }
#mfile { display: none; }
#selectSong {margin-right: 8px; border: 2px solid #ccc; border-radius: 6px; outline: none; background: none; color: snow; cursor: pointer;}
#selectSong:hover { background: darkred; }
#curSong { color: #eee; padding: 4px; cursor: pointer; }
#mlist { position: absolute; padding: 12px 20px; left: 10px; top: 60px; width: 90%; height: calc(100% - 12px); color: silver; line-height: 2.5em; overflow: hidden; scrollbar-width: thin; scrollbar-color: tan transparent; z-index: 20; transition: all .5s; }
#mlist:hover { overflow: auto; }
.list1 { cursor: pointer; }
.list2 { color: cyan; cursor:default; }
.list1:hover { color: white; }
#mplayer { --bg1: teal; --bg2: snow; --ppLen: 4px; --prog: white; --track: silver; --prg: 0%; --ppCap: white; position: absolute; 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; filter: drop-shadow(0 0 10px gray); 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%; }
#playbtn { position: absolute; padding: 2px 20px; bottom: 20px; color: white; font-size: 1.2em; border: 3px solid white; border-radius: 12px; cursor: pointer; user-select: none; }
#playbtn:hover { background: darkred; }
.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%; }
.hidden { overflow: hidden; }
#mpic { position: absolute; width: 65%; border-radius: 8px; object-fit: cover; transition: .35s; }
#mpic:hover { transform: scale(1.2); }
#mMsg pre { font-family: monospace; line-height: 30px; white-space: pre-wrap; }
</style>
<h1>天籁之音</h1>
<audio id="aud"></audio>
<div class="papa">
<div class="son">
<div id="openFile">
<input id="selectSong" type="button" value="选择音乐">
<input type="file" id="mfile" accept=".mp3, .ogg, .wav, .acc, .webm" multiple>
<span id="curSong"></span>
</div>
<div id="mlist"></div>
</div>
<div class="son">
<div id="mplayer"></div>
<div id="playbtn" title="随机选择">⏭ 下一曲</div>
</div>
<div class="son hidden">
<!-- 封面图片可以换成本地文件 -->
<!--img id="mpic" src="./api/piano.svg" alt="" title="歌曲封面"-->
<img id="mpic" src="https://638183.freep.cn/638183/web/svg/piano.svg" alt="" title="歌曲封面">
</div>
<div id="mMsg" class="son"></div>
</div>
<!-- 可以下载 jsmediatags 文件到本地使用 -->
<!--script src="./jsmediatags.min.js"></script-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsmediatags/3.9.5/jsmediatags.min.js"></script>
<script>
let files = [], playAr = [], output = [], pps = [], total = 30, Ac = null, currentIdx, mData = {};
// 音频信息输出
const outputData = (data) => {
if (!data.size) return;
mMsg.innerHTML = [
`<pre>`,
`文 件 名 :${data.name}`,
``,
`专辑名称 :${data.album}`,
`歌曲标题 :${data.title}`,
`艺 术 家 :${data.artist}`,
`文件大小 :${data.size}`,
`音频时长 :${data.duration}`,
`</pre>`
].join('\n');
};
// 获取音频元数据
const getTrackMsg = (file) => {
jsmediatags.read(file, {
onSuccess: (tag) => {
if (tag.tags.picture) {
mpic.src = URL.createObjectURL(new Blob([new Uint8Array(tag.tags.picture.data).buffer]));
}
mData.name = file.name;
mData.album = tag.tags.album || '未知';
mData.artist = tag.tags.artist || '未知';
mData.size = formatFileSize(file.size);
mData.title = tag.tags.title || file.name.replace(/\..+/, '');
outputData(mData);
},
onError: console.error
});
};
//获取波形数据
const getAcDatas = () => {
if (Ac !== null) return;
Ac = new AudioContext;
source = Ac.createMediaElementSource(aud);
analyser = Ac.createAnalyser();
source.connect(analyser);
analyser.connect(Ac.destination);
output = new Uint8Array(total);
};
// 格式化文件大小
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
//生成频谱条
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[j].style.height = output[j] / 2 + 'px';
}
window.requestAnimationFrame(update);
})();
//播放音频 :idx为空时随机播放
const mplay = (idx = null) => {
if(files.length === 0) return;
if(idx === null) {
if(playAr.length === 0) playAr = ranNum(files.length);
let tmpIdx = Math.floor(Math.random() * playAr.length);
idx = currentIdx = playAr[tmpIdx];
playAr.splice(tmpIdx, 1);
} else currentIdx = idx;
aud.src = URL.createObjectURL(files[idx]);
let name = files[idx].name;
curSong.innerText = name.substring(0, name.lastIndexOf('.')) + `(${files.length}/${idx+1})`;
aud.play();
mlist.innerHTML = showList(files, idx);
scrollList();
getTrackMsg(files[idx]);
aud.onloadedmetadata = () => {
mData.duration = s2m(aud.duration);
outputData(mData);
};
};
//生成音乐列表
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[j].name}</span>` :
`<span class="list1" onclick="mplay(${j})">${ar[j].name}</span>`;
res += item + '<br>';
}
return res;
};
// 列表滚动
const scrollList = () => {
const lists = mlist.querySelectorAll('span');
if(lists.length > 0 && isInViewport(mlist) && mlist.scrollHeight > mlist.clientHeight) {
lists[currentIdx].scrollIntoView({ behavior: 'smooth', block: 'center' });
console.log('Yes')
}
};
//生成不重复随机数组
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;
// mlist进入视口时项目滚动
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
scrollList();
}
});
});
observer.observe(mlist);
// 元素是否进入视口
const isInViewport = (elm) => {
const rect = elm.getBoundingClientRect();
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
return (rect.bottom > 0 && rect.right > 0 && rect.top < viewHeight && rect.left < viewWidth);
};
//audio timeupdate监听事件
aud.ontimeupdate = () => {
mplayer.style.setProperty('--prg', aud.currentTime / aud.duration * 100 + '%');
mplayer.dataset.cu = s2m(aud.currentTime);
mplayer.dataset.du = s2m(aud.duration);
};
//单曲播放结束
aud.onended = () => 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[j]);
}
playAr = ranNum(files.length);
mplay();
getAcDatas();
}
//播放器点击
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 ? '点击播放' : '点击暂停');
};
curSong.onclick = () => scrollList();
playbtn.onclick = () => mplay();
</script>
</body>
</html>
|