ダウンロードエンジンアーキテクチャ

FlowPick ダウンロードエンジンの技術アーキテクチャの詳細解説——セグメント並列ダウンロード、AES-128 復号、3段階書き込み戦略、メモリセキュリティ管理、進捗トラッキング。

FlowPick のダウンロードエンジンはシステム全体の中核であり、ストリーミングメディアセグメントのダウンロード、復号、マージ、ディスクへの書き込みを担当します。本ドキュメントは内部メカニズムを深く理解したい開発者と上級ユーザー向けです。


アーキテクチャ概要

ダウンロードエンジンは3つのコアモジュールで構成され、データはパイプライン方式で各モジュールを順次流れます:

┌─────────────────────────────────────────────────┐
│                   ダウンロードエンジン              │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
│  │ セグメント │→│ セグメント │→│ ファイル書き込み  │  │
│  │ ダウンロー │  │ 処理      │  │               │  │
│  │ ドモジュール│  │ モジュール │  │ モジュール      │  │
│  │          │  │          │  │                │  │
│  │ · 並列   │  │ · 復号   │  │ · FSA ストリーム│  │
│  │ · リトライ│  │ · 連結   │  │ · StreamSaver  │  │
│  │ · 速度制限│  │ · リマックス│  │ · Blob 兜底    │  │
│  └──────────┘  └──────────┘  └───────────────┘  │
│                                                  │
│  ┌─────────────────────────────────────────────┐ │
│  │           メモリセキュリティマネージャー         │ │
│  │  · サイズ見積もり · 閾値チェック · 戦略選択     │ │
│  └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘

データフロー全景

ユーザーがダウンロードをクリックしてからファイルがディスクに書き込まれるまでの完全なチェーン:

ユーザーがダウンロードをクリック
    │
    ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ 1. マニフェスト│ ──→ │ 2. セグメント │ ──→ │ 3. セグメント │
│ 解析          │     │ ダウンロード  │     │ 処理          │
│              │     │              │     │              │
│ · M3U8 を   │     │ · Worker Pool│     │ · AES 復号   │
│   ダウンロード│     │ · 並列制御   │     │ · TS 連結    │
│ · セグメント │     │ · 指数バックオフ│     │ · FFmpeg リマックス│
│   リストを解析│     │ · 進捗報告   │     │ · 音声/動画マージ│
│ · キーを抽出 │     │              │     │              │
│ · サイズを見積│     │              │     │              │
└──────────────┘     └──────────────┘     └──────┬───────┘
                                                  │
                                                  ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ 6. 完了通知   │ ←── │ 5. クリーン   │ ←── │ 4. ファイル   │
│              │     │ アップ        │     │ 書き込み      │
│ · デスクトップ│     │              │     │              │
│   通知       │     │ · メモリ解放 │     │ · FSA ストリーム│
│ · パスコピー │     │ · 一時ファイル│     │ · StreamSaver│
│ · キュー進行 │     │   削除       │     │ · Blob 兜底  │
│              │     │ · 状態リセット│     │              │
└──────────────┘     └──────────────┘     └──────────────┘
マニフェスト解析フェーズの具体的な実装(Master Playlist vs Media Playlist、暗号化検知)については 動画検知 — HLS ストリーム を参照してください。セグメント処理における FFmpeg リマックスの詳細については フォーマット変換 を参照してください。エンジン全体システムでの位置については プロジェクトアーキテクチャ を参照してください。

エンジンのライフサイクル

