Реалізація донатів/подарунків під час прямої трансляції в мобільному додатку
Донати в стрімі — це не просто платіж. Це real-time подія: користувач надіслав подарунок → анімація пролітає поверх відео → глядачі бачать ім'я донатера в ленті → стример отримує сповіщення. Між натисканням кнопки та появою анімації має пройти менше секунди. Це вимагає продуманої інтеграції: платіжний процесор → бекенд → WebSocket → клієнт.
Архітектура: три паралельні потоки
Донат проходить через три незалежні шари одночасно:
- Платіжний потік — списання через Stripe/IAP/Google Play Billing з підтвердженням
- Real-time потік — WebSocket-подія всім глядачам трансляції
- Лента донатів — оновлення UI-счетчика та скрол-лога
Помилка в одному потоці не повинна блокувати інші. Анімація подарунка показується після підтвердження платежу, не до.
Віртуальна валюта: чому не прямі платежі
Більшість стриминг-додатків використовують віртуальну валюту (монети, кристали) замість прямих транзакцій:
- App Store та Google Play беруть 30% з in-app покупок, але віртуальна валюта дозволяє розділити покупку монет (IAP) та трату монет (серверна логіка) — списання відбувається на сервері, App Store не бере участі
- Користувач купує пачку монет через IAP, витрачає у будь-який час — асинхронно від платіжного флоу
- Агрегація дрібних донорів: надіслати 5 рублів напрямку — дорого за комісіями, надіслати 5 монет з куплених раніше — дешево
// Покупка монет через Google Play Billing
val productDetails = // загруженi через BillingClient.queryProductDetailsAsync
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build()
)
)
.build()
billingClient.launchBillingFlow(activity, billingFlowParams)
Типи подарунків: об'єкт GiftItem
data class GiftItem(
val id: String,
val name: String, // "Роза", "Ракета", "Корона"
val coinCost: Int, // вартість в монетах
val animationUrl: String, // Lottie JSON або MP4
val displayDurationMs: Long // як довго показувати анімацію
)
Анімації подарунків — Lottie (JSON, ~50-200 KB) або короткі MP4 (~500 KB). Lottie краще: масштабується без артефактів, підтримує прозорість, не потребує медіа-декодера.
Real-time: WebSocket подія подарунка
Після списання монет на сервері (атомарна операція в БД) — публікуємо подію в 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": "2024-06-15T14:30:01.234Z"
}
Сервер повинен перевірити баланс монет перед публікацією. Ніколи не довіряйте клієнту: клієнт говорить «відправити ракету за 50 монет» → сервер перевіряє баланс, списує, потім публікує подію. Не навпаки.
Клієнт: черга анімацій
Кілька глядачів можуть одночасно надіслати подарунки. Не можна показувати всі анімації паралельно — екран перетворюється на хаос. Потрібна черга:
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()
}
}
}
На Android аналогічна черга через Lottie AnimationView та LinkedList<GiftEvent> з Handler.
Лента донатів: 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)
}
}
Обробка offline-глядачів
Глядачі можуть переподключитися в середині трансляції. При переподключенні не потрібно відтворювати всі пропущені анімації — показуємо лише лог донатів текстом та останні N подій.
Топ донатерів: агрегація в реальному часі
// Redis ZSET для топ донатерів трансляції
// ZINCRBY stream:{streamId}:donations {coinAmount} {userId}
// ZREVRANGE stream:{streamId}:donations 0 9 WITHSCORES — топ 10
Оновлюється при кожному донаті, розсилається всім глядачам щокожні 5–10 секунд через окремий WebSocket-канал.
Що входить в роботу
- Серверна логіка списання монет (атомарна транзакція)
- WebSocket-рассилка подій всім глядачам
- Черга анімацій подарунків на клієнті (Lottie)
- Лента донатів з prepend-логікою
- Топ донатерів в реальному часі
- Обробка reconnect та відновлення стану
Тривалість
5 днів. Серверна частина з WebSocket та біллінгом монет — 2 дні. Клієнтська частина з анімаціями та лентою — 2 дні. Інтеграція, тестування, edge cases — 1 день. Вартість розраховується індивідуально.







