Browse Source

按钮限制,增加静音按钮,优化mqtt,webrtc写法

Caner 1 year ago
parent
commit
695ff3b027

+ 7 - 1
README.md

@@ -1,4 +1,4 @@
-# 控制端 使用Tauri
+# control use tauri
 ```
 ```
 1.tauri 兼容性存在问题
 1.tauri 兼容性存在问题
     webrtc 在mac m2 safari 16.1中 safari 无法正确播放opus格式音频
     webrtc 在mac m2 safari 16.1中 safari 无法正确播放opus格式音频
@@ -13,3 +13,9 @@
 ```
 ```
 1. cargo cache -a
 1. cargo cache -a
 ```
 ```
+
+### can
+- [x] 车辆信号
+- [x] 车辆电量
+- [x] 开启录音
+- [x] 开启静音

+ 8 - 9
package.json

@@ -23,18 +23,17 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@tauri-apps/cli": "^2.2.7",
     "@tauri-apps/cli": "^2.2.7",
-    "@typescript-eslint/parser": "^8.15.0",
-    "@vitejs/plugin-vue": "^5.2.0",
-    "eslint": "^8.2.0",
+    "@typescript-eslint/parser": "^8.24.0",
+    "@vitejs/plugin-vue": "^5.2.1",
+    "eslint": "^8.57.0",
     "eslint-config-airbnb-base": "^15.0.0",
     "eslint-config-airbnb-base": "^15.0.0",
     "eslint-plugin-import": "^2.31.0",
     "eslint-plugin-import": "^2.31.0",
-    "eslint-plugin-vue": "^9.31.0",
-    "sass": "^1.81.0",
-    "typescript": "^5.6.3",
-    "vite": "^5.4.11",
-    "vite-plugin-compression": "^0.5.1",
+    "eslint-plugin-vue": "^9.32.0",
+    "sass": "^1.85.0",
+    "typescript": "^5.7.3",
+    "vite": "^6.1.1",
     "vite-plugin-eslint": "^1.8.1",
     "vite-plugin-eslint": "^1.8.1",
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-svg-icons": "^2.0.1",
-    "vue-tsc": "^2.1.10"
+    "vue-tsc": "^2.2.2"
   }
   }
 }
 }

+ 8 - 1
src/App.vue

@@ -29,10 +29,17 @@ const show = computed(() => store.loading)
 
 
 watch(() => store.mqtt_message, (val) => {
 watch(() => store.mqtt_message, (val) => {
   console.log('顶级监听', val)
   console.log('顶级监听', val)
-  if (val.type === 'connect') {
+  if (val.type === 'MqttConnect') {
     store.setLoading(true)
     store.setLoading(true)
     router.push('/room')
     router.push('/room')
   }
   }
+  // mqtt ERROR
+  if (store.errorDic[val.type]) {
+    store.setLoading(false)
+    store.setRtcConnected(false)
+    mqtt.disconnect()
+    router.push('/')
+  }
 })
 })
 
 
 provide('MQTT', mqtt)
 provide('MQTT', mqtt)

File diff suppressed because it is too large
+ 3 - 0
src/assets/icons/audio.svg


+ 2 - 4
src/assets/native-plugin.ts

@@ -6,11 +6,9 @@ import {
 } from 'naive-ui'
 } from 'naive-ui'
 
 
 const naive = create({
 const naive = create({
-  components: [
-    NConfigProvider,
+  components: [ NConfigProvider,
     NNotificationProvider,
     NNotificationProvider,
-    NButton
-  ]
+    NButton ]
 })
 })
 
 
 export default naive
 export default naive

+ 29 - 0
src/components/audio.vue

