|
|
@@ -0,0 +1,176 @@
|
|
|
+<template>
|
|
|
+ <div class="videoBox">
|
|
|
+ <div class="box">
|
|
|
+ <div class="list">
|
|
|
+ <ul>
|
|
|
+ <li
|
|
|
+ v-for="(item, index) in cList"
|
|
|
+ :key="index"
|
|
|
+ :style="`color: ${index === cIndex ? 'red':''}`"
|
|
|
+ @click="play(item.uuid,index)"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <video
|
|
|
+ id="test"
|
|
|
+ crossorigin="anonymous"
|
|
|
+ autoplay
|
|
|
+ playsinline
|
|
|
+ muted
|
|
|
+ controls
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="contrl">
|
|
|
+ <n-button>
|
|
|
+ 播放
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script setup lang="ts">
|
|
|
+import axios from '@/utils/axios'
|
|
|
+import { onMounted, onUnmounted, ref } from 'vue'
|
|
|
+import MD5 from 'js-md5'
|
|
|
+let webRtc: any = null
|
|
|
+const TOKEN = '2fc01ae175e8676b6a3edc32d2d76cb3'
|
|
|
+const cList = ref()
|
|
|
+const cUUID = ref('')
|
|
|
+const cIndex = ref(-1)
|
|
|
+/**
|
|
|
+ * rtc 初始化
|
|
|
+ * @param HOST
|
|
|
+ * @param token
|
|
|
+ * @param uuid
|
|
|
+ * @param callBack
|
|
|
+ * @param profile 0 主 1 子
|
|
|
+ */
|
|
|
+const initWebRtc = async (HOST: String, token: String, uuid: string, callBack: Function, profile:number, startTime:number, endTime:number) => {
|
|
|
+ try {
|
|
|
+ // init RTC
|
|
|
+ const Peer = new RTCPeerConnection() as any
|
|
|
+ // Peer.addTransceiver('audio', { direction: 'recvonly' })
|
|
|
+ Peer.addTransceiver('video', { direction: 'recvonly' })
|
|
|
+ const offer = await Peer.createOffer()
|
|
|
+ await Peer.setLocalDescription(offer)
|
|
|
+ // 监听视频
|
|
|
+ Peer.ontrack = callBack
|
|
|
+
|
|
|
+ Peer.oniceconnectionstatechange = () => {
|
|
|
+ const state = Peer.iceConnectionState
|
|
|
+ console.log('ICE状态', state)
|
|
|
+ }
|
|
|
+
|
|
|
+ Peer.onicegatheringstatechange = () => {
|
|
|
+ console.log('GatheringState: ', Peer.iceGatheringState)
|
|
|
+ }
|
|
|
+
|
|
|
+ Peer.onconnectionstatechange = () => {
|
|
|
+ const state = Peer.connectionState
|
|
|
+ console.log('连接状态', state)
|
|
|
+ }
|
|
|
+ // SDP SRS params
|
|
|
+ const stream = MD5(uuid + profile)
|
|
|
+ const params = {
|
|
|
+ api: `http://${HOST}:1985/rtc/v1/play/?token=${token}&uuid=${uuid}&stream=${uuid + profile}&profile=${profile}`,
|
|
|
+ clientip: null,
|
|
|
+ sdp: offer.sdp,
|
|
|
+ streamurl: `webrtc://${HOST}/replay/${stream}?token=${token}&starttime=${startTime}&endtime=${endTime}&uuid=${uuid}&stream=${stream}&profile=${profile}`,
|
|
|
+ tid: Number(Math.floor(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)
|
|
|
+ }
|
|
|
+ console.log('params', params)
|
|
|
+
|
|
|
+ // 发送offer
|
|
|
+ const res = await window.fetch(params.api, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ body: JSON.stringify(params)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 接收 answer
|
|
|
+ const { sdp } = await res.json()
|
|
|
+ if (sdp) await Peer.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp }))
|
|
|
+
|
|
|
+ return Peer
|
|
|
+ } catch (error) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 播放
|
|
|
+ * @param uuid
|
|
|
+ */
|
|
|
+const play = async (uuid: string, index:number) => {
|
|
|
+ if (!uuid) return
|
|
|
+ if (uuid === cUUID.value) return
|
|
|
+ if (webRtc) webRtc.close()
|
|
|
+ cUUID.value = uuid
|
|
|
+ cIndex.value = index
|
|
|
+ webRtc = await initWebRtc('47.108.192.202', TOKEN, uuid, (event: any) => {
|
|
|
+ const dom = document.getElementById('test') as any
|
|
|
+ const { streams } = event
|
|
|
+ dom.srcObject = streams[0]
|
|
|
+ console.log('track', event)
|
|
|
+ }, 1, 1667836800000, 1667923199000)
|
|
|
+}
|
|
|
+
|
|
|
+// 获取摄像头播放地址
|
|
|
+const getCamer = async () => {
|
|
|
+ const { data } = await axios.post('http://47.108.192.202:8089', {
|
|
|
+ code: 'categorycamera.list',
|
|
|
+ token: TOKEN,
|
|
|
+ body: {}
|
|
|
+ })
|
|
|
+ // 35可控
|
|
|
+ cList.value = data.filter((el: { name: string }) => (el.name.includes('卡哈洛')))
|
|
|
+ console.log('摄像头列表', cList.value)
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getCamer()
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ if (webRtc) webRtc.close()
|
|
|
+})
|
|
|
+</script>
|
|
|
+<style lang="less" scoped>
|
|
|
+.videoBox {
|
|
|
+ .box {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ .list {
|
|
|
+ color: white;
|
|
|
+ li {
|
|
|
+ padding: 10px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: aqua;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ video {
|
|
|
+ width: 800px;
|
|
|
+ height: 500px;
|
|
|
+ background: none;
|
|
|
+ object-fit: fill;
|
|
|
+ font-size: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .contrl {
|
|
|
+ margin-top: 10px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ button {
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|