קלט אחד, מאות תוכניות — הפעלת ערוצי FAST 24/7 על MediaLive
ערוץ FAST 24/7 מנגן מאות תוכניות ייחודיות ביום, כל יום, לנצח. AWS MediaLive מגביל ערוץ ל-20 חיבורים לקלט. העיצוב הנאיבי — קלט אחד לכל וידאו — נגמר מהחריצים לפני ארוחת הצהריים ומאלץ אתחול מחדש של הערוץ שלוקח את השידור החי לא מקוון. פתרנו זאת עם קלט דינמי אחד שמשרת כל תוכנית שהערוץ ינגן אי פעם, ושכבת תזמור ברמת התוכנית שמחזיקה את ציר הזמן מתקן את עצמו בין פריסות, כשלים חלקיים ועריכות של מפעילים.
זהו סיפור ההנדסה של איך התזמור הזה באמת עובד.
סקירה מהירה
| אספקט | פרטים |
|---|---|
| תחום | תזמור ערוצי FAST 24/7 על AWS MediaLive |
| חיבורים לקלט לכל ערוץ | 1 דינמי + 1 לוח (ועוד SRT אופציונלי) — הרבה מתחת למגבלת 20 הקלטים |
| זהות לכל תוכנית | 8-byte hex programId משובץ בכל שם פעולה |
| סף מילוי לוח | פערים ≥ 6 שניות מקבלים פעולה של החלפת לוח |
| קיבולת לוח זמנים | 1500 פעולות לכל ערוץ (מגבלה קשה של AWS, נבדק מראש) |
| תיקון עצמי | טאטוא פעולות יתומות רץ אחרי כל פריסה |
| סטטוס | בייצור |
הבעיה העסקית
ערוץ FAST הוא שירות 24/7. מפעילים מתזמנים שבוע או חודש מלא של וידאו ייחודי מראש, והערוץ חייב לנגן כל אחד בזמן הנכון — להחליף מקורות בצורה נקייה, לשמור על סימן המים יציב, לפלוט רמזים להפסקות פרסומות, למלא כל פער עם לוח ממותג של התחנה. הצופה לעולם לא צריך לראות פריים שחור, לוגו תקוע או תוכנית מהשבוע שעבר בלולאה.
התזמור הזה חייב לשרוד כל מה שהמפעילים עושים לו: עריכת התור של מחר, הוספת הפסקות פרסומות באמצע השבוע, פריסה מחדש אחרי תוכנית אחת שנכשלה, מרוץ של שני לחיצות על Deploy. המערכת או מבצעת כל שינוי באופן אטומי מול הערוץ החי או מתאוששת בצורה נקייה. "הערוץ נמצא כעת במצב שאף אחד לא מבין" אינו תוצאה מקובלת על תשתית שמשדרת בשידור חי.
מדריך AWS MediaLive ב-60 שניות
AWS MediaLive הוא מקודד ענן שפועל לאורך זמן. אתה נותן לו קלט אחד או יותר (זרמי מקור או כתובות URL של קבצים) ולוח זמנים של פעולות מתוזמנות — לעבור לקלט זה, להפעיל שכבה זו, להכניס רמז זה. המקודד פועל לנצח, מבצע פעולות בזמנים המתוזמנים שלהם ופולט מניפסט HLS שהנגן של הצופה צורך.
שתי מגבלות של AWS מעצבות כל מה שמתרחש בהמשך:
- 20 חיבורים לקלט לכל ערוץ. מגבלה קשה. ערוץ "20 הסרטים המובילים" שתוכנן באופן נאיבי עם קלט אחד לכל וידאו ממלא את המגבלה בסרט ה-21.
- 1500 פעולות לוח זמנים לכל ערוץ. גם מגבלה קשה. כל תוכנית היא מספר פעולות (החלפת קלט, סימן מים לכל רנדיציה, רמזי פרסומות, החלפת לוח), כך שהתקרה האמיתית קרובה יותר למספר מאות תוכניות בכל זמן נתון.
שתי המגבלות חשובות לערוץ 24/7 שצפוי לנגן תוכן ייחודי ללא הגבלה.
מדוע גישות נאיביות נכשלות
הדרכים הברורות כל אחת נשברת בדרכים שונות:
- "קלט אחד לכל תוכנית." משלים את מגבלת 20 החיבורים של היום הראשון. הוספת עוד דורשת יצירת ערוץ מחדש — ויצירת ערוץ מחדש לוקחת 60–90 שניות במהלכן השידור החי נעלם. לא מקובל על שירות 24/7.
- "ליצור מחדש את הערוץ לכל פריסה." אותה בעיה, כל פריסה. הצופים חווים הפסקה בכל פעם שהתכנות משתנה. זו לא באמת אפשרות לערוץ שאמור להיות חי.
- "לקודד מראש את כל השבוע לקובץ לולאה ענק אחד." הורג את מודל העריכה. רוצים לשנות סדר למחר? לקודד מחדש את כל השבוע. רוצים להכניס פרסומת? לקודד מחדש. כל הסיבה שערוצי FAST עובדים כעסק היא תכנות דינמי והכנסת פרסומות לכל הפסקה — צריבת הכל לקובץ אחד סוגרת את שתי האפשרויות.
- "להפעיל מספר ערוצים במקביל." דורש מהנגן לעבור ביניהם, מכפיל את עלות AWS, ושובר את הצינור של האודיו, MediaPackage וה-CDN. לא פותר את הבעיה אלא מכפיל אותה.
המנוף שהיה לנו היה תכונה מתועדת אחת של AWS — ה$urlPath$ placeholder על קלט דינמי — והחופש לבנות כל תזמור שרצינו מעליו.
למה זה חשוב. הטריק הוא לא להשתמש ב$urlPath$. AWS מתעדת את זה. הטריק הוא לבנות שכבת תזמור שמתקנת את עצמה מעליו ששורדת פריסות חלקיות, עריכות של מפעילים וניסיונות חוזרים במקביל — מבלי לשים את הערוץ החי במצב שאף אחד לא יכול לתאר מה-UI.
הפתרון שלנו
ערוץ MediaLive אחד. קלט דינמי אחד מקושר לכתובת ה-URL המדויקת $urlPath$. קלט לוח אחד למילוי פערים. בכל גבול תוכנית, פעולה של InputSwitchScheduleActionSettings מחליפה את כתובת ה-URL של הקלט הדינמי עם הנתיב האמיתי של התוכנית ב-S3. הערוץ לעולם לא צריך חיבור קלט חדש, לעולם לא צריך אתחול מחדש, לעולם לא יוצא לא מקוון.
העבודה המעניינת היא שכבת התזמור שרצה מעל הקלט האחד הזה — מתן שם לכל פעולה עם מזהה תוכנית כך שניתן יהיה לנקות יתומים אחרי כשל, מילוי פערים ≥ 6 שניות עם לוח כך שהצופה לעולם לא יראה פריים קפוא, הפעלת סימן המים ברגע מדוד אחרי כל החלפת קלט כך שהשכבה לא תבהב דרך פריימים בבאפר, ובדיקה מראש נגד תקרת 1500 הפעולות כך שפריסות ייכשלו ב-UI במקום באמצע הטיסה על AWS.
תרשים 1 · ארכיטקטורת ערוץ
ארכיטקטורה
- קלט לוח — נכס סטטי מצורף לערוץ למילוי פערים בין תוכניות.
- קלט דינמי — נוצר פעם אחת עם Sources: [{ Url: "$urlPath$" }], Type: MP4_FILE. ה-URL הוא placeholder; הנתיב האמיתי מסופק לכל תוכנית בזמן התזמון.
- מתזמר Lambda (fastChannel-lambda-fun/index.js) — אחראי על כל פעילות שמופיעה בערוץ. מייצר מזהה hex של 8 בתים programId לכל תוכנית ומסמן כל פעולה קשורה איתו.
- לוח זמנים של MediaLive — רשימה מסודרת אחת של פעולות, כולן עוברות דרך BatchUpdateScheduleCommand אחד. מוגבל ל-1500 פעולות על ידי AWS.
- בדיקה מראש של ה-Backend (schedule.service.ts) — קורא ל-GET_SCHEDULE_COUNT של Lambda לפני כל פריסה ומסרב להמשיך אם הפעולות החדשות יחרגו מהמגבלה.
טאטוא יתומים (sweepIncompleteProgramGroups) — רץ אחרי כל פריסה. מקבץ פעולות לפי programId, מוחק כל קבוצה שחסרה את פעולת העוגן שלה input-switch.
החלטות הנדסיות מרכזיות
1. קלט דינמי אחד, החלפת כתובת URL אחת לכל תוכנית
הקלט הדינמי נוצר עם Sources: [{ Url: "$urlPath$" }]. בכל גבול תוכנית, ה-Lambda פולט פעולה של InputSwitchScheduleActionSettings שמספקת UrlPath: [program.videoUrl] — כתובת ה-S3 האמיתית של ה-MP4 של התוכנית. MediaLive מחליף את ה-placeholder בזמן הביצוע ומושך מהמקור האמיתי.
חיבור קלט אחד כעת משרת כל תוכנית שהערוץ ינגן אי פעם — למעשה וידאו ייחודי בלתי מוגבל לאורך חיי הערוץ, מוגבל רק על ידי מגבלת פעולות הלוח זמנים לכל פריסה בזמן נתון. מגבלת 20 הקלטים מפסיקה להיות מגבלה, והערוץ לעולם לא צריך אתחול מחדש כדי להוסיף תוכן חדש.
תרשים 2 · החלפת כתובת URL של קלט דינמי

