2023-10-19 00:14:29 +08:00
|
|
|
import { Span, Button, List, ListItem, UploadMusic, createElement } from './weigets.js'
|
2023-09-28 15:20:02 +08:00
|
|
|
|
|
|
|
export default class MusicList {
|
2023-10-02 05:30:20 +08:00
|
|
|
constructor({ list = [], EventListeners = {}, onplay, onstop, onadd, onremove, onlike, onunlike, onban, onload }) {
|
|
|
|
this.event = { onplay, onstop, onadd, onremove, onlike, onunlike, onban, onload }
|
2023-10-12 03:10:23 +08:00
|
|
|
this.ul = List({ classList: ['music-list'] })
|
2023-09-29 21:21:26 +08:00
|
|
|
this.EventListeners = EventListeners
|
2023-09-29 17:31:37 +08:00
|
|
|
this.list = []
|
2023-09-29 21:21:26 +08:00
|
|
|
list.forEach(item => this.add(item)) // 列表逐一添加
|
2023-09-28 15:20:02 +08:00
|
|
|
|
|
|
|
// 添加音乐播放器
|
|
|
|
this.audio = new Audio()
|
2023-10-19 00:14:29 +08:00
|
|
|
this.audio.autoplay = true
|
|
|
|
this.audio.controls = true
|
|
|
|
this.audio.style.margin = '1rem 2rem'
|
|
|
|
this.audio.addEventListener('play', () => {
|
|
|
|
this.event.onplay(this.playing)
|
|
|
|
})
|
2023-09-28 15:20:02 +08:00
|
|
|
this.audio.addEventListener('ended', () => {
|
|
|
|
this.next()
|
|
|
|
})
|
2023-09-28 17:59:23 +08:00
|
|
|
//this.audio.addEventListener('timeupdate', () => {
|
|
|
|
// console.log(this.audio.currentTime)
|
|
|
|
//})
|
2023-10-12 03:00:30 +08:00
|
|
|
//this.audio.addEventListener('error', event => {
|
|
|
|
// console.error('音乐播放器错误:', event)
|
|
|
|
//})
|
2023-10-19 00:14:29 +08:00
|
|
|
|
|
|
|
// 收起到右上角, 音乐播放器基于浮窗定位, 不再占用页面空间
|
|
|
|
const element = createElement({
|
2023-10-19 00:21:45 +08:00
|
|
|
//textContent: '音乐',
|
2023-10-19 00:14:29 +08:00
|
|
|
style: { position: 'fixed', top: '5rem', right: '1rem', backgroundColor: '#eee', padding: '.5rem', borderRadius: '1rem', cursor: 'pointer' },
|
|
|
|
onclick: event => {
|
|
|
|
this.ul.classList.toggle('disable')
|
|
|
|
},
|
|
|
|
children: [
|
|
|
|
this.audio,
|
2023-10-19 00:21:45 +08:00
|
|
|
this.ul,
|
|
|
|
UploadMusic({
|
|
|
|
style: { width: '20rem', height: '5rem', margin: '1rem 2rem' },
|
|
|
|
onchange: files => {
|
|
|
|
for (const file of files) {
|
|
|
|
const id = 'music' + Date.now()
|
|
|
|
const { name, size, type } = file
|
|
|
|
const reader = new FileReader()
|
|
|
|
reader.onload = async event => {
|
|
|
|
const arrayBuffer = event.target.result
|
|
|
|
this.add({ id, name, size, type, arrayBuffer }) // 添加到列表(默认并不存储)
|
|
|
|
this.like({ id, name, size, type, arrayBuffer }) // 本地缓存的必要条件是喜欢
|
|
|
|
}
|
|
|
|
reader.readAsArrayBuffer(file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2023-10-19 00:14:29 +08:00
|
|
|
]
|
|
|
|
})
|
|
|
|
document.body.appendChild(element)
|
|
|
|
|
2023-10-01 20:09:28 +08:00
|
|
|
// 写入 css 样式到 head
|
2023-10-01 03:57:01 +08:00
|
|
|
const style = document.createElement('style')
|
|
|
|
style.innerText = `
|
2023-10-04 01:03:20 +08:00
|
|
|
ul.music-list {
|
|
|
|
max-height: 70vh;
|
|
|
|
overflow-y: auto;
|
|
|
|
}
|
2023-10-02 21:59:43 +08:00
|
|
|
ul.music-list > li > span {
|
2023-10-01 03:57:01 +08:00
|
|
|
cursor: pointer;
|
|
|
|
}
|
2023-10-02 23:09:53 +08:00
|
|
|
ul.music-list > li.play > span {
|
2023-10-02 06:44:37 +08:00
|
|
|
color: #02be08;
|
|
|
|
}
|
2023-10-01 19:59:22 +08:00
|
|
|
ul.music-list > li.cache::marker {
|
|
|
|
color: #02be08;
|
|
|
|
font-size: 1em;
|
|
|
|
contentx: '⚡';
|
|
|
|
}
|
|
|
|
ul.music-list > li.disable {
|
|
|
|
color: #999999;
|
|
|
|
}
|
2023-10-01 03:57:01 +08:00
|
|
|
ul.music-list > li > button {
|
|
|
|
margin-left: 10px;
|
|
|
|
border: none;
|
2023-10-02 07:07:33 +08:00
|
|
|
border-radius: 1em;
|
2023-10-01 03:57:01 +08:00
|
|
|
cursor: pointer;
|
2023-10-02 22:08:12 +08:00
|
|
|
user-select: none;
|
2023-10-02 07:07:33 +08:00
|
|
|
font-size: .5rem;
|
|
|
|
padding: 0 .5rem;
|
|
|
|
color: #555555;
|
2023-10-01 03:57:01 +08:00
|
|
|
}
|
|
|
|
ul.music-list > li > button:hover {
|
|
|
|
background-color: #ccc;
|
|
|
|
}
|
2023-10-04 01:03:20 +08:00
|
|
|
`
|
2023-10-01 03:57:01 +08:00
|
|
|
document.head.appendChild(style)
|
2023-09-28 15:20:02 +08:00
|
|
|
}
|
2023-09-29 21:21:26 +08:00
|
|
|
add(item) {
|
2023-10-02 00:53:17 +08:00
|
|
|
// 如果ID已存在则不添加
|
|
|
|
if (this.list.find(i => i.id === item.id)) {
|
|
|
|
return
|
|
|
|
}
|
2023-10-01 03:20:16 +08:00
|
|
|
// 将字节转换为可读的单位
|
|
|
|
const bytesToSize = bytes => {
|
|
|
|
if (bytes === 0) return '0 B'
|
|
|
|
const k = 1024
|
|
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
|
|
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]
|
|
|
|
}
|
2023-09-29 21:21:26 +08:00
|
|
|
this.list.push(item)
|
|
|
|
this.ul.appendChild(ListItem({
|
2023-09-28 15:20:02 +08:00
|
|
|
id: item.id,
|
2023-10-01 19:59:22 +08:00
|
|
|
classList: item.arrayBuffer ? ['cache'] : [],
|
2023-09-28 15:20:02 +08:00
|
|
|
children: [
|
2023-10-02 22:24:38 +08:00
|
|
|
Span({
|
|
|
|
textContent: `${item.name} - ${bytesToSize(item.size)}`,
|
2023-10-02 21:59:43 +08:00
|
|
|
onclick: event => {
|
2023-10-06 13:52:09 +08:00
|
|
|
event.stopPropagation() // !如果使用async则此处不能阻止冒泡传递
|
2023-10-02 23:09:53 +08:00
|
|
|
const li = event.target.parentElement // ListItem
|
|
|
|
const ul = li.parentElement // List
|
|
|
|
const list = Array.from(ul.children) // ListItems
|
|
|
|
list.forEach(li => li.classList.remove('play'))
|
|
|
|
if (!this.audio.paused && this.playing === item) {
|
|
|
|
li.classList.remove('play')
|
2023-10-02 21:59:43 +08:00
|
|
|
this.stop(item)
|
|
|
|
} else {
|
2023-10-02 23:09:53 +08:00
|
|
|
li.classList.add('play')
|
2023-10-02 21:59:43 +08:00
|
|
|
this.play(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}),
|
2023-09-28 15:20:02 +08:00
|
|
|
Button({
|
2023-10-04 06:32:53 +08:00
|
|
|
textContent: item.save ? '移除' : '缓存',
|
2023-09-28 15:20:02 +08:00
|
|
|
onclick: event => {
|
2023-10-06 13:52:09 +08:00
|
|
|
event.stopPropagation() // !如果使用async则此处不能阻止冒泡传递
|
2023-10-04 06:32:53 +08:00
|
|
|
if (item.save) {
|
2023-10-02 22:24:38 +08:00
|
|
|
event.target.textContent = '缓存'
|
2023-10-04 06:59:24 +08:00
|
|
|
this.ul.querySelector(`#${item.id}`).classList.remove('cache')
|
2023-10-02 06:30:20 +08:00
|
|
|
this.unlike(item)
|
|
|
|
} else {
|
2023-10-04 06:59:24 +08:00
|
|
|
item.save = true
|
2023-10-02 22:24:38 +08:00
|
|
|
event.target.textContent = '移除'
|
2023-10-02 06:30:20 +08:00
|
|
|
this.ul.querySelector(`#${item.id}`).classList.add('cache')
|
|
|
|
this.like(item)
|
|
|
|
}
|
2023-09-29 18:36:05 +08:00
|
|
|
}
|
2023-09-28 15:20:02 +08:00
|
|
|
})
|
|
|
|
]
|
2023-09-29 21:21:26 +08:00
|
|
|
}))
|
2023-10-02 00:30:47 +08:00
|
|
|
this.event.onadd(item, this.list)
|
2023-09-29 17:31:37 +08:00
|
|
|
}
|
2023-10-02 00:30:47 +08:00
|
|
|
async remove(item) {
|
2023-10-01 11:28:35 +08:00
|
|
|
this.ul.querySelector(`#${item.id}`)?.remove()
|
2023-10-02 22:09:54 +08:00
|
|
|
if (!this.audio.paused) this.stop() // 停止播放
|
2023-10-03 00:28:02 +08:00
|
|
|
this.list = this.list.filter(i => i.id !== item.id)
|
2023-10-02 00:30:47 +08:00
|
|
|
this.event.onremove(item)
|
2023-09-28 17:59:23 +08:00
|
|
|
}
|
2023-09-30 00:13:30 +08:00
|
|
|
async load(item) {
|
2023-10-02 00:30:47 +08:00
|
|
|
await this.event.onload(item)
|
2023-09-30 00:13:30 +08:00
|
|
|
}
|
2023-09-29 20:20:00 +08:00
|
|
|
async play(item) {
|
2023-10-04 10:16:21 +08:00
|
|
|
if (!item.arrayBuffer) {
|
2023-10-08 00:17:28 +08:00
|
|
|
console.log('加载音乐类型:', item.type)
|
|
|
|
// 不支持流式加载wav和flac和m4a, 需要全部加载完毕才能播放
|
|
|
|
if (item.type === 'audio/wav' || item.type === 'audio/flac' || item.type === 'audio/x-m4a') {
|
2023-10-04 13:07:00 +08:00
|
|
|
await this.load(item)
|
|
|
|
this.audio.src = URL.createObjectURL(new Blob([item.arrayBuffer], { type: item.type }))
|
|
|
|
this.audio.play()
|
|
|
|
} else {
|
|
|
|
// 边加载边播放
|
|
|
|
const mediaSource = new MediaSource()
|
|
|
|
this.audio.src = URL.createObjectURL(mediaSource)
|
|
|
|
if (!item.arrayBufferChunks) item.arrayBufferChunks = []
|
|
|
|
mediaSource.addEventListener('sourceopen', async () => {
|
|
|
|
const sourceBuffer = mediaSource.addSourceBuffer(item.type)
|
|
|
|
const arrayBufferLoader = async (index = 0) => {
|
|
|
|
console.log('开始加载====================================')
|
|
|
|
// 按照数据长度计算出分片应有数量, 如果数量不到且没有停止加载则一直读取
|
|
|
|
const chunkNumber = Math.ceil(item.size / 1024 / 64) // 64KB每片
|
|
|
|
console.log({ index, chunkNumber, paused: this.audio.paused })
|
|
|
|
while (index < chunkNumber && !this.audio.paused) {
|
|
|
|
const 播放状态 = !this.audio.paused && this.playing === item
|
|
|
|
const 加载状态 = item.arrayBufferChunks.length < chunkNumber
|
|
|
|
const 结束时间 = sourceBuffer.buffered.length && sourceBuffer.buffered.end(0)
|
|
|
|
const 缓冲时间 = 结束时间 - this.audio.currentTime
|
|
|
|
if (!播放状态 && !加载状态) break // 播放停止且加载完毕则退出
|
|
|
|
if (this.audio.paused || this.playing !== item) break // 播放停止或已经切歌则退出
|
|
|
|
if (缓冲时间 > 60) { // 缓冲超过60秒则等待30秒
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 30000))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (sourceBuffer.updating) { // sourceBuffer正在更新则等待更新结束
|
|
|
|
await new Promise(resolve => sourceBuffer.addEventListener('updateend', resolve))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (item.arrayBufferChunks.length <= index) { // 分片数量不足则等待
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 200))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
console.log('播放器加载分片:', item.name, `${index + 1}/${chunkNumber}`)
|
|
|
|
const chunk = item.arrayBufferChunks[index] // 顺序取出一个arrayBuffer分片
|
|
|
|
sourceBuffer.appendBuffer(chunk) // 添加到sourceBuffer
|
|
|
|
index++
|
2023-10-04 10:16:21 +08:00
|
|
|
}
|
2023-10-04 13:07:00 +08:00
|
|
|
console.log('加载完毕====================================')
|
|
|
|
item.arrayBufferChunks = null // 加载完毕释放分片内存
|
2023-10-04 10:16:21 +08:00
|
|
|
}
|
2023-10-04 13:07:00 +08:00
|
|
|
this.event.onload(item)
|
|
|
|
this.audio.play()
|
|
|
|
arrayBufferLoader()
|
|
|
|
})
|
|
|
|
}
|
2023-10-03 11:23:20 +08:00
|
|
|
} else {
|
|
|
|
// 本地缓存直接播放
|
|
|
|
this.audio.src = URL.createObjectURL(new Blob([item.arrayBuffer], { type: item.type }))
|
|
|
|
this.audio.play()
|
2023-09-29 20:20:00 +08:00
|
|
|
}
|
2023-10-02 00:30:47 +08:00
|
|
|
this.playing = item
|
|
|
|
this.event.onplay(item)
|
2023-09-28 17:59:23 +08:00
|
|
|
}
|
2023-10-02 00:30:47 +08:00
|
|
|
async stop() {
|
2023-10-04 07:10:49 +08:00
|
|
|
if (this.audio.paused) {
|
|
|
|
return console.error('暂停播放:音乐播放器不是播放状态!')
|
|
|
|
}
|
2023-09-28 17:59:23 +08:00
|
|
|
this.audio.pause()
|
2023-10-02 00:30:47 +08:00
|
|
|
this.event.onstop(this.playing)
|
2023-10-02 06:30:20 +08:00
|
|
|
this.playing = null
|
2023-09-28 15:20:02 +08:00
|
|
|
}
|
2023-10-02 00:30:47 +08:00
|
|
|
async like(item) {
|
2023-09-29 18:36:05 +08:00
|
|
|
if (!item.arrayBuffer) {
|
2023-10-02 00:30:47 +08:00
|
|
|
await this.load(item)
|
2023-09-29 18:36:05 +08:00
|
|
|
}
|
2023-10-02 05:38:44 +08:00
|
|
|
this.event.onlike(item, this.list)
|
2023-10-02 00:30:47 +08:00
|
|
|
}
|
2023-10-02 05:30:20 +08:00
|
|
|
async unlike(item) {
|
2023-10-02 05:38:44 +08:00
|
|
|
this.event.onunlike(item, this.list)
|
2023-10-02 05:30:20 +08:00
|
|
|
}
|
2023-10-02 00:30:47 +08:00
|
|
|
async ban(item) {
|
|
|
|
this.event.onban(item)
|
2023-09-29 18:36:05 +08:00
|
|
|
}
|
2023-09-28 15:20:02 +08:00
|
|
|
next() { }
|
|
|
|
prev() { }
|
|
|
|
}
|