fix: dplayer can not playing

This commit is contained in:
2026-03-23 18:08:59 +08:00
parent 08ae7414d0
commit 970aae1c5f
8 changed files with 1117 additions and 1120 deletions

237
proxy.js
View File

@@ -1,113 +1,124 @@
import express from 'express';
import fetch from 'node-fetch';
import { URL } from 'url';
const app = express();
const port = 3000;
// 允许跨域请求
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Range');
next();
});
// 通用代理路由
app.get('/proxy', async (req, res) => {
console.log('Received Range header:', req.headers.range);
// 获取目标 URL
const targetUrl = req.query.url;
console.log('Fetching data from:', targetUrl);
if (!targetUrl) {
return res.status(400).json({ error: 'URL parameter is required' });
}
try {
// 设置请求头
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
};
if (req.headers.range) {
headers['Range'] = req.headers.range; // 转发 Range 请求头,支持视频分段加载
}
// 向目标 URL 发起请求
const response = await fetch(targetUrl, { headers });
// 检查响应状态
if (!response.ok) {
return res.status(response.status).json({
error: `Failed to fetch: ${response.statusText}`,
status: response.status
});
}
// 设置响应头
response.headers.forEach((value, key) => {
// 跳过一些可能导致问题的头信息
if (key.toLowerCase() !== 'content-encoding' &&
key.toLowerCase() !== 'transfer-encoding' &&
key.toLowerCase() !== 'connection') {
res.setHeader(key, value);
}
});
// 特殊处理 Transfer-Encoding 和 Content-Length
if (response.headers.get('transfer-encoding') === 'chunked') {
res.removeHeader('Content-Length');
}
// 将响应体直接流式传输给客户端
if (response.body) {
response.body.pipe(res, { end: true });
// 错误处理
response.body.on('error', (err) => {
console.error('Error during data transfer:', err);
if (!res.headersSent) {
res.status(500).json({ error: 'Error during data transfer' });
}
});
} else {
res.status(500).json({ error: 'No response body' });
}
} catch (error) {
console.error('Error fetching data:', error);
// 根据错误类型返回更具体的错误信息
let errorMessage = 'Error fetching data';
let statusCode = 500;
let hostname = '';
try {
hostname = new URL(targetUrl).hostname;
} catch (e) {
hostname = 'unknown';
}
if (error.code === 'ENOTFOUND') {
errorMessage = `DNS解析失败无法解析域名 "${hostname}"。请检查URL是否正确或网络连接是否正常。`;
statusCode = 502;
} else if (error.code === 'ECONNREFUSED') {
errorMessage = `连接被拒绝:无法连接到目标服务器 "${hostname}"。`;
statusCode = 502;
} else if (error.code === 'ETIMEDOUT') {
errorMessage = `请求超时:目标服务器 "${hostname}" 响应时间过长。`;
statusCode = 504;
}
if (!res.headersSent) {
res.status(statusCode).json({
error: errorMessage,
code: error.code,
targetUrl: targetUrl
});
}
}
});
app.listen(port, () => {
console.log(`Proxy server running at http://localhost:${port}`);
});
import express from 'express';
import fetch from 'node-fetch';
import { URL } from 'url';
const app = express();
const port = 3000;
// 允许跨域请求
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Range');
next();
});
// 本地静态文件代理:/local -> C:\Users\xyf17\Merlin\data
const localDataPath = 'C:\\Users\\xyf17\\Merlin\\data';
app.use('/local', express.static(localDataPath));
// 通用代理路由
app.get('/proxy', async (req, res) => {
console.log('Received Range header:', req.headers.range);
// 获取目标 URL
const targetUrl = req.query.url;
console.log('Fetching data from:', targetUrl);
if (!targetUrl) {
return res.status(400).json({ error: 'URL parameter is required' });
}
try {
// 设置请求头
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
};
// 避免上游压缩导致 Range/MSE 解析异常mp4 一般不会压缩,但这里保守处理)
headers['Accept-Encoding'] = 'identity';
if (req.headers.range) {
headers['Range'] = req.headers.range; // 转发 Range 请求头,支持视频分段加载
}
// 向目标 URL 发起请求
const response = await fetch(targetUrl, { headers });
// 检查响应状态
if (!response.ok) {
return res.status(response.status).json({
error: `Failed to fetch: ${response.statusText}`,
status: response.status
});
}
// 关键Range 请求上游通常返回 206必须把状态码透传给浏览器
res.status(response.status);
// 设置响应头
response.headers.forEach((value, key) => {
// 跳过一些可能导致问题的头信息
if (key.toLowerCase() !== 'content-encoding' &&
key.toLowerCase() !== 'transfer-encoding' &&
key.toLowerCase() !== 'connection') {
res.setHeader(key, value);
}
});
// 特殊处理 Transfer-Encoding 和 Content-Length
if (response.headers.get('transfer-encoding') === 'chunked') {
res.removeHeader('Content-Length');
}
// 将响应体直接流式传输给客户端
if (response.body) {
response.body.pipe(res, { end: true });
// 错误处理
response.body.on('error', (err) => {
console.error('Error during data transfer:', err);
if (!res.headersSent) {
res.status(500).json({ error: 'Error during data transfer' });
}
});
} else {
res.status(500).json({ error: 'No response body' });
}
} catch (error) {
console.error('Error fetching data:', error);
// 根据错误类型返回更具体的错误信息
let errorMessage = 'Error fetching data';
let statusCode = 500;
let hostname = '';
try {
hostname = new URL(targetUrl).hostname;
} catch (e) {
hostname = 'unknown';
}
if (error.code === 'ENOTFOUND') {
errorMessage = `DNS解析失败无法解析域名 "${hostname}"。请检查URL是否正确或网络连接是否正常。`;
statusCode = 502;
} else if (error.code === 'ECONNREFUSED') {
errorMessage = `连接被拒绝:无法连接到目标服务器 "${hostname}"。`;
statusCode = 502;
} else if (error.code === 'ETIMEDOUT') {
errorMessage = `请求超时:目标服务器 "${hostname}" 响应时间过长。`;
statusCode = 504;
}
if (!res.headersSent) {
res.status(statusCode).json({
error: errorMessage,
code: error.code,
targetUrl: targetUrl
});
}
}
});
app.listen(port, () => {
console.log(`Proxy server running at http://localhost:${port}`);
});

