259 lines
6.7 KiB
TypeScript
259 lines
6.7 KiB
TypeScript
import { onMounted, onUnmounted, readonly, ref } from 'vue'
|
|
|
|
interface MediaSessionSong {
|
|
id: number
|
|
title: string
|
|
artist: string
|
|
cover?: string | null
|
|
musicUrl?: string | null
|
|
}
|
|
|
|
interface MediaSessionCallbacks {
|
|
onPlay?: () => void
|
|
onPause?: () => void
|
|
onStop?: () => void
|
|
onSeekBackward?: (details: MediaSessionActionDetails) => void
|
|
onSeekForward?: (details: MediaSessionActionDetails) => void
|
|
onSeekTo?: (details: MediaSessionActionDetails) => void
|
|
onPreviousTrack?: () => void
|
|
onNextTrack?: () => void
|
|
}
|
|
|
|
export const useMediaSession = () => {
|
|
const isSupported = ref(false)
|
|
const isActive = ref(false)
|
|
const currentSong = ref<MediaSessionSong | null>(null)
|
|
const callbacks = ref<MediaSessionCallbacks>({})
|
|
|
|
// 检查浏览器是否支持 Media Session API
|
|
const checkSupport = () => {
|
|
isSupported.value = 'mediaSession' in navigator
|
|
return isSupported.value
|
|
}
|
|
|
|
// 设置媒体元数据
|
|
const setMetadata = (song: MediaSessionSong) => {
|
|
if (!isSupported.value || !song) return
|
|
|
|
currentSong.value = song
|
|
|
|
try {
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
title: song.title || '未知歌曲',
|
|
artist: song.artist || '未知艺术家',
|
|
album: '', // 可以根据需要添加专辑信息
|
|
artwork: song.cover
|
|
? [
|
|
{
|
|
src: song.cover,
|
|
sizes: '96x96',
|
|
type: 'image/png'
|
|
},
|
|
{
|
|
src: song.cover,
|
|
sizes: '128x128',
|
|
type: 'image/png'
|
|
},
|
|
{
|
|
src: song.cover,
|
|
sizes: '192x192',
|
|
type: 'image/png'
|
|
},
|
|
{
|
|
src: song.cover,
|
|
sizes: '256x256',
|
|
type: 'image/png'
|
|
},
|
|
{
|
|
src: song.cover,
|
|
sizes: '384x384',
|
|
type: 'image/png'
|
|
},
|
|
{
|
|
src: song.cover,
|
|
sizes: '512x512',
|
|
type: 'image/png'
|
|
}
|
|
]
|
|
: []
|
|
})
|
|
} catch (error) {
|
|
console.warn('设置媒体元数据失败:', error)
|
|
}
|
|
}
|
|
|
|
// 设置播放状态
|
|
const setPlaybackState = (state: MediaSessionPlaybackState) => {
|
|
if (!isSupported.value) return
|
|
|
|
try {
|
|
navigator.mediaSession.playbackState = state
|
|
isActive.value = state !== 'none'
|
|
} catch (error) {
|
|
console.warn('设置播放状态失败:', error)
|
|
}
|
|
}
|
|
|
|
// 设置位置状态
|
|
const setPositionState = (duration: number, playbackRate: number = 1, position: number = 0) => {
|
|
if (!isSupported.value || !navigator.mediaSession.setPositionState) return
|
|
|
|
try {
|
|
navigator.mediaSession.setPositionState({
|
|
duration: Math.max(0, duration),
|
|
playbackRate: Math.max(0, playbackRate),
|
|
position: Math.max(0, Math.min(position, duration))
|
|
})
|
|
} catch (error) {
|
|
console.warn('设置位置状态失败:', error)
|
|
}
|
|
}
|
|
|
|
// 设置动作处理器
|
|
const setActionHandlers = (newCallbacks: MediaSessionCallbacks) => {
|
|
if (!isSupported.value) return
|
|
|
|
callbacks.value = { ...callbacks.value, ...newCallbacks }
|
|
|
|
try {
|
|
// 播放/暂停
|
|
if (callbacks.value.onPlay) {
|
|
navigator.mediaSession.setActionHandler('play', callbacks.value.onPlay)
|
|
}
|
|
if (callbacks.value.onPause) {
|
|
navigator.mediaSession.setActionHandler('pause', callbacks.value.onPause)
|
|
}
|
|
if (callbacks.value.onStop) {
|
|
navigator.mediaSession.setActionHandler('stop', callbacks.value.onStop)
|
|
}
|
|
|
|
// 跳转控制
|
|
if (callbacks.value.onSeekBackward) {
|
|
navigator.mediaSession.setActionHandler('seekbackward', callbacks.value.onSeekBackward)
|
|
}
|
|
if (callbacks.value.onSeekForward) {
|
|
navigator.mediaSession.setActionHandler('seekforward', callbacks.value.onSeekForward)
|
|
}
|
|
if (callbacks.value.onSeekTo) {
|
|
navigator.mediaSession.setActionHandler('seekto', callbacks.value.onSeekTo)
|
|
}
|
|
|
|
// 上一首/下一首
|
|
if (callbacks.value.onPreviousTrack) {
|
|
navigator.mediaSession.setActionHandler('previoustrack', callbacks.value.onPreviousTrack)
|
|
}
|
|
if (callbacks.value.onNextTrack) {
|
|
navigator.mediaSession.setActionHandler('nexttrack', callbacks.value.onNextTrack)
|
|
}
|
|
} catch (error) {
|
|
console.warn('设置动作处理器失败:', error)
|
|
}
|
|
}
|
|
|
|
// 清除所有动作处理器
|
|
const clearActionHandlers = () => {
|
|
if (!isSupported.value) return
|
|
|
|
try {
|
|
const actions: MediaSessionAction[] = [
|
|
'play',
|
|
'pause',
|
|
'stop',
|
|
'seekbackward',
|
|
'seekforward',
|
|
'seekto',
|
|
'previoustrack',
|
|
'nexttrack'
|
|
]
|
|
|
|
actions.forEach((action) => {
|
|
navigator.mediaSession.setActionHandler(action, null)
|
|
})
|
|
} catch (error) {
|
|
console.warn('清除动作处理器失败:', error)
|
|
}
|
|
}
|
|
|
|
// 初始化 Media Session
|
|
const initialize = (song?: MediaSessionSong, actionCallbacks?: MediaSessionCallbacks) => {
|
|
if (!checkSupport()) {
|
|
console.warn('当前浏览器不支持 Media Session API')
|
|
return false
|
|
}
|
|
|
|
if (song) {
|
|
setMetadata(song)
|
|
}
|
|
|
|
if (actionCallbacks) {
|
|
setActionHandlers(actionCallbacks)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 清理 Media Session
|
|
const cleanup = () => {
|
|
if (!isSupported.value) return
|
|
|
|
try {
|
|
clearActionHandlers()
|
|
navigator.mediaSession.metadata = null
|
|
setPlaybackState('none')
|
|
isActive.value = false
|
|
currentSong.value = null
|
|
} catch (error) {
|
|
console.warn('清理 Media Session 失败:', error)
|
|
}
|
|
}
|
|
|
|
// 更新歌曲信息
|
|
const updateSong = (song: MediaSessionSong) => {
|
|
if (!isSupported.value) return
|
|
setMetadata(song)
|
|
}
|
|
|
|
// 更新播放状态
|
|
const updatePlaybackState = (isPlaying: boolean) => {
|
|
if (!isSupported.value) return
|
|
setPlaybackState(isPlaying ? 'playing' : 'paused')
|
|
}
|
|
|
|
// 更新播放进度
|
|
const updatePosition = (position: number, duration: number, playbackRate: number = 1) => {
|
|
if (!isSupported.value) return
|
|
setPositionState(duration, playbackRate, position)
|
|
}
|
|
|
|
// 组件挂载时检查支持
|
|
onMounted(() => {
|
|
checkSupport()
|
|
})
|
|
|
|
// 组件卸载时清理
|
|
onUnmounted(() => {
|
|
cleanup()
|
|
})
|
|
|
|
return {
|
|
// 状态
|
|
isSupported: readonly(isSupported),
|
|
isActive: readonly(isActive),
|
|
currentSong: readonly(currentSong),
|
|
|
|
// 方法
|
|
initialize,
|
|
cleanup,
|
|
updateSong,
|
|
updatePlaybackState,
|
|
updatePosition,
|
|
setActionHandlers,
|
|
clearActionHandlers,
|
|
|
|
// 底层方法(高级用法)
|
|
setMetadata,
|
|
setPlaybackState,
|
|
setPositionState
|
|
}
|
|
}
|