OpenAIExpoOct 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.

openaienmodel: gpt-5-mini-2025-08-07

Faster, more reliable video uploads with Expo Modules

Key Points

  • Native background uploads with Expo Modules
  • AWS S3 multipart uploads with presigned URLs
  • ~20% faster uploads; no stuck uploads

Summary

Boom replaced a JavaScript-only uploader with a native-backed pipeline using Expo Modules. A SharedObject-based UploadTask (exposed from native iOS/Swift and used from TypeScript) performs long-lived background uploads, handles chunking, concurrency, and retries, and uploads parts to AWS S3 multipart presigned URLs. The JS layer orchestrates: preProcess(), request presigned URLs from the backend, set clipUrls/coverUrl/completionUrl, subscribe to onProgress, then await upload().

Key Points

  • Goals: improve speed and resilience for large mobile video uploads (survive app backgrounding and network hiccups).
  • Architecture: Expo Modules + SharedObject provides native long-lived tasks with a small JS surface (UploadTask with preProcess, parts, clipUrls, completionUrl, coverUrl, upload(), and onProgress events).
  • Backend role: return presigned S3 multipart upload URLs and a completion URL; client uploads parts in parallel with retries.
  • Integration tips: keep the heavy work (chunking, retries, background continuation) in native code; keep orchestration and UI events in JS.
  • Results: ~20% median end-to-end time improvement for 100–300MB clips and no "stuck" uploads in tests.
  • Maintenance: Expo Modules required less boilerplate than TurboModules and integrates smoothly with Expo projects, lowering long-term cost.

Practical checklist for engineers

  • Implement a long-lived native upload task (SharedObject) exposed via an Expo Module class.
  • Add a preProcess step to split files into parts and expose parts length to request presigned URLs from the backend.
  • Use S3 multipart uploads with per-part presigned URLs and a completion endpoint.
  • Emit progress events to JS and make upload() an async native method that handles retries, concurrency, and background execution.
  • Monitor metrics: end-to-end time (tap → backend confirm) and failure/stuck rates after deployment.

Full Translation

Translations

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

openaijamodel: gpt-5-mini-2025-08-07

Expo Modulesを使った、より高速で信頼性の高い動画アップロード

Expo Modulesを使った、より高速で信頼性の高い動画アップロード

Users • React Native • Development • 2025-10-28 • 3分で読めます

ゲスト投稿:Petr Chalupa(SRTVのソフトウェアエンジニア、React Pragueオーガナイザー)

Boomは動画を軸としたコンペティションプラットフォームです(App Store、Google Play)。クリエイターは毎月11のカテゴリに映像を投稿し、ユーザー投票や審査員による選出で各カテゴリの勝者が決まり、賞金が授与されます。Boomにおいては、アップロードは単なる副機能ではなく、クリエイターがコンテストに参加するための主要な操作です。そのため、信頼できてストレスのないアップロード体験が非常に重要です。

課題:モバイルでの大容量動画アップロード

初期実装はシンプルなJavaScript製のアップローダーでした。MVPには十分でしたが、スケールさせるといくつかの重大な欠点が露呈しました:

  • バックグラウンド実行の欠如:JavaScriptの実行はアプリのライフサイクルに紐づいており、アプリがバックグラウンドになるとOSがアップロードを停止する可能性があります。
  • ネットワーク中断の取り扱い:一度のネットワークの途切れでアップロード全体が失敗し、ユーザーに再実行を強いることがありました。

目標:高速で信頼できるモバイル動画アップロード

目標は次のとおりです:

  • 速さ:ユーザーの端末からサーバーへできるだけ速く動画を転送する。
  • 信頼性:アプリの切り替え、ネットワークの断続、その他の中断が発生してもアップロードが継続する。

解決策:Expo Modulesを用いたネイティブのバックグラウンドアップロード

我々はExpo Modulesを使って全く新しいアップロードパイプラインを構築しました。SharedObjectを用いることで長時間生存する状態を持ったアップロードタスクを実現し、AWS S3のmultipart uploadに切り替えることで大容量ビデオのアップロードをより高速かつ信頼性の高いものにしました。

以下は我々の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>
}

次に、iOSネイティブモジュールがUploadTaskクラスをJavaScript側に公開している例です。

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での統合を簡略化したコード例です。ネイティブモジュール側でチャンク分割、並列度、リトライ、バックグラウンド実行を扱います。

// instantiate an UploadTask that owns the parts, progress, and URLs
const uploadTask = new BackgroundUpload.UploadTask(videoUri, thumbnailUri)

// listen for progress
const progressListener = uploadTask.addListener('onProgress', ({ progress }) => {
  updateProgress({ uploadProgress: progress })
})

// split the file into independent parts
uploadTask.preProcess()

// request the backend for presigned URLs
const { data: uploadInfo } = await createUploadUrls({
  variables: { input: { partsCount: uploadTask.parts.length } },
})

// configure task
uploadTask.clipUrls = uploadInfo.clip.uploadUrls.map(({ uploadUrl }) => uploadUrl)
uploadTask.completionUrl = uploadInfo.clip.completionUrl
uploadTask.coverUrl = uploadInfo.cover.uploadUrl

// begin upload parts in parallel with retries
try {
  await uploadTask.upload()
} catch (error) {
  logError(error)
} finally {
  progressListener.remove()
}

結果:より高速で小さく、信頼できるアップロード

  • 速度:100〜300MBのクリップにおけるエンドツーエンドの中央値が約20%改善しました。
  • 信頼性:最近のテスト実行では“停止した”アップロードは観測されませんでした。
  • 測定方法:end‑to‑end = tap Agree and Post → backend confirms completion

結論:ネイティブアップロードを追加するならExpo Modulesが最適

大容量メディアのアップロードのような高性能で回復力が必要な機能には、JavaScriptとネイティブコードを組み合わせたハイブリッドアプローチが最適です。Expo Modulesはこれらの機能を構築するのに非常に適しており、TurboModulesと比べて保守が容易でボイラープレートが少なく、Expoプロジェクトとの統合もスムーズです。長期的な保守コストを下げつつ、ネイティブの性能を必要な箇所で享受できるため、投資の効果としてより滑らかなユーザー体験と堅牢なアプリが得られました。