wx-starlock/pages/p2p/p2pPlayer.vue

582 lines
16 KiB
Vue
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.

<template>
<view>
<view>
<!-- #ifdef MP-WEIXIN -->
<iot-p2p-player-with-mjpg
v-if="deviceInfo"
id="playerRef"
:deviceInfo="deviceInfo"
:xp2pInfo="xp2pInfo"
:rotate="90"
:muted="isMute"
mode="RTC"
:acceptPlayerEvents="true"
soundMode="speaker"
sceneType="live"
:streamQuality="range[index].value"
:minCache="0.2"
:maxCache="0.8"
:fill="true"
orientation="horizontal"
@playsuccess="handlePlaySuccess"
>
</iot-p2p-player-with-mjpg>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<video
:key="videoKey"
autoplay
id="playerRef"
v-if="url"
:muted="isMute"
:src="urlPrefix"
:advanced="advancedOptions"
object-fit="cover"
class="w-[100vw] h-[100vh]"
:is-live="true"
:controls="false"
:show-progress="false"
:show-fullscreen-btn="false"
:show-play-btn="false"
:enable-progress-gesture="false"
:vslide-gesture="true"
/>
<!-- #endif -->
</view>
<view v-if="!isVideoLoaded">
<image
src="https://oss-lock.xhjcn.ltd/mp/background_monitor.png"
class="w-full h-full absolute top-0 left-0"
></image>
<view
class="fixed bottom-[calc(48rpx+env(safe-area-inset-bottom))] w-full flex justify-center"
>
<up-loading-icon
size="70rpx"
:vertical="true"
textSize="28rpx"
text="连接中"
mode="circle"
></up-loading-icon>
</view>
</view>
<cover-view v-if="isVideoLoaded">
<cover-view
:style="{
top: isApp ? '60px' : buttonInfo.bottom + 15 + 'px'
}"
class="fixed right-32"
>
<cover-view
class="bg-[rgba(0,0,0,0.35)] rounded-full px-4 py-1.5"
style="background-color: rgba(0, 0, 0, 0.35)"
>
<cover-view class="text-white text-xs" @click="showQualitySelector">
{{ range[index].name }}
</cover-view>
</cover-view>
</cover-view>
<cover-view
class="fixed bottom-[calc(32rpx+env(safe-area-inset-bottom))] left-1/2 -translate-x-1/2 w-622"
>
<cover-view
class="bg-[rgba(0,0,0,0.3)] rounded-xl p-4 shadow-lg"
style="background-color: rgba(0, 0, 0, 0.3)"
>
<cover-view class="flex items-center justify-around mx-15">
<cover-view class="relative">
<cover-image
v-show="isMute"
src="https://oss-lock.xhjcn.ltd/mp/icon_mute.png"
:class="isApp ? 'size-48' : 'size-48 p-2'"
></cover-image>
<cover-image
v-show="!isMute"
src="https://oss-lock.xhjcn.ltd/mp/icon_not_mute.png"
:class="isApp ? 'size-48' : 'size-48 p-2'"
></cover-image>
<cover-view
@click="toggleMute"
class="absolute top-0 left-0 w-full h-full size-48 p-2"
></cover-view>
</cover-view>
<cover-view class="relative">
<cover-image
src="https://oss-lock.xhjcn.ltd/mp/icon_screenshot.png"
:class="isApp ? 'size-48' : 'size-48 p-2'"
></cover-image>
<cover-view
@click="handleScreenshot"
class="absolute top-0 left-0 w-full h-full"
></cover-view>
</cover-view>
</cover-view>
<cover-view class="flex items-center justify-between text-white mt-2 px-10">
<cover-view class="flex flex-col items-center">
<cover-view
class="bg-white w-80 h-80 rounded-full flex items-center justify-center relative"
>
<cover-image
:src="
isVoice
? 'https://oss-lock.xhjcn.ltd/mp/icon_microphone.png'
: 'https://oss-lock.xhjcn.ltd/mp/icon_no_microphone.png'
"
class="w-55 h-55"
></cover-image>
<cover-view
@click="toggleVoice"
class="absolute top-0 left-0 w-full h-full"
></cover-view>
</cover-view>
<cover-view class="mt-2 text-center whitespace-nowrap text-xs">{{
isVoice ? '点击停止' : '点击说话'
}}</cover-view>
</cover-view>
<cover-view class="flex flex-col items-center">
<cover-view
class="bg-[#eb292b] w-80 h-80 rounded-full flex items-center justify-center relative"
>
<cover-image
src="https://oss-lock.xhjcn.ltd/mp/icon_hang_up.png"
class="w-60 h-60"
></cover-image>
<cover-view
@click="handleHangUp"
class="absolute top-0 left-0 w-full h-full"
></cover-view>
</cover-view>
<cover-view class="mt-2 text-center whitespace-nowrap text-xs">挂断</cover-view>
</cover-view>
<cover-view class="flex flex-col items-center">
<cover-view
class="bg-[#63b8af] w-80 h-80 rounded-full flex items-center justify-center relative"
>
<cover-image
src="https://oss-lock.xhjcn.ltd/mp/icon_lock_white.png"
class="w-60 h-60"
></cover-image>
<cover-view
@click="handleLock"
class="absolute top-0 left-0 w-full h-full"
></cover-view>
</cover-view>
<cover-view class="mt-2 text-center whitespace-nowrap text-xs">开锁</cover-view>
</cover-view>
</cover-view>
</cover-view>
</cover-view>
</cover-view>
<view class="safe-area-bottom"></view>
<!-- #ifdef MP-WEIXIN -->
<iot-p2p-voice
v-if="deviceInfo"
id="voiceComponent"
:deviceInfo="deviceInfo"
:xp2pInfo="xp2pInfo"
voiceType="Pusher"
>
</iot-p2p-voice>
<!-- #endif -->
</view>
</template>
<script setup>
import { onMounted, ref, computed } from 'vue'
import { onUnload } from '@dcloudio/uni-app'
import { useBluetoothStore } from '@/stores/bluetooth'
import { useBasicStore } from '@/stores/basic'
import { useUserStore } from '@/stores/user'
import { passthrough } from '@/api/sdk'
import { getLockNetTokenRequest } from '@/api/lock'
// #ifdef MP-WEIXIN
import * as A from './exportForXp2pPlugin'
import * as B from './exportForPlayerPlugin'
// #endif
// #ifdef APP-PLUS
import {
startServiceFunction,
stopServiceFunction,
runSendServiceFunction,
stopSendServiceFunction,
dataSendFunction
} from '@/uni_modules/xhj-tencent-xp2p'
import { initAudio, onStartRecord, stopRecord, releaseRecord } from '@/uni_modules/xhj-record'
// #endif
const $bluetooth = useBluetoothStore()
const $basic = useBasicStore()
const $user = useUserStore()
const buttonInfo = ref(null)
const isApp = ref(false)
const onlineToken = ref('0')
const lockId = ref()
const time = ref(0)
const pending = ref(false)
const index = ref(1)
const range = ref([
{ name: '标清', value: 'standard' },
{ name: '高清', value: 'high' },
{ name: '超清', value: 'super' }
])
const deviceInfo = ref()
const xp2pInfo = ref()
const url = ref()
const isVoice = ref(false)
const isMute = ref(false)
const isVideoLoaded = ref(false)
const videoKey = ref(Date.now())
const cleanupCalled = ref(false)
const advancedOptions = ref([
{
key: 'videotoolbox',
value: 0,
type: 'player'
},
{
key: 'framedrop',
value: 5,
type: 'player'
},
{
key: 'skip_loop_filter',
value: 48,
type: 'player'
}
])
const urlPrefix = computed(() => {
const data =
url.value + `ipc.flv?action=live&channel=0&quality=${range.value[index.value].value}`
return data
})
const cleanupResources = async () => {
if (cleanupCalled.value) return
cleanupCalled.value = true
// #ifdef APP-PLUS
// 停止录音
url.value = null
isVideoLoaded.value = false
if (isVoice.value) {
stopRecord()
}
releaseRecord()
if (deviceInfo.value) {
await stopSendServiceFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`)
await stopServiceFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`)
}
// #endif
// #ifdef MP-WEIXIN
const page = getCurrentPages().pop()
if (page) {
const player = page.selectComponent('#playerRef')
if (player && player.stopAll) {
player.stopAll()
}
}
// #endif
}
onMounted(async () => {
// #ifdef APP-PLUS
initAudio()
isApp.value = true
// #endif
// #ifdef MP-WEIXIN
console.log(A, B)
// #endif
buttonInfo.value = await $basic.getButtonInfo()
const { code, data, message } = await passthrough({
request_method: 'GET',
request_uri: '/api/v1/tencentYun/getDeviceDetailData',
post_args: {
lockId: $bluetooth.currentLockInfo.lockId
}
})
// #ifdef MP-WEIXIN
if (code === 0) {
deviceInfo.value = {
deviceId: `${data.productId}/${data.deviceName}`,
productId: data.productId,
deviceName: data.deviceName
}
xp2pInfo.value = data.xp2pInfo
} else {
$basic.backAndToast(message)
}
await getServeTime()
// #endif
// #ifdef APP-PLUS
if (code === 0) {
deviceInfo.value = data
const params = {
appKey: 'aanuJXFtISXFYVVsd',
appSecret: 'SsnOMHJUcazCvpULSVWY',
productId: deviceInfo.value.productId,
deviceName: deviceInfo.value.deviceName,
xp2pInfo: deviceInfo.value.xp2pInfo
}
const result = await startServiceFunction(
params.appKey,
params.appSecret,
params.productId,
params.deviceName,
params.xp2pInfo
)
console.log('初始化SDK结果', result)
if (result?.code === 0) {
handlePlaySuccess()
setTimeout(() => {
url.value = result.data.url
runSendServiceFunction(
`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`,
'channel=0',
false
)
}, 0)
} else {
$basic.backAndToast(message)
}
} else {
$basic.backAndToast(message)
}
// #endif
})
onUnload(() => {
cleanupResources()
})
const showQualitySelector = () => {
uni.showActionSheet({
itemList: range.value.map(item => item.name),
success: res => {
const value = res.tapIndex
if (index.value === value) {
return
}
index.value = value
videoKey.value = Date.now()
}
})
}
const handleScreenshot = () => {
// #ifdef MP-WEIXIN
const page = getCurrentPages().pop()
const player = page.selectComponent('#playerRef')
player.snapshot().then(res => {
uni.saveImageToPhotosAlbum({
filePath: res.tempImagePath,
success: () => {
uni.showToast({
title: '截图已保存到相册',
icon: 'none'
})
},
fail: () => {
uni.showToast({
title: '保存失败',
icon: 'none'
})
}
})
})
// #endif
// #ifdef APP-PLUS
uni.showModal({
title: '提示',
content:
'由于技术限制APP端无法直接截取视频画面。请使用手机系统截图功能\n\niOS同时按下电源键+音量上键\nAndroid同时按下电源键+音量下键',
confirmText: '我知道了',
showCancel: false
})
// #endif
}
const handleHangUp = () => {
cleanupResources()
uni.navigateBack()
}
const handleLock = () => {
uni.showModal({
title: '提示',
content: '是否确认开锁?',
success: res => {
if (res.confirm) {
openDoorOperate()
}
}
})
}
const getServeTime = async () => {
const { code, data } = await $bluetooth.updateServerTimestamp()
if (code === 0) {
time.value = parseInt((data.date - new Date().getTime()) / 1000, 10)
}
}
const openDoorOperate = async () => {
const timestamp = new Date().getTime()
if (pending.value) {
return
}
const netWork = await $basic.getNetworkType()
if (!netWork) {
return
}
if ($bluetooth.currentLockInfo.appUnlockOnline) {
const result = await getNetToken()
if (!result) {
pending.value = false
return
}
}
uni.showLoading({
title: '开锁中'
})
uni.vibrateLong()
pending.value = true
const openMode = $bluetooth.currentLockInfo.appUnlockOnline ? 1 : 0
const { code } = await $bluetooth.openDoor({
name: $bluetooth.currentLockInfo.lockName,
uid: $user.userInfo.uid.toString(),
openMode,
openTime: parseInt(new Date().getTime() / 1000, 10) + time.value,
onlineToken: onlineToken.value
})
$bluetooth
.syncRecord({
keyId: $bluetooth.keyId.toString(),
uid: $user.userInfo.uid.toString()
})
.then(() => {
$bluetooth.closeBluetoothConnection()
})
// #ifdef MP-WEIXIN
uni.reportEvent('open_door', {
result: code,
duration: new Date().getTime() - timestamp
})
// #endif
if (code === 0) {
uni.showToast({
title: `开门成功`,
icon: 'none'
})
} else if (code === 7) {
uni.showToast({
title: `钥匙过期`,
icon: 'none'
})
} else if (code === 13) {
uni.showToast({
title: `钥匙当前不可用`,
icon: 'none'
})
} else if (code === -1) {
uni.showToast({
title: `开锁失败`,
icon: 'none'
})
}
uni.hideLoading()
pending.value = false
}
const getNetToken = async () => {
const { code, data, message } = await getLockNetTokenRequest({
lockId: lockId.value
})
if (code === 0) {
onlineToken.value = data.token
return true
}
uni.showToast({
title: message,
icon: 'none'
})
return false
}
const toggleVoice = () => {
// #ifdef MP-WEIXIN
if (isVoice.value) {
isVoice.value = false
const page = getCurrentPages().pop()
const voice = page.selectComponent('#voiceComponent')
if (voice) {
voice.stopVoice()
}
} else {
uni.vibrateLong()
isVoice.value = true
const page = getCurrentPages().pop()
const voice = page.selectComponent('#voiceComponent')
if (voice) {
voice.startVoice()
}
}
// #endif
// #ifdef APP-PLUS
if (isVoice.value) {
isVoice.value = false
stopRecord()
} else {
uni.vibrateLong()
isVoice.value = true
onStartRecord(callback)
}
// #endif
}
const callback = async audioData => {
const result = await dataSendFunction(
`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`,
audioData
)
console.log(
`数据传输结果:${result?.data?.result ? result?.data?.result : result?.data?.dynamicJSONFields?.result}`,
audioData
)
}
const handlePlaySuccess = () => {
isVideoLoaded.value = true
}
const toggleMute = () => {
isMute.value = !isMute.value
}
</script>
<style lang="scss" scoped>
:deep(.mjpg-player--iot-player) {
height: 100vh !important;
}
</style>