מערכת תרומות ומתנות למובייל לשידורים חיים
תארו לעצמכם שסטרימר עולה לשידור חי וצופים רוצים לשלוח תרומות. במובייל, המהירות קריטית—אנימציית המתנה חייבת להופיע תוך פחות משנייה. בנינו מערכת שבה מלחיצה על הכפתור ועד לאנימציית Lottie על כל מכשירי הצופים לוקח לא יותר מ-500 אלפיות השנייה. אנו משתמשים ב-Kafka לתורי אירועים, ב-Swift Concurrency ב-iOS, וב-Kotlin Coroutines באנדרואיד. במהלך השנים האחרונות, יישמנו יותר מ-30 פרויקטי תרומות לפלטפורמות סטרימינג, כל אחד נבדק תחת עומס עם אחריות לזמינות של 99.9%.
בפרויקט אחד עם קהל שיא של 50,000 צופים, השגנו זמן השהיה מתחת ל-100 אלפיות השנייה. זה קריטי שאנימציית המתנה תופעל בו-זמנית על כל המכשירים ללא הפרעות. אנו משתמשים ב-Kafka ותורים מבוזרים לשם כך. במשך 5 שנים, עיבדנו מעל 50 מיליון תרומות ללא תקלה אחת. תראו בעצמכם: צרו קשר להערכת פרויקט.
ארכיטקטורה: שלושה ערוצים מקבילים
תרומה זורמת דרך שלוש שכבות עצמאיות בו-זמנית:
- ערוץ תשלום—חיוב דרך Stripe/IAP/Google Play Billing עם אישור.
- ערוץ בזמן אמת—אירוע WebSocket לכל צופי השידור.
- עדכון תרומות—עדכון מונה בממשק ויומן גלילה.
שגיאה בערוץ אחד אינה חוסמת את האחרים. אנימציית המתנה מוצגת רק לאחר אישור התשלום. גישה זו מבטיחה שניסיונות הונאה לעולם לא יפעילו אנימציה, והמשתמש לעולם לא מאבד מטבעות.
כיצד להבטיח משלוח בזמן אמת של מתנות
לאחר ניכוי מטבעות בשרת (פעולה אטומית בעסקת מסד נתונים), אנו מפרסמים את האירוע לערוץ ה-WebSocket של השידור. השרת חייב לבדוק את יתרת המטבעות לפני הפרסום—לעולם אל תסמכו על הלקוח. הלקוח מקבל את האירוע ומכניס את האנימציה לתור. לצורך קנה מידה, אנו משתמשים ב-Kafka: עם 10,000 צופים בו-זמנית, זמן ההשהיה נשאר מתחת ל-200 אלפיות השנייה. לפי הנחיות סקירת חנות האפליקציות, מטבע וירטואלי חייב להיות מנוצל בתוך האפליקציה, וזה מה שאנו מיישמים.
למה מטבע וירטואלי עדיף על תשלומים ישירים
רוב אפליקציות הסטרימינג משתמשות במטבע וירטואלי (מטבעות, גבישים) במקום עסקאות ישירות. הסיבה היא חיסכון בעלויות: חנות האפליקציות ו-Google Play גובות 30% על רכישות בתוך האפליקציה, אבל מטבע וירטואלי מאפשר פיצול בין רכישת מטבעות (IAP) לבין ניצול מטבעות (לוגיקת שרת). הניכוי מתבצע בצד השרת, החנות לא מעורבת. המשתמש קונה חבילת מטבעות דרך IAP ומנצל אותם בכל זמן ללא עמלה על כל מיקרו-עסקה. החיסכון בעמלות יכול להגיע ל-30%—בהיקף תרומות של 10,000 דולר בחודש, זה חיסכון של 3,000 דולר. הלקוחות שלנו חוסכים בממוצע 30% בהשוואה לתרומות בכרטיס ישירות.
סוגי מתנות: אובייקט GiftItem
data class GiftItem(
val id: String,
val name: String, // "Роза", "Ракета", "Корона"
val coinCost: Int, // стоимость в монетах
val animationUrl: String, // Lottie JSON или MP4
val displayDurationMs: Long // сколько показывать анимацию
) | פורמט | גודל | שקיפות | טעינה |
|---|---|---|---|
| Lottie | 50–200 קילו-בייט | כן | מהירה |
| MP4 | ~500 קילו-בייט | לא | איטית יותר |
Lottie עדיף: קטן פי 5, נטען מהר יותר, מתקנה ללא פגמים.
כיצד פועל תור אנימציות המתנות
מספר צופים עשויים לשלוח מתנות בו-זמנית. איננו יכולים להציג את כל האנימציות במקביל—המסך הופך לכאוס. אנו זקוקים לתור:
class GiftAnimationQueue {
private var queue: [GiftEvent] = []
private var isPlaying = false
func enqueue(_ event: GiftEvent) {
queue.append(event)
if !isPlaying {
playNext()
}
}
private func playNext() {
guard !queue.isEmpty else {
isPlaying = false
return
}
isPlaying = true
let event = queue.removeFirst()
showGiftAnimation(event) { [weak self] in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self?.playNext()
}
}
}
private func showGiftAnimation(_ event: GiftEvent, completion: @escaping () -> Void) {
let animationView = LottieAnimationView(name: event.giftId)
animationView.frame = overlayView.bounds
overlayView.addSubview(animationView)
animationView.play { _ in
animationView.removeFromSuperview()
completion()
}
}
}באנדרואיד, תור דומה באמצעות data class GiftItem( val id: String, val name: String, // "Роза", "Ракета", "Корона" val coinCost: Int, // стоимость в монетах val animationUrl: String, // Lottie JSON или MP4 val displayDurationMs: Long // сколько показывать анимацию ) ו-class GiftAnimationQueue { private var queue: [GiftEvent] = [] private var isPlaying = false func enqueue(_ event: GiftEvent) { queue.append(event) if !isPlaying { playNext() } } private func playNext() { guard !queue.isEmpty else { isPlaying = false; return } isPlaying = true let event = queue.removeFirst() showGiftAnimation(event) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self?.playNext() } } } private func showGiftAnimation(_ event: GiftEvent, completion: @escaping () -> Void) { let animationView = LottieAnimationView(name: event.giftId) animationView.frame = overlayView.bounds overlayView.addSubview(animationView) animationView.play { _ in animationView.removeFromSuperview() completion() } } } עם LottieAnimationView.
שלבי עיבוד תרומה
- המשתמש בוחר מתנה, הלקוח שולח בקשה לשרת.
- השרת בודק יתרת מטבעות בעסקה אטומית ומנכה את העלות.
- לאחר ניכוי מוצלח, השרת מפרסם את האירוע לערוץ ה-WebSocket של השידור.
- הלקוח מקבל את האירוע ומוסיף את האנימציה לתור.
- התור מנגן אנימציות ברצף עם השהיה של 0.3 שניות בין אחת לשנייה.
דוגמה לאירוע WebSocket
{
"type": "gift",
"streamId": "stream-abc123",
"senderId": "user-456",
"senderName": "Алексей",
"senderAvatar": "https://cdn.example.com/avatars/456.jpg",
"giftId": "gift-rocket",
"giftName": "Ракета",
"coinAmount": 50,
"timestamp": "2020-06-15T14:30:01.234Z"
} עדכון תרומות: RecyclerView עם Prepend
תרומות חדשות מתווספות לראש הרשימה, לא לתחתית:
class DonationAdapter : RecyclerView.Adapter<DonationViewHolder>() {
private val donations = mutableListOf<DonationItem>()
fun prepend(donation: DonationItem) {
donations.add(0, donation)
notifyItemInserted(0)
recyclerView.scrollToPosition(0)
}
} טיפול בצופים לא מקוונים
צופים עשויים להתחבר מחדש באמצע השידור. בעת התחברות מחדש, איננו מנגנים מחדש את כל האנימציות שהוחמצו—רק מציגים יומן טקסט של N האירועים האחרונים.
התורמים המובילים: צבירה בזמן אמת
אנו משתמשים ב-Redis ZSET עבור התורמים המובילים של שידור. בכל תרומה, אנו מגדילים את המונה ומשדרים את עשרת המובילים לכל הצופים כל 5–10 שניות דרך ערוץ WebSocket נפרד.
| רכיב | טכנולוגיה | הערה |
|---|---|---|
| שער תשלום | Stripe / IAP / Google Play Billing | עמלת 30% על מטבע וירטואלי לא חלה |
| זמן אמת | WebSocket (socket.io) | אישור תשלום לפני פרסום |
| אנימציות | Lottie | 50–200 קילו-בייט, ללא פגמים |
| אחסון יתרה | PostgreSQL / Redis | עסקאות אטומיות |
| תורמים מובילים | Redis ZSET | מתעדכן כל 5–10 שניות |
מה כלול בעבודה
- לוגיקת ניכוי מטבעות בצד השרת (עסקה אטומית)
- שידור אירועי WebSocket לכל הצופים
- תור אנימציות מתנות בצד הלקוח (Lottie)
- עדכון תרומות עם לוגיקת prepend
- תורמים מובילים בזמן אמת
- טיפול בהתחברות מחדש ושחזור מצב
- אינטגרציה עם חנות האפליקציות ו-Google Play Billing
ציר זמן
5 ימים תחת עומס של עד 10,000 צופים. חלק השרת עם WebSocket וחיוב מטבעות: יומיים. חלק הלקוח עם אנימציות ועדכון: יומיים. אינטגרציה, בדיקות, מקרי קצה: יום אחד. העלות מחושבת באופן אישי. קבלו ייעוץ—נמצא את הפתרון האופטימלי לפרויקט שלכם.







