feat: playroom sync finished
This commit is contained in:
@@ -11,6 +11,8 @@ const videoRef = ref(null);
|
|||||||
const player = ref(null);
|
const player = ref(null);
|
||||||
const hlsInstance = ref(null);
|
const hlsInstance = ref(null);
|
||||||
|
|
||||||
|
const emit = defineEmits(['canplay', 'play', 'pause', 'remote-play-failed']);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
autoplay: { type: Boolean, default: false },
|
autoplay: { type: Boolean, default: false },
|
||||||
videoUrl: { type: String, required: true },
|
videoUrl: { type: String, required: true },
|
||||||
@@ -121,10 +123,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
player.value.on('play', () => {
|
player.value.on('play', () => {
|
||||||
console.log('播放:', props.videoUrl);
|
console.log('播放:', props.videoUrl);
|
||||||
|
emit('play', { time: getCurrentTime() });
|
||||||
});
|
});
|
||||||
|
|
||||||
player.value.on('pause', () => {
|
player.value.on('pause', () => {
|
||||||
console.log('暂停');
|
console.log('暂停');
|
||||||
|
emit('pause', { time: getCurrentTime() });
|
||||||
});
|
});
|
||||||
|
|
||||||
player.value.on('ended', () => {
|
player.value.on('ended', () => {
|
||||||
@@ -140,13 +144,82 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
player.value.on('canplay', () => {
|
player.value.on('canplay', () => {
|
||||||
console.log('视频可以播放');
|
// 缓冲、seek 后常会多次触发;不在此刷屏打印,由父组件用 pending* 决定是否执行同步
|
||||||
|
emit('canplay');
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化播放器失败:', error);
|
console.error('初始化播放器失败:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getCurrentTime = () => {
|
||||||
|
const video = player.value?.video;
|
||||||
|
if (!video || typeof video.currentTime !== 'number' || Number.isNaN(video.currentTime)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return video.currentTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
const seekTo = (timeSec) => {
|
||||||
|
const t = Number(timeSec);
|
||||||
|
if (!Number.isFinite(t) || t < 0) return;
|
||||||
|
if (player.value?.seek) {
|
||||||
|
try {
|
||||||
|
player.value.seek(t);
|
||||||
|
return;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
const video = player.value?.video;
|
||||||
|
if (video && typeof video.currentTime === 'number') {
|
||||||
|
try {
|
||||||
|
video.currentTime = t;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ remote?: boolean }} options
|
||||||
|
* remote=true:来自 WebSocket 同步,无用户手势;需静音才能通过多数浏览器的自动播放策略
|
||||||
|
*/
|
||||||
|
const play = async (options = {}) => {
|
||||||
|
const remote = options?.remote === true;
|
||||||
|
const video = player.value?.video;
|
||||||
|
if (!video?.play) return false;
|
||||||
|
if (remote) {
|
||||||
|
try {
|
||||||
|
video.muted = true;
|
||||||
|
video.setAttribute?.('playsinline', 'true');
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await video.play();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[videoPlayer] play() 被拒绝(多为自动播放策略):', e?.name, e?.message);
|
||||||
|
if (remote) {
|
||||||
|
emit('remote-play-failed', { error: e });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pause = () => {
|
||||||
|
if (player.value?.pause) {
|
||||||
|
try {
|
||||||
|
player.value.pause();
|
||||||
|
return;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
const video = player.value?.video;
|
||||||
|
if (video?.pause) {
|
||||||
|
try {
|
||||||
|
video.pause();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ getCurrentTime, seekTo, play, pause });
|
||||||
|
|
||||||
watch(() => props.videoUrl, (newUrl) => {
|
watch(() => props.videoUrl, (newUrl) => {
|
||||||
if (player.value && newUrl) {
|
if (player.value && newUrl) {
|
||||||
console.log('切换视频到:', newUrl);
|
console.log('切换视频到:', newUrl);
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export const PlayroomStore = defineStore("PlayroomStore",
|
|||||||
return {
|
return {
|
||||||
currentPlayroom,
|
currentPlayroom,
|
||||||
currentUrl,
|
currentUrl,
|
||||||
|
getCurrentId,
|
||||||
setCurrentPlayroom,
|
setCurrentPlayroom,
|
||||||
clearPlayroom,
|
clearPlayroom,
|
||||||
addmember,
|
addmember,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,162 +1,191 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { userInfoStore } from "@/store/user";
|
import { userInfoStore } from "@/store/user";
|
||||||
|
|
||||||
|
export const ROOM_SOCKET_VIDEO_SYNC_EVENT = "room:video_sync";
|
||||||
// userinfo 实例
|
export const ROOM_SOCKET_VIDEO_PLAY_EVENT = "room:video_play";
|
||||||
const userinfo = userInfoStore();
|
export const ROOM_SOCKET_VIDEO_PAUSE_EVENT = "room:video_pause";
|
||||||
|
|
||||||
const roomid = ref<number>(0);
|
// userinfo 实例
|
||||||
|
const userinfo = userInfoStore();
|
||||||
|
|
||||||
// WebSocket 实例
|
const roomid = ref<number>(0);
|
||||||
const socket = ref<WebSocket | null>(null);
|
|
||||||
|
|
||||||
const isManualClose = ref<boolean>(false);
|
// WebSocket 实例
|
||||||
|
const socket = ref<WebSocket | null>(null);
|
||||||
const reconnectScheduled = ref<boolean>(false);
|
|
||||||
|
const isManualClose = ref<boolean>(false);
|
||||||
const retryCount = ref<number>(0);
|
|
||||||
|
const reconnectScheduled = ref<boolean>(false);
|
||||||
const getRetryCount = () => {
|
|
||||||
return retryCount.value;
|
const retryCount = ref<number>(0);
|
||||||
};
|
|
||||||
|
const getRetryCount = () => {
|
||||||
const addRetryCount = () => {
|
return retryCount.value;
|
||||||
retryCount.value = retryCount.value + 1;
|
};
|
||||||
};
|
|
||||||
|
const addRetryCount = () => {
|
||||||
const resetRetryCount = () => {
|
retryCount.value = retryCount.value + 1;
|
||||||
retryCount.value = 0;
|
};
|
||||||
};
|
|
||||||
const setReconnectScheduled = (value: boolean) => {
|
const resetRetryCount = () => {
|
||||||
reconnectScheduled.value = value;
|
retryCount.value = 0;
|
||||||
};
|
};
|
||||||
|
const setReconnectScheduled = (value: boolean) => {
|
||||||
const getReconnectScheduled = () => {
|
reconnectScheduled.value = value;
|
||||||
return reconnectScheduled.value;
|
};
|
||||||
};
|
|
||||||
|
const getReconnectScheduled = () => {
|
||||||
export const setIsManualClose = (value: boolean) => {
|
return reconnectScheduled.value;
|
||||||
isManualClose.value = value;
|
};
|
||||||
};
|
|
||||||
|
export const setIsManualClose = (value: boolean) => {
|
||||||
const getIsManualClose = () => {
|
isManualClose.value = value;
|
||||||
return isManualClose.value;
|
};
|
||||||
};
|
|
||||||
|
const getIsManualClose = () => {
|
||||||
// 连接WebSocket
|
return isManualClose.value;
|
||||||
export const connectWebSocket = (r_id: number) => {
|
};
|
||||||
roomid.value = r_id;
|
|
||||||
const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
|
// 连接WebSocket
|
||||||
const host = window.location.host;
|
export const connectWebSocket = (r_id: number) => {
|
||||||
const socketUrl = `${protocol}${host}/ws/playroom?r_id=${r_id}`;
|
roomid.value = r_id;
|
||||||
|
const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||||
// const socketUrl = `ws://localhost:8080/online?u_id=${userinfo.user.u_id}&u_name=${userinfo.user.u_name}`;
|
const host = window.location.host;
|
||||||
if (socket.value && socket.value.readyState !== WebSocket.CLOSED) {
|
const socketUrl = `${protocol}${host}/ws/playroom?r_id=${r_id}`;
|
||||||
console.log("还在重连中...");
|
|
||||||
return;
|
// 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) {
|
||||||
const retrytime = getRetryCount();
|
console.log("还在重连中...");
|
||||||
if (retrytime >= 10) {
|
return;
|
||||||
console.log("重连失败,请稍后再试");
|
}
|
||||||
return;
|
const retrytime = getRetryCount();
|
||||||
}
|
if (retrytime >= 10) {
|
||||||
console.log(retrytime);
|
console.log("重连失败,请稍后再试");
|
||||||
socket.value = new WebSocket(socketUrl, "token-"+ userinfo.token);
|
return;
|
||||||
|
}
|
||||||
socket.value.onopen = (event: any) => {
|
console.log(retrytime);
|
||||||
console.log("WebSocket for video 连接已建立", event);
|
// 调试:房间 WS 不依赖 user.id(URL 只有 r_id,鉴权在子协议 token);id 只影响你主动发的消息里的 from
|
||||||
setReconnectScheduled(false);
|
console.log("[playroom-debug][connectWebSocket]", {
|
||||||
setIsManualClose(false);
|
r_id,
|
||||||
resetRetryCount();
|
"user.id": userinfo.user?.id,
|
||||||
};
|
"user.id > 0": typeof userinfo.user?.id === "number" && userinfo.user.id > 0,
|
||||||
|
"user.u_id": userinfo.user?.u_id,
|
||||||
//处理消息逻辑
|
hasToken: Boolean(userinfo.token),
|
||||||
socket.value.onmessage = (event) => {
|
socketUrl,
|
||||||
console.log("从服务器收到消息:", event.data);
|
});
|
||||||
try{
|
socket.value = new WebSocket(socketUrl, "token-"+ userinfo.token);
|
||||||
const MessageData = JSON.parse(event.data);
|
|
||||||
const cmd = MessageData.cmd;
|
socket.value.onopen = (event: any) => {
|
||||||
switch(cmd){
|
console.log("[playroom-debug][ws open] 连接已建立,此时 user.id =", userinfo.user?.id, "u_id =", userinfo.user?.u_id);
|
||||||
case "PING":
|
console.log("WebSocket for video 连接已建立", event);
|
||||||
console.log("收到PING消息");
|
setReconnectScheduled(false);
|
||||||
const msg = {
|
setIsManualClose(false);
|
||||||
cmd: "PONG",
|
resetRetryCount();
|
||||||
from: MessageData.to,
|
};
|
||||||
// 可扩展字段
|
|
||||||
time: new Date().toLocaleString()
|
//处理消息逻辑
|
||||||
}
|
socket.value.onmessage = (event) => {
|
||||||
sendMessage(msg);
|
console.log("从服务器收到消息:", event.data);
|
||||||
break;
|
try{
|
||||||
case "VIDEO_SYNC":
|
const MessageData = JSON.parse(event.data);
|
||||||
// console.log("视频同步消息", MessageData);
|
const cmd = MessageData.cmd;
|
||||||
break;
|
switch(cmd){
|
||||||
}
|
case "PING":
|
||||||
}catch(error){
|
console.log("收到PING消息");
|
||||||
console.error("解析 JSON 失败:", error);
|
const msg = {
|
||||||
}
|
cmd: "PONG",
|
||||||
|
from: MessageData.to,
|
||||||
};
|
// 可扩展字段
|
||||||
|
time: new Date().toLocaleString()
|
||||||
socket.value.onerror = (error) => {
|
}
|
||||||
console.error("WebSocket for video 发生错误:", error);
|
sendMessage(msg);
|
||||||
// console.log(error);
|
break;
|
||||||
setReconnectScheduled(true);
|
case "VIDEO_SYNC":
|
||||||
socket.value.close();
|
console.log("视频同步消息", MessageData);
|
||||||
};
|
// 通过事件分发给页面(避免 websocket 层直接依赖具体视图)
|
||||||
|
window.dispatchEvent(
|
||||||
socket.value.onclose = (event) => {
|
new CustomEvent(ROOM_SOCKET_VIDEO_SYNC_EVENT, { detail: MessageData })
|
||||||
if (!getIsManualClose()) {
|
);
|
||||||
if (getReconnectScheduled()) {
|
break;
|
||||||
socket.value = null;
|
case "VIDEO_PLAY":
|
||||||
addRetryCount();
|
console.log("视频播放");
|
||||||
setTimeout(reConnectWebSocket, 5000);
|
window.dispatchEvent(
|
||||||
setReconnectScheduled(false);
|
new CustomEvent(ROOM_SOCKET_VIDEO_PLAY_EVENT, { detail: MessageData })
|
||||||
} else {
|
);
|
||||||
// console.log("websocket因为浏览器省电设置断开");
|
break;
|
||||||
console.log("WebSocket for video 连接已关闭", event);
|
case "VIDEO_PAUSE":
|
||||||
}
|
console.log("视频暂停");
|
||||||
}
|
window.dispatchEvent(
|
||||||
};
|
new CustomEvent(ROOM_SOCKET_VIDEO_PAUSE_EVENT, { detail: MessageData })
|
||||||
};
|
);
|
||||||
|
break;
|
||||||
// 断开WebSocket连接
|
}
|
||||||
export const disconnectWebSocket = () => {
|
}catch(error){
|
||||||
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
|
console.error("解析 JSON 失败:", error);
|
||||||
roomid.value = 0;
|
}
|
||||||
socket.value.close();
|
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
socket.value.onerror = (error) => {
|
||||||
// 重连机制
|
console.error("WebSocket for video 发生错误:", error);
|
||||||
export const reConnectWebSocket = () => {
|
// console.log(error);
|
||||||
connectWebSocket(roomid.value);
|
setReconnectScheduled(true);
|
||||||
};
|
socket.value.close();
|
||||||
|
};
|
||||||
// 发送消息
|
|
||||||
export const sendMessage = (message: any) => {
|
socket.value.onclose = (event) => {
|
||||||
try{
|
if (!getIsManualClose()) {
|
||||||
const jsonmessage = JSON.stringify(message);
|
if (getReconnectScheduled()) {
|
||||||
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
|
socket.value = null;
|
||||||
socket.value.send(jsonmessage);
|
addRetryCount();
|
||||||
} else {
|
setTimeout(reConnectWebSocket, 5000);
|
||||||
console.warn("WebSocket for video 未连接,无法发送消息");
|
setReconnectScheduled(false);
|
||||||
}
|
} else {
|
||||||
}
|
// console.log("websocket因为浏览器省电设置断开");
|
||||||
catch(error){
|
console.log("WebSocket for video 连接已关闭", event);
|
||||||
console.error("Failed to stringify message:", error);
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
//没有错误的重连,只是浏览器在后台断开了连接
|
|
||||||
document.addEventListener("visibilitychange", () => {
|
// 断开WebSocket连接
|
||||||
if (document.hidden) {
|
export const disconnectWebSocket = () => {
|
||||||
} else {
|
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
|
||||||
if (!getIsManualClose() && socket.value.readyState === WebSocket.CLOSED) {
|
roomid.value = 0;
|
||||||
if (getReconnectScheduled()) {
|
socket.value.close();
|
||||||
return;
|
}
|
||||||
}
|
};
|
||||||
reConnectWebSocket();
|
|
||||||
}
|
// 重连机制
|
||||||
}
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user