Expo Modulesによる高速で信頼性の高い動画アップロード
Users • React Native • Development • October 28, 2025 • 3分で読める
Petr Chalupa
ゲスト著者
BoomがExpo Modulesを使用してネイティブバックグラウンドアップロードとAWS S3マルチパートサポートにより、より高速で信頼性の高いモバイル動画アップロードパイプラインを構築した方法を学びましょう。
これは、SRTVのソフトウェアエンジニアでReact Pragueの主催者であるPetr Chalupaからのゲスト投稿です。
...
Boom([App Store](App Store)、[Google Play](Google Play))は動画ファーストの競技プラットフォームです。クリエイターは11のカテゴリーにわたる月次コンテストにクリップを投稿し、ユーザーがお気に入りに投票します。その後、審査員が各カテゴリーの勝者を選出します。各勝者は多額の賞金を受け取ります。
Boomの核心は競技であり、単なるソーシャル共有ではありません。動画のアップロードはサイド機能ではなく、クリエイターがコンテストに参加する方法なのです。そのため、信頼性があり、ストレスフリーなアップロードフローが体験にとって重要です。
課題:モバイルでの大容量動画アップロード
初期実装は単純なJavaScriptアップローダーでした。MVPには機能しましたが、それ以上にはスケールしませんでした。いくつかの重要な欠陥がありました:
- バックグラウンド実行の欠如:JavaScript実行はアプリのライフサイクルに紐づいています。アプリがバックグラウンドになると、OSによってアップロードが中断されます。
- ネットワーク中断の処理:単一のネットワーク障害で全体のアップロードが失敗し、ユーザーは最初からやり直しを強いられます。
目標:高速で信頼性の高いモバイル動画アップロード
私たちの目標は、以下のようなアップロード体験を作ることでした:
- 高速:ユーザーの携帯電話から私たちのサーバーに可能な限り迅速に動画を送信する。
- 信頼性:アップロードはアプリの切り替え、ネットワークの問題、その他の中断を乗り越える必要がある。
解決策:Expo Modulesによるネイティブバックグラウンドアップロード
私たちは[Expo Modules](Expo Modules)を使用して全く新しいアップロードパイプラインを構築しました。SharedObjectにより、長時間存続するステートフルなアップロードタスクを作成しました。AWS S3マルチパートアップロードに切り替えることで、大容量動画がより高速かつ信頼性高くアップロードされるようになりました。
以下は私たちのUploadTaskのTypeScriptインターフェースです:
export type UploadTaskEvents = {
onProgress: (params: { progress: number }) => void
}
export declare class UploadTask extends SharedObject<UploadTaskEvents> {
constructor(clipPath: string, coverPath: string)
readonly parts: URL[]
clipUrls?: string[]
completionUrl?: string
coverUrl?: string
preProcess(): void
upload(): Promise<void>
}
UploadTaskクラスをJavaScript側に公開するiOSネイティブモジュール:
import ExpoModulesCore
public class BackgroundUploadModule: Module {
public func definition() -> ModuleDefinition {
Name("BackgroundUpload")
Class(UploadTask.self) {
Constructor { (clip: URL, cover: URL) -> UploadTask in
return UploadTask(clip: clip, cover: cover)
}
Property("parts") { uploadTask in
uploadTask.clip.parts
}
Property("clipUrls") { uploadTask in
uploadTask.clip.uploadUrls
}.set { (uploadTask: UploadTask, uploadUrls: [URL]) in
uploadTask.clip.uploadUrls = uploadUrls
}
Property("completionUrl") { uploadTask in
uploadTask.clip.completionUrl
}.set { (uploadTask: UploadTask, completionUrl: URL) in
uploadTask.clip.completionUrl = completionUrl
}
Property("coverUrl") { uploadTask in
uploadTask.cover.uploadUrl
}.set { (uploadTask: UploadTask, uploadUrl: URL) in
uploadTask.cover.uploadUrl = uploadUrl
}
Function("preProcess") { uploadTask in
try uploadTask.preProcessAssets()
}
AsyncFunction("upload") { uploadTask in
try await uploadTask.upload()
}
}
}
}
React Native統合例
以下はReact Nativeでの統合の簡略版です。ネイティブモジュールがチャンク化、並行処理、リトライ、バックグラウンド実行を処理します。
const uploadTask = new BackgroundUpload.UploadTask(videoUri, thumbnailUri)
const progressListener = uploadTask.addListener('onProgress', ({ progress }) => {
updateProgress({ uploadProgress: progress })
})
uploadTask.preProcess()
const { data: uploadInfo } = await createUploadUrls({
variables: {
input: {
partsCount: uploadTask.parts.length
}
},
})
uploadTask.clipUrls = uploadInfo.clip.uploadUrls.map(({ uploadUrl }) => uploadUrl)
uploadTask.completionUrl = uploadInfo.clip.completionUrl
uploadTask.coverUrl = uploadInfo.cover.uploadUrl
try {
await uploadTask.upload()
} catch (error) {
logError(error)
} finally {
progressListener.remove()
}
結果:より高速で小さく、信頼性の高いアップロード
- 速度:100-300MBのクリップで、エンドツーエンドのアップロード時間の中央値が約20%改善。
- 信頼性:最新のテスト実行で「スタック」したアップロードは観測されず。
- 測定:エンドツーエンド = 「同意して投稿」をタップ → バックエンドが完了を確認。
結論:Expo Modulesがネイティブアップロードを追加する最良の方法である理由
大容量メディアアップロードなどの高性能で堅牢な機能において、JavaScriptとネイティブコードを組み合わせたハイブリッドアプローチがしばしば最良の解決策です。
Expo Modulesは、この種の機能を構築するのに最適です。TurboModulesと比較して、保守が容易で、ボイラープレートが少なく、Expoプロジェクトとスムーズに統合できます。これにより、重要な部分でネイティブパフォーマンスを提供しながら、長期的な保守コストを削減できます。
この投資により、よりスムーズなユーザー体験と、より堅牢で信頼性の高いアプリを実現しました。