@@ -0,0 +1,29 @@
+<template>
+  <Icon
+    name="audio"
+    :size="20"
+    :color="show && rtcConnected ? '#00CED1' : 'rgba(255, 255, 255, 0.5)'"
+    style="margin-left: 5px;"
+    @click="change"
+  />
+</template>
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import useStore from '@/store'
+
+const show = ref(true)
+const store = useStore()
+const rtcConnected = computed(() => store.rtcConnected)
+const emit = defineEmits<{(evt: 'callBack', value: boolean): void }>()
+
+function change() {
+  if (!rtcConnected.value) return
+  show.value = !show.value
+  emit('callBack', show.value)
+}
+</script>
+<style lang="scss" scoped>
+svg {
+  cursor: pointer;
+}
+</style>

+ 7 - 7
src/components/battery.vue

@@ -6,11 +6,11 @@
     <div class="electric-panel">
     <div class="electric-panel">
       <div
       <div
         class="electric-panel-remainder"
         class="electric-panel-remainder"
-        :style="{ width: quantity + '%' }"
+        :style="{ width: value + '%' }"
       />
       />
     </div>
     </div>
     <div class="electric-berText">
     <div class="electric-berText">
-      {{ quantity }}%
+      {{ value }}%
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
@@ -19,16 +19,16 @@
 import { computed } from 'vue'
 import { computed } from 'vue'
 
 
 const props = withDefaults(defineProps<{
 const props = withDefaults(defineProps<{
-  quantity: number
+  value: number
 }>(), {
 }>(), {
-  quantity: 0
+  value: 0
 })
 })
 const bgClass = computed(() => {
 const bgClass = computed(() => {
-  if (props.quantity >= 50) {
+  if (props.value >= 50) {
     return 'success'
     return 'success'
-  } if (props.quantity >= 20) {
+  } if (props.value >= 20) {
     return 'warning'
     return 'warning'
-  } if (props.quantity >= 1) {
+  } if (props.value >= 1) {
     return 'danger'
     return 'danger'
   }
   }
   return ''
   return ''

+ 6 - 20
src/components/gauge.vue

@@ -22,38 +22,26 @@ const option = ref({
   series: {
   series: {
     name: 'Pressure',
     name: 'Pressure',
     type: 'gauge',
     type: 'gauge',
-    itemStyle: {
-      color: '#FFFFFF'
-    },
+    itemStyle: { color: '#FFFFFF' },
     startAngle: 180,
     startAngle: 180,
     max: 60,
     max: 60,
     endAngle: 0,
     endAngle: 0,
-    axisLine: {
-      lineStyle: {
-        width: 1
-      }
-    },
+    axisLine: { lineStyle: { width: 1 } },
     axisTick: {
     axisTick: {
       distance: 0,
       distance: 0,
       length: 10,
       length: 10,
-      lineStyle: {
-        color: '#FFFFFF'
-      }
+      lineStyle: { color: '#FFFFFF' }
     },
     },
     splitLine: {
     splitLine: {
       length: 15,
       length: 15,
       distance: 0,
       distance: 0,
-      lineStyle: {
-        color: '#FFFFFF'
-      }
+      lineStyle: { color: '#FFFFFF' }
     },
     },
     axisLabel: {
     axisLabel: {
       distance: 8,
       distance: 8,
       color: '#FFFFFF'
       color: '#FFFFFF'
     },
     },
-    progress: {
-      show: true
-    },
+    progress: { show: true },
     radius: '190%',
     radius: '190%',
     center: [ '50%', '98%' ],
     center: [ '50%', '98%' ],
     detail: {
     detail: {
@@ -78,9 +66,7 @@ const option = ref({
         }
         }
       }
       }
     },
     },
-    pointer: {
-      show: false
-    },
+    pointer: { show: false },
     data: [ 0 ]
     data: [ 0 ]
   }
   }
 })
 })

+ 9 - 3
src/components/mic.vue

@@ -2,7 +2,7 @@
   <Icon
   <Icon
     name="mic"
     name="mic"
     :size="22"
     :size="22"
-    :color="show ? '#00CED1' : 'rgba(255, 255, 255, 0.5)'"
+    :color="show && rtcConnected ? '#00CED1' : 'rgba(255, 255, 255, 0.5)'"
     style="margin-left: 5px;"
     style="margin-left: 5px;"
     @click="change"
     @click="change"
   />
   />
