Files
QueueCube/web/frontend/src/hooks/useScreenShare.ts
T

136 lines
4.4 KiB
TypeScript
Raw Normal View History

2025-05-12 20:27:09 -07:00
import { useState, useEffect, useRef, useCallback } from 'react';
import { API } from '../api/player';
interface UseScreenShareResult {
isScreenSharing: boolean;
isScreenSharingSupported: boolean;
toggleScreenShare: () => Promise<void>;
stopScreenShare: () => void;
}
function getBestSupportedMimeType() {
// Ordered by preference (best first) - all of these include audio+video
const mimeTypes = [
'video/webm;codecs=vp9,opus', // Best quality, good compression
'video/webm;codecs=vp8,opus', // Good fallback, well supported
'video/webm;codecs=h264,opus', // Better compatibility with some systems
'video/mp4;codecs=h264,aac', // Good for Safari but may not be supported for MediaRecorder
'video/webm', // Generic fallback (browser will choose codecs)
'video/mp4' // Last resort
];
// Find the first supported mimetype
for (const type of mimeTypes) {
if (MediaRecorder.isTypeSupported(type)) {
console.log(`Using mime type: ${type}`);
return type;
}
}
// If none are supported, return null or a basic fallback
console.warn('No preferred mime types supported by this browser');
return 'video/webm'; // Most basic fallback
}
export const useScreenShare = (): UseScreenShareResult => {
const [isScreenSharing, setIsScreenSharing] = useState(false);
const [isScreenSharingSupported, setIsScreenSharingSupported] = useState(false);
const screenShareSocketRef = useRef<WebSocket | null>(null);
// Check if screen sharing is supported
useEffect(() => {
setIsScreenSharingSupported(
typeof navigator !== 'undefined' &&
navigator.mediaDevices !== undefined &&
typeof navigator.mediaDevices.getDisplayMedia === 'function'
);
}, []);
const stopScreenShare = useCallback(() => {
if (screenShareSocketRef.current) {
screenShareSocketRef.current.close();
screenShareSocketRef.current = null;
}
setIsScreenSharing(false);
}, []);
const startScreenShare = useCallback(async () => {
try {
const mediaStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true,
});
let mimeType = getBestSupportedMimeType();
console.log('Using MIME type:', mimeType);
const mediaRecorder = new MediaRecorder(mediaStream, {
mimeType: mimeType,
videoBitsPerSecond: 2500000, // 2.5 Mbps
audioBitsPerSecond: 128000, // 128 kbps
});
// Connect to WebSocket
screenShareSocketRef.current = API.startScreenShare(mimeType);
// Set up WebSocket event handlers
screenShareSocketRef.current.onopen = () => {
console.log('Screen sharing WebSocket connected');
setIsScreenSharing(true);
mediaRecorder.start(100);
};
screenShareSocketRef.current.onclose = () => {
console.log('Screen sharing WebSocket closed');
setIsScreenSharing(false);
// Stop all tracks when WebSocket is closed
mediaStream.getTracks().forEach(track => track.stop());
};
screenShareSocketRef.current.onerror = (error) => {
console.error('Screen sharing WebSocket error:', error);
setIsScreenSharing(false);
// Stop all tracks on error
mediaStream.getTracks().forEach(track => track.stop());
};
// Send data over WebSocket when available
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0 && screenShareSocketRef.current && screenShareSocketRef.current.readyState === WebSocket.OPEN) {
screenShareSocketRef.current.send(event.data);
}
};
// Handle stream ending (user clicks "Stop sharing")
mediaStream.getVideoTracks()[0].onended = () => {
if (screenShareSocketRef.current) {
screenShareSocketRef.current.close();
screenShareSocketRef.current = null;
}
setIsScreenSharing(false);
};
} catch (error) {
console.error('Error starting screen share:', error);
setIsScreenSharing(false);
}
}, []);
const toggleScreenShare = useCallback(async () => {
if (screenShareSocketRef.current) {
stopScreenShare();
} else {
await startScreenShare();
}
}, [startScreenShare, stopScreenShare]);
return {
isScreenSharing,
isScreenSharingSupported,
toggleScreenShare,
stopScreenShare
};
};