Реализация AI-генерации видео (Runway/Sora) в мобильном приложении
Генерация видео через AI — это пока исключительно серверная задача. Модели класса Runway Gen-3, Sora, Kling, Hailuo требуют A100/H100 GPU и от 30 секунд до нескольких минут на один клип. Задача мобильного разработчика — правильно организовать асинхронный флоу, состояние интерфейса и доставку результата пользователю.
Обзор доступных API
| Провайдер | API | Длина клипа | Типичное время | Входные данные |
|---|---|---|---|---|
| Runway Gen-3 Alpha | REST + polling | 5–10 сек | 30–90 сек | Text, Image-to-Video |
| Kling AI | REST API | 5–10 сек | 60–180 сек | Text, Image-to-Video |
| Hailuo (MiniMax) | REST API | 6 сек | 45–120 сек | Text, Image-to-Video |
| Luma Dream Machine | REST API | 5 сек | 30–60 сек | Text, Image, Keyframes |
| Replicate (различные) | REST + WebSocket | 2–10 сек | 30–120 сек | Зависит от модели |
Sora от OpenAI доступна через API только в рамках ChatGPT Enterprise — публичного API нет по состоянию на март 2025. Runway — наиболее зрелый публичный API с SDK для TypeScript/Python.
Асинхронная архитектура: главная сложность
Пользователь жмёт «Генерировать», ждёт минуту. Приложение должно:
- Показывать прогресс (хотя API обычно отдают только
PENDING/PROCESSING/SUCCEEDED) - Пережить сворачивание приложения
- Доставить результат даже если пользователь вернулся через 5 минут
// iOS: генерация через Runway API
class VideoGenerationService {
func generate(prompt: String, sourceImage: UIImage?) async throws -> URL {
// 1. Создаём задачу
let taskId = try await runwayClient.createTask(
prompt: prompt,
imageURL: sourceImage.map { try await uploadImage($0) },
duration: 5,
ratio: "1280:768"
)
// 2. Сохраняем taskId — на случай сворачивания
UserDefaults.standard.set(taskId, forKey: "pendingVideoTaskId")
// 3. Polling с нарастающим интервалом
return try await pollWithBackoff(taskId: taskId)
}
private func pollWithBackoff(taskId: String) async throws -> URL {
let intervals: [TimeInterval] = [3, 5, 8, 10, 10, 15, 15, 20, 20, 30]
for interval in intervals + Array(repeating: 30.0, count: 10) {
try await Task.sleep(nanoseconds: UInt64(interval * 1e9))
let task = try await runwayClient.getTask(id: taskId)
switch task.status {
case .succeeded:
UserDefaults.standard.removeObject(forKey: "pendingVideoTaskId")
return task.output.first!
case .failed:
throw VideoGenError.generationFailed(task.failure ?? "Unknown")
default: continue
}
}
throw VideoGenError.timeout
}
}
На Android: WorkManager с CoroutineWorker — правильный выбор для долгих фоновых задач. Polling в doWork(), Result.retry() при PROCESSING, Result.success(outputData) при SUCCEEDED.
Оценка реального прогресса без данных от API
Runway и большинство API не возвращают процент выполнения — только статус. Но пользователь хочет видеть прогресс. Решение: симулированный прогресс-бар на основе типичного времени генерации.
Запускаем таймер с момента начала. Зная, что среднее время — 60 секунд, анимируем прогресс до 95% за 55 секунд, затем останавливаем и ждём реального ответа. При успехе — быстро добегаем до 100%. Это лучше, чем спиннер без контекста.
Загрузка и воспроизведение результата
Runway возвращает URL временного файла (TTL обычно 24–48 часов). Не полагаемся на него — сразу скачиваем в local storage.
// Android: скачивание и кеш видео
class VideoDownloader(private val context: Context) {
suspend fun downloadAndCache(remoteUrl: String, videoId: String): File {
val cacheDir = File(context.filesDir, "generated_videos")
cacheDir.mkdirs()
val file = File(cacheDir, "$videoId.mp4")
withContext(Dispatchers.IO) {
URL(remoteUrl).openStream().use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
}
return file
}
}
Воспроизведение: AVPlayer (iOS) / ExoPlayer (Android) — оба поддерживают mp4 напрямую из FileURL/Uri. Для генерированного контента обычно достаточно стандартного плеера без HLS.
Push-уведомление о готовности
Если генерация занимает больше 30 секунд — обязательно push. Backend отслеживает задачи и шлёт FCM/APNs при смене статуса на SUCCEEDED. Deep link ведёт в экран результата с автовоспроизведением.
UX: что делать пока ждём
Лучший паттерн — не блокировать пользователя. Задача уходит в фоновый список «В обработке», пользователь может создавать другие запросы или пользоваться приложением. Bottom sheet или notification bar показывает прогресс ненавязчиво. По завершении — badge на иконке вкладки или inline уведомление.
Сроки
Базовая интеграция (промпт → генерация → воспроизведение) с polling и локальным кешем — 5–7 дней. Полный флоу с фоновой обработкой, push-уведомлениями, галереей и Image-to-Video режимом — 3–4 недели. Стоимость зависит от платформы и выбранного провайдера.