@@ -10,11 +10,16 @@
 
 
 <script setup lang='ts'>
 <script setup lang='ts'>
 import { useNotification } from 'naive-ui'
 import { useNotification } from 'naive-ui'
-import { onMounted, onUnmounted, ref } from 'vue'
+import {
+  computed, onMounted, onUnmounted, ref
+} from 'vue'
+import useStore from '@/store'
 
 
 const emit = defineEmits<{(evt: 'callBack', value: Blob): void }>()
 const emit = defineEmits<{(evt: 'callBack', value: Blob): void }>()
+const store = useStore()
 const notice = useNotification()
 const notice = useNotification()
 const show = ref(false)
 const show = ref(false)
+const rtcConnected = computed(() => store.rtcConnected)
 let chunks = [] as Blob[]
 let chunks = [] as Blob[]
 let audio = null as null | MediaRecorder
 let audio = null as null | MediaRecorder
 
 
@@ -45,9 +50,10 @@ function distory() {
 }
 }
 
 
 function change() {
 function change() {
+  if (!rtcConnected.value) return
   show.value = !show.value
   show.value = !show.value
   if (!audio) return
   if (!audio) return
-  show.value ? audio.start() : audio.stop()
+  if (show.value) { audio.start() } else { audio.stop() }
   console.log('mic', show.value)
   console.log('mic', show.value)
 }
 }
 
 

+ 3 - 3
src/components/signal.vue

@@ -14,9 +14,9 @@
 import { ref, watch } from 'vue'
 import { ref, watch } from 'vue'
 
 
 const props = withDefaults(defineProps<{
 const props = withDefaults(defineProps<{
-  signalValue: number
+  value: number
 }>(), {
 }>(), {
-  signalValue: 0
+  value: 0
 })
 })
 
 
 const list = ref([
 const list = ref([
@@ -42,7 +42,7 @@ const list = ref([
   }
   }
 ])
 ])
 
 
