שילוב Twilio Video לשיחות וידאו באתר
צוותים נתקלים לעיתים קרובות באתגרים בעת הוספת שיחות וידאו לאתר עם Twilio Video. המורכבות כוללת הגדרת שרת, יצירת טוקנים ובניית רכיב לקוח. טעויות נפוצות כוללות TTL שגוי של טוקן, התעלמות מטיפול בחיבור מחדש ובחירה לא אופטימלית של סוג חדר. לדוגמה, לקוח איבד עד 30% מהפגישות עקב TTL קצר מדי (5 דקות במקום 3600). לאחר יישום רענון וטיפול נכון ב-DisconnectedEvent, הסבילות לתקלות עלתה ל-99.9%. לקוח אחר—שירות טלרפואה—איבד עד 20% מהשיחות עקב טיפול לא נכון בחיבור מחדש. יישמנו מנגנון ניסיון חוזר והרשמה נכונה לאירועים, והעלינו את שיעור השיחות המוצלחות ל-99.5%. ניסיון השילוב שלנו כולל 5+ שנים ו-20+ פרויקטים. אנו מבטיחים פעולה יציבה ותמיכה במהלך הפיתוח.
למה Twilio Video עדיף על פתרונות מוכנים
בניגוד ל-Zoom SDK או Jitsi, Twilio Video נותן לך שליטה מלאה על הממשק והלוגיקה. אתה לא קשור לרשת משתתפים סטנדרטית—אתה יכול ליישם רשת משלך, לעבור בין דוברים, להוסיף אפקטים או מידרציה. בנוסף, המערכת האקולוגית של Twilio מאפשרת לשלב וידאו עם טלפוניה ו-SMS, מה שנוח לשירותי טלרפואה או ייעוץ. אינטגרציית API עם הפלטפורמה פשוטה הודות ל-REST API ו-SDKs. השירות מבוסס על WebRTC, מה שמבטיח תאימות לרוב הדפדפנים והמכשירים הניידים.
כיצד לנהל משתתפים בחדר
לאחר חיבור לחדר, כל משתתף מיוצג על ידי אובייקט RemoteParticipant. האירועים participantConnected ו-participantDisconnected מנוטרים ברמת החדר. עבור כל משתתף מרוחק, הירשם ל-trackSubscribed ו-trackUnsubscribed כדי להוסיף/להסיר זרמי וידאו. זה מאפשר לך לעדכן את הרשת באופן דינמי. חשוב לטפל במקרים שבהם משתתף מחליף מצלמה—המסלול משתנה, ויש לחבר אותו מחדש ל-DOM.
שילוב Twilio Video באפליקציית React
תהליך השילוב כולל מספר שלבים. נעבור על כל אחד באמצעות TypeScript ו-React 18.
שלב 1: הגדרת שרת—יצירת חדרים ויצירת Access Tokens. שלב 2: פיתוח רכיב React—חיבור לחדר והצגת זרמים.
שלב 3: טיפול בהקלטת שיחות—הפעלת הקלטה וקבלת קישורים.
שלב 1: יצירת חדר ו-Access Token בשרת
npm install twilio import twilio from 'twilio';
const AccessToken = twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
const client = twilio(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_AUTH_TOKEN!
);
// Создать комнату
async function createRoom(name: string) {
const room = await client.video.v1.rooms.create({
uniqueName: name,
type: 'group', // 'go' | 'peer-to-peer' | 'group' | 'group-small'
maxParticipants: 10,
recordParticipantsOnConnect: false,
statusCallback: `${process.env.APP_URL}/api/webhooks/twilio-video`,
statusCallbackMethod: 'POST',
});
return room.sid;
}
// Выдать токен участнику
function generateVideoToken(identity: string, roomName: string): string {
const token = new AccessToken(
process.env.TWILIO_ACCOUNT_SID!,
process.env.TWILIO_API_KEY!,
process.env.TWILIO_API_SECRET!,
{ identity, ttl: 3600 }
);
const grant = new VideoGrant({ room: roomName });
token.addGrant(grant);
return token.toJwt();
}
// API endpoint
app.post('/api/video/join', authenticate, async (req, res) => {
const { roomName } = req.body;
// Убедиться, что комната существует или создать
try {
await client.video.v1.rooms(roomName).fetch();
} catch {
await createRoom(roomName);
}
const token = generateVideoToken(req.user.id, roomName);
res.json({ token, roomName });
});
שלב 2: רכיב React עם Twilio Video JS SDK
npm install twilio-video import { connect, Room, LocalVideoTrack } from 'twilio-video';
import { useEffect, useRef, useState } from 'react';
function TwilioVideoRoom({ token, roomName }: { token: string; roomName: string }) {
const [room, setRoom] = useState<Room | null>(null);
const [participants, setParticipants] = useState<string[]>([]);
const localVideoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
let connectedRoom: Room;
connect(token, {
name: roomName,
audio: true,
video: { width: 1280, height: 720 },
}).then((room) => {
connectedRoom = room;
setRoom(room);
// Отображение локального видео
const localTrack = [...room.localParticipant.videoTracks.values()][0]?.track;
if (localTrack && localVideoRef.current) {
localVideoRef.current.srcObject = new MediaStream([localTrack.mediaStreamTrack]);
}
// Обработка участников
room.participants.forEach((p) => {
setParticipants(prev => [...prev, p.identity]);
});
room.on('participantConnected', (p) => {
setParticipants(prev => [...prev, p.identity]);
p.on('trackSubscribed', (track) => {
if (track.kind === 'video') {
const el = document.getElementById(`participant-${p.identity}`);
if (el) track.attach(el as HTMLVideoElement);
}
});
});
room.on('participantDisconnected', (p) => {
setParticipants(prev => prev.filter(id => id !== p.identity));
});
});
return () => {
connectedRoom?.disconnect();
};
}, [token, roomName]);
return (
<div className="grid grid-cols-2 gap-4">
<div className="relative">
<video ref={localVideoRef} autoPlay muted playsInline className="w-full rounded-xl" />
<span className="absolute bottom-2 left-2 text-white text-sm bg-black/50 px-2 py-1 rounded">
Вы
</span>
</div>
{participants.map(identity => (
<div key={identity} className="relative">
<video id={`participant-${identity}`} autoPlay playsInline className="w-full rounded-xl" />
<span className="absolute bottom-2 left-2 text-white text-sm bg-black/50 px-2 py-1 rounded">
{identity}
</span>
</div>
))}
</div>
);
} שלב 3: הקלטת שיחות
// Включить запись для комнаты
async function enableRoomRecording(roomSid: string) {
await client.video.v1.rooms(roomSid).recordings.create({
// Записывает всех участников
});
}
// Получить ссылку на запись после звонка
async function getRoomRecordings(roomSid: string) {
const recordings = await client.video.v1.rooms(roomSid).recordings.list();
return recordings.map(r => ({
sid: r.sid,
duration: r.duration,
url: `https://video.twilio.com/v1/Recordings/${r.sid}/Media`,
}));
} בחירת סוג חדר
בחירת סוג החדר תלויה בתרחיש. Peer-to-Peer (P2P) מתאים לשיחות וידאו אחד-על-אחד: זמן השהיה מתחת ל-150 אלפיות השנייה, ללא עיבוד שרת, אך מוגבל ל-2 משתתפים. Group Small תומך בעד 4 משתתפים עם זמן השהיה בינוני. Group תומך בעד 50 משתתפים, הקלטה ומעקב, אך זמן השהיה עד 300 אלפיות השנייה. אם אתה מתכנן סמינרים מקוונים, השתמש ב-Group והפעל הקלטה.
השוואת סוגי חדרים
| סוג חדר | מקסימום משתתפים | זמן השהיה | תכונות |
|---|---|---|---|
| Peer-to-Peer | 2 | <150 אלפיות השנייה | זמן השהיה נמוך, ללא עיבוד שרת |
| Group Small | 4 | <200 אלפיות השנייה | איזון בין ביצועים למספר משתתפים |
| Group | 50 | <300 אלפיות השנייה | ועידות מלאות, הקלטה, מעקב |
פרמטרי תצורה מרכזיים
| פרמטר | ערך | הערה |
|---|---|---|
npm install twilio |
import twilio from 'twilio'; const AccessToken = twilio.jwt.AccessToken; const VideoGrant = AccessToken.VideoGrant; const client = twilio( process.env.TWILIO_ACCOUNT_SID!, process.env.TWILIO_AUTH_TOKEN! ); // Создать комнату async function createRoom(name: string) { const room = await client.video.v1.rooms.create({ uniqueName: name, type: 'group', // 'go' | 'peer-to-peer' | 'group' | 'group-small' maxParticipants: 10, recordParticipantsOnConnect: false, statusCallback: `${process.env.APP_URL}/api/webhooks/twilio-video`, statusCallbackMethod: 'POST', }); return room.sid; } // Выдать токен участнику function generateVideoToken(identity: string, roomName: string): string { const token = new AccessToken( process.env.TWILIO_ACCOUNT_SID!, process.env.TWILIO_API_KEY!, process.env.TWILIO_API_SECRET!, { identity, ttl: 3600 } ); const grant = new VideoGrant({ room: roomName }); token.addGrant(grant); return token.toJwt(); } // API endpoint app.post('/api/video/join', authenticate, async (req, res) => { const { roomName } = req.body; // Убедиться, что комната существует или создать try { await client.video.v1.rooms(roomName).fetch(); } catch { await createRoom(roomName); } const token = generateVideoToken(req.user.id, roomName); res.json({ token, roomName }); }); , npm install twilio-video , import { connect, Room, LocalVideoTrack } from 'twilio-video'; import { useEffect, useRef, useState } from 'react'; function TwilioVideoRoom({ token, roomName }: { token: string; roomName: string }) { const [room, setRoom] = useState<Room | null>(null); const [participants, setParticipants] = useState<string[]>([]); const localVideoRef = useRef<HTMLVideoElement>(null); useEffect(() => { let connectedRoom: Room; connect(token, { name: roomName, audio: true, video: { width: 1280, height: 720 }, }).then((room) => { connectedRoom = room; setRoom(room); // Отображение локального видео const localTrack = [...room.localParticipant.videoTracks.values()][0]?.track; if (localTrack && localVideoRef.current) { localVideoRef.current.srcObject = new MediaStream([localTrack.mediaStreamTrack]); } // Обработка участников room.participants.forEach((p) => { setParticipants(prev => [...prev, p.identity]); }); room.on('participantConnected', (p) => { setParticipants(prev => [...prev, p.identity]); p.on('trackSubscribed', (track) => { if (track.kind === 'video') { const el = document.getElementById(`participant-${p.identity}`); if (el) track.attach(el as HTMLVideoElement); } }); }); room.on('participantDisconnected', (p) => { setParticipants(prev => prev.filter(id => id !== p.identity)); }); }); return () => { connectedRoom?.disconnect(); }; }, [token, roomName]); return ( <div className="grid grid-cols-2 gap-4"> <div className="relative"> <video ref={localVideoRef} autoPlay muted playsInline className="w-full rounded-xl" /> <span className="absolute bottom-2 left-2 text-white text-sm bg-black/50 px-2 py-1 rounded"> Вы </span> </div> {participants.map(identity => ( <div key={identity} className="relative"> <video id={`participant-${identity}`} autoPlay playsInline className="w-full rounded-xl" /> <span className="absolute bottom-2 left-2 text-white text-sm bg-black/50 px-2 py-1 rounded"> {identity} </span> </div> ))} </div> ); } |
סוג החדר קובע את הארכיטקטורה |
// Включить запись для комнаты async function enableRoomRecording(roomSid: string) { await client.video.v1.rooms(roomSid).recordings.create({ // Записывает всех участников }); } // Получить ссылку на запись после звонка async function getRoomRecordings(roomSid: string) { const recordings = await client.video.v1.rooms(roomSid).recordings.list(); return recordings.map(r => ({ sid: r.sid, duration: r.duration, url: `https://video.twilio.com/v1/Recordings/${r.sid}/Media`, })); } |
2-50 | מקסימום משתתפים בו-זמנית |
type |
3600 (שניות) | אורך חיים של Access Token, מומלץ שעה אחת |
peer-to-peer |
group-small/group |
הקלטה אוטומטית בעת חיבור |
maxParticipants |
1280x720 | רזולוציית וידאו, משפיעה על רוחב הפס |
מה כלול בעבודת השילוב
- הגדרת חשבון Twilio ומפתחות API
- נקודות קצה בשרת ליצירת חדרים ויצירת טוקנים
- רכיב React עם ממשק משתמש מותאם להצגת משתתפים
- הקלטת שיחות ושילוב webhooks לטיפול באירועים
- פריסה ותיעוד תמיכה
- הדרכת צוות על שימוש ב-SDK
טעויות נפוצות וכיצד להימנע מהן
- פגות טוקן במהלך שיחה: הגדר
ttlל-3600 שניות לפחות ויישם מנגנון רענון באמצעות אירועי שרת. - שאילתות N+1 בעת שליפת רשימת הקלטות: השתמש ב-
recordParticipantsOnConnectאו בפאג'ינציה. - רשת וידאו לא מסונכרנת: הירשם ל-
trueלעדכוני DOM נכונים. - התעלמות מטיפול באובדן חיבור: השתמש במנגנון ניסיון חוזר עם backoff אקספוננציאלי. אם אתה נתקל בבעיות דומות, צור קשר—נעזור לך לפתור אותן.
לוחות זמנים ועלות
שילוב Twilio Video בסיסי + רכיב React + Access Token — 2–3 ימים. עם ניהול משתתפים, הקלטה ו-webhooks — 4–5 ימים. עלות השילוב נעה בדרך כלל בין $2,500 ל-$5,000 לפתרון מותאם מלא, תלוי במורכבות הממשק ובתכונות נוספות כמו הקלטה או מידרציה מותאמת. צור קשר לייעוץ חינמי והערכה מדויקת.
גורמים המשפיעים על העלות
עלות השילוב תלויה במורכבות הממשק המותאם, בצורך בהקלטה ו-webhooks, במספר סוגי החדרים ובשילוב עם שירותים אחרים. אנו מחשבים את העלות באופן אישי לאחר ניתוח הדרישות. עלויות לדקה לשימוש בווידאו נעות בין $0.004 ל-$0.01 למשתתף, תלוי ברזולוציה ובהקלטה. צור קשר להערכה מדויקת.
לפרטים נוספים, ראה בדוק את התיעוד הרשמי.