ダウンロードエンジンの1回の完全な実行は以下の状態遷移を経ます:

  [アイドル]
    │ ユーザーがダウンロードをトリガー
    ▼
  [初期化] ──── WASM の読み込み、API 利用可能性チェック、書き込み戦略の選択
    │
    ▼
  [マニフェスト解析] ── M3U8/MPD をダウンロードして解析し、セグメントリストとキーを抽出
    │
    ▼
  [セグメントダウンロード] ── Worker Pool で並列ダウンロード、リアルタイム進捗報告
    │
    ▼
  [セグメント処理] ── 復号 → 連結/リマックス → ディスクに書き込み
    │
    ├── 成功 → [クリーンアップ] → [完了] → [アイドル]
    │
    ├── キャンセル → [クリーンアップ] → [アイドル]
    │
    └── 失敗 → [クリーンアップ] → [エラー] → [アイドル]

各状態遷移は対応するライフサイクルフックをトリガーし、UI 層はこれらのフックを監視して画面状態を更新します。UI とエンジンのインタラクションの詳細については、プロジェクトアーキテクチャ — データフロー を参照してください。


セグメントダウンロードモジュール

並列ダウンローダー

セグメントダウンロードは Worker Pool モードで並列制御を実装しています:

const downloadSegmentsConcurrently = async (
  segments: SegmentInfo[],
  onSegmentDownloaded: (buffer: ArrayBuffer, index: number) => void
) => {
  const concurrency = Math.min(config.concurrency, 8)
  let nextIndex = 0

  const worker = async () => {
    while (nextIndex < segments.length) {
      const index = nextIndex++
      const buffer = await downloadWithRetry(segments[index]!)
      onSegmentDownloaded(buffer, index)
    }
  }

  const workers = Array.from(
    { length: Math.min(concurrency, segments.length) },
    () => worker()
  )
  await Promise.all(workers)
}

設計要点

  • 共有の nextIndex カウンタを使用してタスクを割り当て、事前分割による負荷不均衡を回避
  • Worker 数はセグメント総数を超えず、アイドル Worker の作成を回避
  • 各 Worker は独立して動作し、単一セグメントの失敗は他の Worker に影響しない

Worker Pool の深掘り

Worker Pool モードのコアメリットは動的負荷分散にあります。事前にセグメント配列を N 等分する場合と異なり、共有カウンタにより以下を保証します:

事前分割(非推奨):
Worker 1: [セグメント 0-49]   ← これらのセグメントが大きい場合、Worker 1 がボトルネックに
Worker 2: [セグメント 50-99]  ← 早く完了してからアイドル待機する可能性あり

共有カウンタ(FlowPick 採用):
Worker 1: セグメント 0 → セグメント 3 → セグメント 5 → ...
Worker 2: セグメント 1 → セグメント 4 → セグメント 7 → ...
Worker 3: セグメント 2 → セグメント 6 → セグメント 8 → ...

各 Worker は現在のセグメントを完了すると直ちに次の未割当てセグメントを取得し、すべてのセグメント処理が完了するまで続きます。このモードはセグメントサイズ不均一のシーンに自然に適応します(例:HLS ストリームの先頭・末尾セグメントは通常小さく、中間セグメントは大きい)。

並列数は config.concurrency で制御され、上限は 8 です。ユーザーは 設定リファレンス でデフォルト値を調整でき、ダウンロード画面でリアルタイムに変更できます。一括ダウンロードシーンでのキュースケジューリングについては、一括ダウンロード — 並列数選択ガイド を参照してください。

非同期ジェネレーターモード

超大容量ファイル(セグメント数 > 500)の場合、エンジンは非同期ジェネレーター(AsyncGenerator)モードを使用して、すべてのセグメントデータを一度にメモリに読み込むことを回避します:

async function* segmentGenerator(
  segments: SegmentInfo[],
  signal?: AbortSignal
): AsyncGenerator<ArrayBuffer> {
  for (const segment of segments) {
    if (signal?.aborted) break
    const buffer = await downloadWithRetry(segment)
    yield buffer
  }
}

配列モードとの違い

次元配列モードジェネレーターモード
メモリピークすべてのセグメントが同時にメモリにある現在処理中のセグメントのみがメモリにある
適用シーンセグメント数 < 500セグメント数 > 500
進捗トラッキング総数既知、正確なパーセント総数既知、正確なパーセント
キャンセル応答現在のバッチ完了を待つ必要あり即座に応答

