218 lines
6.1 KiB
JavaScript
218 lines
6.1 KiB
JavaScript
/**
|
||
* 早安电台 — 音频管理器
|
||
* 封装 wx.getBackgroundAudioManager(),管理全局背景播放
|
||
* 确保小程序切后台后音频不断
|
||
* 播放时自动上报收听历史
|
||
*/
|
||
|
||
const api = require('./api')
|
||
|
||
let bgAudioManager = null
|
||
let appInstance = null
|
||
let _switching = false // 切换音频时的锁,防止 onStop 事件干扰
|
||
let _ended = 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
|
||
|
||
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 // 重置结束标记
|
||
|
||
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 || srcEmpty) {
|
||
_ended = 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
|
||
}
|