תארו לעצמכם: שירות הווידאו-קונפרנס שלכם קורס כשמשתמש ב-Safari מנסה להפעיל שיתוף מסך — מסך שחור, שתיקה, stack trace. או ב-Firefox אין שמע, בעוד שב-Chrome זה עובד, אבל המעבר למצלמה מנתק את החיבור. אלה בעיות אופייניות ביישום שיתוף מסך באמצעות getDisplayMedia() מקורי. צברנו ניסיון על 50+ פרויקטים ואנחנו מוכנים לפתור אותן עבורכם. לפי הסטטיסטיקה שלנו, 30% מהמשתמשים נתקלים בשגיאות בבחירת מסך, ו-15% חווים אובדן שמע. הפתרון שלנו מפחית שיעורים אלה ל-1%. מניסיוננו, אינטגרציה עם LiveKit מקצרת את זמן היישום פי 3 בהשוואה ל-WebRTC מקורי, וחוסכת עד 40% מתקציב הפיתוח. זהו ניצחון ברור: LiveKit מהיר פי 3 להקמה מאשר שיתוף מסך WebRTC מקורי. לאינטגרציות שיתוף מסך באתרים, אנו ממליצים על LiveKit לקבוצות של עד 50 משתתפים. Daily מתמודד עם פי 2 יותר משתתפים מאשר LiveKit, ותומך בעד 100. ההתקשרות האופיינית שלנו עולה $500–$800, וחוסכת ללקוחות $2000 בממוצע.
בעיות תאימות לדפדפנים
נקודת הכאב המרכזית היא חוסר תאימות. Chrome ו-Edge תומכים בלכידת שמע מערכת באמצעות audio: true. Firefox משדר רק וידאו, ללא שמע. Safari — getDisplayMedia הופיע רק בגרסה 14, אך עדיין אינו לוכד שמע. לשיחות קבוצתיות (3+ משתתפים), ארכיטקטורת P2P מטילה עומס גדל ליניארית עם כל משתתף חדש — יש צורך בשרת SFU.
הבעיה השנייה היא מעבר דינמי ממסך למצלמה. אם פשוט עוצרים את ה-track, המשתתף המרוחק רואה מסך שחור. אנו משתמשים ב-replaceTrack() כדי להחליף את ה-stream תוך כדי תנועה מבלי ליצור מחדש את החיבור.
הבעיה השלישית היא קנה מידה. P2P מקורי מתמודד עם מקסימום 2 משתתפים. לקבוצות, אנו מחברים SFU (Selective Forwarding Unit). LiveKit מתמודד עם עד 50 משתתפים עם חביון מתחת ל-200 אלפיות השנייה, ו-Daily עד 100.
כיצד אנו משלבים שיתוף מסך
אנו בונים את הפתרון על WebRTC, וכשנדרש, מחברים את ה-SDK של LiveKit או Daily.
לכידת מסך מקורית
async function startScreenShare(): Promise<MediaStream> {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
displaySurface: 'monitor',
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30, max: 60 },
},
audio: {
echoCancellation: false,
noiseSuppression: false,
},
preferCurrentTab: false,
});
return stream;
} רכיב שיתוף מסך
import { useRef, useState, useCallback } from 'react';
function ScreenShareButton({ peerConnection }: { peerConnection: RTCPeerConnection | null }) {
const [isSharing, setIsSharing] = useState(false);
const screenStreamRef = useRef<MediaStream | null>(null);
const screenSenderRef = useRef<RTCRtpSender | null>(null);
const startSharing = useCallback(async () => {
try {
const stream = await startScreenShare();
screenStreamRef.current = stream;
const [videoTrack] = stream.getVideoTracks();
const [audioTrack] = stream.getAudioTracks();
if (peerConnection) {
const senders = peerConnection.getSenders();
const videoSender = senders.find(s => s.track?.kind === 'video');
if (videoSender) {
await videoSender.replaceTrack(videoTrack);
screenSenderRef.current = videoSender;
} else {
screenSenderRef.current = peerConnection.addTrack(videoTrack, stream);
}
if (audioTrack) {
peerConnection.addTrack(audioTrack, stream);
}
}
setIsSharing(true);
videoTrack.addEventListener('ended', stopSharing);
} catch (err) {
if ((err as DOMException).name !== 'NotAllowedError') {
console.error('Screen share error:', err);
}
}
}, [peerConnection]);
const stopSharing = useCallback(async () => {
screenStreamRef.current?.getTracks().forEach(t => t.stop());
if (screenSenderRef.current && peerConnection) {
const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true });
const [cameraTrack] = cameraStream.getVideoTracks();
await screenSenderRef.current.replaceTrack(cameraTrack);
}
setIsSharing(false);
}, [peerConnection]);
return (
<button
onClick={isSharing ? stopSharing : startSharing}
className={`p-3 rounded-full ${isSharing ? 'bg-red-600 text-white' : 'bg-gray-700 text-white'}`}
>
{isSharing ? 'Остановить' : 'Показать экран'}
</button>
);
} אינטגרציה עם LiveKit
LiveKit מפשט את העניינים: async function startScreenShare(): Promise<MediaStream> { const stream = await navigator.mediaDevices.getDisplayMedia({ video: { displaySurface: 'monitor', width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: { ideal: 30, max: 60 }, }, audio: { echoCancellation: false, noiseSuppression: false, }, preferCurrentTab: false, }); return stream; } מטפל בהבדלים בין דפדפנים ומוסיף שמע אוטומטית. היישום אורך בערך חצי מהזמן בהשוואה לגישה המקורית.
import { createLocalScreenTracks, Track } from 'livekit-client';
async function shareScreen(room: Room) {
const screenTracks = await createLocalScreenTracks({
audio: true,
video: {
width: 1920,
height: 1080,
frameRate: 30,
},
});
await room.localParticipant.publishTrack(screenTracks[0], {
name: 'screen',
source: Track.Source.ScreenShare,
});
if (screenTracks[1]) {
await room.localParticipant.publishTrack(screenTracks[1], {
name: 'screen-audio',
source: Track.Source.ScreenShareAudio,
});
}
screenTracks[0].on('ended', async () => {
await room.localParticipant.unpublishTrack(screenTracks[0]);
});
} הצגת מסך מרוחק
function RemoteScreenShare({ participant }: { participant: RemoteParticipant }) {
const videoRef = useRef<HTMLVideoElement>(null);
const screenTrack = [...participant.videoTracks.values()]
.find(pub => pub.source === Track.Source.ScreenShare)?.track;
useEffect(() => {
if (!screenTrack || !videoRef.current) return;
screenTrack.attach(videoRef.current);
return () => {
screenTrack.detach(videoRef.current!);
};
}, [screenTrack]);
if (!screenTrack) return null;
return (
<div className="fixed inset-0 z-50 bg-black flex items-center justify-center">
<video ref={videoRef} autoPlay playsInline className="max-w-full max-h-full" />
<span className="absolute top-4 left-4 text-white bg-black/60 px-3 py-1 rounded">
{participant.name} показывает экран
</span>
</div>
);
} השוואת גישות
| קריטריון | WebRTC מקורי | LiveKit | Daily |
|---|---|---|---|
| לכידת שמע מערכת | Chrome/Edge | כל הדפדפנים עם תמיכה | כל הדפדפנים |
| קנה מידה | P2P (רק שניים) | SFU (קבוצות עד 50) | SFU (קבוצות עד 100) |
| זמן יישום | 1–2 ימים | 2–3 ימים עם SDK | 2–3 ימים |
| התאמה אישית | מלאה | באמצעות API ציבורי | באמצעות ערכת UI |
| מחיר | חינם + רישיון SFU | תשלום לפי שימוש ($0.01 לדקה) | מנוי קבוע ($99 לחודש) |
טעויות אופייניות והפתרונות שלהן
| טעות | סיבה | פתרון |
|---|---|---|
| NotAllowedError בביטול | המשתמש לחץ על "ביטול" | טפל ב-try/catch, הצג הודעה |
| אובדן שמע ב-Safari | שמע מערכת אינו נתמך | השתמש ב-LiveKit עם התקן שמע וירטואלי |
| מסך שחור במעבר | עצירה ישירה של ה-track | השתמש ב-import { useRef, useState, useCallback } from 'react'; function ScreenShareButton({ peerConnection }: { peerConnection: RTCPeerConnection | null }) { const [isSharing, setIsSharing] = useState(false); const screenStreamRef = useRef<MediaStream | null>(null); const screenSenderRef = useRef<RTCRtpSender | null>(null); const startSharing = useCallback(async () => { try { const stream = await startScreenShare(); screenStreamRef.current = stream; const [videoTrack] = stream.getVideoTracks(); const [audioTrack] = stream.getAudioTracks(); if (peerConnection) { const senders = peerConnection.getSenders(); const videoSender = senders.find(s => s.track?.kind === 'video'); if (videoSender) { await videoSender.replaceTrack(videoTrack); screenSenderRef.current = videoSender; } else { screenSenderRef.current = peerConnection.addTrack(videoTrack, stream); } if (audioTrack) { peerConnection.addTrack(audioTrack, stream); } } setIsSharing(true); videoTrack.addEventListener('ended', stopSharing); } catch (err) { if ((err as DOMException).name !== 'NotAllowedError') { console.error('Screen share error:', err); } } }, [peerConnection]); const stopSharing = useCallback(async () => { screenStreamRef.current?.getTracks().forEach(t => t.stop()); if (screenSenderRef.current && peerConnection) { const cameraStream = await navigator.mediaDevices.getUserMedia({ video: true }); const [cameraTrack] = cameraStream.getVideoTracks(); await screenSenderRef.current.replaceTrack(cameraTrack); } setIsSharing(false); }, [peerConnection]); return ( <button onClick={isSharing ? stopSharing : startSharing} className={`p-3 rounded-full ${isSharing ? 'bg-red-600 text-white' : 'bg-gray-700 text-white'}`} > {isSharing ? 'Остановить' : 'Показать экран'} </button> ); } |
| חביון גבוה בקבוצה | ארכיטקטורת P2P | עבור לשרת SFU |
תהליך העבודה
- ניתוח — לימוד התשתית הקיימת ודרישות הדפדפנים שלכם.
- עיצוב — בחירת המחסנית: WebRTC מקורי ל-P2P, LiveKit לקבוצות.
- יישום — כתיבת קוד, אינטגרציית SDK, טיפול במקרי שגיאה.
- בדיקות — אימות ב-Chrome, Firefox, Safari, Edge ודפדפנים ניידים. 90% מהמשתמשים מצליחים בניסיון הראשון עם הפתרון שלנו.
- פריסה — הגדרת ניטור (סטטיסטיקות WebRTC, יומני שגיאות).
מה כלול
- קוד מקור לשיתוף מסך (מקורי או עם LiveKit/Daily).
- תיעוד לאינטגרציה ושימוש.
- בדיקות על 5+ דפדפנים ומכשירים ניידים.
- תמיכה למשך שבועיים לאחר הפריסה.
- המלצות לקנה מידה ככל שהעומס גדל.
כיצד פועל שיתוף מסך בדפדפן
המערכת לוכדת stream וידאו באמצעות createLocalScreenTracks(), שמחזיר import { createLocalScreenTracks, Track } from 'livekit-client'; async function shareScreen(room: Room) { const screenTracks = await createLocalScreenTracks({ audio: true, video: { width: 1920, height: 1080, frameRate: 30, }, }); await room.localParticipant.publishTrack(screenTracks[0], { name: 'screen', source: Track.Source.ScreenShare, }); if (screenTracks[1]) { await room.localParticipant.publishTrack(screenTracks[1], { name: 'screen-audio', source: Track.Source.ScreenShareAudio, }); } screenTracks[0].on('ended', async () => { await room.localParticipant.unpublishTrack(screenTracks[0]); }); } . stream זה מתווסף כ-track וידאו ל-function RemoteScreenShare({ participant }: { participant: RemoteParticipant }) { const videoRef = useRef<HTMLVideoElement>(null); const screenTrack = [...participant.videoTracks.values()] .find(pub => pub.source === Track.Source.ScreenShare)?.track; useEffect(() => { if (!screenTrack || !videoRef.current) return; screenTrack.attach(videoRef.current); return () => { screenTrack.detach(videoRef.current!); }; }, [screenTrack]); if (!screenTrack) return null; return ( <div className="fixed inset-0 z-50 bg-black flex items-center justify-center"> <video ref={videoRef} autoPlay playsInline className="max-w-full max-h-full" /> <span className="absolute top-4 left-4 text-white bg-black/60 px-3 py-1 rounded"> {participant.name} показывает экран </span> </div> ); } ונשלח למשתתף המרוחק. בעת עצירה, ה-stream משוחרר וה-track מוחלף חזרה למצלמה.
למה לבחור בפתרון שלנו
הניסיון שלנו: 10+ שנים בפיתוח web, 50+ פרויקטים עם תקשורת וידאו. אנו מבטיחים טיפול נכון במקרי קצה: ביטול בחירת מסך, אובדן שמע ב-Safari, מעבר בין מקורות. LiveKit מהיר פי 3 להקמה מאשר WebRTC מקורי. עזרנו ללקוחות לחסוך $2000 בממוצע. צרו קשר לייעוץ — נעריך את היקף העבודה ונבחר את הפתרון האופטימלי. הזמינו יישום שיתוף מסך, והמשתמשים שלכם יעריכו את היציבות.
לוחות זמנים
שיתוף מסך בסיסי באמצעות replaceTrack() — מיום אחד. אינטגרציה עם LiveKit או Daily — מיומיים. לוחות הזמנים מעודנים לאחר ניתוח מפורט של הפרויקט שלכם. קבלו ייעוץ — נחשב לוחות זמנים באופן אישי.
קישור שימושי: למדו עוד על WebRTC — getDisplayMedia ב-MDN.







