Xp2p录像功能列表
在此章节,我们将介绍Xp2p实时视频的各个功能,以及如何使用它们。
- 该章节的代码中,将用this.player来引用播放器的实例,app代表小程序实例
- 本章节只介绍具体功能点,不介绍如何初始化。初始化流程请参考 历史录像流程
- wxml中的播放器配置如下
<iot-p2p-playback-player id="{{playerId}}"
class="player"
deviceInfo="{{XP2PDeviceInfo}}"
xp2pInfo="{{xp2pInfo}}"
streamChannel="{{1}}"
muted="{{muted}}"
videoInfo="{{videoInfo}}"
bind:timeupdate="onTimeUpdate"
progressInterval="{{3000}}"
bind:playstart="onPlayStateEvent"
bind:playpause="onPlayStateEvent"
bind:playresume="onPlayStateEvent"
bind:playstop="onPlayStateEvent"
bind:playend="onPlayStateEvent">
<view wx:if="{{isFullScreen}}"
slot="inner"
class="player-fullscreen-tools">
<view class="player-fullscreen-tools__tool"
bind:tap="switchFullScreen">退出全屏</view>
</view>
</iot-p2p-playback-player>- this.data中的配置如下
data: {
playerId: 'p2p-player',
muted: true,
XP2PDeviceInfo: null,
videoInfo: {
startTime: '',
endTime: ''
},
currentPlayTime: {
time: 0,
timeText: ''
},
activeDateList: [],
currentDate: '',
recordVideos: [],
isFullScreen: false,
speed: 1,
isPlaySuccess: false,
isPlayLoading: false,
isPlayError: false,
isPaused: false,
isPlayerFull: false
}1. 切换静音模式
switchMute() {
this.setData({
muted: !this.data.muted
})
}2. 切换全屏模式
switchFullScreen() {
if (!this.data.isFullScreen) {
player.requestFullScreen({
direction: 90,
});
} else {
player.exitFullScreen();
}
}3. 查询SD卡状态
- 通过getSdcardStatus方法来获取
getSdcardStatus() {
app.imcamWx.getSdcardStatus({
sn: 'xxx' // 此处填入实际SN
}).then(res => {
if (res.errCode === 0) {
const sdcardInfo = res.sdcardInfo
if (!sdcardInfo.isHaveSdcard) return wx.showToast({
title: '设备未插入sd卡',
})
else if (sdcardInfo.sdcardNeedFormat) return wx.showToast({
title: 'SD卡状态异常,需要格式化',
})
else wx.showToast({
title: 'SD卡状态正常',
})
}
})
}4. 获取历史视频日期列表
- 通过sendUserCommand方法来获取
- topic固定传'history'
- data中mode固定传0,begin_time固定传0,end_time传当前时间戳
- 如果res返回code === 3,则表示观看人数达到上限
getActiveDateList() {
app.xp2pManager
.sendUserCommand(this.data.XP2PDeviceInfo.deviceId, {
cmd: {
topic: `history`,
data: {
mode: 0,
begin_time: 0,
end_time: Math.round(new Date().valueOf() / 1000)
}
},
})
.then(res => {
if (res?.code === 3) return wx.showToast({
title: '观看人数达到上限',
})
const dateList = res?.data?.value?.data
if (!dateList || dateList.length === 0) return wx.showToast({
title: '没有历史视频',
})
const activeDateList = dateList.map(item => {
return {
date: new Date(item * 1000).toLocaleDateString('en-CA', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}),
}
})
this.setData({
activeDateList
})
})
}5. 获取指定日期的历史视频列表
- 通过sendUserCommand方法来获取
- topic固定传'history'
- data中mode固定传1,begin_time固定传0,end_time传指定日期的时间戳
- 如果res返回code === 3,则表示观看人数达到上限
getVideosFromDate(date) {
const today = new Date(date)
today.setUTCHours(0)
today.setUTCMinutes(0)
today.setUTCSeconds(0)
today.setUTCMilliseconds(0)
const todayValue = Math.round(today.valueOf() / 1000) // 当天的00:00时间戳
const tomorrow = new Date(today.valueOf() + 24 * 60 * 60 * 1000)
const tomorrowValue = Math.round(tomorrow.valueOf() / 1000) // 第二天的00:00时间戳
app.xp2pManager
.sendUserCommand(this.data.XP2PDeviceInfo.deviceId, {
cmd: {
topic: `history`,
data: {
mode: 1,
begin_time: todayValue,
end_time: tomorrowValue
}
},
})
.then(async res => {
if (res?.code === 3) return wx.showToast({
title: '观看人数达到上限',
})
const data = res.data
const parseRes = await app.imcamWx.parseRecordVideoListData({ // 调用API来解析数据
data: res.data,
todayValue
})
if (parseRes.errCode === 0) {
const recordVideos = parseRes.recordVideos
this.setData({
recordVideos,
currentDate: date
})
} else {
wx.showToast({
title: parseRes.errMsg
})
}
})
}6. 播放指定视频文件
- 该方法传参的video为recordVideos中的video对象
- 如果是第一次播放,则直接设置videoInfo
- 如果不是第一次播放,则发送sendUserCommand
changeVideo(video) {
if (!this.data.videoInfo.startTime) { // 若没有startTime,说明是第一次播放
this.setData({
videoInfo: video
})
}else { // 若不是第一次播放,则不要替换videoInfo,而是发送userData来通知设备切换流
// 需要判断是否因为onTimeUpdate触发了暂停,如果是则恢复播放
if (this.isPlayEnd) {
this.isPlayEnd = false
this.player().resume()
}
app.xp2pManager.sendUserCommand(this.data.XP2PDeviceInfo.deviceId, {
cmd: {
topic: `history`,
data: {
mode: 2,
begin_time: video.startTime,
}
}
})
}
this.setData({
currentPlayTime: {
time: video.startTime,
timeText: new Date(video.startTime * 1000).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
})
}7. 更新播放时间
- 通过ontimeUpdate来更新播放时间
- 在wxml中可通过progressInterval设置从设备端拿播放时间的频率。建议不要设置太频繁,最低设置1000毫秒比较好
onTimeUpdate({ // 设备回调的当前视频播放的时间戳
detail
}) {
const currentTime = detail.currentTime
if (currentTime === 0) { // 返回0代表从这往后已经没有视频文件了
wx.showToast({
title: '当天录像已播放完毕',
})
this.player.pause() // 暂停播放。如果不暂停的话,xp2p播放器会自动重新载入流,会不符合预期
// 如果此处引入了暂停播放,则在changeVideo处需要做继续播放的处理
this.isPlayEnd = true
} else {
const timeStamp = new Date(currentTime * 1000)
this.setData({
currentPlayTime: { // 更新播放时间。主要用于UI显示
time: currentTime,
timeText: new Date(timeStamp).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
})
}
}8. 切换播放速度
- 通过sendUserCommand来切换播放速度
- topic固定传'speed'
- data中的value固定传1或者2。代表1倍/2倍
switchSpeed(){
const speed = this.data.speed
this.setData({
speed: speed === 1 ? 2 : 1
})
app.xp2pManager.sendUserCommand(this.data.XP2PDeviceInfo.deviceId, {
cmd: {
topic: `speed`,
data: {
value: speed
}
}
})
}9. 截屏
snapShot() {
this.player.snapshot()
.then(res => {
const filePath = res.tempImagePath
wx.saveImageToPhotosAlbum({
filePath,
success: () => {
wx.showToast({
title: '截图已保存至手机相册'
})
}
})
})
}10. 录屏
- 完全同于实时视频,参考 实时视频录屏
11. 错误处理
1. 播放器出错时的重试
- 通过onPlayStateEvent来监听播放状态
- 调用player.retry()来重试
onPlayStateEvent({
type,
detail
}) {
console.log(`历史播放器状态变更。类型:${type}。${detail ? '详情:' + JSON.stringify(detail) : ''}`);
if (type === 'playstart') {
this.setData({
isPlaySuccess: false,
isPlayError: false,
isPlayLoading: true,
isPlayerFull: false,
isPaused: false
})
} else if (type === 'playsuccess') {
this.setData({
isPlaySuccess: true,
isPlayError: false,
isPlayLoading: false,
isPlayerFull: false,
isPaused: false
})
} else if (type === 'playpause') {
this.setData({
isPlaySuccess: true,
isPlayError: false,
isPlayLoading: false,
isPlayerFull: false,
isPaused: true
})
} else if (type === 'playresume') {
this.setData({
isPlaySuccess: false,
isPlayError: false,
isPlayLoading: true,
isPlayerFull: false,
isPaused: false
})
} else if (type === 'playstop' || type === 'playend') {
this.setData({
isPlaySuccess: false,
isPlayError: false,
isPlayLoading: false,
isPlayerFull: false,
isPaused: false
})
} else if(type === 'playerror') {
this.setData({
isPlaySuccess: false,
isPlayError: true,
isPlayLoading: false,
isPlayerFull: false,
isPaused: false
});
}
}
retry() {
this.player.retry()
}2. 开始P2P通道时判断通道是否已满
- 请在每次p2p通道建立成功后调用本方法,避免出现通道已满的情况
- 固定用法,使用sendUserCommand
- topic固定传'channel_full'
- data固定传空
checkIsChannelFull() {
app.xp2pManager
.sendUserCommand(this.data.XP2PDeviceInfo.deviceId, {
cmd: {
topic: `channel_full`,
data: {},
},
})
.then(res => {
if (res[0]?.status === '405' || res[0]?.status === 405) {
this.setData({
isPlaySuccess: false,
isPlayError: false,
isPlayLoading: false,
isPlayerFull: true,
isPaused: false
})
wx.showToast({
title: '通道已满,无法继续播放',
})
}
})
}