Files
sundynix-radio-mp/pages/player/index.js
T
2026-04-28 10:32:19 +08:00

523 lines
15 KiB
JavaScript

/**
* 播放器详情页
* 大封面、进度条、倍速切换、播放控制
* 从 globalData.activeContent 获取当前节目
*/
const app = getApp()
const api = require('../../utils/api')
const util = require('../../utils/util')
Page({
data: {
domain: {},
activeContent: null,
isPlaying: false,
isVip: false,
isLiked: false,
isFavorited: false,
currentTime: 0,
duration: 0,
currentTimeText: '00:00',
durationText: '00:00',
displayDate: '',
playbackRate: 1.0,
statusBarHeight: 0,
showTranscript: false,
// 评论弹层
showComments: false,
commentList: [],
commentText: '',
commentLoading: false,
submitting: false,
// 分享面板
// 分享面板
showSharePanel: false,
// 倍速 ActionSheet
showSpeedSheet: false,
speedItems: [
{ label: '0.75x' },
{ label: '1.0x' },
{ label: '1.25x' },
{ label: '1.5x' },
{ label: '2.0x' }
],
// 频道节目列表(用于上/下期切换)
_channelPrograms: []
},
_isSeeking: false,
onLoad() {
},
onShow() {
this._syncState()
// 监听播放状态
this._onPlayerChange = (state) => {
if (this._isSeeking) return
this.setData({
activeContent: state.activeContent,
isPlaying: state.isPlaying,
playbackRate: state.playbackRate
})
this._updateDomain()
}
// 监听时间更新
this._onTimeUpdate = (data) => {
if (this._isSeeking) return
this.setData({
currentTime: data.currentTime,
duration: data.duration || this.data.duration,
currentTimeText: util.formatTime(data.currentTime),
durationText: util.formatTime(data.duration || this.data.duration)
})
}
app.on('playerStateChange', this._onPlayerChange)
app.on('timeUpdate', this._onTimeUpdate)
// 查询当前节目点赞状态
this._loadLikeStatus()
},
onHide() {
if (this._onPlayerChange) app.off('playerStateChange', this._onPlayerChange)
if (this._onTimeUpdate) app.off('timeUpdate', this._onTimeUpdate)
},
/**
* 同步当前状态
*/
_syncState() {
const gd = app.globalData
const content = gd.activeContent
if (!content) {
wx.navigateBack()
return
}
var dateStr = ''
if (content.createdAt) {
dateStr = content.createdAt.substring(0, 10).replace(/-/g, '.')
} else if (content.date) {
dateStr = util.dateToDot(content.date)
}
this.setData({
activeContent: content,
isPlaying: gd.isPlaying,
isVip: gd.isVip,
currentTime: gd.currentTime,
duration: gd.duration || content.duration,
currentTimeText: util.formatTime(gd.currentTime),
durationText: util.formatTime(gd.duration || content.duration),
displayDate: dateStr,
playbackRate: gd.playbackRate,
statusBarHeight: gd.statusBarHeight || 0,
isLiked: !!content.hasLiked,
isFavorited: content.HasFavorite === 1 || content.hasFavorite === 1
})
this._updateDomain()
this._loadChannelPrograms()
},
/**
* 获取频道信息
*/
_updateDomain() {
const content = this.data.activeContent
if (!content) return
var channelId = content.channelId || (content.channel && content.channel.id)
if (!channelId) {
if (content.channel) {
this.setData({ domain: content.channel })
}
return
}
if (this.data.domain && this.data.domain.id === channelId) return
var self = this
api.getChannelDetail(channelId).then(function (res) {
if (res.code === 200 && res.data) {
self.setData({ domain: res.data })
}
}).catch(function (err) {
console.error('[Player] 获取频道信息失败:', err)
})
},
/**
* 查询当前节目点赞和收藏状态(刷新最新值)
*/
_loadLikeStatus() {
const content = this.data.activeContent
if (!content) return
var self = this
api.getProgramDetail(content.id).then(function (res) {
if (res.code === 200 && res.data) {
self.setData({
isLiked: res.data.hasLiked === 1,
isFavorited: res.data.HasFavorite === 1 || res.data.hasFavorite === 1
})
}
}).catch(function () { })
},
/**
* 加载当前频道的节目列表(用于上/下期切换)
*/
_loadChannelPrograms() {
const content = this.data.activeContent
if (!content) return
var channelId = content.channelId || (content.channel && content.channel.id)
if (!channelId) return
var self = this
api.getProgramList({ channelId: channelId, current: 1, pageSize: 100 })
.then(function (res) {
if (res.code === 200 && res.data) {
var list = res.data.list || res.data || []
self.setData({ _channelPrograms: list })
}
}).catch(function () { })
},
/**
* 上一期
*/
onPrev() {
var programs = this.data._channelPrograms
var content = this.data.activeContent
if (!content || programs.length === 0) {
wx.showToast({ title: '没有更多了', icon: 'none' })
return
}
var idx = -1
for (var i = 0; i < programs.length; i++) {
if (programs[i].id === content.id) { idx = i; break }
}
if (idx <= 0) {
wx.showToast({ title: '已是第一期', icon: 'none' })
return
}
app.playContent(programs[idx - 1])
},
/**
* 下一期
*/
onNext() {
var programs = this.data._channelPrograms
var content = this.data.activeContent
if (!content || programs.length === 0) {
wx.showToast({ title: '没有更多了', icon: 'none' })
return
}
var idx = -1
for (var i = 0; i < programs.length; i++) {
if (programs[i].id === content.id) { idx = i; break }
}
if (idx < 0 || idx >= programs.length - 1) {
wx.showToast({ title: '已是最新一期', icon: 'none' })
return
}
app.playContent(programs[idx + 1])
},
/**
* 播放/暂停
*/
onTogglePlay() {
app.togglePlay()
},
/**
* 进度条拖动中
*/
onSliderChanging(e) {
this._isSeeking = true
this.setData({
currentTime: e.detail.value,
currentTimeText: util.formatTime(e.detail.value)
})
},
/**
* 进度条拖动完成 → 跳转播放
*/
onSliderChange(e) {
this._isSeeking = false
app.seekTo(e.detail.value)
},
/**
* 快退 15 秒
*/
onBackward() {
const newTime = Math.max(0, this.data.currentTime - 15)
app.seekTo(newTime)
},
/**
* 快进 15 秒
*/
onForward() {
const newTime = Math.min(this.data.duration, this.data.currentTime + 15)
app.seekTo(newTime)
},
/**
* 倍速设置(VIP 功能)
*/
onSpeed() {
if (!this.data.isVip) {
wx.showModal({
title: '会员提示',
content: '倍速播放是会员专属功能,是否前往开通?',
success: (res) => {
if (res.confirm) {
wx.navigateTo({ url: '/pages/vip/index' })
}
}
})
} else {
this.setData({ showSpeedSheet: true })
}
},
onSpeedSelect(e) {
const label = this.data.speedItems[e.detail.index].label
const rate = parseFloat(label)
app.setPlaybackRate(rate)
this.setData({ showSpeedSheet: false, playbackRate: rate })
},
onSpeedCancel() {
this.setData({ showSpeedSheet: false })
},
/**
* 下载(VIP 功能)
*/
onDownload() {
if (!this.data.isVip) {
wx.showModal({
title: '会员提示',
content: '音频下载是会员专属功能,是否前往开通?',
success: (res) => {
if (res.confirm) {
wx.navigateTo({ url: '/pages/vip/index' })
}
}
})
} else {
wx.showToast({ title: '下载功能开发中', icon: 'none' })
}
},
/**
* 查看文案
*/
onTranscript() {
const content = this.data.activeContent
if (content && content.content) {
wx.showModal({
title: '完整文案',
content: content.content,
showCancel: false
})
}
},
/**
* 点击中央 Banner:封面 ⇔ 文案切换
*/
onBannerTap() {
this.setData({ showTranscript: !this.data.showTranscript })
},
onLike() {
const content = this.data.activeContent
if (!content) return
const self = this
const wasLiked = this.data.isLiked
// 乐观更新
this.setData({ isLiked: !wasLiked })
api.toggleLike(content.id).then(function (res) {
if (res.code !== 200) {
self.setData({ isLiked: wasLiked })
wx.showToast({ title: res.msg || '操作失败', icon: 'none' })
} else {
wx.showToast({ title: wasLiked ? '已取消喜欢' : '已喜欢 ♥', icon: 'none' })
}
}).catch(function () {
self.setData({ isLiked: wasLiked })
wx.showToast({ title: '网络异常', icon: 'none' })
})
},
onFavorite() {
const content = this.data.activeContent
if (!content) return
const self = this
const wasFavorited = this.data.isFavorited
// 乐观更新
this.setData({ isFavorited: !wasFavorited })
const fn = wasFavorited
? api.removeFavorite(content.id)
: api.addFavorite(content.id)
fn.then(function (res) {
if (res.code !== 200) {
self.setData({ isFavorited: wasFavorited })
wx.showToast({ title: res.msg || '操作失败', icon: 'none' })
} else {
wx.showToast({ title: wasFavorited ? '已取消收藏' : '已收藏 🔖', icon: 'none' })
}
}).catch(function () {
self.setData({ isFavorited: wasFavorited })
wx.showToast({ title: '网络异常', icon: 'none' })
})
},
// ===== 评论弹层 =====
onOpenComments() {
this.setData({ showComments: true })
this._loadComments()
},
onCloseComments() {
this.setData({ showComments: false, commentText: '' })
},
_loadComments() {
const content = this.data.activeContent
if (!content) return
const self = this
this.setData({ commentLoading: true })
api.getCommentList(content.id, { current: 1, pageSize: 30 }).then(function (res) {
if (res.code === 200 && res.data) {
var list = (res.data.list || res.data || []).map(function (c) {
return Object.assign({}, c, {
_isOwn: c.userId === (getApp().globalData.userInfo && getApp().globalData.userInfo.id)
})
})
self.setData({ commentList: list, commentLoading: false })
} else {
self.setData({ commentLoading: false })
}
}).catch(function () {
self.setData({ commentLoading: false })
})
},
onCommentInput(e) {
this.setData({ commentText: e.detail.value })
},
onSubmitComment() {
const text = (this.data.commentText || '').trim()
if (!text) return
const content = this.data.activeContent
if (!content) return
const self = this
this.setData({ submitting: true })
api.addComment(content.id, text).then(function (res) {
self.setData({ submitting: false, commentText: '' })
if (res.code === 200) {
wx.showToast({ title: '发布成功', icon: 'none' })
self._loadComments()
} else {
wx.showToast({ title: res.msg || '发布失败', icon: 'none' })
}
}).catch(function () {
self.setData({ submitting: false })
wx.showToast({ title: '网络异常', icon: 'none' })
})
},
onDeleteComment(e) {
const id = e.currentTarget.dataset.id
const self = this
wx.showModal({
title: '提示',
content: '确认删除该评论吗?',
success(res) {
if (!res.confirm) return
api.deleteComment(id).then(function (r) {
if (r.code === 200) {
self._loadComments()
} else {
wx.showToast({ title: '删除失败', icon: 'none' })
}
})
}
})
},
onShareTap() {
this.setData({ showSharePanel: true })
},
onCloseShare() {
this.setData({ showSharePanel: false })
},
/**
* 朋友圈分享按钮点击 → 引导用户使用右上角胶囊菜单
* 微信小程序限制:无法编程式触发朋友圈分享,只能从胶囊菜单触发
*/
onShareMomentTip() {
this.setData({ showSharePanel: false })
wx.showModal({
title: '分享到朋友圈',
content: '请点击右上角「···」菜单,选择「分享」即可发布到朋友圈',
showCancel: false,
confirmText: '我知道了'
})
},
onShare() {
this.onShareTap()
},
goBack() {
wx.navigateBack()
},
// ===== 小程序分享生命周期钩子 =====
/**
* 转发给朋友(胶囊菜单内「转发」,或 button open-type=share 触发)
*/
onShareAppMessage() {
const content = this.data.activeContent || {}
const domain = this.data.domain || {}
this.setData({ showSharePanel: false })
return {
title: (domain.name ? '【' + domain.name + '】' : '') + (content.title || '全声汇'),
path: '/pages/index/index',
imageUrl: ''
}
},
/**
* 分享到朋友圈(定义此函数后,胶囊菜单中「分享」自动出现)
*/
onShareTimeline() {
const content = this.data.activeContent || {}
const domain = this.data.domain || {}
return {
title: (domain.name ? '【' + domain.name + '】' : '') + (content.title || '全声汇'),
query: '',
imageUrl: ''
}
}
})