-watch(() => props.signalValue, (v: number) => {
+watch(() => props.value, (v: number) => {
   list.value.forEach((el: { id: number, class: string }) => {
   list.value.forEach((el: { id: number, class: string }) => {
     if (el.id <= v) {
     if (el.id <= v) {
       if (v <= 2) {
       if (v <= 2) {

+ 2 - 4
src/components/topBar.vue

@@ -37,14 +37,12 @@ import useStore from '@/store'
 const appWindow = new Window('main')
 const appWindow = new Window('main')
 withDefaults(defineProps<{
 withDefaults(defineProps<{
   showBtn?: boolean
   showBtn?: boolean
-}>(), {
-  showBtn: true
-})
+}>(), { showBtn: true })
 const route = useRoute()
 const route = useRoute()
 const store = useStore()
 const store = useStore()
 
 
 function close() {
 function close() {
-  if (route.path !== '/') store.setLoading(false); store.setMqttMessage({ type: 'disconnect' })
+  if (route.path !== '/') store.setLoading(false); store.setMqttMessage({ type: 'Mqttdisconnect' })
   if (route.path === '/') appWindow.close()
   if (route.path === '/') appWindow.close()
 }
 }
 </script>
 </script>

+ 1 - 3
src/main.ts

@@ -1,8 +1,6 @@
 import { createApp } from 'vue'
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
 import { createPinia } from 'pinia'
-import {
-  createRouter, createWebHistory, RouteRecordRaw
-} from 'vue-router'
+import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
 import App from '@/App.vue'
 import App from '@/App.vue'
 import naive from '@/assets/native-plugin'
 import naive from '@/assets/native-plugin'
 import Icon from '@/components/icon.vue'
 import Icon from '@/components/icon.vue'

+ 1 - 3
src/pages/login/route.ts

@@ -3,8 +3,6 @@ import login from './index.vue'
 
 
 export default {
 export default {
   path: '/',
   path: '/',
-  meta: {
-    authorize: true
-  },
+  meta: { authorize: true },
   component: login
   component: login
 } as RouteRecordRaw
 } as RouteRecordRaw

+ 45 - 47
src/pages/room/index.vue

@@ -1,9 +1,16 @@
 <template>
 <template>
   <div class="room">
   <div class="room">
     <topBar class="room-bar">
     <topBar class="room-bar">
-      <signal :signal-value="signalValue" />
-      <battery :quantity="batteryValue" />
-      <mic @callBack="sendAudio" />
+      <template
+        v-for="(item, index) in menuList"
+        :key="index"
+      >
+        <component
+          :is="item.component"
+          :value="item.value"
+          @callBack="item.callBack"
+        />
+      </template>
     </topBar>
     </topBar>
     <video
     <video
       ref="remoteVideo"
       ref="remoteVideo"
@@ -11,9 +18,9 @@
       playsinline
       playsinline
     />
     />
     <div class="room-gauge">
     <div class="room-gauge">
-      <gauge
-        :value="SpeedValue"
-        :gears="conctrlNum % 2 ? '倒档' : '前进'"
+      <Gauge
+        :value="gauge.speed"
+        :gears="gauge.num % 2 ? '倒档' : '前进'"
       />
       />
     </div>
     </div>
   </div>
   </div>
@@ -21,64 +28,55 @@
 
 
 <script setup lang='ts'>
 <script setup lang='ts'>
 import {
 import {
-  ref, onMounted, inject, watch
+  ref, onMounted, inject, watch,
+  onUnmounted,
+  shallowRef,
+  defineAsyncComponent
 } from 'vue'
 } from 'vue'
-import { useRouter } from 'vue-router'
 import topBar from '@/components/topBar.vue'
 import topBar from '@/components/topBar.vue'
-import signal from '@/components/signal.vue'
-import battery from '@/components/battery.vue'
-import gauge from '@/components/gauge.vue'
-import mic from '@/components/mic.vue'
+import Gauge from '@/components/gauge.vue'
 import WebRtcService from '@/services/webrtc.service'
 import WebRtcService from '@/services/webrtc.service'
 import useStore from '@/store/index'
 import useStore from '@/store/index'
 
 
-const signalValue = ref(0)
-const batteryValue = ref(0)
-const SpeedValue = ref(0)
-const conctrlNum = ref(0)
 const remoteVideo = ref(null as unknown as HTMLVideoElement)
 const remoteVideo = ref(null as unknown as HTMLVideoElement)
 const RTC = new WebRtcService()
 const RTC = new WebRtcService()
 const store = useStore()
 const store = useStore()
 const mqtt = inject('MQTT') as Any
 const mqtt = inject('MQTT') as Any
-const router = useRouter()
+const menuList = shallowRef([ { name: '信号', value: 0, component: defineAsyncComponent(() => import('@/components/signal.vue')) },
+  { name: '电量', value: 0, component: defineAsyncComponent(() => import('@/components/battery.vue')) },
+  {
+    name: '录音',
+    value: 0,
+    callBack: (blob: Blob) => {
+      console.log('send audio', blob)
+    },
+    component: defineAsyncComponent(() => import('@/components/mic.vue'))
+  },
+  {
+    name: '静音', value: 0, callBack: (mute: boolean) => RTC.muteRemoteAudio(mute), component: defineAsyncComponent(() => import('@/components/audio.vue'))
+  } ])
+const gauge = ref({ speed: 0, num: 0 })
 
 
-/**
- * 发送语音
- * @param blob blob
- */
-function sendAudio(blob: Blob) {
-  console.log('send audio', blob)
-}
-
-onMounted(() => RTC.initRTC(remoteVideo.value, (event) => {
-  const { type, data } = event
-  if (type === 'connected') {
-    store.setLoading(false)
-    console.log('可以发送控制数据')
-  }
-  if (type === 'answer') {
-    console.log('answer', data)
-    mqtt.send(JSON.stringify(data))
-  }
-  if (store.errorDic[type]) mqtt.send(data)
-}))
+watch(() => store.mqtt_message, async (value: { type: string, data?: RTCSessionDescriptionInit }) => {
+  const { type, data } = value
+  console.log('二级监听', value)
 
 
-watch(() => store.mqtt_message, async (val: { type: string, data?: RTCSessionDescriptionInit }) => {
-  // 接收offer
-  if (val.type === 'offer') {
-    console.log('offer', val)
-    await RTC.Peer?.setRemoteDescription(val.data!)
+  // 接收远程offer
+  if (type === 'offer') {
+    console.log('offer', data)
+    await RTC.Peer?.setRemoteDescription(data!)
     const answerd = await RTC.Peer?.createAnswer()
     const answerd = await RTC.Peer?.createAnswer()
     await RTC.Peer?.setLocalDescription(answerd)
     await RTC.Peer?.setLocalDescription(answerd)
   }
   }
-  // mqtt|webrtc ERROR
-  if (store.errorDic[val.type]) {
-    RTC.distory()
-    mqtt.disconnect()
-    router.push('/')
+  // 发送本地answer
+  if (type === 'answer') {
+    console.log('send answer', data)
+    mqtt.send(JSON.stringify(data))
   }
   }
 })
 })
 
 
+onMounted(() => RTC.initRTC(remoteVideo.value))
+onUnmounted(() => RTC.distory())
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .room {
 .room {

+ 1 - 3
src/pages/room/route.ts

@@ -3,8 +3,6 @@ import index from './index.vue'
 
 
 export default {
 export default {
   path: '/room',
   path: '/room',
-  meta: {
-    authorize: true
-  },
+  meta: { authorize: true },
   component: index
   component: index
 } as RouteRecordRaw
 } as RouteRecordRaw

+ 2 - 2
src/services/mqtt.service.ts

@@ -65,13 +65,13 @@ export default class MqttService {
       // 连接成功
       // 连接成功
       if (event.connect) {
       if (event.connect) {
         console.log('已连接服务器')
         console.log('已连接服务器')
-        this.store.setMqttMessage({ type: 'connect' })
+        this.store.setMqttMessage({ type: 'MqttConnect' })
       }
       }
 
 
       // 断开连接
       // 断开连接
       if (event.disconnect) {
       if (event.disconnect) {
         console.log('已断开服务器,插件未自动断开已提is')
         console.log('已断开服务器,插件未自动断开已提is')
-        this.store.setMqttMessage({ type: 'disconnect' })
+        this.store.setMqttMessage({ type: 'Mqttdisconnect' })
       }
       }
 
 
       // 收到消息
       // 收到消息

+ 30 - 16
src/services/webrtc.service.ts

@@ -1,24 +1,29 @@
+import useStore from '@/store'
 /**
 /**
  * WebRTC服务
  * WebRTC服务
  */
  */
-
 export default class WebRtcService {
 export default class WebRtcService {
-  private ICE = [
-    {
-      urls: [ 'stun:caner.top:3478' ]
-    },
+  private ICE = [ { urls: [ 'stun:caner.top:3478' ] },
     {
     {
       urls: 'turn:caner.top:3478',
       urls: 'turn:caner.top:3478',
       username: 'admin',
       username: 'admin',
       credential: '123456'
       credential: '123456'
-    }
-  ]
+    } ]
 
 
   public Peer: RTCPeerConnection | null = null
   public Peer: RTCPeerConnection | null = null
 
 
-  initRTC(DOM: HTMLVideoElement, callBack: (event: Any) => void) {
+  private audioTack: MediaStreamTrack | null = null
+
+  private store = useStore()
+
+  /**
+   * 初始化
+   * @param DOM HTMLVideoElement
+   */
+  initRTC(DOM: HTMLVideoElement) {
     try {
     try {
-      if (!DOM) { callBack({ type: 'WebRtcIntError' }); return }
+      if (this.Peer) this.distory()
+      if (!DOM) { this.store.setMqttMessage({ type: 'WebRtcIntError' }); return }
       console.log('start initRTC')
       console.log('start initRTC')
 
 
       // create Peer
       // create Peer
@@ -32,8 +37,7 @@ export default class WebRtcService {
         console.log('GatheringState: ', this.Peer?.iceGatheringState)
         console.log('GatheringState: ', this.Peer?.iceGatheringState)
         if (this.Peer?.iceGatheringState === 'complete') {
         if (this.Peer?.iceGatheringState === 'complete') {
           const answer = this.Peer?.localDescription
           const answer = this.Peer?.localDescription
-          console.log('send answer', answer)
-          callBack({ type: 'answer', data: answer?.toJSON() })
+          this.store.setMqttMessage({ type: 'answer', data: answer?.toJSON() })
         }
         }
       }
       }
 
 
@@ -41,6 +45,7 @@ export default class WebRtcService {
       this.Peer.ontrack = (evt) => {
       this.Peer.ontrack = (evt) => {
         console.log('track', evt)
         console.log('track', evt)
         DOM.srcObject = evt.streams[0]
         DOM.srcObject = evt.streams[0]
+        if (evt.type === 'audio') this.audioTack = evt.track
       }
       }
 
 
       // listen changestate·
       // listen changestate·
@@ -50,25 +55,34 @@ export default class WebRtcService {
         // P2P 连接失败
         // P2P 连接失败
         if (state === 'failed' || state === 'closed') {
         if (state === 'failed' || state === 'closed') {
           console.log('P2P通信失败')
           console.log('P2P通信失败')
-          this.distory()
-          callBack({ type: 'WebRtcFailed' })
+          this.store.setMqttMessage({ type: 'WebRtcFailed' })
         }
         }
         // ICE连接成功
         // ICE连接成功
         if (state === 'connected') {
         if (state === 'connected') {
-          callBack({ type: 'connected', data: null })
+          this.store.setMqttMessage({ type: 'WebRtcConnected', data: null })
         }
         }
       }
       }
 
 
       console.log('RTC success')
       console.log('RTC success')
     } catch (error) {
     } catch (error) {
       console.log('RTC 初始化失败', error)
       console.log('RTC 初始化失败', error)
-      this.distory()
-      callBack({ type: 'WebRtcIntError' })
+      this.store.setMqttMessage({ type: 'WebRtcIntError' })
     }
     }
   }
   }
 
 
+  /**
+   * 远程静音
+   * @param mute boolean
+   */
+  muteRemoteAudio(mute: boolean) {
+    console.log('远程静音', mute)
+    if (!this.audioTack) return
+    this.audioTack!.enabled = mute
+  }
+
   distory() {
   distory() {
     this.Peer?.close()
     this.Peer?.close()
     this.Peer = null
     this.Peer = null
+    this.audioTack = null
   }
   }
 }
 }

+ 6 - 2
src/store/index.ts

@@ -6,13 +6,14 @@ export interface MqttMessage {
 }
 }
 
 
 // id必填,且需要唯一
 // id必填,且需要唯一
-const useStore = defineStore('index', {
+const useStore = defineStore('_index_', {
   state: () => ({
   state: () => ({
     loading: false,
     loading: false,
     mqtt_message: { type: '' },
     mqtt_message: { type: '' },
+    rtcConnected: false,
     errorDic: {
     errorDic: {
       leave: '对方离开房间',
       leave: '对方离开房间',
-      disconnect: '连接断开',
+      Mqttdisconnect: '连接断开',
       WebRtcIntError: 'webrtc初始化失败',
       WebRtcIntError: 'webrtc初始化失败',
       WebRtcFailed: 'P2P视频通信失败'
       WebRtcFailed: 'P2P视频通信失败'
     }
     }
@@ -23,6 +24,9 @@ const useStore = defineStore('index', {
     },
     },
     setLoading(data:boolean) {
     setLoading(data:boolean) {
       this.loading = data
       this.loading = data
+    },
+    setRtcConnected(data:boolean) {
+      this.rtcConnected = data
     }
     }
   }
   }
 })
 })

Some files were not shown because too many files changed in this diff