fix: (ai gen) streaming stability improvementsk

This commit is contained in:
2026-01-12 22:33:07 +01:00
parent bda1297689
commit 103644acc3
4 changed files with 182 additions and 32 deletions

View File

@@ -35,7 +35,7 @@ const isConnected = ref(false);
const viewerStore = useViewerStore();
const { code: codeRef } = storeToRefs(viewerStore);
const wsUrl = useWebSocketUrl();
const { send } = useWebSocket(wsUrl, {
const { send, close: closeWebSocket } = useWebSocket(wsUrl, {
autoReconnect: true,
heartbeat: {
message: JSON.stringify({ event: "ping" }),
@@ -80,6 +80,18 @@ const { send } = useWebSocket(wsUrl, {
if (peerConnection.connectionState === "connected") {
viewerStore.setConnectionStatus("connected!");
}
// Handle disconnection or failed connection
if (
peerConnection.connectionState === "disconnected" ||
peerConnection.connectionState === "failed" ||
peerConnection.connectionState === "closed"
) {
viewerStore.setConnectionStatus(
`connection ${peerConnection.connectionState}`,
);
isConnected.value = false;
}
};
peerConnection.oniceconnectionstatechange = () => {
@@ -95,21 +107,32 @@ const { send } = useWebSocket(wsUrl, {
};
viewerStore.setConnectionStatus("sending an sdp description");
await peerConnection.setRemoteDescription(
new RTCSessionDescription(message.sdp),
);
try {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(message.sdp),
);
} catch (error) {
console.error("Error setting remote description:", error);
viewerStore.setConnectionStatus("failed to connect");
return;
}
viewerStore.setConnectionStatus("sending an answer");
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
try {
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
send(
JSON.stringify({
event: "answer",
targetId: message.senderId,
sdp: answer,
}),
);
send(
JSON.stringify({
event: "answer",
targetId: message.senderId,
sdp: answer,
}),
);
} catch (error) {
console.error("Error creating answer:", error);
viewerStore.setConnectionStatus("failed to send answer");
}
}
if (message.event === "ice-candidate") {
@@ -120,11 +143,21 @@ const { send } = useWebSocket(wsUrl, {
viewerStore.setConnectionStatus(
`got an ice candidate from remote peer (type: ${message.candidate.type})`,
);
await viewerStore.peerConnection.addIceCandidate(
new RTCIceCandidate(message.candidate),
);
try {
await viewerStore.peerConnection.addIceCandidate(
new RTCIceCandidate(message.candidate),
);
} catch (error) {
console.error("Error adding ICE candidate:", error);
}
}
}
if (message.event === "room-closed") {
viewerStore.setConnectionStatus("room closed by host");
cleanupViewing();
isConnected.value = false;
}
},
});
@@ -145,4 +178,40 @@ watch(codeRef, (newCode) => {
startWebRTCConnection();
}
});
function cleanupViewing() {
// Close peer connection
if (viewerStore.peerConnection) {
viewerStore.peerConnection.close();
viewerStore.setPeerConnection(null);
}
// Clear video element
if (videofeedRef.value) {
videofeedRef.value.srcObject = null;
}
// Reset connection status
viewerStore.setConnectionStatus("disconnected");
isConnected.value = false;
}
// Cleanup on component unmount
onBeforeUnmount(() => {
cleanupViewing();
closeWebSocket();
});
// Cleanup on window/tab close
onMounted(() => {
const handleBeforeUnload = () => {
cleanupViewing();
};
window.addEventListener("beforeunload", handleBeforeUnload);
onUnmounted(() => {
window.removeEventListener("beforeunload", handleBeforeUnload);
});
});
</script>

View File

@@ -21,7 +21,7 @@ const videofeedRef = ref<HTMLVideoElement | null>(null);
const localStream = ref<MediaStream | null>(null);
const wsUrl = useWebSocketUrl();
const { send } = useWebSocket(wsUrl, {
const { send, close: closeWebSocket } = useWebSocket(wsUrl, {
autoReconnect: true,
heartbeat: {
message: JSON.stringify({ event: "ping" }),
@@ -80,35 +80,110 @@ const { send } = useWebSocket(wsUrl, {
if (message.event === "ice-candidate") {
const pc = streamerStore.peerConnections[message.from];
if (pc) {
await pc.addIceCandidate(new RTCIceCandidate(message.candidate));
try {
await pc.addIceCandidate(new RTCIceCandidate(message.candidate));
} catch (error) {
console.error("Error adding ICE candidate:", error);
}
}
}
if (message.event === "answer") {
const pc = streamerStore.peerConnections[message.from];
if (pc) {
await pc.setRemoteDescription(new RTCSessionDescription(message.sdp));
try {
await pc.setRemoteDescription(new RTCSessionDescription(message.sdp));
} catch (error) {
console.error("Error setting remote description:", error);
}
}
}
if (message.event === "viewer-left") {
const pc = streamerStore.peerConnections[message.viewerId];
if (pc) {
pc.close();
streamerStore.removePeerConnection(message.viewerId);
}
}
},
});
async function startScreenShare() {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false,
});
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false,
});
localStream.value = stream;
localStream.value = stream;
if (videofeedRef.value) {
videofeedRef.value.srcObject = stream;
if (videofeedRef.value) {
videofeedRef.value.srcObject = stream;
}
// Detect when user stops sharing via browser UI
stream.getTracks().forEach((track) => {
track.onended = () => {
console.log("Screen sharing stopped by user");
cleanupStreaming();
};
});
send(
JSON.stringify({
event: "create-room",
}),
);
} catch (error) {
console.error("Failed to start screen share:", error);
// User cancelled or permission denied
cleanupStreaming();
}
}
function cleanupStreaming() {
// Stop all media tracks
if (localStream.value) {
localStream.value.getTracks().forEach((track) => {
track.stop();
});
localStream.value = null;
}
send(
JSON.stringify({
event: "create-room",
}),
);
// Close all peer connections
Object.values(streamerStore.peerConnections).forEach((pc) => {
pc.close();
});
// Clear peer connections from store
streamerStore.clearPeerConnections();
// Clear video element
if (videofeedRef.value) {
videofeedRef.value.srcObject = null;
}
// Clear room code
streamerStore.setCode("");
}
// Cleanup on component unmount
onBeforeUnmount(() => {
cleanupStreaming();
closeWebSocket();
});
// Cleanup on window/tab close
onMounted(() => {
const handleBeforeUnload = () => {
cleanupStreaming();
};
window.addEventListener("beforeunload", handleBeforeUnload);
onUnmounted(() => {
window.removeEventListener("beforeunload", handleBeforeUnload);
});
});
</script>

View File

@@ -13,6 +13,12 @@ export const useStreamerStore = defineStore("streamer", {
addPeerConnection(id: string, pc: RTCPeerConnection) {
this.peerConnections[id] = pc;
},
removePeerConnection(id: string) {
delete this.peerConnections[id];
},
clearPeerConnections() {
this.peerConnections = {};
},
setIceServers(iceServers: RTCIceServer[]) {
this.iceServers = iceServers;
},

View File

@@ -10,7 +10,7 @@ export const useViewerStore = defineStore('viewer', {
setCode(code: string) {
this.code = code;
},
setPeerConnection(pc: RTCPeerConnection) {
setPeerConnection(pc: RTCPeerConnection | null) {
this.peerConnection = pc;
},
setConnectionStatus(status: string) {