Files
Henry_Du 4ce4649aaf
Build VoiceHub FPK / build-fpk (push) Failing after 56m5s
Build and Push Docker Image / build-and-push (push) Canceled after 1h9m35s
初次提交
2026-09-06 08:19:02 +08:00

701 lines
23 KiB
TypeScript

import { ref } from 'vue'
import { useAudioPlayer } from '~/composables/useAudioPlayer'
import { useAudioPlayerControl } from '~/composables/useAudioPlayerControl'
import { useMusicWebSocket } from '~/composables/useMusicWebSocket'
import { useAuth } from '~/composables/useAuth'
import { useLyrics } from '~/composables/useLyrics'
export const useAudioPlayerSync = () => {
const globalAudioPlayer = useAudioPlayer()
const control = useAudioPlayerControl()
const musicWebSocket = useMusicWebSocket()
const lyrics = useLyrics()
// 节流控制
const lastWebSocketUpdate = ref(0)
const lastHarmonyUpdate = ref(0)
const lastProgressDiff = ref(0)
// HarmonyOS 事件处理器引用
let harmonyOSHandlers: Record<string, Function> | null = null
// 检测是否在鸿蒙环境
const isHarmonyOS = () => {
if (typeof window === 'undefined') return false
// 1. UA 检测(最可靠的初始检测)
if (window.navigator.userAgent.includes('HarmonyOS')) return true
// 2. 桥接对象检测
if (window.harmonyOSBridge) return true
// 3. 播放器对象检测(确保是原生注入的,即包含特定的原生方法)
if (window.voiceHubPlayer && typeof window.voiceHubPlayer.onPlayStateChanged === 'function') {
return true
}
return false
}
// 通知鸿蒙应用播放状态变化(改进事件驱动机制)
const notifyHarmonyOS = (
action: string,
extraData: Record<string, any> = {},
song?: any,
lyrics?: string
) => {
if (typeof window !== 'undefined' && song) {
// 处理封面URL,确保是完整的URL
let coverUrl = song.cover || ''
if (coverUrl && !coverUrl.startsWith('http')) {
if (coverUrl.startsWith('/')) {
coverUrl = window.location.origin + coverUrl
} else {
coverUrl = window.location.origin + '/' + coverUrl
}
}
// 映射播放模式
// HarmonyOS: 0=SEQUENCE, 1=SINGLE, 2=LIST, 3=SHUFFLE
let loopMode = 0; // SEQUENCE ('off')
if (control.playMode.value === 'loopOne') {
loopMode = 1; // SINGLE
} else if (control.playMode.value === 'order') {
loopMode = 2; // LIST
}
const songInfo = {
title: extraData.title || song.title || '',
artist: extraData.artist || song.artist || '',
album: extraData.album || song.album || '',
cover: extraData.artwork || coverUrl,
duration: extraData.duration !== undefined ? extraData.duration : 0,
position: extraData.position !== undefined ? extraData.position : 0,
lyrics: lyrics || '',
loopMode: loopMode
}
// 只有在鸿蒙环境中才调用相关API
if (window.voiceHubPlayer) {
if (action === 'play') {
window.voiceHubPlayer.onPlayStateChanged(true, {
position: songInfo.position,
duration: songInfo.duration,
loopMode: songInfo.loopMode
})
} else if (action === 'pause') {
window.voiceHubPlayer.onPlayStateChanged(false, {
position: songInfo.position,
duration: songInfo.duration,
loopMode: songInfo.loopMode
})
} else if (action === 'stop') {
window.voiceHubPlayer.onPlayStateChanged(false, {
position: 0,
duration: songInfo.duration,
loopMode: songInfo.loopMode
})
} else if (action === 'progress') {
// 进度更新时,只更新位置,不改变播放状态
if (window.voiceHubPlayer.updatePosition) {
window.voiceHubPlayer.updatePosition(songInfo.position)
} else {
window.voiceHubPlayer.onPlayStateChanged(true, {
position: songInfo.position,
duration: songInfo.duration,
loopMode: songInfo.loopMode
})
}
} else if (action === 'metadata') {
// 元数据更新时,只更新歌曲信息,不改变播放状态和位置
window.voiceHubPlayer.onSongChanged(songInfo)
// 延迟同步播放列表状态,但不更新播放状态
setTimeout(() => {
notifyPlaylistState()
}, 100)
} else if (action === 'seek') {
window.voiceHubPlayer.onPlayStateChanged(true, {
position: songInfo.position,
duration: songInfo.duration,
loopMode: songInfo.loopMode
})
}
}
}
}
// 通知播放列表状态变化
const notifyPlaylistState = () => {
if (
typeof window !== 'undefined' &&
window.voiceHubPlayer &&
window.voiceHubPlayer.updatePlaylistState
) {
const currentIndex = globalAudioPlayer.getCurrentPlaylistIndex().value
const playlist = globalAudioPlayer.getCurrentPlaylist().value
const hasNext = globalAudioPlayer.hasNext.value
const hasPrevious = globalAudioPlayer.hasPrevious.value
// 映射播放模式
// HarmonyOS: 0=SEQUENCE, 1=SINGLE, 2=LIST, 3=SHUFFLE
let loopMode = 0; // SEQUENCE ('off')
if (control.playMode.value === 'loopOne') {
loopMode = 1; // SINGLE
} else if (control.playMode.value === 'order') {
// 为了区分,把 'order' 映射为 2 (LIST),显示列表循环图标。
loopMode = 2; // LIST
}
const playlistState = {
currentIndex: currentIndex,
playlistLength: playlist ? playlist.length : 0,
hasNext: hasNext,
hasPrevious: hasPrevious,
loopMode: loopMode
}
// 播放列表状态更新(减少日志输出)
window.voiceHubPlayer.updatePlaylistState(playlistState)
// 备用方案:直接通过HarmonyOS接口发送
if (window.HarmonyOS && window.HarmonyOS.callNative) {
window.HarmonyOS.callNative('updatePlaylistState', playlistState)
}
}
}
// 节流的进度更新
const throttledProgressUpdate = (
currentTime: number,
duration: number,
isPlaying: boolean,
song?: any
) => {
if (!isHarmonyOS()) return
// 只在播放状态下发送进度更新,避免暂停时发送错误的位置信息
if (!isPlaying) return
const now = Date.now()
const progressDiff = Math.abs(currentTime - lastProgressDiff.value)
// WebSocket状态更新(每2秒最多一次)
if (now - lastWebSocketUpdate.value > 2000) {
lastWebSocketUpdate.value = now
musicWebSocket.sendStateUpdate({
songId: song?.id,
isPlaying: isPlaying,
position: currentTime,
duration: duration,
volume: 1,
playlistIndex: globalAudioPlayer.getCurrentPlaylistIndex().value
})
}
// 鸿蒙应用进度更新(每1秒最多一次,或进度差值超过0.5秒)
if (now - lastHarmonyUpdate.value > 1000 || progressDiff > 0.5) {
lastHarmonyUpdate.value = now
lastProgressDiff.value = currentTime
// 只更新播放状态和进度,不传递歌曲信息(避免覆盖元数据)
if (
typeof window !== 'undefined' &&
window.voiceHubPlayer &&
window.voiceHubPlayer.onPlayStateChanged
) {
// 获取当前循环模式
let loopMode = 0;
if (control.playMode.value === 'loopOne') {
loopMode = 1;
} else if (control.playMode.value === 'order') {
loopMode = 2;
}
window.voiceHubPlayer.onPlayStateChanged(isPlaying, {
position: currentTime,
duration: duration,
loopMode: loopMode
})
}
}
}
// WebSocket 状态更新
const sendWebSocketUpdate = (data: any) => {
if (isHarmonyOS()) {
musicWebSocket.sendStateUpdate(data)
}
}
// 播放上一首歌曲
const playPrevious = async (song?: any) => {
try {
// 检查是否有上一首歌曲
if (!globalAudioPlayer.hasPrevious.value) {
if (window.$showNotification) {
window.$showNotification('没有上一首歌曲', 'warning')
}
return { success: false, newSong: null }
}
// 记录当前播放状态 - 优先检查鸿蒙侧状态
let wasPlaying = globalAudioPlayer.getPlayingStatus().value
// 如果在鸿蒙环境,尝试获取鸿蒙侧的播放状态
if (isHarmonyOS() && window.voiceHubPlayer && window.voiceHubPlayer.getPlayState) {
try {
wasPlaying = window.voiceHubPlayer.getPlayState()
} catch (e) {
console.warn('无法获取鸿蒙侧播放状态,使用Web端状态')
}
}
const success = await globalAudioPlayer.playPrevious()
if (success) {
const newSong = globalAudioPlayer.getCurrentSong().value
if (newSong) {
// 只在鸿蒙环境下发送WebSocket消息
if (isHarmonyOS()) {
await musicWebSocket.sendSongChange({
songId: newSong.id,
title: newSong.title,
artist: newSong.artist,
cover: newSong.cover || '',
duration: 0,
playlistIndex: globalAudioPlayer.getCurrentPlaylistIndex().value
})
// 获取新歌曲的歌词(但不立即设置元数据,让AudioPlayer的handleLoaded处理)
if (newSong.musicPlatform && newSong.musicId) {
await lyrics.fetchLyrics(newSong.musicPlatform, newSong.musicId)
}
// 如果之前是播放状态,切换歌曲后自动开始播放
// 不在这里传递歌词,让AudioPlayer的handleLoaded方法负责歌词传递
if (wasPlaying) {
setTimeout(() => {
notifyHarmonyOS(
'play',
{
position: 0,
duration: newSong.duration || 0
},
newSong
) // 移除歌词参数,避免与handleLoaded竞争
}, 500) // 延迟确保handleLoaded完成元数据设置
}
// 通知播放列表状态变化
setTimeout(() => {
notifyPlaylistState()
}, 100)
}
return { success: true, newSong }
}
} else {
if (window.$showNotification) {
window.$showNotification('播放上一首歌曲失败', 'error')
}
}
} catch (error) {
console.error('播放上一首歌曲失败:', error)
if (window.$showNotification) {
window.$showNotification('播放上一首歌曲失败', 'error')
}
}
return { success: false, newSong: null }
}
// 获取音乐链接
const getMusicUrl = async (platform, musicId, quality) => {
try {
let apiUrl
if (platform === 'netease') {
apiUrl = `https://api.vkeys.cn/v2/music/netease?id=${musicId}&quality=${quality}`
} else if (platform === 'tencent') {
apiUrl = `https://api.vkeys.cn/v2/music/tencent?id=${musicId}&quality=${quality}`
} else {
return { success: false, error: '不支持的音乐平台' }
}
const response = await fetch(apiUrl, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
})
if (!response.ok) {
return { success: false, error: `获取音乐URL失败: ${response.status}` }
}
const data = await response.json()
if (data.code === 200 && data.data && data.data.url) {
// 将HTTP URL改为HTTPS
let url = data.data.url
if (url.startsWith('http://')) {
url = url.replace('http://', 'https://')
}
return { success: true, url }
} else {
return { success: false, error: data.message || `API错误: ${data.code}` }
}
} catch (error) {
console.error('获取音乐链接失败:', error)
return { success: false, error: error.message || '网络错误' }
}
}
// 播放下一首歌曲
const playNext = async (song?: any) => {
try {
// 检查是否有下一首歌曲
if (!globalAudioPlayer.hasNext.value) {
if (window.$showNotification) {
window.$showNotification('没有下一首歌曲', 'warning')
}
return { success: false, newSong: null }
}
// 记录当前播放状态 - 优先检查鸿蒙侧状态
let wasPlaying = globalAudioPlayer.getPlayingStatus().value
// 如果在鸿蒙环境,尝试获取鸿蒙侧的播放状态
if (isHarmonyOS() && window.voiceHubPlayer && window.voiceHubPlayer.getPlayState) {
try {
wasPlaying = window.voiceHubPlayer.getPlayState()
} catch (e) {
console.warn('无法获取鸿蒙侧播放状态,使用Web端状态')
}
}
const success = await globalAudioPlayer.playNext()
if (success) {
const newSong = globalAudioPlayer.getCurrentSong().value
if (newSong) {
// 只在鸿蒙环境下发送WebSocket消息
if (isHarmonyOS()) {
await musicWebSocket.sendSongChange({
songId: newSong.id,
title: newSong.title,
artist: newSong.artist,
cover: newSong.cover || '',
duration: 0,
playlistIndex: globalAudioPlayer.getCurrentPlaylistIndex().value
})
// 获取新歌曲的歌词(但不立即设置元数据,让AudioPlayer的handleLoaded处理)
if (newSong.musicPlatform && newSong.musicId) {
await lyrics.fetchLyrics(newSong.musicPlatform, newSong.musicId)
}
// 如果之前是播放状态,切换歌曲后自动开始播放
// 不在这里传递歌词,让AudioPlayer的handleLoaded方法负责歌词传递
if (wasPlaying) {
setTimeout(() => {
notifyHarmonyOS(
'play',
{
position: 0,
duration: newSong.duration || 0
},
newSong
) // 移除歌词参数,避免与handleLoaded竞争
}, 500) // 延迟确保handleLoaded完成元数据设置
}
// 通知播放列表状态变化
setTimeout(() => {
notifyPlaylistState()
}, 100)
}
return { success: true, newSong }
}
} else {
if (window.$showNotification) {
window.$showNotification('播放下一首歌曲失败', 'error')
}
}
} catch (error) {
console.error('播放下一首歌曲失败:', error)
if (window.$showNotification) {
window.$showNotification('播放下一首歌曲失败', 'error')
}
}
return { success: false, newSong: null }
}
// 同步播放状态到全局
const syncPlayStateToGlobal = (isPlaying: boolean, song?: any) => {
if (isPlaying && song) {
globalAudioPlayer.playSong(song)
} else {
globalAudioPlayer.pauseSong()
}
}
// 同步停止状态到全局
const syncStopToGlobal = () => {
globalAudioPlayer.stopSong()
}
// 更新全局播放位置
const updateGlobalPosition = (position: number, duration?: number) => {
globalAudioPlayer.updatePosition(position)
if (duration !== undefined) {
globalAudioPlayer.setDuration(duration)
}
}
// 设置全局播放列表
const setGlobalPlaylist = (song: any, playlist: any[]) => {
if (song && playlist && playlist.length > 0) {
globalAudioPlayer.playSong(song, playlist)
}
}
// 初始化 WebSocket 连接
const initializeWebSocket = () => {
if (isHarmonyOS()) {
const { getToken } = useAuth()
const token = getToken()
musicWebSocket.connect(token || undefined)
// 设置WebSocket事件监听器
musicWebSocket.setStateUpdateListener((state) => {
// 处理来自其他客户端的状态更新
})
musicWebSocket.setSongChangeListener((songInfo) => {
// 处理歌曲切换通知
console.log('Received song change notification:', songInfo)
})
}
}
// 初始化鸿蒙系统控制事件
const initializeHarmonyOSControls = (callbacks: {
onPlay: () => void
onPause: () => void
onStop: () => void
onNext: () => void
onPrevious: () => void
onSeek: (time: number) => void
onPositionUpdate?: (time: number) => void
}) => {
// 只有在检测到鸿蒙环境时才初始化
if (!isHarmonyOS()) return
const handleHarmonyOSPlay = () => {
callbacks.onPlay()
}
const handleHarmonyOSPause = () => {
callbacks.onPause()
}
const handleHarmonyOSStop = () => {
callbacks.onStop()
}
const handleHarmonyOSNext = () => {
callbacks.onNext()
}
const handleHarmonyOSPrevious = () => {
callbacks.onPrevious()
}
const handleHarmonyOSSeek = (event: CustomEvent) => {
const time = event.detail?.time || event.time
if (typeof time === 'number') {
callbacks.onSeek(time)
}
}
const handleHarmonyOSPositionUpdate = (event: CustomEvent) => {
const time = event.detail?.time || event.time
if (typeof time === 'number' && callbacks.onPositionUpdate) {
callbacks.onPositionUpdate(time)
}
}
const handleHarmonyOSSetLoopMode = (event: CustomEvent) => {
const mode = event.detail?.mode;
// 处理数字模式(HarmonyOS原生值)或字符串模式
// HarmonyOS: 0=SEQUENCE, 1=SINGLE, 2=LIST, 3=SHUFFLE
// Web: 'off', 'loopOne', 'order'
let targetMode: 'off' | 'order' | 'loopOne' | null = null;
if (typeof mode === 'number') {
if (mode === 1) { // SINGLE
targetMode = 'loopOne';
} else if (mode === 2) { // LIST
// 映射为列表模式
targetMode = 'order';
} else if (mode === 0) { // SEQUENCE
targetMode = 'off';
} else if (mode === 3) { // SHUFFLE
targetMode = 'order';
}
} else if (typeof mode === 'string') {
if (['off', 'order', 'loopOne'].includes(mode)) {
targetMode = mode as 'off' | 'order' | 'loopOne';
} else if (mode === 'shuffle') {
targetMode = 'order';
}
}
if (targetMode) {
control.setPlayMode(targetMode);
// 立即通知状态更新
notifyPlaylistState();
}
}
// 保存处理函数引用以便清理
harmonyOSHandlers = {
handleHarmonyOSPlay,
handleHarmonyOSPause,
handleHarmonyOSStop,
handleHarmonyOSNext,
handleHarmonyOSPrevious,
handleHarmonyOSSeek,
handleHarmonyOSPositionUpdate,
handleHarmonyOSSetLoopMode
}
// 添加事件监听器
window.addEventListener('harmonyos-play', handleHarmonyOSPlay)
window.addEventListener('harmonyos-pause', handleHarmonyOSPause)
window.addEventListener('harmonyos-stop', handleHarmonyOSStop)
window.addEventListener('harmonyos-next', handleHarmonyOSNext)
window.addEventListener('harmonyos-previous', handleHarmonyOSPrevious)
window.addEventListener('harmonyos-seek', handleHarmonyOSSeek as EventListener)
window.addEventListener('harmonyos-position-update',
handleHarmonyOSPositionUpdate as EventListener
)
window.addEventListener('harmonyos-set-loop-mode', handleHarmonyOSSetLoopMode as EventListener)
// 增强:直接暴露方法给 window.voiceHubPlayer,确保 Index.ets 可以直接调用
if (typeof window !== 'undefined') {
// 确保对象存在
window.voiceHubPlayer = window.voiceHubPlayer || {};
// 添加/覆盖控制方法
// 注意:我们不覆盖 onPlayStateChanged 和 onSongChanged,因为它们可能是由 Index.ets 注入的
// 我们只添加控制方法,这样 Index.ets 可以直接调用它们
const methodsToAdd = {
play: handleHarmonyOSPlay,
pause: handleHarmonyOSPause,
stop: handleHarmonyOSStop,
next: handleHarmonyOSNext, // Index.ets 使用 next()
playNext: handleHarmonyOSNext, // 兼容性
previous: handleHarmonyOSPrevious, // Index.ets 使用 previous()
playPrevious: handleHarmonyOSPrevious, // 兼容性
seek: (time: number) => callbacks.onSeek(time),
setLoopMode: (mode: number | string) => {
// 模拟事件对象调用处理函数
const event = { detail: { mode } } as CustomEvent;
handleHarmonyOSSetLoopMode(event);
}
};
Object.assign(window.voiceHubPlayer, methodsToAdd);
// 同时也尝试暴露给 voiceHubPlayerInstance (Index.ets 的备用方案)
if (window.voiceHubPlayerInstance) {
Object.assign(window.voiceHubPlayerInstance, {
setPlayMode: (mode: number | string) => {
const event = { detail: { mode } } as CustomEvent;
handleHarmonyOSSetLoopMode(event);
}
});
}
console.log('VoiceHub: HarmonyOS direct control methods attached');
}
}
// 清理鸿蒙系统控制事件
const cleanupHarmonyOSControls = () => {
if (harmonyOSHandlers) {
window.removeEventListener(
'harmonyos-play',
harmonyOSHandlers.handleHarmonyOSPlay as EventListener
)
window.removeEventListener(
'harmonyos-pause',
harmonyOSHandlers.handleHarmonyOSPause as EventListener
)
window.removeEventListener(
'harmonyos-stop',
harmonyOSHandlers.handleHarmonyOSStop as EventListener
)
window.removeEventListener(
'harmonyos-next',
harmonyOSHandlers.handleHarmonyOSNext as EventListener
)
window.removeEventListener(
'harmonyos-previous',
harmonyOSHandlers.handleHarmonyOSPrevious as EventListener
)
window.removeEventListener(
'harmonyos-seek',
harmonyOSHandlers.handleHarmonyOSSeek as EventListener
)
window.removeEventListener(
'harmonyos-position-update',
harmonyOSHandlers.handleHarmonyOSPositionUpdate as EventListener
)
window.removeEventListener(
'harmonyos-set-loop-mode',
harmonyOSHandlers.handleHarmonyOSSetLoopMode as EventListener
)
harmonyOSHandlers = null
}
}
return {
// 状态
globalAudioPlayer,
lyrics,
// 鸿蒙通知
notifyHarmonyOS,
notifyPlaylistState,
// 节流更新
throttledProgressUpdate,
sendWebSocketUpdate,
// 播放控制
playPrevious,
playNext,
// 全局状态同步
syncPlayStateToGlobal,
syncStopToGlobal,
updateGlobalPosition,
setGlobalPlaylist,
// 初始化和清理
initializeWebSocket,
initializeHarmonyOSControls,
cleanupHarmonyOSControls,
// 工具函数
isHarmonyOS
}
}