エンジンはセグメント数に基づいて自動的にモードを選択し、ユーザーの介入は不要です。超大容量ファイルダウンロードの実際のシーンについては、ライブ配信アーカイブ保存 を参照してください。

リトライメカニズム

セグメントダウンロード失敗時には指数バックオフリトライを採用します:

const downloadWithRetry = async (
  segment: SegmentInfo,
  maxRetries = 3
): Promise<ArrayBuffer> => {
  let lastError: unknown
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await downloadAndDecryptSegment(segment)
    } catch (e) {
      lastError = e
      if (attempt < maxRetries - 1) {
        // 指数バックオフ:400ms, 800ms, 1600ms
        await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 400))
      }
    }
  }
  throw lastError
}

リトライ戦略

試行回数遅延累計待機時間
第1回失敗400ms400ms
第2回失敗800ms1200ms
第3回失敗1600ms2800ms

リトライがトリガーされない場合

  • HTTP 4xx クライアントエラー(403、404 など)—— リトライは無意味
  • CORS エラー —— 戦略的問題、リトライしても結果は変わらない
  • 暗号化非サポートエラー —— リトライでは解決できない
リトライメカニズムは オンラインツール — エラー処理 のエラー分類表と連携して使用されます。HTTP 4xx と CORS エラーは直接スローされ、上位 UI 層で対応するユーザープロンプトが表示されます。ダウンロード途中の失敗のトラブルシューティングについては、よくある問題のトラブルシューティング — ダウンロード途中失敗 を参照してください。

エラー分類

class FetchError extends Error {
  constructor(
    message: string,
    public status: number,
    public url: string
  ) {
    super(message)
    this.name = 'FetchError'
  }
}

エラーは HTTP ステータスコード別に分類処理されます:

ステータスコードエラータイプユーザープロンプト
403認証/認可失敗"アクセス拒否、URL が有効かどうか確認してください"
404リソース不存在"セグメント未検出、ストリームの有効期限切れの可能性があります"
502/503/504サーバーエラー"サーバーが一時的に利用不可です、後でもう一度お試しください"
CORSクロスドメイン制限"クロスドメインリクエストがブロックされました、拡張機能バージョンの使用をお勧めします"
Networkネットワーク中断"ネットワーク接続失敗、ネットワークを確認してください"
CORS エラーに遭遇した場合は、ブラウザ拡張機能 のインストールをお勧めします。拡張機能は webRequest 権限経由で同一オリジンポリシーをバイパスできます。より多くのトラブルシューティング方法については よくある問題のトラブルシューティング を参照してください。CORS の技術的原理と制限については 既知の制限 — ブラウザ制限 を参照してください。

セグメント処理モジュール

AES-128 復号

暗号化された HLS ストリームの場合、FlowPick は Web Crypto API を使用してブラウザ内で復号します:

const decryptAES128 = async (
  encryptedData: ArrayBuffer,
  key: ArrayBuffer,
  iv?: Uint8Array
): Promise<ArrayBuffer> => {
  const keyBytes = await crypto.subtle.importKey(
    'raw', key,
    { name: 'AES-CBC' },
    false,
    ['decrypt']
  )

  const ivBuffer = iv ? new Uint8Array(iv) : new Uint8Array(16)

  const decrypted = await crypto.subtle.decrypt(
    { name: 'AES-CBC', iv: ivBuffer },
    keyBytes,
    encryptedData
  )

  return decrypted
}

復号フロー

  1. M3U8 の #EXT-X-KEY タグからキー URI と IV を抽出
  2. キーファイルをダウンロード(通常は 16 バイトのバイナリファイル)
  3. AES-128-CBC モードを使用して各セグメントを復号
  4. IV が指定されていない場合、セグメント番号を IV として使用(HLS 仕様のデフォルト動作)

