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

View File

@@ -13,6 +13,10 @@ app.use((req, res, next) => {
next(); 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) => { app.get('/proxy', async (req, res) => {
console.log('Received Range header:', req.headers.range); console.log('Received Range header:', req.headers.range);
@@ -28,6 +32,10 @@ app.get('/proxy', async (req, res) => {
const headers = { const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' '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) { if (req.headers.range) {
headers['Range'] = req.headers.range; // 转发 Range 请求头,支持视频分段加载 headers['Range'] = req.headers.range; // 转发 Range 请求头,支持视频分段加载
} }
@@ -43,6 +51,9 @@ app.get('/proxy', async (req, res) => {
}); });
} }
// 关键Range 请求上游通常返回 206必须把状态码透传给浏览器
res.status(response.status);
// 设置响应头 // 设置响应头
response.headers.forEach((value, key) => { response.headers.forEach((value, key) => {
// 跳过一些可能导致问题的头信息 // 跳过一些可能导致问题的头信息

View File

@@ -65,3 +65,29 @@ export const joinPlayroom = async (r_id: number) => {
throw new Error(response.data.message); 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

@@ -2,6 +2,8 @@
#app { #app {
max-width: 1280px; max-width: 1280px;
width: 100%;
height: 100%;
/* margin: 0 auto; */ /* margin: 0 auto; */
/* padding: 2rem; */ /* padding: 2rem; */
font-weight: normal; font-weight: normal;

View File

@@ -1,6 +1,6 @@
import { ref } from "vue"; import { ref } from "vue";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { connectWebSocket, disconnectWebSocket, sendMessage, setIsManualClose } from '@/websocket/roomSocket' import { connectWebSocket, disconnectWebSocket, sendMessage, setIsManualClose } from "@/websocket/roomSocket";
interface PlayroomState { interface PlayroomState {
id: number id: number
@@ -11,10 +11,18 @@ interface PlayroomState {
role: number; role: number;
} }
interface member {
id: number;
u_id: string;
u_name: string;
u_avatar: string;
}
export const PlayroomStore = defineStore("PlayroomStore", export const PlayroomStore = defineStore("PlayroomStore",
() =>{ () =>{
const currentPlayroom = ref<PlayroomState>(); const currentPlayroom = ref<PlayroomState>();
const members = ref<member[]>([]);
const currentUrl = ref<string>(""); const currentUrl = ref<string>("");
const setCurrentPlayroom = (playroom: PlayroomState) => { const setCurrentPlayroom = (playroom: PlayroomState) => {
@@ -34,16 +42,27 @@ export const PlayroomStore = defineStore("PlayroomStore",
} }
const clearPlayroom = () => { const clearPlayroom = () => {
currentPlayroom.value = undefined; currentPlayroom.value = null;
currentUrl.value = ""; 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 { return {
getCurrentPlayroom, currentPlayroom,
getCurrentId, currentUrl,
setCurrentPlayroom, setCurrentPlayroom,
setCurrentUrl,
clearPlayroom, clearPlayroom,
addmember,
getmembers,
} }
}) })
@@ -58,7 +77,7 @@ export const videoSocketStore = defineStore("videoSocketStore",{
connect(id: number) { connect(id: number) {
this.id = id; this.id = id;
if (this.isConnected === true) return if (this.isConnected === true) return
connectWebSocket(); connectWebSocket(id);
this.isConnected = true; this.isConnected = true;
}, },
disconnect() { disconnect() {

View File

@@ -55,7 +55,7 @@ const goToRoom = (r) => {
roominfo.setCurrentPlayroom(r); roominfo.setCurrentPlayroom(r);
// 跳转到房间页面 // 跳转到房间页面
const baseUrl = window.location.origin; const baseUrl = window.location.origin;
const targetUrl = `${baseUrl}/room`; // 替换为你的目标路由 const targetUrl = `${baseUrl}/room?r_id=${r.r_id}`; // 替换为你的目标路由
window.open(targetUrl, "room"); window.open(targetUrl, "room");
}; };

View File

@@ -36,46 +36,9 @@
</el-row> </el-row>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="20">
<video-player :autoplay="false" :videoUrl="currentURL" /> <video-player :autoplay="false" :videoUrl="currentURL" />
</el-col> </el-col>
<el-col :span="4">
<el-row style="height: 50%">
<!-- 语音大厅 -->
<el-col :span="24" class="voice-room">
<!-- <audio-player :audioUrl="audioUrl" /> -->
<el-row>
<!-- 加入的语言用户 -->
<el-text size="large">语言聊天室:</el-text>
<el-button type="success" round style="margin-left: 10px;" @click="joinVoiceRoom"
v-if="!invoice">加入</el-button>
<el-button type="info" round style="margin-left: 10px;" @click="leaveVoiceRoom"
v-if="invoice">离开</el-button>
<el-scrollbar style="height: 150px;width: 100%;">
<div class="invoice">
<div v-for="(item) in membersInVoice" class="user-profile-invoice">
<img :src="item.u_avatar" alt="User Avatar" />
</div>
</div>
</el-scrollbar>
</el-row>
</el-col>
</el-row>
<el-row style="height: 50%">
<el-col :span="24">
<!-- 弹幕显示区域 -->
<el-scroller class="comments">
<div v-for="comment in roomMessages" :key="comment.id" class="comment">
{{ comment.u_name }}:
{{ comment.content }}
</div>
</el-scroller>
<!-- 发送弹幕输入框 -->
<el-input v-model="newComment" placeholder="发送弹幕" style="height: 20%;margin-left: 3px;"
@keyup.enter="sendComment" />
</el-col>
</el-row>
</el-col>
</el-row> </el-row>
</div> </div>
</div> </div>
@@ -162,6 +125,8 @@ import { ElMessage } from 'element-plus';
import { userInfoStore } from '@/store/user'; import { userInfoStore } from '@/store/user';
import videoPlayer from '@/components/videoPlayer.vue'; import videoPlayer from '@/components/videoPlayer.vue';
import { PlayroomStore } from '@/store/playroom'; import { PlayroomStore } from '@/store/playroom';
import { getPlayroomDetails } from '@/api/playroom';
import { connectWebSocket } from '@/websocket/roomSocket';
//import audioPlayer from '@/components/audioPlayer.vue'; // 假设你有一个音频播放组件 //import audioPlayer from '@/components/audioPlayer.vue'; // 假设你有一个音频播放组件
@@ -171,7 +136,9 @@ const userinfo = userInfoStore();
const dialogVisibleCode = ref(false) const dialogVisibleCode = ref(false)
const dialogVisibleVideo = ref(false) const dialogVisibleVideo = ref(false)
const invoice = ref(false) const invoice = ref(false);
const drawer = ref(false); const drawer = ref(false);
const role = ref(null); const role = ref(null);
const invitingCode = ref('666666') const invitingCode = ref('666666')
@@ -259,7 +226,7 @@ const membersInVoice = ref([
]) ])
const videoUrl = ref('https://www.5dm.link/api/dd.php?vid=ccccxhndnys1&cid=ccccxhndnys1&xid=0&pid=55293&tid=1742788904&t=616d5131b6ade51a0e20814466b13515&ext=.mp4'); // 替换为你的视频流地址 const videoUrl = ref(''); // 替换为你的视频流地址
const audioUrl = ref('https://example.com/audio/stream'); // 替换为你的音频流地址 const audioUrl = ref('https://example.com/audio/stream'); // 替换为你的音频流地址
const danmakuOptions = ref({ const danmakuOptions = ref({
@@ -289,8 +256,13 @@ const sendComment = () => {
} }
}; };
const getRoomInfo = () => { const getRoomInfo = async (r_id) => {
try {
const response = await getPlayroomDetails(r_id)
curruentRoomInfo.setCurrentPlayroom(response)
} catch (error) {
ElMessage.error('获取房间信息失败:' + error)
}
} }
const handleAvatarChange = (event) => { const handleAvatarChange = (event) => {
@@ -303,32 +275,7 @@ const handleAvatarChange = (event) => {
//上传到服务器 //上传到服务器
const uploadAvatar = () => { const uploadAvatar = () => {
if (!avatar.value) {
ElMessage('请选择头像')
return
}
const formdata = new FormData();
formdata.append('file', avatar.value)
formdata.append('r_id', curruentRoomInfo.r_id)
axios({
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': userinfo.token
},
method: 'post',
url: '/api/avatar/upload',
data: formdata
}).then((response) => {
if (response.data.code !== 200) {
ElMessage.error(response.data.msg || '上传失败')
return
}
getUserInfo()
ElMessage.success('上传成功')
}).catch((error) => {
console.error('上传失败:', error);
ElMessage.error('上传失败')
})
} }
const getInvitingCode = () => { const getInvitingCode = () => {
@@ -362,27 +309,10 @@ const goback = () => {
} }
onMounted(() => { onMounted(async () => {
getRoomInfo() const r_id = window.location.search.split('=')[1];
await getRoomInfo(r_id)
//测试代码 connectWebSocket(r_id)
curruentRoomInfo.r_id = '123456';
curruentRoomInfo.r_name = '弹幕聊天室';
curruentRoomInfo.setCurrentUrl('https://www.5dm.link/api/dd.php?vid=ccccxhndnys1&cid=ccccxhndnys1&xid=0&pid=55293&tid=1742788904&t=616d5131b6ade51a0e20814466b13515&ext=.mp4')
curruentRoomInfo.r_avatar = 'https://merlin.xin/avatars/avatar';
role.value = 0;
avatarPreview.value = curruentRoomInfo.r_avatar;
watchEffect(() => {
if (curruentRoomInfo.r_id !== '' && curruentRoomInfo.r_id !== null) { // 如果 r_id 不为空
console.log('Room ID is available:', curruentRoomInfo.r_id);
//页面需要加载的逻辑
//头像预览容器赋值
avatarPreview.value = curruentRoomInfo.r_avatar;
}
})
}) })
@@ -399,11 +329,12 @@ onMounted(() => {
.container { .container {
position: relative; position: relative;
top: 50px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 80vh; height: 80%;
width: 80vw; width: 100%;
padding: 50px;
max-height: 100vh;
} }
/* .maincontent {} */ /* .maincontent {} */

View File

@@ -1,59 +1,55 @@
import { ref } from "vue"; import { ref } from "vue";
import { userInfoStore } from "@/store/user"; import { userInfoStore } from "@/store/user";
import { ElMessage } from "element-plus";
import { PlayroomStore } from "@/store/playroom";
// userinfo 实例 // userinfo 实例
const userinfo = userInfoStore(); const userinfo = userInfoStore();
// 延迟获取 playroom 实例,避免循环依赖 const roomid = ref<number>(0);
const getPlayroom = () => {
return PlayroomStore();
};
// WebSocket 实例 // WebSocket 实例
const socket = ref(null); const socket = ref<WebSocket | null>(null);
const isManualClose = ref(false); const isManualClose = ref<boolean>(false);
const reconnectScheduled = ref(false); const reconnectScheduled = ref<boolean>(false);
const retryCount = ref(0); const retryCount = ref<number>(0);
export const getRetryCount = () => { const getRetryCount = () => {
return retryCount.value; return retryCount.value;
}; };
export const addRetryCount = () => { const addRetryCount = () => {
retryCount.value = retryCount.value + 1; retryCount.value = retryCount.value + 1;
}; };
export const resetRetryCount = () => { const resetRetryCount = () => {
retryCount.value = 0; retryCount.value = 0;
}; };
export const setReconnectScheduled = (value) => { const setReconnectScheduled = (value: boolean) => {
reconnectScheduled.value = value; reconnectScheduled.value = value;
}; };
export const getReconnectScheduled = () => { const getReconnectScheduled = () => {
return reconnectScheduled.value; return reconnectScheduled.value;
}; };
export const setIsManualClose = (value) => { export const setIsManualClose = (value: boolean) => {
isManualClose.value = value; isManualClose.value = value;
}; };
export const getIsManualClose = () => { const getIsManualClose = () => {
return isManualClose.value; return isManualClose.value;
}; };
// 连接WebSocket // 连接WebSocket
export const connectWebSocket = () => { export const connectWebSocket = (r_id: number) => {
const playroom = getPlayroom(); roomid.value = r_id;
const protocol = window.location.protocol === "https:" ? "wss://" : "ws://"; const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
const host = window.location.host; const host = window.location.host;
const socketUrl = `${protocol}${host}/ws/video?r_id=${playroom.getCurrentId()}`; 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}`; // 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) { if (socket.value && socket.value.readyState !== WebSocket.CLOSED) {
@@ -68,7 +64,7 @@ export const connectWebSocket = () => {
console.log(retrytime); console.log(retrytime);
socket.value = new WebSocket(socketUrl, "token-"+ userinfo.token); socket.value = new WebSocket(socketUrl, "token-"+ userinfo.token);
socket.value.onopen = (event) => { socket.value.onopen = (event: any) => {
console.log("WebSocket for video 连接已建立", event); console.log("WebSocket for video 连接已建立", event);
setReconnectScheduled(false); setReconnectScheduled(false);
setIsManualClose(false); setIsManualClose(false);
@@ -82,8 +78,18 @@ export const connectWebSocket = () => {
const MessageData = JSON.parse(event.data); const MessageData = JSON.parse(event.data);
const cmd = MessageData.cmd; const cmd = MessageData.cmd;
switch(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": case "VIDEO_SYNC":
console.log("视频同步消息", MessageData); // console.log("视频同步消息", MessageData);
break; break;
} }
}catch(error){ }catch(error){
@@ -117,22 +123,29 @@ export const connectWebSocket = () => {
// 断开WebSocket连接 // 断开WebSocket连接
export const disconnectWebSocket = () => { export const disconnectWebSocket = () => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) { if (socket.value && socket.value.readyState === WebSocket.OPEN) {
roomid.value = 0;
socket.value.close(); socket.value.close();
} }
}; };
// 重连机制 // 重连机制
export const reConnectWebSocket = () => { export const reConnectWebSocket = () => {
connectWebSocket(); connectWebSocket(roomid.value);
}; };
// 发送消息 // 发送消息
export const sendMessage = (message) => { export const sendMessage = (message: any) => {
try{
const jsonmessage = JSON.stringify(message);
if (socket.value && socket.value.readyState === WebSocket.OPEN) { if (socket.value && socket.value.readyState === WebSocket.OPEN) {
socket.value.send(message); socket.value.send(jsonmessage);
} else { } else {
console.warn("WebSocket for video 未连接,无法发送消息"); console.warn("WebSocket for video 未连接,无法发送消息");
} }
}
catch(error){
console.error("Failed to stringify message:", error);
}
}; };
//没有错误的重连,只是浏览器在后台断开了连接 //没有错误的重连,只是浏览器在后台断开了连接

View File

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