ClaudeExpoOct 28, 2025, 1:15 PM

Faster, more reliable video uploads with Expo Modules

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

claudeenmodel: claude-sonnet-4-20250514

Faster, more reliable video uploads with Expo Modules

Key Points

  • 20% faster upload times for large video files
  • Native background uploads survive app switching
  • Eliminated stuck uploads with S3 multipart support

Summary

Boom, a video-first competition platform, improved their mobile video upload pipeline by migrating from JavaScript-based uploads to native background uploads using Expo Modules and AWS S3 multipart support. This solved critical issues with app backgrounding and network interruptions.

Key Points

  • Problem: JavaScript uploads failed when app was backgrounded and couldn't handle network interruptions
  • Solution: Built native upload pipeline using Expo Modules SharedObject with AWS S3 multipart upload
  • Architecture: UploadTask class handles chunking, concurrency, retries, and background execution natively
  • Performance: ~20% improvement in median upload time for 100-300MB clips
  • Reliability: Eliminated "stuck" uploads in testing
  • Integration: Seamless React Native integration with progress events and async/await patterns

Technical Implementation

  • Uses SharedObject for long-lived, stateful upload tasks
  • Native iOS module exposes UploadTask to JavaScript
  • Supports parallel part uploads with automatic retries
  • Handles presigned URL configuration for S3 multipart uploads

Full Translation

Translations

A translation section that keeps the flow of the original article.

claudejamodel: claude-sonnet-4-20250514

Expo Modulesによる高速で信頼性の高い動画アップロード

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での統合の簡略版です。ネイティブモジュールがチャンク化、並行処理、リトライ、バックグラウンド実行を処理します。

// パーツ、進捗、URLを所有するUploadTaskをインスタンス化
const uploadTask = new BackgroundUpload.UploadTask(videoUri, thumbnailUri)

// 進捗をリッスン
const progressListener = uploadTask.addListener('onProgress', ({ progress }) => {
  updateProgress({ uploadProgress: progress })
})

// ファイルを独立したパーツに分割
uploadTask.preProcess()

// 署名付きURLをバックエンドにリクエスト
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プロジェクトとスムーズに統合できます。これにより、重要な部分でネイティブパフォーマンスを提供しながら、長期的な保守コストを削減できます。

この投資により、よりスムーズなユーザー体験と、より堅牢で信頼性の高いアプリを実現しました。