הערה: כאשר 50 אנשים משתתפים בוובינר, עיכוב השמע הופך למורגש (מעל 500 אלפיות השנייה), והווידאו עלול לקפוא. פתרון P2P טיפוסי עם 50 עמיתים יוצר 1225 חיבורים — עומס כבד על הלקוח והרשת גם יחד. נתקלנו בכך: לקוח איבד 20% מהקהל שלו עקב ניתוקים. כדי להימנע מבעיות כאלה, אנו משתמשים בשרת SFU LiveKit, שמפחית את העיכוב ל-150 אלפיות השנייה ומתרחב ל-1000+ משתתפים. אנו משלבים ועידות וידאו קבוצתיות ומטמיעים צ'אט וידאו באתרים עם 3–50+ משתתפים, ניהול מיקרופון ומצלמה, חדרי breakout, צ'אט, שיתוף מסך והקלטה. מדובר במערכת הניתנת להטמעה, הבנויה על React ו-LiveKit, מוכנה לסביבת production. ארכיטקטורה כזו מבטיחה עיכוב נמוך ואיכות גבוהה גם עם אינטרנט חלש. הפתרונות שלנו כבר נמצאים בשימוש בפלטפורמות חינוכיות, טלרפואה ופורטלים ארגוניים.
LiveKit היא פלטפורמה פתוחה עם ארכיטקטורת SFU, המספקת עיכוב נמוך פי 2 בהשוואה ל-Janus תחת אותו עומס.
כיצד להרחיב ועידת וידאו באמצעות SFU?
בחירת התשתית תלויה במספר המשתתפים בו-זמנית. השוו בין שתי גישות:
| פרמטר | P2P (עמית-לעמית) | SFU (יחידת העברה סלקטיבית) |
|---|---|---|
| מספר משתתפים מקסימלי | עד 4–6 | עד 1000+ |
| עיכוב | 100–200 אלפיות השנייה | 150–300 אלפיות השנייה |
| עומס על הלקוח | גבוה (כל מכשיר מעבד N זרמים) | נמוך (זרם אחד מ-SFU) |
| Simulcast | לא נתמך | נתמך |
| יעילות עם 50 משתתפים | 10 נקודות (ירודה) | 100 נקודות (מצוינת) |
עבור רוב הפרויקטים, LiveKit הוא הבחירה האופטימלית. אנו משתמשים בו בסביבת production ומבטיחים יציבות.
מדוע Simulcast משפר את איכות הווידאו?
Simulcast — פרסום מספר זרמי וידאו באיכויות שונות (גבוהה/בינונית/נמוכה). LiveKit עושה זאת אוטומטית. המנוי מקבל את השכבה התואמת לרוחב הפס ולגודל האריח שלו. ללא Simulcast, הווידאו קופץ או מתפוקסל ברשתות חלשות. הניסיון שלנו מראה: Simulcast יעיל פי 2.5 מקידוד רגיל ברשתות חלשות.
// Явно задать качество для конкретного участника
participant.setTrackSubscriptionPermissions(true, [
{
trackSid: videoTrack.sid,
quality: VideoQuality.MEDIUM
}
]); כיצד ליישם ניהול משתתפים באמצעות API?
המארח יכול להשתיק מיקרופון או להסיר משתתף דרך ה-API של השרת. דוגמאות לקצוות:
// Заглушить конкретного участника (по запросу ведущего)
app.post('/api/conferences/:roomName/mute/:participantId', authenticate, async (req, res) => {
const conference = await db.conferences.findByRoomName(req.params.roomName);
if (conference.hostId !== req.user.id) return res.status(403).end();
await svc.mutePublishedTrack(
req.params.roomName,
req.params.participantId,
'microphone-track',
true
);
// Уведомить через Data channel
await svc.sendData(
req.params.roomName,
Buffer.from(JSON.stringify({ type: 'muted_by_host', targetId: req.params.participantId })),
[req.params.participantId]
);
res.json({ ok: true });
});
// Удалить участника
app.delete('/api/conferences/:roomName/participants/:participantId', authenticate, async (req, res) => {
await svc.removeParticipant(req.params.roomName, req.params.participantId);
res.json({ ok: true });
}); יישום חדרי breakout
חדרי breakout — חדרים נפרדים לקבוצות קטנות. אנו יוצרים אותם באופן פרוגרמטי ושולחים הזמנות דרך ערוץ Data:
async function createBreakoutRooms(mainRoomName: string, groups: string[][]) {
const breakoutRooms = await Promise.all(
groups.map((group, i) =>
createConference(`${mainRoomName}-breakout-${i}`, {
maxParticipants: group.length + 2,
})
)
);
for (let i = 0; i < groups.length; i++) {
for (const participantId of groups[i]) {
const token = generateParticipantToken(
`conf-${mainRoomName}-breakout-${i}`,
participantId,
'',
'participant'
);
await svc.sendData(
`conf-${mainRoomName}`,
Buffer.from(
JSON.stringify({
type: 'breakout_invite',
token,
roomIndex: i,
})
),
[participantId]
);
}
}
} כיצד לשלב LiveKit ב-5 שלבים
- התקינו את חבילות השרת והלקוח:
npm install livekit-server-sdk # сервер npm install @livekit/components-react livekit-client # клиент - הגדירו את השרת: צרו מפתחות API וכתובת URL (לדוגמה,
// Явно задать качество для конкретного участника participant.setTrackSubscriptionPermissions(true, [ { trackSid: videoTrack.sid, quality: VideoQuality.MEDIUM } ]);). - צרו חדר באמצעות
// Заглушить конкретного участника (по запросу ведущего) app.post('/api/conferences/:roomName/mute/:participantId', authenticate, async (req, res) => { const conference = await db.conferences.findByRoomName(req.params.roomName); if (conference.hostId !== req.user.id) return res.status(403).end(); await svc.mutePublishedTrack( req.params.roomName, req.params.participantId, 'microphone-track', true ); // Уведомить через Data channel await svc.sendData( req.params.roomName, Buffer.from(JSON.stringify({ type: 'muted_by_host', targetId: req.params.participantId })), [req.params.participantId] ); res.json({ ok: true }); }); // Удалить участника app.delete('/api/conferences/:roomName/participants/:participantId', authenticate, async (req, res) => { await svc.removeParticipant(req.params.roomName, req.params.participantId); res.json({ ok: true }); });. - צרו טוקן למשתתף עם ההרשאות הנדרשות.
- חברו את הלקוח: השתמשו ב-
async function createBreakoutRooms(mainRoomName: string, groups: string[][]) { const breakoutRooms = await Promise.all( groups.map((group, i) => createConference(`${mainRoomName}-breakout-${i}`, { maxParticipants: group.length + 2 }) ) ); for (let i = 0; i < groups.length; i++) { for (const participantId of groups[i]) { const token = generateParticipantToken( `conf-${mainRoomName}-breakout-${i}`, participantId, '', 'participant' ); await svc.sendData( `conf-${mainRoomName}`, Buffer.from(JSON.stringify({ type: 'breakout_invite', token, roomIndex: i })), [participantId] ); } } }מ-npm install livekit-server-sdk # сервер npm install @livekit/components-react livekit-client # клиент.
דוגמה ליצירת ועידה:
import { RoomServiceClient, AccessToken, RoomOptions } from 'livekit-server-sdk';
const svc = new RoomServiceClient(
process.env.LIVEKIT_URL!,
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!
);
async function createConference(
conferenceId: string,
options: { maxParticipants?: number; enableRecording?: boolean; }
): Promise<void> {
await svc.createRoom({
name: `conf-${conferenceId}`,
maxParticipants: options.maxParticipants ?? 50,
emptyTimeout: 300,
metadata: JSON.stringify({
conferenceId,
createdAt: new Date().toISOString()
}),
} as RoomOptions);
}
function generateParticipantToken(
roomName: string,
userId: string,
displayName: string,
role: 'host' | 'moderator' | 'participant' | 'viewer'
): string {
const at = new AccessToken(
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!,
{
identity: userId,
name: displayName,
ttl: 4 * 60 * 60
}
);
at.addGrant({
roomJoin: true,
room: roomName,
canPublish: role !== 'viewer',
canSubscribe: true,
canPublishData: true,
roomAdmin: role === 'host',
hidden: false,
});
return at.toJwt();
} מה כלול בעבודה
- ארכיטקטורת הוועידה: בחירת SFU, הגדרת Simulcast, עיצוב ערוץ Data.
- פיתוח ממשק משתמש ב-React עם רכיבי LiveKit: רשת משתתפים, לוח בקרה, צ'אט, כפתור הרמת יד.
- צד השרת: יצירה וניהול חדרים, אימות, תפקידים (מארח/מנחה/משתתף/צופה).
- שילוב הקלטה וחדרי breakout (אופציונלי).
- תיעוד API ופריסה.
- בדיקות עומס עד 50 משתתפים.
נוסף: אפשרות עם סטרימינג HLS ל-1000+ משתתפים
אם אתם צריכים שידור לקהל גדול (1000+), אנו משתמשים בסטרימינג HLS במקום WebRTC. LiveKit תומך בפלט HLS דרך ה-API של Egress. הצופים צריכים רק נגן HLS.js — עיכוב של 15 עד 30 שניות, אך ללא הגבלה על מספר החיבורים.
לוחות זמנים ועלות
ועידה קבוצתית בסיסית עם LiveKit ו-React — משבוע אחד. עם חדרי breakout, הרמת יד, הקלטה וניהול — 2–3 שבועות. העלות מחושבת באופן אישי לפי היקף העבודה וההתאמות הנדרשות. חיסכון ברוחב פס הודות ל-Simulcast מגיע ל-40% בהשוואה לקידוד רגיל. צרו קשר — נמצא את הפתרון הטוב ביותר לתקציב שלכם. קבלו ייעוץ בנושא יישום.
טעויות נפוצות והפתרונות שלהן
| טעות | השלכות | פתרון |
|---|---|---|
| שימוש ב-P2P ל-50+ משתתפים | עיכוב גבוה, ניתוקים | הטמעת SFU (LiveKit) |
| התעלמות מ-Simulcast | גמגום וידאו בערוצים חלשים | הפעלת Simulcast כברירת מחדל |
| אין ניהול תפקידים | לא ניתן להשתיק רעש | הטמעת תפקידים ומודרציה בצד השרת |
| חוויית הרמת יד ירודה | המארח לא רואה את המתנדבים | שימוש בערוץ Data וברשימת הרמת יד |
יש לנו ניסיון של למעלה מ-5 שנים בפיתוח WebRTC והשלמנו יותר מ-50 פרויקטים לוועידות ושידורים חיים. צרו קשר — נעזור לכם להטמיע תקשורת וידאו באתר שלכם.