הוויתור שעשינו בכוונה. קלט דינמי לא מאמת מראש את ה-URL — MediaLive פותר את ה-placeholder רק בזמן ההחלפה, כך ש-404 מתגלה כטעות בצד הזרם ולא כדחייה בזמן הפריסה. אנו מקבלים את העלות הזו בתמורה לפשטות הארכיטקטונית של קלט אחד. אימות ffprobe נפרד של ה-backend תופס מקורות פגומים לפני הפריסה.
2. שמות פעולות מתויגים בתוכנית מאפשרים תיקון עצמי
כל פעולה שנפלטת על ידי ה-Lambda נושאת את ה-8-byte hex programId של התוכנית בשמה: input-switch-${programId}, watermark-on-${programId}-${rendition}, ad-break-start-${programId}-${i}, program-end-${programId}, slate-switch-${programId}.
אחרי כל פריסה, sweepIncompleteProgramGroups מונה כל פעולה שנמצאת כרגע בערוץ, מקבץ אותן לפי הprogramId המשובץ שלהן, ומוחק כל קבוצה שחסרה את פעולת העוגן שלה input-switch. זו הדרך לניקוי כשלי פריסות חלקיות, עריכות מתחרות וכל תנאי אחר שיכול להשאיר את הערוץ עם חצי פעולות של תוכנית תקועות.
הקידוד של שם הפעולה הוא כל מנגנון הזהות. MediaLive עצמו אין לו מושג של "תוכנית" — שכבת התזמור מקרינה אחת עליו באמצעות מוסכמות שמות.
למה זה חשוב. ללא ה-מזהה תוכנית המשובץ בכל שם פעולה, הטאטוא של היתומים לא היה יודע אילו פעולות שייכות יחד. ניקוי ברמת הפעולה היה או מוחק יותר מדי (איפוס ערוץ מלא) או מעט מדי (סימני מים תקועים שלעולם לא נכבים). מוסכמת השמות היא מודל הנתונים.
3. כלל ה-6 שניות ללוח
תוכניות לעיתים רחוקות נוגעות בצורה מושלמת — כמעט תמיד יש פער של כמה שניות בין סוף MP4 אחד לתחילת התוכנית הבאה המתוזמנת. התזמור פולט פעולה של החלפת לוח בכל פעם שהפער הוא ≥ 6 שניות (MIN_SLATE_GAP_MS = 6000).
הסף אינו שרירותי. MediaLive אוכף מרווח מינימלי של 5 שניות בין כל שתי פעולות לוח זמנים; פליטת פעולה של החלפת לוח קרוב יותר לתוכנית הבאה של החלפת קלט מייצרת דחייה. כלל ה-6 שניות נותן ל-MediaLive את הפער הנדרש וגם משאיר לשכבת התזמור מרווח בטיחות של שנייה אחת לסטיית זמן. מתחת ל-6 שניות, אנו נותנים לפריים האחרון של התוכנית הקודמת לקפוא בקצרה במקום לסכן דחיית פריסה.
4. סימן מים לכל רנדיציה עם עיכוב הפעלה מדוד
סימן המים הוא פעולה של StaticImageOutputActivate שנפלטת לכל רנדיציה של פלט (1080p, 720p, 480p, 360p) — ארבע פעולות לכל תוכנית. כל פעולה מופעלת 1,500 ms אחרי החלפת הקלט של אותה תוכנית.
העיכוב קיים כי הפריימים הראשונים אחרי החלפת קלט עדיין מתבצעים בבאפר; הפעלת השכבה ברגע ההחלפה המדויק יכולה לייצר הבהוב קצר כשהשכבה מציירת על פריים שעדיין לא נרנדר במלואו. 1,500 ms היה הערך שהפיק באופן עקבי הפעלה נקייה בכל ארבע הרנדיציות בבדיקות. זהו קבוע מדוד, לא פרמטר מתועד של MediaLive — והוא חי במקום אחד בקוד כך שכיוונון עתידי הוא שינוי שורת קוד אחת.
הוויתור שעשינו בכוונה. פעולות שכבה לכל רנדיציה עולות 4× את ספירת הפעולות לעומת שכבה גלובלית אחת. אנו מקבלים את העלות כי הדרך לכל רנדיציה מאפשרת לכל פלט לקבל סימן מים בגודל מדויק למידות הפיקסלים שלו, במקום לתת ל-MediaLive להקטין שכבה אחת לכל ארבעתן. התוצאה היא לוגו חד יותר באופן נראה על פלטים SD — וזה משאיר מספיק תקציב פעולות למאות תוכניות לפני שמגבלת 1500 הפעולות חשובה.
5. תקרת 1500 הפעולות, נבדקת מראש ב-UI
AWS מגבילה באופן קשה ערוץ MediaLive ל-1500 פעולות לוח זמנים. עם ~7–8 פעולות לכל תוכנית (החלפת קלט + 4 סימני מים + 2 רמזי פרסומות + לוח מדי פעם), הערוץ מחזיק בערך 180–200 תוכניות פעילות תלוי בצפיפות הפרסומות ותדירות הלוח. זו תקרה אמיתית לפריסות ארוכות טווח, והמספר המדויק תלוי במורכבות לכל תוכנית.
לפני כל פריסה, ה-backend קורא ל-GET_SCHEDULE_COUNT של Lambda, שסופר פעולות חיות על הערוץ דרך DescribeScheduleCommand ומחזיר { liveCount, capacity: 1500 }. אם liveCount + (newPrograms × 8) יחרוג מ-1500, ה-backend זורק SCHEDULE_ACTION_CAP_EXCEEDED עם מספר המרווח המדויק — לפני שהוא מגיש משהו ל-MediaLive. המפעיל רואה את המגבלה ב-UI עם הנחיות לנקות תוכניות קודמות קודם. הפריסה לעולם לא חצי רצה לתוך הקיר.
תרשים 3 · ציר זמן פעולות של תוכנית אחת