パフォーマンス考慮事項

  • Web Crypto API はハードウェアアクセラレーションを使用しており、復号速度は通常ボトルネックにならない
  • 各セグメントは独立して復号可能であり、ダウンロードと並行して実行できる
  • キーは一度だけダウンロードすればよく、メモリにキャッシュされる
すべての復号操作はブラウザローカルで完了し、キーとセグメントデータはどのサーバーにもアップロードされません。暗号化検知の詳細については 動画検知 — 暗号化ストリーム を参照してください。プライバシー保護戦略については プライバシーとセキュリティ を参照してください。サポートされていない暗号化タイプ(Widevine、PlayReady など)については 既知の制限 — DRM 保護コンテンツ を参照してください。

TS セグメント連結

TS 出力フォーマットの場合、セグメントはバイナリで直接連結されます:

// 疑似コード
const merged = new Uint8Array(totalSize)
let offset = 0
for (const segment of segments) {
  merged.set(new Uint8Array(segment), offset)
  offset += segment.byteLength
}

この方式は CPU オーバヘッドゼロ、速度はメモリコピ速度のみに依存します。

FFmpeg WASM リマックス

MP4 出力フォーマットの場合、FFmpeg WASM を使用してコンテナ変換を行います。詳細は フォーマット変換 ドキュメントを参照してください。

TS フォーマットのみが必要な場合、TS 出力を選択することで FFmpeg を完全にスキップでき、マージ速度を大幅に向上できます。TS ファイルは VLC、PotPlayer などのプレイヤーで直接再生できます。出力フォーマットの選択に関するアドバイスについては、フォーマット変換 — 出力フォーマット選択 を参照してください。

DASH ストリームの特殊処理

DASH ストリームと HLS ストリームはセグメント処理において顕著な違いがあります:

HLS ストリーム処理:
  セグメント 0 → セグメント 1 → セグメント 2 → ... → 連結 → 出力

DASH ストリーム処理(音声/動画分離):
  初期化セグメント → 動画セグメント 0 → 動画セグメント 1 → ... ─┐
                                                    ├→ FFmpeg マージ → 出力
  初期化セグメント → 音声セグメント 0 → 音声セグメント 1 → ... ─┘

DASH の FMP4(Fragmented MP4)フォーマットは特殊な処理が必要です:

  1. 初期化セグメントftyp + moov box)はファイル先頭に配置する必要がある
  2. メディアセグメントmoof + mdat box)は順番に追記
  3. 音声/動画分離時、動画トラックと音声トラックをそれぞれダウンロードし、最後に FFmpeg でマージ
DASH ストリームのマニフェスト解析の詳細については 動画検知 — DASH ストリーム を参照してください。FMP4 再構成の具体的な実装については フォーマット変換 — FMP4 再構成 を参照してください。DASH 音声/動画分離の既知の問題については 既知の制限 — 分離音声/動画ストリーム を参照してください。

ファイル書き込みモジュール

書き込みモジュールは優先順位に従って戦略を選択し、可能な限り多くのブラウザで動作することを保証します。

戦略1:File System Access API(最適)

async function createFSAStream(
  filename: string,
  dirHandle?: FileSystemDirectoryHandle | null
): Promise<WritableStream<Uint8Array>> {
  let handle: FileSystemFileHandle

  if (dirHandle) {
    // 既にディレクトリ権限がある場合、直接ファイルを作成
    handle = await dirHandle.getFileHandle(filename, { create: true })
  } else {
    // ポップアップでユーザーに保存場所を選択させる
    handle = await window.showSaveFilePicker!({
      suggestedName: filename,
      types: [{
        description: 'Video',
        accept: { 'video/mp4': ['.mp4'] }
      }]
    })
  }

  const writable = await handle.createWritable()

  return new WritableStream<Uint8Array>({
    async write(chunk) { await writable.write(chunk) },
    async close() { await writable.close() },
    async abort(reason) { await writable.abort(reason) }
  }, {
    highWaterMark: 16 * 1024 * 1024 // 16MB バッファ
  })
}

