音频组件列表播放控制:列表播放、循环控制、随机播放、单曲循环
概述
本节实现音频播放列表的四种播放模式控制逻辑:顺序播放、列表循环、单曲循环和随机播放,处理边界情况(首尾曲目切换)。
四种播放模式逻辑
模式定义
enum LoopMode {
Sequential = 0, // 顺序播放
ListLoop = 1, // 列表循环
SingleLoop = 2, // 单曲循环
Shuffle = 3 // 随机播放
}
typescript
各模式行为
| 模式 | 当前曲播完 | 点击上一曲(第一首时) | 点击下一曲(最后一首时) |
|---|---|---|---|
| 顺序播放 | 停止 | 无操作 | 无操作 |
| 列表循环 | 播放下一首(循环) | 跳到最后一首 | 跳到第一首 |
| 单曲循环 | 重播当前曲 | 正常上一曲 | 正常下一曲 |
| 随机播放 | 随机下一首 | 随机上一首 | 随机下一首 |
核心逻辑实现
// composables/usePlaylistControls.ts
import { ref, computed } from 'vue'
import type { Howl } from 'howler'
export function usePlaylistControls(
audioInstance: Ref<Howl | null>,
list: Ref<AudioListItem[]>,
currentIndex: Ref<number>
) {
const loopMode = ref<LoopMode>(LoopMode.Sequential)
const currentTrack = computed(() => list.value[currentIndex.value])
// 获取下一首索引
function getNextIndex(): number {
const len = list.value.length
const cur = currentIndex.value
switch (loopMode.value) {
case LoopMode.Sequential:
return cur < len - 1 ? cur + 1 : -1 // -1 表示停止
case LoopMode.ListLoop:
return (cur + 1) % len
case LoopMode.SingleLoop:
return cur // 保持当前
case LoopMode.Shuffle:
return getRandomIndex(cur, len)
default:
return -1
}
}
// 获取上一首索引
function getPrevIndex(): number {
const len = list.value.length
const cur = currentIndex.value
switch (loopMode.value) {
case LoopMode.Sequential:
return cur > 0 ? cur - 1 : -1
case LoopMode.ListLoop:
return (cur - 1 + len) % len
case LoopMode.SingleLoop:
return cur
case LoopMode.Shuffle:
return getRandomIndex(cur, len)
default:
return -1
}
}
// 随机索引(排除当前)
function getRandomIndex(current: number, total: number): number {
if (total <= 1) return 0
let random: number
do {
random = Math.floor(Math.random() * total)
} while (random === current)
return random
}
// 切换到下一曲
function handleNext() {
const nextIndex = getNextIndex()
if (nextIndex >= 0) {
switchTrack(nextIndex)
} else {
// 顺序播放到最后一首,停止
audioInstance.value?.stop()
}
}
// 切换到上一曲
function handlePrev() {
const prevIndex = getPrevIndex()
if (prevIndex >= 0) {
switchTrack(prevIndex)
}
}
// 切换曲目
function switchTrack(index: number) {
// 单曲循环:使用 Howler 的 loop 能力
if (loopMode.value === LoopMode.SingleLoop && audioInstance.value) {
audioInstance.value.loop(false) // 先关闭,切换后重新设置
}
currentIndex.value = index
// 单曲循环:在新曲目上启用 loop
if (loopMode.value === LoopMode.SingleLoop && audioInstance.value) {
audioInstance.value.loop(true)
}
}
// 播放结束回调
function handleTrackEnd() {
handleNext()
}
// 切换播放模式
function toggleLoopMode() {
const oldMode = loopMode.value
loopMode.value = (loopMode.value + 1) % 4
// 从单曲循环切出时,恢复 Howler.loop 为 false
if (oldMode === LoopMode.SingleLoop && audioInstance.value) {
audioInstance.value.loop(false)
}
// 切到单曲循环时,设置 Howler.loop 为 true
if (loopMode.value === LoopMode.SingleLoop && audioInstance.value) {
audioInstance.value.loop(true)
}
}
return {
loopMode,
currentTrack,
handleNext,
handlePrev,
handleTrackEnd,
toggleLoopMode
}
}
typescript
单曲循环的特殊处理
// 切换音频时需要保存和恢复 loop 状态
let oldLoop = false
function switchAudio(newSrc: string) {
// 记录旧的 loop 状态
oldLoop = loopMode.value === LoopMode.SingleLoop
// 重置进度
if (loopMode.value !== LoopMode.SingleLoop) {
state.progress = 0
}
// 初始化新音频
initAudio(newSrc, {
loop: oldLoop // 恢复 loop 状态
})
}
typescript
小结
- 顺序播放:到最后一首停止;列表循环:首尾相连;单曲循环:Howler
loop(true);随机播放:Math.random() - 列表循环的边界处理:
(index + 1) % length和(index - 1 + length) % length - 单曲循环使用 Howler.js 的
loop()方法,切换曲目时需手动管理 loop 状态 - 随机模式需要排除当前曲目,避免连续播放同一首
↑