View File

@@ -1,67 +1,93 @@
import { userInfoStore } from "@/store/user";
import axios from "axios";
const userinfo = userInfoStore();
export const searchPlayRoom = async (inputValue: string) => {
const response = await axios.post('/api/playroom/search',{
r_name: inputValue
},{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return response.data.data.records
} else {
console.error(response.data.msg)
return []
}
}
export const createPlayroom = async (room_name: string) => {
const response = await axios.post('/api/playroom/create',{
r_name: room_name
},{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return true;
} else {
throw new Error(response.data.message);
}
}
export const getPlayrooms = async ()=> {
const response = await axios.get('/api/playroom/get',{
headers:{
'Authorization' : `Bearer ${userinfo.token}`
}
})
if(response.data.code === "200"){
return response.data.data;
} else {
throw new Error(response.data.message);
}
}
export const joinPlayroom = async (r_id: number) => {
const response = await axios.post('/api/inviting/playroom',{
inviter: userinfo.user.id,
target: userinfo.user.id,
status: 0,
room: r_id,
},{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return true;
} else {
throw new Error(response.data.message);
}
import { userInfoStore } from "@/store/user";
import axios from "axios";
const userinfo = userInfoStore();
export const searchPlayRoom = async (inputValue: string) => {
const response = await axios.post('/api/playroom/search',{
r_name: inputValue
},{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return response.data.data.records
} else {
console.error(response.data.msg)
return []
}
}
export const createPlayroom = async (room_name: string) => {
const response = await axios.post('/api/playroom/create',{
r_name: room_name
},{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return true;
} else {
throw new Error(response.data.message);
}
}
export const getPlayrooms = async ()=> {
const response = await axios.get('/api/playroom/get',{
headers:{
'Authorization' : `Bearer ${userinfo.token}`
}
})
if(response.data.code === "200"){
return response.data.data;
} else {
throw new Error(response.data.message);
}
}
export const joinPlayroom = async (r_id: number) => {
const response = await axios.post('/api/inviting/playroom',{
inviter: userinfo.user.id,
target: userinfo.user.id,
status: 0,
room: r_id,
},{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return true;
} else {
throw new Error(response.data.message);
}
}
export const getPlayroomDetails = async (r_id:number) =>{
const response = await axios.get('/api/playroom/detail/'+r_id,{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return response.data.data;
} else {
throw new Error(response.data.message);
}
}
export const getPlayroomMembers = async (r_id: number) => {
const response = await axios.get('/api/playroom/member/'+r_id,{
headers:{
'Authorization': "Bearer " + userinfo.token
}
})
if(response.data.code === "200"){
return response.data.data;
} else {
throw new Error(response.data.message);
}
}

View File

@@ -1,35 +1,37 @@
@import './base.css';
#app {
max-width: 1280px;
/* margin: 0 auto; */
/* padding: 2rem; */
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
/* background-color: hsla(160, 100%, 37%, 0.2); */
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
/* grid-template-columns: 1fr 1fr;
padding: 0 2rem; */
}
}
@import './base.css';
#app {
max-width: 1280px;
width: 100%;
height: 100%;
/* margin: 0 auto; */
/* padding: 2rem; */
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
/* background-color: hsla(160, 100%, 37%, 0.2); */
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
/* grid-template-columns: 1fr 1fr;
padding: 0 2rem; */
}
}

View File

@@ -1,73 +1,92 @@
import { ref } from "vue";
import { defineStore } from "pinia";
import { connectWebSocket, disconnectWebSocket, sendMessage, setIsManualClose } from '@/websocket/roomSocket'
interface PlayroomState {
id: number
r_id: number;
r_name: string;
r_introduction: string;
r_avatar: string;
role: number;
}
export const PlayroomStore = defineStore("PlayroomStore",
() =>{
const currentPlayroom = ref<PlayroomState>();
const currentUrl = ref<string>("");
const setCurrentPlayroom = (playroom: PlayroomState) => {
currentPlayroom.value = playroom;
}
const getCurrentPlayroom = () =>{
return currentPlayroom.value;
}
const getCurrentId = () =>{
return currentPlayroom.value?.r_id;
}
const setCurrentUrl = (url: string) => {
currentUrl.value = url;
}
const clearPlayroom = () => {
currentPlayroom.value = undefined;
currentUrl.value = "";
}
return {
getCurrentPlayroom,
getCurrentId,
setCurrentPlayroom,
setCurrentUrl,
clearPlayroom,
}
})
export const videoSocketStore = defineStore("videoSocketStore",{
state: () => ({
isConnected: false,
hasGotMessage: false,
id: 0
}),
actions: {
connect(id: number) {
this.id = id;
if (this.isConnected === true) return
connectWebSocket();
this.isConnected = true;
},
disconnect() {
setIsManualClose(true);
disconnectWebSocket();
this.isConnected = false;
},
send(message: string) {
sendMessage(JSON.stringify(message));
}
}
})
import { ref } from "vue";
import { defineStore } from "pinia";
import { connectWebSocket, disconnectWebSocket, sendMessage, setIsManualClose } from "@/websocket/roomSocket";
interface PlayroomState {
id: number
r_id: number;
r_name: string;
r_introduction: string;
r_avatar: string;
role: number;
}
interface member {
id: number;
u_id: string;
u_name: string;
u_avatar: string;
}
export const PlayroomStore = defineStore("PlayroomStore",
() =>{
const currentPlayroom = ref<PlayroomState>();
const members = ref<member[]>([]);
const currentUrl = ref<string>("");
const setCurrentPlayroom = (playroom: PlayroomState) => {
currentPlayroom.value = playroom;
}
const getCurrentPlayroom = () =>{
return currentPlayroom.value;
}
const getCurrentId = () =>{
return currentPlayroom.value?.r_id;
}
const setCurrentUrl = (url: string) => {
currentUrl.value = url;
}
const clearPlayroom = () => {
currentPlayroom.value = null;
currentUrl.value = "";
members.value = [];
}
const addmember = (member: member) => {
members.value.push(member);
}
const getmembers = (page: number, pageSize: number) => {
return members.value.slice((page - 1) * pageSize, page * pageSize);
}
return {
currentPlayroom,
currentUrl,
setCurrentPlayroom,
clearPlayroom,
addmember,
getmembers,
}
})
export const videoSocketStore = defineStore("videoSocketStore",{
state: () => ({
isConnected: false,
hasGotMessage: false,
id: 0
}),
actions: {
connect(id: number) {
this.id = id;
if (this.isConnected === true) return
connectWebSocket(id);
this.isConnected = true;
},
disconnect() {
setIsManualClose(true);
disconnectWebSocket();
this.isConnected = false;
},
send(message: string) {
sendMessage(JSON.stringify(message));
}
}
})

View File

@@ -1,119 +1,119 @@
<template>
<div class="container">
<el-button @Click="createWindow = true">创建房间</el-button>
<el-table :data="rooms">
<el-table-column label="" prop="r_avatar" width="100">
<template #default="scope">
<!-- 使用 el-avatar 组件显示头像 -->
<el-avatar :src="rooms[scope.$index].r_avatar" size="large" />
</template>
</el-table-column>
<el-table-column label="房间名" prop="r_name"></el-table-column>
<el-table-column label="房间ID" prop="r_id"></el-table-column>
<el-table-column label="角色" prop="role"></el-table-column>
<el-table-column label="简介" prop="r_introduction"></el-table-column>
<el-table-column label="">
<template #default="scope">
<el-button @click="goToRoom(scope.row)">进入房间</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="createWindow" title="创建房间" style="width: 400px;height: 300px;">
<el-form ref="form" :model="formData" label-width="80px" @submit.prevent="createroom">
<el-form-item label="房间名" prop="roomName">
<el-input v-model="formData.roomName" placeholder="请输入房间名" clearable></el-input>
</el-form-item>
<el-button type="primary" native-type="submit">创建</el-button>
</el-form>
</el-dialog>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { PlayroomStore } from '@/store/playroom';
import { ElMessage } from 'element-plus';
import { getPlayrooms, createPlayroom } from '@/api/playroom';
const roominfo = PlayroomStore()
const createWindow = ref(false)
const formData = reactive({
roomName: ''
})
const rooms = ref([])
const goToRoom = (r) => {
roominfo.setCurrentPlayroom(r);
// 跳转到房间页面
const baseUrl = window.location.origin;
const targetUrl = `${baseUrl}/room`; // 替换为你的目标路由
window.open(targetUrl, "room");
};
const createroom = async () => {
if (!formData.roomName || formData.roomName.trim() === '') {
ElMessage.error('房间名不能为空')
return
}
try {
if(await createPlayroom(formData.roomName)){
ElMessage.success('创建成功')
}else{
ElMessage.error('创建失败')
}
formData.roomName = ''
createWindow.value = false
rooms.value = await getPlayrooms()
}
catch (error) {
console.log(error)
}
}
const getrooms = async () => {
try {
rooms.value = await getPlayrooms()
}
catch (error) {
ElMessage.error('获取房间列表失败' + error)
}
}
onMounted(() => {
getrooms()
})
</script>
<style>
.container {
margin-top: 20px;
width: 800px;
height: 600px;
}
.profile {
top: 50px;
left: 100px;
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.profile img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
<template>
<div class="container">
<el-button @Click="createWindow = true">创建房间</el-button>
<el-table :data="rooms">
<el-table-column label="" prop="r_avatar" width="100">
<template #default="scope">
<!-- 使用 el-avatar 组件显示头像 -->
<el-avatar :src="rooms[scope.$index].r_avatar" size="large" />
</template>
</el-table-column>
<el-table-column label="房间名" prop="r_name"></el-table-column>
<el-table-column label="房间ID" prop="r_id"></el-table-column>
<el-table-column label="角色" prop="role"></el-table-column>
<el-table-column label="简介" prop="r_introduction"></el-table-column>
<el-table-column label="">
<template #default="scope">
<el-button @click="goToRoom(scope.row)">进入房间</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="createWindow" title="创建房间" style="width: 400px;height: 300px;">
<el-form ref="form" :model="formData" label-width="80px" @submit.prevent="createroom">
<el-form-item label="房间名" prop="roomName">
<el-input v-model="formData.roomName" placeholder="请输入房间名" clearable></el-input>
</el-form-item>
<el-button type="primary" native-type="submit">创建</el-button>
</el-form>
</el-dialog>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { PlayroomStore } from '@/store/playroom';
import { ElMessage } from 'element-plus';
import { getPlayrooms, createPlayroom } from '@/api/playroom';
const roominfo = PlayroomStore()
const createWindow = ref(false)
const formData = reactive({
roomName: ''
})
const rooms = ref([])
const goToRoom = (r) => {
roominfo.setCurrentPlayroom(r);
// 跳转到房间页面
const baseUrl = window.location.origin;
const targetUrl = `${baseUrl}/room?r_id=${r.r_id}`; // 替换为你的目标路由
window.open(targetUrl, "room");
};
const createroom = async () => {
if (!formData.roomName || formData.roomName.trim() === '') {
ElMessage.error('房间名不能为空')
return
}
try {
if(await createPlayroom(formData.roomName)){
ElMessage.success('创建成功')
}else{
ElMessage.error('创建失败')
}
formData.roomName = ''
createWindow.value = false
rooms.value = await getPlayrooms()
}
catch (error) {
console.log(error)
}
}
const getrooms = async () => {
try {
rooms.value = await getPlayrooms()
}
catch (error) {
ElMessage.error('获取房间列表失败' + error)
}
}
onMounted(() => {
getrooms()
})
</script>
<style>
.container {
margin-top: 20px;
width: 800px;
height: 600px;
}
.profile {
top: 50px;
left: 100px;
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.profile img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,149 +1,162 @@
import { ref } from "vue";
import { userInfoStore } from "@/store/user";
import { ElMessage } from "element-plus";
import { PlayroomStore } from "@/store/playroom";
// userinfo 实例
const userinfo = userInfoStore();
// 延迟获取 playroom 实例,避免循环依赖
const getPlayroom = () => {
return PlayroomStore();
};
// WebSocket 实例
const socket = ref(null);
const isManualClose = ref(false);
const reconnectScheduled = ref(false);
const retryCount = ref(0);
export const getRetryCount = () => {
return retryCount.value;
};
export const addRetryCount = () => {
retryCount.value = retryCount.value + 1;
};
export const resetRetryCount = () => {
retryCount.value = 0;
};
export const setReconnectScheduled = (value) => {
reconnectScheduled.value = value;
};
export const getReconnectScheduled = () => {
return reconnectScheduled.value;
};
export const setIsManualClose = (value) => {
isManualClose.value = value;
};
export const getIsManualClose = () => {
return isManualClose.value;
};
// 连接WebSocket
export const connectWebSocket = () => {
const playroom = getPlayroom();
const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
const host = window.location.host;
const socketUrl = `${protocol}${host}/ws/video?r_id=${playroom.getCurrentId()}`;
// const socketUrl = `ws://localhost:8080/online?u_id=${userinfo.user.u_id}&u_name=${userinfo.user.u_name}`;
if (socket.value && socket.value.readyState !== WebSocket.CLOSED) {
console.log("还在重连中...");
return;
}
const retrytime = getRetryCount();
if (retrytime >= 10) {
console.log("重连失败,请稍后再试");
return;
}
console.log(retrytime);
socket.value = new WebSocket(socketUrl, "token-"+ userinfo.token);
socket.value.onopen = (event) => {
console.log("WebSocket for video 连接已建立", event);
setReconnectScheduled(false);
setIsManualClose(false);
resetRetryCount();
};
//处理消息逻辑
socket.value.onmessage = (event) => {
console.log("从服务器收到消息:", event.data);
try{
const MessageData = JSON.parse(event.data);
const cmd = MessageData.cmd;
switch(cmd){
case "VIDEO_SYNC":
console.log("视频同步消息", MessageData);
break;
}
}catch(error){
console.error("解析 JSON 失败:", error);
}
};
socket.value.onerror = (error) => {
console.error("WebSocket for video 发生错误", error);
// console.log(error);
setReconnectScheduled(true);
socket.value.close();
};
socket.value.onclose = (event) => {
if (!getIsManualClose()) {
if (getReconnectScheduled()) {
socket.value = null;
addRetryCount();
setTimeout(reConnectWebSocket, 5000);
setReconnectScheduled(false);
} else {
// console.log("websocket因为浏览器省电设置断开");
console.log("WebSocket for video 连接已关闭", event);
}
}
};
};
// 断开WebSocket连接
export const disconnectWebSocket = () => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
socket.value.close();
}
};
// 重连机制
export const reConnectWebSocket = () => {
connectWebSocket();
};
// 发送消息
export const sendMessage = (message) => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
socket.value.send(message);
} else {
console.warn("WebSocket for video 未连接,无法发送消息");
}
};
//没有错误的重连,只是浏览器在后台断开了连接
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
} else {
if (!getIsManualClose() && socket.value.readyState === WebSocket.CLOSED) {
if (getReconnectScheduled()) {
return;
}
reConnectWebSocket();
}
}
});
import { ref } from "vue";
import { userInfoStore } from "@/store/user";
// userinfo 实例
const userinfo = userInfoStore();
const roomid = ref<number>(0);
// WebSocket 实例
const socket = ref<WebSocket | null>(null);
const isManualClose = ref<boolean>(false);
const reconnectScheduled = ref<boolean>(false);
const retryCount = ref<number>(0);
const getRetryCount = () => {
return retryCount.value;
};
const addRetryCount = () => {
retryCount.value = retryCount.value + 1;
};
const resetRetryCount = () => {
retryCount.value = 0;
};
const setReconnectScheduled = (value: boolean) => {
reconnectScheduled.value = value;
};
const getReconnectScheduled = () => {
return reconnectScheduled.value;
};
export const setIsManualClose = (value: boolean) => {
isManualClose.value = value;
};
const getIsManualClose = () => {
return isManualClose.value;
};
// 连接WebSocket
export const connectWebSocket = (r_id: number) => {
roomid.value = r_id;
const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
const host = window.location.host;
const socketUrl = `${protocol}${host}/ws/playroom?r_id=${r_id}`;
// const socketUrl = `ws://localhost:8080/online?u_id=${userinfo.user.u_id}&u_name=${userinfo.user.u_name}`;
if (socket.value && socket.value.readyState !== WebSocket.CLOSED) {
console.log("还在重连中...");
return;
}
const retrytime = getRetryCount();
if (retrytime >= 10) {
console.log("重连失败,请稍后再试");
return;
}
console.log(retrytime);
socket.value = new WebSocket(socketUrl, "token-"+ userinfo.token);
socket.value.onopen = (event: any) => {
console.log("WebSocket for video 连接已建立", event);
setReconnectScheduled(false);
setIsManualClose(false);
resetRetryCount();
};
//处理消息逻辑
socket.value.onmessage = (event) => {
console.log("从服务器收到消息:", event.data);
try{
const MessageData = JSON.parse(event.data);
const cmd = MessageData.cmd;
switch(cmd){
case "PING":
console.log("收到PING消息");
const msg = {
cmd: "PONG",
from: MessageData.to,
// 可扩展字段
time: new Date().toLocaleString()
}
sendMessage(msg);
break;
case "VIDEO_SYNC":
// console.log("视频同步消息", MessageData);
break;
}
}catch(error){
console.error("解析 JSON 失败", error);
}
};
socket.value.onerror = (error) => {
console.error("WebSocket for video 发生错误:", error);
// console.log(error);
setReconnectScheduled(true);
socket.value.close();
};
socket.value.onclose = (event) => {
if (!getIsManualClose()) {
if (getReconnectScheduled()) {
socket.value = null;
addRetryCount();
setTimeout(reConnectWebSocket, 5000);
setReconnectScheduled(false);
} else {
// console.log("websocket因为浏览器省电设置断开");
console.log("WebSocket for video 连接已关闭", event);
}
}
};
};
// 断开WebSocket连接
export const disconnectWebSocket = () => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
roomid.value = 0;
socket.value.close();
}
};
// 重连机制
export const reConnectWebSocket = () => {
connectWebSocket(roomid.value);
};
// 发送消息
export const sendMessage = (message: any) => {
try{
const jsonmessage = JSON.stringify(message);
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
socket.value.send(jsonmessage);
} else {
console.warn("WebSocket for video 未连接,无法发送消息");
}
}
catch(error){
console.error("Failed to stringify message:", error);
}
};
//没有错误的重连,只是浏览器在后台断开了连接
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
} else {
if (!getIsManualClose() && socket.value.readyState === WebSocket.CLOSED) {
if (getReconnectScheduled()) {
return;
}
reConnectWebSocket();
}
}
});

View File

@@ -29,11 +29,6 @@ export default defineConfig({
changeOrigin: true,
ws: true,
},
// '/ws': {
// target: 'ws://localhost:8080',
// changeOrigin: true,
// ws: true,
// }
},
host: '0.0.0.0',
port: 5173,