メリット

  • ストリーミング書き込み、メモリ消費量一定(バッファ 16MB のみ)
  • 任意サイズのファイルをサポート
  • ディレクトリ永続化後、繰り返しポップアップ不要

制限

  • Chrome 86+ および Edge 86+ のみサポート
  • ユーザージェスチャーが必要(showDirectoryPicker はユーザークリックイベント内で呼び出す必要がある)

戦略2:StreamSaver.js(代替案)

async function createStreamSaverStream(
  filename: string,
  fileSize?: number
): Promise<WritableStream<Uint8Array>> {
  const ss = await getStreamSaver()
  const fileStream = ss.createWriteStream(filename, { size: fileSize })
  return fileStream
}

メリット

  • ストリーミング書き込み、メモリ消費量低い
  • FSA API より互換性が高い

制限

  • Service Worker サポートが必要
  • mitm.htmlstreamsaver-sw.js が正しくデプロイされている必要がある
  • 一部の企業ネットワーク環境で Service Worker がブロックされる可能性がある

戦略3:Blob 兜底

// 疑似コード
const blob = new Blob([mergedData], { type: 'video/mp4' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)

制限

  • ファイル全体をメモリに読み込む
  • 硬上限 1.5GB(MEMORY_CONFIG.maxBlobSize
  • 800MB 超過時にコンソール警告(MEMORY_CONFIG.warnBlobSize

戦略比較まとめ

次元FSA APIStreamSaver.jsBlob
メモリ消費16MB(一定)低(ストリーミング)ファイルサイズと同等
ファイルサイズ上限制限なし制限なし1.5GB
ブラウザ要件Chrome/Edge 86+Service Worker サポートすべてのブラウザ
ディレクトリ永続化サポート非サポート非サポート
ダウンロード体験最良良好一般
各ブラウザでのこれら3つの戦略のサポート状況については、ブラウザ互換性 — 機能フォールバック戦略 を参照してください。オンラインツールでの書き込み戦略の使用については、オンラインツール — 書き込み戦略優先順位 を参照してください。Blob モードのサイズ制限と解決策については、既知の制限 — ファイルサイズ制限 を参照してください。

メモリセキュリティ管理

サイズ見積もり

ダウンロード開始前、エンジンはサンプリングで総ファイルサイズを見積もります:

async function estimateSegmentsSize(
  segments: ArrayBuffer[] | AsyncGenerator<ArrayBuffer>,
  totalCount: number
): Promise<{ estimatedSize: number; isEstimate: boolean }> {
  // サンプリング数:min(5, max(1, totalCount * 0.1))
  const sampleCount = Math.min(5, Math.max(1, Math.floor(totalCount * 0.1)))

  // 最初の数セグメントをダウンロードして平均サイズを計算
  let sampledTotal = 0
  for (let i = 0; i < sampleCount; i++) {
    sampledTotal += sampleSegment.byteLength
  }

  const avgSample = sampledTotal / sampleCount
  return {
    estimatedSize: Math.round(avgSample * totalCount),
    isEstimate: true
  }
}

サンプリング戦略説明

  • サンプリング数は min(5, max(1, セグメント総数 × 10%)) を取り、小さいストリームは少なくとも1個、大きいストリームは最大5個をサンプリング
  • サンプリング結果は isEstimate: true とマークされ、UI 層はこれに基づき「約 XX MB」と表示(正確な値ではなく)
  • HLS ストリームの場合、Master Playlist で BANDWIDTH 属性が宣言されている場合、エンジンはその値を優先的に見積もり参考として使用

戦略選択ロジック

見積もりサイズ < 800MB  → 任意の戦略を使用可能
見積もりサイズ 800MB-1.5GB → ストリーミング書き込み優先、Blob モードで警告表示
見積もりサイズ > 1.5GB → 強制的にストリーミング書き込み、Blob モード拒否

メモリ設定定数

const MEMORY_CONFIG = {
  maxBlobSize: 1500 * 1024 * 1024,    // 1.5GB 硬制限
  warnBlobSize: 800 * 1024 * 1024,    // 800MB 警告閾値
}

ランタイムメモリ監視

ダウンロード前の見積もりに加え、エンジンは実行中も継続的にメモリプレッシャーを監視します:

const checkMemoryPressure = (): 'normal' | 'warning' | 'critical' => {
  // 現在割り当て済みのセグメントバッファ合計サイズを確認
  const allocatedMB = allocatedBuffers.reduce((sum, buf) => sum + buf.byteLength, 0) / 1048576

  if (allocatedMB > 1200) return 'critical'
  if (allocatedMB > 600) return 'warning'
  return 'normal'
}

メモリプレッシャーが critical レベルに達した場合、エンジンは以下を行います:

  • 新しいセグメントダウンロードリクエストを一時停止
  • 既にダウンロードされたセグメントのディスクへの書き込みを優先
  • 書き込み完了済みのセグメントバッファを解放
メモリセキュリティは FlowPick ダウンロードエンジンの中核的な設計考慮事項の1つです。超大容量ファイル(20GB+ のライブ配信アーカイブなど)の場合、エンジンは強制的にストリーミング書き込み戦略を使用し、ブラウザがメモリ不足でクラッシュしないように保証します。大容量ファイルダウンロードの実際のシーンについては、ライブ配信アーカイブ保存 を参照してください。大容量ファイルダウンロード失敗のトラブルシューティングについては、よくある問題のトラブルシューティング — 大容量ファイルダウンロード失敗 を参照してください。

進捗トラッキング

ダウンロード進捗はリアルタイムで UI に報告され、以下の指標が含まれます:

進捗データ構造

interface DownloadProgress {
  percentage: number           // 完了パーセント (0-100)
  downloadedBytes: number      // ダウンロード済みバイト数
  totalBytes: number           // 推定総バイト数
  speed: number                // 現在の速度 (bytes/sec)
  eta: number                  // 推定残り時間 (秒)
  currentPhase: DownloadPhase  // 現在のフェーズ
}

type DownloadPhase =
  | 'parsing'      // マニフェスト解析中
  | 'downloading'  // セグメントダウンロード中
  | 'processing'   // セグメント処理(復号/マージ)中
  | 'writing'      // ファイル書き込み中
  | 'completed'    // 完了
  | 'error'        // エラー発生
  | 'cancelled'    // ユーザーによりキャンセル

進捗計算方法

パーセンテージ計算

全体パーセンテージ = (現在のフェーズ基本値 + フェーズ内進捗) / 総フェーズ重み

フェーズ重み:
- 解析:5% (0% ~ 5%)
- ダウンロード:80% (5% ~ 85%)
- 処理・書き込み:15% (85% ~ 100%)

速度計算

const calculateSpeed = (
  downloadedBytes: number[],
  windowSize: number = 10
): number => {
  if (downloadedBytes.length < 2) return 0

  const recent = downloadedBytes.slice(-windowSize)
  const timeDiff = (recent.length - 1) * intervalMs
  const bytesDiff = recent[recent.length - 1] - recent[0]

  return bytesDiff / timeDiff
}

スライディングウィンドウ(デフォルト10サンプル)を使用して瞬間的な速度変動を平滑化します。

ETA 推定

const calculateETA = (
  remainingBytes: number,
  speed: number
): number => {
  if (speed <= 0) return Infinity
  return remainingBytes / speed
}

UI 更新頻度

進捗イベントの発火頻度:

条件発火間隔
通常時500ms ごと
最終フェーズ(90%+)200ms ごと
完了/エラー即座

過度な UI 更新を避け、レスポンシブなフィードバックを保証します。

進捗トラッキングシステムはイベント駆動型アーキテクチャを採用しています。UI コンポーネントは進捗イベントを購読し、必要に応じて再レンダリングします。UI とエンジンのインタラクションの詳細については、プロジェクトアーキテクチャ — イベントシステム を参照してください。

エラーハンドリング

ダウンロードエンジンは多層エラーハンドリングメカニズムを実装しています:

エラーレベル

enum ErrorLevel {
  Warning = 'warning',    // 回復可能、継続可能
  Error = 'error',        // 回復不能、再試行可能
  Fatal = 'fatal'         // 致命的、即座に停止
}

エラーカテゴリー

カテゴリーレベル対処
ネットワークエラータイムアウト、接続切断Warning自動リトライ
HTTP エラー403、404、5xxError上位層に報告
復号エラーキーダウンロード失敗Error中止して報告
メモリエラーメモリ不足Fatal直ちに停止
ユーザーキャンセル-Info正常終了

エラー伝播チェーン

低層(Worker)
  ↓ FetchError / CryptoError
中層(セグメント処理)
  ↓ SegmentError / MergeError
高層(エンジンコーディネーター)
  ↓ DownloadError
UI 層
  ↓ ユーザーにフレンドリーなプロンプトを表示

各層でエラーをキャッチし、コンテキスト情報を付加して上位層に伝播させます。


パフォーマンス最適化

並列チューニング

最適な並列数は環境によって異なります:

環境推奨並列数理由
高速ネットワーク(100Mbps+)6-8ネットワーク帯域幅を最大活用
一般ネットワーク(10-50Mbps)4-6バランスの取れた負荷
低速ネットワーク(<10Mbps)2-4過負荷回避
CDN 制限のあるサイト2-3レート制限回避
並列数は 設定リファレンス で調整できます。自動調整機能は将来のバージョンで予定されています。

メモリ最適化

ストリーミング処理

  • FSA または StreamSaver 使用時、メモリ消費は 16MB バッファに固定
  • Blob モードのみ全ファイルをメモリに保持

早期書き込み

  • セグメント処理完了後、即座にディスクに書き込み
  • バッファを循環使用し、ピークメモリを抑制

GC フレンドリー

  • 大きな ArrayBuffer を使用後、参照を解除して GC を促進
  • Transferable Objects を使用してゼロコピ転送

デバッグと診断

ログレベル

enum LogLevel {
  Debug = 0,    // 開発者向け詳細情報
  Info = 1,     // 通常操作ログ
  Warn = 2,     // 注意が必要な状況
  Error = 3,    // エラー発生
  None = 4      // ログ無効
}

開発者ツール統合

Chrome DevTools Console で詳細ログを有効にする:

// コンソールで実行
localStorage.setItem('flowpick_debug', 'true')
ページをリロード

有効化すると、以下のログが出力されます:

[FlowPick] [Engine] Initializing with strategy: fsa
[FlowPick] [Engine] Manifest parsed: 180 segments, estimated 450MB
[FlowPick] [Download] Starting worker pool with concurrency: 6
[FlowPick] [Download] Segment 42/180 downloaded (23%), speed: 2.5 MB/s
[FlowPick] [Merge] Using TSToMP4Muxer for streaming remux
[FlowPick] [Engine] Download completed: 450MB in 185s

パフォーマンスプロファイリング

Performance API を使用して各フェーズの所要時間を計測:

const perfMarks = [
  'engine:init',
  'manifest:parse',
  'segments:download:start',
  'segments:download:end',
  'segments:process:start',
  'segments:process:end',
  'file:write:start',
  'file:write:end'
]

Chrome DevTools → Performance パネルでタイムラインを視覚化できます。


関連ドキュメント