|
@@ -1,353 +1,20 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div id="app">
|
|
|
|
|
- <template v-if="isLogin">
|
|
|
|
|
- <video id="v2" autoplay playsinline muted></video>
|
|
|
|
|
- <div class="marke">
|
|
|
|
|
- <!-- 手柄状态 -->
|
|
|
|
|
- <div class="contrl" :style="`color:${contrlState ? '#00CED1' : 'rgba(0, 0, 0, 0.3)'}`"></div>
|
|
|
|
|
- <!-- 音频状态 -->
|
|
|
|
|
- <Record class="audio" @callBack="sendAudio" :audioState="audioState" />
|
|
|
|
|
- <!-- 喇叭状态 -->
|
|
|
|
|
- <div class="arcode" :style="`color:${muted ? '#00CED1' : 'rgba(0, 0, 0, 0.3)'}`"></div>
|
|
|
|
|
- <!-- 电量状态 -->
|
|
|
|
|
- <Battery :quantity="60" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <!-- 码数 -->
|
|
|
|
|
- <div class="gauge">
|
|
|
|
|
- <Gauge :value="SpeedValue" :gears="speed" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <Loading v-if="showLoading" />
|
|
|
|
|
- </template>
|
|
|
|
|
- <Login v-else :err="error" @loginBack="login" />
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <loading v-if="showLoading" />
|
|
|
|
|
+ <router-view
|
|
|
|
|
+ v-else
|
|
|
|
|
+ v-resize
|
|
|
|
|
+ />
|
|
|
</template>
|
|
</template>
|
|
|
-
|
|
|
|
|
-<script>
|
|
|
|
|
-const { io } = require("socket.io-client");
|
|
|
|
|
-import Login from "@/components/login";
|
|
|
|
|
-import Loading from "@/components/loading";
|
|
|
|
|
-import Record from "@/components/record";
|
|
|
|
|
-import Battery from "@/components/battery";
|
|
|
|
|
-import Gauge from "@/components/gauge";
|
|
|
|
|
-
|
|
|
|
|
-const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
|
|
|
-export default {
|
|
|
|
|
- components: { Login, Loading, Record, Battery, Gauge },
|
|
|
|
|
- data() {
|
|
|
|
|
- return {
|
|
|
|
|
- socket: null,
|
|
|
|
|
- HOST: "wss://car.caner.top",
|
|
|
|
|
- Peer: null,
|
|
|
|
|
- isLogin: false,
|
|
|
|
|
- error: "",
|
|
|
|
|
- remoteVideo: null,
|
|
|
|
|
- showLoading: true,
|
|
|
|
|
- contrlState: false,
|
|
|
|
|
- audioState: false,
|
|
|
|
|
- mutedState: true,
|
|
|
|
|
- warnAudio: false,// 鸣笛
|
|
|
|
|
- speed: 1, //1低速档 | 2 高速档
|
|
|
|
|
- muted: true,// 是否静音
|
|
|
|
|
- SpeedValue: 0,
|
|
|
|
|
- iceServers: [
|
|
|
|
|
- {
|
|
|
|
|
- urls: ["stun:caner.top:3478"],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- urls: "turn:caner.top:3478",
|
|
|
|
|
- username: "admin",
|
|
|
|
|
- credential: "123456",
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- num: 0
|
|
|
|
|
- };
|
|
|
|
|
- },
|
|
|
|
|
- methods: {
|
|
|
|
|
- // 网络连接
|
|
|
|
|
- intSoketRtc(host) {
|
|
|
|
|
- // int socket
|
|
|
|
|
- this.socket = io(host, {
|
|
|
|
|
- autoConnect: false,
|
|
|
|
|
- transports: ["websocket"],
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // socket
|
|
|
|
|
- this.socket.on("connect", () => {
|
|
|
|
|
- try {
|
|
|
|
|
- this.isLogin = true;
|
|
|
|
|
- // init webrtc
|
|
|
|
|
- this.Peer = new RTCPeerConnection({
|
|
|
|
|
- iceServers: this.iceServers,
|
|
|
|
|
- bundlePolicy: "max-bundle",
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // listen state
|
|
|
|
|
- this.Peer.onicegatheringstatechange = () => {
|
|
|
|
|
- console.log("GatheringState: ", this.Peer.iceGatheringState);
|
|
|
|
|
- if (this.Peer.iceGatheringState === "complete") {
|
|
|
|
|
- const answer = this.Peer.localDescription;
|
|
|
|
|
- this.socket.emit("msg", answer);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // listen track
|
|
|
|
|
- this.Peer.ontrack = async (evt) => {
|
|
|
|
|
- console.log("track", evt);
|
|
|
|
|
- this.remoteVideo = document.getElementById("v2");
|
|
|
|
|
- this.remoteVideo.srcObject = evt.streams[0];
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // listen changestate·
|
|
|
|
|
- this.Peer.oniceconnectionstatechange = async () => {
|
|
|
|
|
- const state = this.Peer.iceConnectionState;
|
|
|
|
|
- console.log("ICE状态", state);
|
|
|
|
|
- if (
|
|
|
|
|
- state === "failed" ||
|
|
|
|
|
- state === "disconnected" ||
|
|
|
|
|
- state === "closed"
|
|
|
|
|
- ) {
|
|
|
|
|
- this.close("P2P通信失败");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // ICE连接成功|初始化摇杆
|
|
|
|
|
- if (state === "connected") {
|
|
|
|
|
- // init Control
|
|
|
|
|
- window.addEventListener("gamepadconnected", this.conControl);
|
|
|
|
|
- window.addEventListener("gamepaddisconnected", this.disControl);
|
|
|
|
|
- await sleep(3000);
|
|
|
|
|
- this.showLoading = false;
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- this.close('webrtc初始化失败')
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- this.socket.on("msg", async (data) => {
|
|
|
|
|
- const key = data.type
|
|
|
|
|
- if (key === 'offer') {
|
|
|
|
|
- await this.Peer.setRemoteDescription(data);
|
|
|
|
|
- const answer = await this.Peer.createAnswer();
|
|
|
|
|
- await this.Peer.setLocalDescription(answer);
|
|
|
|
|
- } else if (key === 'power') {
|
|
|
|
|
- console.log("电量", data);
|
|
|
|
|
- } else if (key === 'speed') {
|
|
|
|
|
- const d = Math.floor(data.data)
|
|
|
|
|
- this.SpeedValue = d
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- this.socket.on("joined", async () =>
|
|
|
|
|
- this.socket.emit("msg", { type: "startRTC" })
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- this.socket.on("leaved", () => this.close("车端断开"));
|
|
|
|
|
-
|
|
|
|
|
- this.socket.on("connect_error", () => this.close('服务器连接失败'));
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 手柄数据
|
|
|
|
|
- ControlData() {
|
|
|
|
|
- const data = navigator.getGamepads();
|
|
|
|
|
- const db = data[0];
|
|
|
|
|
- if (!db) return;
|
|
|
|
|
- // 挡位选择AB
|
|
|
|
|
- if (db.buttons[1].touched) this.speed = 2;
|
|
|
|
|
- if (db.buttons[0].touched) this.speed = 1;
|
|
|
|
|
- // 语音按键R2
|
|
|
|
|
- this.audioState = db.buttons[7].touched
|
|
|
|
|
- // 静音X3
|
|
|
|
|
- this.mutedState = db.buttons[3].touched
|
|
|
|
|
- // 播放警笛Y2
|
|
|
|
|
- this.warnAudio = 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: this.Gear(this.speed, Math.floor(db.axes[3] * 128 + 128)),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (this.socket && this.socket.connected) this.socket.emit("msg", { type: "conctrl", conctrl: params });
|
|
|
|
|
- requestAnimationFrame(this.ControlData);
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 发送语音
|
|
|
|
|
- sendAudio(blob) {
|
|
|
|
|
- if (!this.socket && !this.socket.connected) return;
|
|
|
|
|
- this.socket.emit("msg", {
|
|
|
|
|
- type: "Meadia",
|
|
|
|
|
- Meadia: blob,
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 挡位
|
|
|
|
|
- Gear(speed, num) {
|
|
|
|
|
- // 低速档
|
|
|
|
|
- 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;
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 登录
|
|
|
|
|
- login(data) {
|
|
|
|
|
- if (this.socket) {
|
|
|
|
|
- this.socket.auth = {
|
|
|
|
|
- roomID: data.roomID,
|
|
|
|
|
- name: data.name,
|
|
|
|
|
- };
|
|
|
|
|
- this.socket.connect();
|
|
|
|
|
- } else {
|
|
|
|
|
- this.error = "服务器连接失败";
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 关闭
|
|
|
|
|
- close(err) {
|
|
|
|
|
- if (this.Peer) this.Peer.close();
|
|
|
|
|
- if (this.remoteVideo) this.remoteVideo.srcObject = null;
|
|
|
|
|
- if (this.socket) this.socket.disconnect();
|
|
|
|
|
- this.isLogin = false;
|
|
|
|
|
- this.showLoading = false;
|
|
|
|
|
- this.error = err || "";
|
|
|
|
|
- this.socket = null;
|
|
|
|
|
- this.Peer = null
|
|
|
|
|
- this.num = 0
|
|
|
|
|
- cancelAnimationFrame(this.ControlData);
|
|
|
|
|
- window.removeEventListener("gamepadconnected", this.conControl);
|
|
|
|
|
- window.removeEventListener("gamepaddisconnected", this.disControl);
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 手柄连接
|
|
|
|
|
- conControl() {
|
|
|
|
|
- this.contrlState = true;
|
|
|
|
|
- this.ControlData();
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- // 手柄断开连接
|
|
|
|
|
- disControl() {
|
|
|
|
|
- this.contrlState = false;
|
|
|
|
|
- cancelAnimationFrame(this.ControlData);
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- mounted() {
|
|
|
|
|
- this.intSoketRtc(this.HOST);
|
|
|
|
|
- },
|
|
|
|
|
- watch: {
|
|
|
|
|
- // 静音
|
|
|
|
|
- mutedState(v) {
|
|
|
|
|
- if (v) {
|
|
|
|
|
- const state = !!(this.num % 2 )
|
|
|
|
|
- this.muted = state
|
|
|
|
|
- if (this.socket && this.socket.connected) this.socket.emit("msg", { type: "contrlAudio", contrlAudio: state });
|
|
|
|
|
- this.num++
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- // 鸣笛
|
|
|
|
|
- warnAudio(v) {
|
|
|
|
|
- if (v && this.socket && this.socket.connected) this.socket.emit("msg", { type: "warnAudio", warnAudio: v });
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- beforeDestroy() {
|
|
|
|
|
- this.close()
|
|
|
|
|
|
|
+<script setup lang='ts'>
|
|
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
|
|
+import loading from '@/components/loading.vue'
|
|
|
|
|
+
|
|
|
|
|
+const showLoading = ref(false)
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ // 通知主进程是否完成渲染
|
|
|
|
|
+ const electron = window.$electron || null
|
|
|
|
|
+ if (electron) {
|
|
|
|
|
+ electron.ipcRenderer.send('close-loading')
|
|
|
}
|
|
}
|
|
|
-};
|
|
|
|
|
-</script>
|
|
|
|
|
-<style scoped>
|
|
|
|
|
-#app {
|
|
|
|
|
- font-family: "fonts";
|
|
|
|
|
- font-style: normal;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-video {
|
|
|
|
|
- background: none;
|
|
|
|
|
- object-fit: fill;
|
|
|
|
|
- font-size: 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.marke {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- top: 0;
|
|
|
|
|
- left: 0;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- padding: 5px 0;
|
|
|
|
|
- z-index: 1;
|
|
|
|
|
- background: rgba(0, 0, 0, 0.25);
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.marke>div {
|
|
|
|
|
- margin: 0 18px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.gauge {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- bottom: 0;
|
|
|
|
|
- left: 50%;
|
|
|
|
|
- transform: translate(-50%, 0);
|
|
|
|
|
- width: 500px;
|
|
|
|
|
- height: 185px;
|
|
|
|
|
- z-index: 9;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.audio,
|
|
|
|
|
-.arcode {
|
|
|
|
|
- font-size: 24px;
|
|
|
|
|
- color: rgba(0, 0, 0, 0.3);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.contrl {
|
|
|
|
|
- font-size: 32px;
|
|
|
|
|
- color: rgba(0, 0, 0, 0.3);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/* 隐藏滚动条 */
|
|
|
|
|
-::-webkit-scrollbar {
|
|
|
|
|
- width: 0 !important;
|
|
|
|
|
- display: none;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-@font-face {
|
|
|
|
|
- font-family: "fonts";
|
|
|
|
|
- src: url("./assets/iconfont.woff2") format("woff2"),
|
|
|
|
|
- url("./assets/iconfont.woff") format("woff"),
|
|
|
|
|
- url("./assets/iconfont.ttf") format("truetype");
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|
|
|
-<style>
|
|
|
|
|
-video,
|
|
|
|
|
-#app,
|
|
|
|
|
-html,
|
|
|
|
|
-body {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
- user-select: none;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- min-width: 1000px;
|
|
|
|
|
- min-height: 900px;
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|