Files
2026-04-28 10:32:19 +08:00

222 lines
6.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 早安电台 — 音频管理器
* 封装 wx.getBackgroundAudioManager(),管理全局背景播放
* 确保小程序切后台后音频不断
* 播放时自动上报收听历史
*/
const api = require('./api')
let bgAudioManager = null
let appInstance = null
let _switching = false // 切换音频时的锁,防止 onStop 事件干扰
let _ended = false // 标记音频是否已自然播放完毕
let _stopped = false // 标记音频是否被系统停止(浮窗关闭等)
/**
* 初始化音频管理器
* @param {Object} app - App 实例
*/
function init(app) {
appInstance = app
bgAudioManager = wx.getBackgroundAudioManager()
// 绑定音频事件
bgAudioManager.onPlay(() => {
_switching = false // 新音频开始播放,释放锁
updatePlayState(true)
})
bgAudioManager.onPause(() => {
updatePlayState(false)
// 暂停时上报进度
reportHistory()
})
bgAudioManager.onStop(() => {
// 如果正在切换音频,不处理 onStop(新的 src 设置会触发旧的 onStop
if (_switching) return
_stopped = true // 标记:音频被系统停止,下次播放需重新设置 src
reportHistory()
updatePlayState(false)
appInstance.globalData.currentTime = 0
appInstance.emit('playerStateChange', getState())
})
bgAudioManager.onEnded(() => {
_ended = true
reportHistory()
updatePlayState(false)
appInstance.globalData.currentTime = appInstance.globalData.duration
appInstance.emit('playerStateChange', getState())
})
// 进度更新回调
bgAudioManager.onTimeUpdate(() => {
if (_switching) return
const ct = Math.floor(bgAudioManager.currentTime || 0)
const dur = Math.floor(bgAudioManager.duration || 0)
appInstance.globalData.currentTime = ct
if (dur > 0) {
appInstance.globalData.duration = dur
}
appInstance.emit('timeUpdate', { currentTime: ct, duration: dur })
})
bgAudioManager.onError((err) => {
console.error('[AudioManager] 播放出错:', err)
_switching = false
updatePlayState(false)
})
bgAudioManager.onWaiting(() => {
console.log('[AudioManager] 缓冲中...')
})
}
/**
* 更新播放状态并通知
*/
function updatePlayState(isPlaying) {
appInstance.globalData.isPlaying = isPlaying
appInstance.emit('playerStateChange', getState())
}
/**
* 获取当前播放器状态
*/
function getState() {
return {
activeContent: appInstance.globalData.activeContent,
isPlaying: appInstance.globalData.isPlaying,
currentTime: appInstance.globalData.currentTime,
duration: appInstance.globalData.duration,
playbackRate: appInstance.globalData.playbackRate
}
}
/**
* 获取音频 URL
*/
function getAudioUrl(content) {
if (!content) return ''
if (content.audio && content.audio.url) return content.audio.url
if (content.audioUrl) return content.audioUrl
return ''
}
/**
* 上报收听历史到后端
*/
function reportHistory() {
const content = appInstance.globalData.activeContent
if (!content || !content.id) return
api.addHistory({
programId: content.id,
progress: appInstance.globalData.currentTime || 0,
duration: appInstance.globalData.duration || content.duration || 0
}).catch(function (err) {
console.warn('[AudioManager] 上报历史失败:', err)
})
}
/**
* 播放指定内容
* @param {Object} content - 音频内容对象 { id, title, duration, audioUrl/audio.url, ... }
*/
function playContent(content) {
if (!bgAudioManager || !content) return
var audioUrl = getAudioUrl(content)
if (!audioUrl) {
console.error('[AudioManager] 音频内容缺少 audioUrl:', content)
wx.showToast({ title: '音频地址无效', icon: 'none' })
return
}
// 如果当前有播放内容,先上报历史
var oldContent = appInstance.globalData.activeContent
if (oldContent && oldContent.id && oldContent.id !== content.id) {
reportHistory()
}
// 标记正在切换,防止旧音频的 onStop 干扰
_switching = true
_ended = false
_stopped = false // 重置停止标记
appInstance.globalData.activeContent = content
appInstance.globalData.currentTime = 0
appInstance.globalData.duration = content.duration || 0
// 设置背景音频属性(必须设置 title,否则 iOS 上不允许播放)
bgAudioManager.title = content.title || '早安电台'
bgAudioManager.singer = '早安电台'
bgAudioManager.epname = content.title || '早安电台'
bgAudioManager.coverImgUrl = (content.cover && content.cover.url) || content.coverUrl || ''
// 设置音频源(这会触发旧音频的 onStop,然后自动开始播放新音频)
bgAudioManager.src = audioUrl
appInstance.emit('playerStateChange', getState())
}
/**
* 切换播放/暂停
* 关键:如果音频已结束或 src 被回收,需要重新设置 src 才能恢复播放
*/
function togglePlay() {
if (!bgAudioManager || !appInstance.globalData.activeContent) return
if (appInstance.globalData.isPlaying) {
bgAudioManager.pause()
} else {
// 音频已结束 / 被系统停止(浮窗关闭) / src 被回收 → 重新播放
var srcEmpty = false
try { srcEmpty = !bgAudioManager.src } catch (e) { srcEmpty = true }
if (_ended || _stopped || srcEmpty) {
_ended = false
_stopped = false
playContent(appInstance.globalData.activeContent)
} else {
bgAudioManager.play()
}
}
}
/**
* 跳转到指定时间(秒)
*/
function seekTo(time) {
if (!bgAudioManager) return
bgAudioManager.seek(time)
appInstance.globalData.currentTime = time
appInstance.emit('timeUpdate', {
currentTime: time,
duration: appInstance.globalData.duration
})
}
/**
* 设置播放速率
* @param {number} rate - 0.5 / 0.75 / 1.0 / 1.25 / 1.5 / 2.0
*/
function setPlaybackRate(rate) {
if (!bgAudioManager) return
bgAudioManager.playbackRate = rate
appInstance.globalData.playbackRate = rate
appInstance.emit('playerStateChange', getState())
}
module.exports = {
init,
getState,
playContent,
togglePlay,
seekTo,
setPlaybackRate
}