◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-05上級

Rork のポッドキャストアプリを本番運用へ — expo-audio への移行・割り込みからの復帰・ダウンロード再開

Rork のポッドキャストアプリを実運用に耐えさせるための実装メモ。SDK 55 で消える expo-av からの移行、着信割り込みからの復帰、中断されたダウンロードの再開、iCloud バックアップと容量予算の設計まで、動くコードで扱います。

ポッドキャストバックグラウンド再生4expo-audio4expo-av5オフライン3Rork584React Native239

✦ プレミアム記事

ポッドキャストアプリを Rork で作ろうと決めたとき、最初に「あれ、思っていたより難しい」と感じた方は少なくないのではないでしょうか。音楽プレイヤーとの違いは、RSS フィードを解析してエピソードを動的に取得しなければならない点、そしてアプリをバックグラウンドに下げた後も音声が途切れてはならない点にあります。

壁紙アプリや健康系アプリを長年作ってきた経験のなかで、バックグラウンド再生まわりの設定ミスが原因で TestFlight のフィードバックに「画面を消すと止まる」という報告を受けたことがあります。原因は些細な設定漏れでした。ここでは同じ轍を踏まないための実装パターンを、動作するコードとともにお伝えします。

完成形から逆算してアーキテクチャを決める

コードを書き始める前に、作るアプリの全体像を決めておくことをおすすめします。行き当たりばったりで実装すると、後から状態管理が破綻しがちです。

今回構築するポッドキャストアプリの機能構成は以下のとおりです。

  • フィード管理: RSS URL を登録してエピソード一覧を取得・表示
  • 再生機能: バックグラウンド再生・シークバー・再生速度変更(0.75x〜2x)
  • ダウンロード: エピソードをデバイスに保存してオフライン再生
  • ロック画面対応: iOS Control Center・Android メディアコントロールに再生情報を表示

状態管理には Zustand を使います。Context API でも構いませんが、音声再生のような「グローバルで唯一の状態」を管理する場面では、Zustand のシンプルさが際立ちます。

ライブラリ構成は次のとおりです。

# 必要なパッケージをインストール
npx expo install expo-av expo-file-system expo-media-library
npm install zustand react-native-xml2js date-fns

データ構造の定義

TypeScript の型定義から始めると、後の実装が格段に楽になります。

// types/podcast.ts
 
export interface Episode {
  id: string;           // RSS の guid をそのまま使う
  title: string;
  description: string;
  audioUrl: string;
  pubDate: Date;
  duration: number;     // 秒数
  imageUrl?: string;
  localPath?: string;   // ダウンロード済みの場合はローカルパス
}
 
export interface PodcastFeed {
  id: string;
  title: string;
  description: string;
  imageUrl: string;
  rssUrl: string;
  episodes: Episode[];
  lastUpdated: Date;
}
 
export interface PlayerState {
  currentEpisode: Episode | null;
  isPlaying: boolean;
  position: number;     // 秒数
  duration: number;     // 秒数
  playbackRate: number; // 0.75 / 1.0 / 1.25 / 1.5 / 2.0
}

この型定義を最初に固めておくことで、Rork の AI にコードを生成させるときも「この型に沿った実装を」と伝えるだけで、期待に近いコードが出てきやすくなります。

expo-av のセットアップとバックグラウンド再生の落とし穴

バックグラウンド再生で最もよく見る失敗は、app.json の設定と Audio.setAudioModeAsync の両方が必要なことを知らないケースです。どちらか一方しか設定していないと、iOS ではアプリが非アクティブになった瞬間に再生が止まります。

app.json の設定

{
  "expo": {
    "plugins": [
      [
        "expo-av",
        {
          "microphonePermission": "ポッドキャストのメモ録音機能に使用します"
        }
      ]
    ],
    "ios": {
      "infoPlist": {
        "UIBackgroundModes": ["audio"]
      }
    }
  }
}

UIBackgroundModes に "audio" を追加することで、iOS がこのアプリをバックグラウンドでも音声を再生できるアプリとして認識します。この設定がないと、staysActiveInBackground: true を設定しても効果がありません。

Audio セッションの初期化

// hooks/useAudioSession.ts
import { Audio } from 'expo-av';
import { useEffect } from 'react';
 
export function useAudioSession() {
  useEffect(() => {
    const initAudioSession = async () => {
      try {
        await Audio.setAudioModeAsync({
          // バックグラウンドでも再生を継続する(iOS 必須設定)
          staysActiveInBackground: true,
          // サイレントモードでも再生する
          playsInSilentModeIOS: true,
          allowsRecordingIOS: false,
          // Android: 音量を下げる(ダック)ではなく完全に停止する
          shouldDuckAndroid: false,
          interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
          interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
        });
        console.log('Audio session initialized');
      } catch (error) {
        // Audio セッション初期化失敗は致命的エラーではないため、
        // ログに残してアプリは継続させる
        console.error('Failed to initialize audio session:', error);
      }
    };
 
    initAudioSession();
  }, []);
}

このフックをルートコンポーネント(app/_layout.tsx)で呼び出します。アプリ起動時に一度だけ実行すれば十分です。

Android では shouldDuckAndroid: false が重要です。true にすると、他のアプリが音声を再生したとき(例: 着信音)に音量が下がるだけで再生が続きます。ポッドキャストアプリとしては、他の音声が割り込んだときは一時停止する方が自然な動作です。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦SDK 55 で削除される expo-av から expo-audio へ移す対応表と、Android で interruptionMode が効かない既知の制約
✦中断されたダウンロードを resumeData で再開する実装。完了判定にファイルの存在を使わない理由と HTTP 206 の扱い
✦着信・イヤホン抜去・容量逼迫という3つの割り込みに耐える設計と、実機で1分ずつ試せる検証5項目
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ 開発ツール2026-08-31
画面ロックから3分後、expo-audio の環境音は静かに止まっていました
expo-audio の音が画面ロックで止まる原因を、ビルド設定・オーディオセッション・ロック画面連携の三層に分けて切り分けます。Android で約3分後に止まるのは、ドキュメントに書かれた止まり方です。
⬡ 開発ツール2026-09-04
EAS の secret は「アプリに入れない」設定ではありません — 接頭辞と可視性を別々に決める
EXPO_PUBLIC_ の接頭辞は成果物に入るかを決め、EAS の可視性は誰が読めるかを決めます。二つを重ねたときに OTA 更新でだけ値が消える筋道と、出荷前に自分の手で確かめる手順をまとめました。
⬡ 開発ツール2026-08-22
一括置換は全ファイル成功しました。壊れたのは、消さなかった行のほうです
生成コードに一括置換をかけると、一致した行ではなく隣の行が壊れます。運用中のプロジェクトで実際に起きた破損と、置換の不変条件を検査する20行のガードスクリプトをまとめました。