|
|
@@ -1,401 +0,0 @@
|
|
|
-<script setup lang="ts">
|
|
|
-import { Socket, io } from 'socket.io-client'
|
|
|
-import {
|
|
|
- onMounted, onUnmounted, ref
|
|
|
-} from 'vue'
|
|
|
-import Login from '@/components/login.vue'
|
|
|
-import Gauge from '@/components/gauge.vue'
|
|
|
-import Record from '@/components/record.vue'
|
|
|
-import Battery from '@/components/battery.vue'
|
|
|
-import Loading from '@/components/loading.vue'
|
|
|
-
|
|
|
-const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms))
|
|
|
-const socket = ref(null as null | Socket)
|
|
|
-const HOST = ref('wss://car.caner.top')
|
|
|
-const Peer = ref(null as null | RTCPeerConnection | undefined)
|
|
|
-const isLogin = ref(false)
|
|
|
-const error = ref('')
|
|
|
-const remoteVideo = ref()
|
|
|
-const showLoading = ref(true)
|
|
|
-const contrlState = ref(false)
|
|
|
-const audioState = ref(false)
|
|
|
-// const mutedState = ref(true)
|
|
|
-// const warnAudio = ref(false)// 鸣笛
|
|
|
-const speed = ref(1) // 1低速档 | 2 高速档
|
|
|
-// const muted = ref(true)// 是否静音
|
|
|
-const SpeedValue = ref(0)
|
|
|
-const iceServers = ref([
|
|
|
- {
|
|
|
- urls: [ 'stun:caner.top:3478' ]
|
|
|
- },
|
|
|
- {
|
|
|
- urls: 'turn:caner.top:3478',
|
|
|
- username: 'admin',
|
|
|
- credential: '123456'
|
|
|
- }
|
|
|
-])
|
|
|
-const num = ref(0)
|
|
|
-const winMaxOrMin = ref(false)
|
|
|
-// 发送语音
|
|
|
-function sendAudio(blob: Blob) {
|
|
|
- if (!socket.value && !socket.value!.connected) return
|
|
|
- socket.value?.emit('msg', {
|
|
|
- type: 'Meadia',
|
|
|
- Meadia: blob
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-// // 挡位
|
|
|
-// function Gear(speed: number, num: number) {
|
|
|
-// // 低速档
|
|
|
-// if (speed === 1) {
|
|
|
-// if (num < 116) {
|
|
|
-// // 前
|
|
|
-// num = 120
|
|
|
-// } else if (num >= 120 && num <= 131) {
|
|
|
-// num = 128
|
|
|
-// } else if (num > 140) {
|
|
|
-// // 后
|
|
|
-// num = 140
|
|
|
-// }
|
|
|
-// }
|
|
|
-// // 高速档
|
|
|
-// if (speed === 2) {
|
|
|
-// if (num < 96) {
|
|
|
-// num = 96
|
|
|
-// } else if (num >= 120 && num <= 131) {
|
|
|
-// num = 128
|
|
|
-// } else if (num > 160) {
|
|
|
-// num = 160
|
|
|
-// }
|
|
|
-// }
|
|
|
-// return num
|
|
|
-// }
|
|
|
-
|
|
|
-// 登录
|
|
|
-function login(data: { name: string, roomID: string }) {
|
|
|
- if (socket.value) {
|
|
|
- socket.value.auth = {
|
|
|
- roomID: data.roomID,
|
|
|
- name: data.name
|
|
|
- }
|
|
|
- socket.value.connect()
|
|
|
- } else {
|
|
|
- error.value = '服务器连接失败'
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// // 手柄数据
|
|
|
-// function ControlData() {
|
|
|
-// const data = navigator.getGamepads()
|
|
|
-// const db = data[0]
|
|
|
-// if (!db) return
|
|
|
-// // 挡位选择AB
|
|
|
-// if (db.buttons[1].touched) speed.value = 2
|
|
|
-// if (db.buttons[0].touched) speed.value = 1
|
|
|
-// // 语音按键R2
|
|
|
-// audioState.value = db.buttons[7].touched
|
|
|
-// // 静音X3
|
|
|
-// mutedState.value = db.buttons[3].touched
|
|
|
-// // 播放警笛Y2
|
|
|
-// warnAudio.value = db.buttons[2].touched
|
|
|
-// // console.log(db.buttons);
|
|
|
-// const params = {
|
|
|
-// v0: Math.floor(db.axes[0] * 128 + 128),
|
|
|
-// v1: Math.floor(db.axes[1] * 128 + 128),
|
|
|
-// v2: Math.floor(db.axes[2] * 128 + 128),
|
|
|
-// v3: Gear(speed.value, Math.floor(db.axes[3] * 128 + 128))
|
|
|
-// }
|
|
|
-
|
|
|
-// if (socket.value && socket.value.connected) socket.value.emit('msg', { type: 'conctrl', conctrl: params })
|
|
|
-// requestAnimationFrame(ControlData)
|
|
|
-// }
|
|
|
-
|
|
|
-// // 手柄连接
|
|
|
-// function conControl() {
|
|
|
-// contrlState.value = true
|
|
|
-// ControlData()
|
|
|
-// }
|
|
|
-
|
|
|
-// // 手柄断开连接
|
|
|
-// function disControl() {
|
|
|
-// contrlState.value = false
|
|
|
-// cancelAnimationFrame(ControlData as any)
|
|
|
-// }
|
|
|
-
|
|
|
-// 关闭
|
|
|
-function close(err?: string) {
|
|
|
- if (Peer.value) Peer.value?.close()
|
|
|
- if (remoteVideo.value) remoteVideo.value.srcObject = null
|
|
|
- if (socket.value) socket.value?.disconnect()
|
|
|
- isLogin.value = false
|
|
|
- showLoading.value = false
|
|
|
- error.value = err || ''
|
|
|
- socket.value = null
|
|
|
- Peer.value = null
|
|
|
- num.value = 0
|
|
|
- // cancelAnimationFrame(ControlData as any)
|
|
|
- // window.removeEventListener('gamepadconnected', conControl)
|
|
|
- // window.removeEventListener('gamepaddisconnected', disControl)
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 网络连接
|
|
|
- * @param host
|
|
|
- */
|
|
|
-function intSoketRtc(host: string) {
|
|
|
- // int socket
|
|
|
- socket.value = io(host, {
|
|
|
- autoConnect: false,
|
|
|
- transports: [ 'websocket' ]
|
|
|
- })
|
|
|
-
|
|
|
- // socket
|
|
|
- socket.value.on('connect', () => {
|
|
|
- try {
|
|
|
- isLogin.value = true
|
|
|
- // init webrtc
|
|
|
- Peer.value = new RTCPeerConnection({
|
|
|
- iceServers: iceServers.value,
|
|
|
- bundlePolicy: 'max-bundle'
|
|
|
- })
|
|
|
-
|
|
|
- // listen state
|
|
|
- Peer.value.onicegatheringstatechange = () => {
|
|
|
- console.log('GatheringState: ', Peer.value?.iceGatheringState)
|
|
|
- if (Peer.value?.iceGatheringState === 'complete') {
|
|
|
- const answer = Peer.value.localDescription
|
|
|
- socket.value?.emit('msg', answer)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // listen track
|
|
|
- Peer.value.ontrack = async (evt) => {
|
|
|
- console.log('track', evt)
|
|
|
- remoteVideo.value.srcObject = evt.streams[0]
|
|
|
- }
|
|
|
-
|
|
|
- // listen changestate·
|
|
|
- Peer.value.oniceconnectionstatechange = async () => {
|
|
|
- const state = Peer.value?.iceConnectionState
|
|
|
- console.log('ICE状态', state)
|
|
|
- if (
|
|
|
- state === 'failed'
|
|
|
- || state === 'disconnected'
|
|
|
- || state === 'closed'
|
|
|
- ) {
|
|
|
- close('P2P通信失败')
|
|
|
- }
|
|
|
-
|
|
|
- // ICE连接成功|初始化摇杆
|
|
|
- if (state === 'connected') {
|
|
|
- // init Control
|
|
|
- // window.addEventListener('gamepadconnected', conControl)
|
|
|
- // window.addEventListener('gamepaddisconnected', disControl)
|
|
|
- window.$electron.onContrl((db: ArrayBuffer) => {
|
|
|
- console.log('123', db)
|
|
|
- })
|
|
|
- console.log('成功?')
|
|
|
-
|
|
|
- await sleep(3000)
|
|
|
- showLoading.value = false
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- close('webrtc初始化失败')
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- socket.value.on('msg', async (data) => {
|
|
|
- const key = data.type
|
|
|
- if (key === 'offer') {
|
|
|
- await Peer.value?.setRemoteDescription(data)
|
|
|
- const answer = await Peer.value?.createAnswer()
|
|
|
- await Peer.value?.setLocalDescription(answer)
|
|
|
- } else if (key === 'power') {
|
|
|
- console.log('电量', data)
|
|
|
- } else if (key === 'speed') {
|
|
|
- const d = Math.floor(data.data)
|
|
|
- SpeedValue.value = d
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- socket.value.on('joined', async () => socket.value?.emit('msg', { type: 'startRTC' }))
|
|
|
-
|
|
|
- socket.value.on('leaved', () => close('车端断开'))
|
|
|
-
|
|
|
- socket.value.on('connect_error', () => close('服务器连接失败'))
|
|
|
-}
|
|
|
-
|
|
|
-// watch(() => mutedState.value, (v) => {
|
|
|
-// if (v) {
|
|
|
-// const state = !!(num.value % 2)
|
|
|
-// muted.value = state
|
|
|
-// if (socket.value && socket.value.connected) socket.value.emit('msg', { type: 'contrlAudio', contrlAudio: state })
|
|
|
-// num.value++
|
|
|
-// }
|
|
|
-// })
|
|
|
-// watch(() => warnAudio.value, (v) => {
|
|
|
-// if (v && socket.value && socket.value.connected) socket.value.emit('msg', { type: 'warnAudio', warnAudio: v })
|
|
|
-// })
|
|
|
-
|
|
|
-window.$electron?.on('contrlData', (db: Uint8Array) => {
|
|
|
- // 拨片 2 是右拨片,1是左拨片,0是取消
|
|
|
- const bp = db[6] === 2 ? '右拨片' : db[6] === 1 ? '左拨片' : ''
|
|
|
- const fx = parseInt(db[44].toString(), 10) // 转10进制
|
|
|
- const fxp = fx < 125 ? `左轮${fx}` : fx > 130 ? `右轮${fx}` : ''
|
|
|
- const ym = parseInt(db[46].toString(), 10) // 油门
|
|
|
- console.log(66, bp, fxp, 255 - ym)
|
|
|
-})
|
|
|
-
|
|
|
-function titleEvent(type: string) {
|
|
|
- if (type === 'maxWin') winMaxOrMin.value = !winMaxOrMin.value
|
|
|
- window.$electron?.send(type, winMaxOrMin.value)
|
|
|
-}
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- intSoketRtc(HOST.value)
|
|
|
-})
|
|
|
-onUnmounted(() => {
|
|
|
- close()
|
|
|
-})
|
|
|
-</script>
|
|
|
-<template>
|
|
|
- <template v-if="isLogin">
|
|
|
- <video
|
|
|
- ref="remoteVideo"
|
|
|
- autoplay
|
|
|
- playsinline
|
|
|
- muted
|
|
|
- />
|
|
|
- <div class="marke">
|
|
|
- <div class="marke-left">
|
|
|
- <!-- 手柄状态 -->
|
|
|
- <Icon
|
|
|
- name="gemePad"
|
|
|
- :size="40"
|
|
|
- :color="contrlState ? '#00CED1' : 'rgba(0, 0, 0, 0.3)'"
|
|
|
- />
|
|
|
- <!-- 音频状态 -->
|
|
|
- <Record
|
|
|
- class="marke-audio"
|
|
|
- :size="33"
|
|
|
- :audio-state="audioState"
|
|
|
- @callBack="sendAudio"
|
|
|
- />
|
|
|
- <!-- 喇叭状态 -->
|
|
|
- <Icon
|
|
|
- name="audio"
|
|
|
- :size="30"
|
|
|
- :color="contrlState ? '#00CED1' : 'rgba(0, 0, 0, 0.3)'"
|
|
|
- />
|
|
|
- <!-- 电量状态 -->
|
|
|
- <Battery :quantity="33" />
|
|
|
- </div>
|
|
|
- <div class="marke-right">
|
|
|
- <Icon
|
|
|
- name="min"
|
|
|
- :size="23"
|
|
|
- color="#fff"
|
|
|
- @click="titleEvent('minWin')"
|
|
|
- />
|
|
|
- <Icon
|
|
|
- :name="winMaxOrMin ? 'max' : 'maxMin'"
|
|
|
- :size="23"
|
|
|
- color="#fff"
|
|
|
- @click="titleEvent('maxWin')"
|
|
|
- />
|
|
|
- <Icon
|
|
|
- name="close"
|
|
|
- :size="23"
|
|
|
- color="#fff"
|
|
|
- @click="titleEvent('closeWin')"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 码数 -->
|
|
|
- <div class="gauge">
|
|
|
- <Gauge
|
|
|
- :value="SpeedValue"
|
|
|
- :gears="speed"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <Loading v-if="showLoading" />
|
|
|
- </template>
|
|
|
- <Login
|
|
|
- v-else
|
|
|
- :err="error"
|
|
|
- @loginBack="login"
|
|
|
- />
|
|
|
-</template>
|
|
|
-<style scoped lang="scss">
|
|
|
-video {
|
|
|
- background: black;
|
|
|
- object-fit: fill;
|
|
|
- font-size: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.marke {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: available;
|
|
|
- width: -webkit-fill-available;
|
|
|
- height: 0.5rem;
|
|
|
- z-index: 1;
|
|
|
- background: #666666;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: flex-end;
|
|
|
- overflow: hidden;
|
|
|
- border-top-left-radius: 20px;
|
|
|
- border-top-right-radius: 20px;
|
|
|
- padding-left: 1.5rem;
|
|
|
-
|
|
|
- &>div {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-
|
|
|
- &-left {
|
|
|
- justify-content: center;
|
|
|
- flex: 1;
|
|
|
- -webkit-app-region: drag;
|
|
|
-
|
|
|
- &>* {
|
|
|
- margin: 0 0.18rem;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- &-right {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-right: 0.15rem;
|
|
|
-
|
|
|
- &>* {
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &:not(:first-child) {
|
|
|
- margin-left: 0.15rem;
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.gauge {
|
|
|
- position: fixed;
|
|
|
- bottom: 0;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, 0);
|
|
|
- width: 3rem;
|
|
|
- height: 1.65rem;
|
|
|
- z-index: 9;
|
|
|
-}
|
|
|
-
|
|
|
-/* 隐藏滚动条 */
|
|
|
-::-webkit-scrollbar {
|
|
|
- width: 0 !important;
|
|
|
- display: none;
|
|
|
-}
|
|
|
-</style>
|