תוצאות
- ערוץ MediaLive יחיד משרת למעשה תוכניות ייחודיות בלתי מוגבלות לאורך חייו, על חיבור קלט דינמי אחד. מגבלת 20 הקלטים אינה עוד מגבלה שעלינו לתכנן סביבה — הקיבולת בכל זמן נתון נשלטת על ידי מגבלת פעולות הלוח זמנים, לא מגבלת הקלט.
- תזמור הערוץ מתקן את עצמו: כל פריסה מסתיימת בטאטוא יתומים, כך שכשלי פריסות חלקיות לא יכולים להשאיר את הלוח זמנים במצב לא עקבי.
- קיבולת הלוח זמנים מוגבלת ונראית. המפעילים רואים את תקרת 1500 הפעולות ב-UI לפני שהם לוחצים, לא כדחייה אטומה של AWS באמצע הפריסה.
- מוסכמת השמות של מזהה התוכנית היא כל שכבת הזהות — והיא מחרוזת. אין תשתית חדשה, אין אחסון נוסף, אין תלות. ההקרנה הפשוטה ביותר האפשרית של "תוכנית" על רשימת הפעולות השטוחה של MediaLive.
טכנולוגיות: AWS MediaLive · AWS Lambda · NestJS · MongoDB · TypeScript · Node 18 · AWS SDK v3 (@aws-sdk/client-medialive)

