◉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-07-15上級

Rorkの癒しアプリにスリープタイマーを付けたら、フェードアウトが実際の音とズレた話

Rorkが生成するスリープタイマーは、壁時計のsetIntervalで音量を下げます。数値上は30分でも、実際の音はもっと早く消えたり不自然に途切れたりします。フェードを再生位置で駆動し直す実装と、対数カーブ・停止処理・画面ロック時の限界までを実運用の視点でまとめました。

Rork583expo-av5スリープタイマーReact Native239音声処理癒しアプリ

✦ プレミアム記事

個人開発で Relaxing Healing という癒し系アプリを長く運営していると、「音が消える瞬間」の設計がアプリの印象を大きく左右することを何度も実感してきました。眠りに落ちる直前、音がプツッと切れれば人は目を覚まします。だからこそスリープタイマーのフェードアウトは、地味ですが最後の一手として重要な機能です。

先日、新しい癒しアプリの試作をRorkで進めていたとき、このスリープタイマーで数時間つまずきました。シミュレータでは30分後にきれいに音が消えていくのに、実機で試すと「思ったより早く音が小さくなる」「最後の数秒だけ急に途切れる」という違和感が残ります。原因を追ってみると、Rorkが生成したコードには音声フェード特有の落とし穴がいくつも重なっていました。同じジャンルのアプリを作る方が必ずぶつかる部分なので、直し方を整理しておきます。

Rorkが生成するスリープタイマーの典型形

「30分後に音量をゆっくり下げて停止するスリープタイマーを付けて」とRorkに依頼すると、多くの場合こういうコードが返ってきます。

// Rorkが生成しやすいパターン
const FADE_MS = 30_000; // 最後の30秒でフェード
const TICK_MS = 200;
 
function startSleepTimer(sound, totalMinutes) {
  const stopAt = Date.now() + totalMinutes * 60_000;
 
  const id = setInterval(async () => {
    const remaining = stopAt - Date.now();
    if (remaining <= 0) {
      await sound.stopAsync();
      clearInterval(id);
      return;
    }
    if (remaining <= FADE_MS) {
      const volume = remaining / FADE_MS; // 1.0 → 0.0
      await sound.setVolumeAsync(volume);
    }
  }, TICK_MS);
 
  return id;
}

一見すると正しく見えます。Date.now() で残り時間を測り、フェード区間に入ったら線形に音量を下げる。動かすと確かに音は小さくなっていきます。けれど、このコードは3つの層で実際の音とズレます。

壁時計で測ると、なぜ実際の音とズレるのか

第一の問題は、setInterval が壁時計(wall clock)を基準にしていることです。setInterval(fn, 200) はぴったり200msごとには呼ばれません。JSスレッドが画像のデコードやリスト描画で忙しければ、200msの間隔は250msにも400msにも伸びます。フェードのように「なめらかさ」が命の処理では、この揺らぎがそのまま音量のガタつきになります。

第二に、sound.setVolumeAsync() は非同期です。200msごとに await を挟むと、ネイティブ側への反映が詰まったときにコールバックが積み上がり、音量の更新が飛び飛びになります。

そして第三に、最も見落とされやすいのが「音声の再生位置」と「壁時計」が同じ速度で進むとは限らないことです。オーディオセッションが割り込み(電話・他アプリの音)から復帰したとき、あるいはループの継ぎ目で僅かに再バッファが起きたとき、実際に鳴っている音の位置は壁時計から遅れます。壁時計だけを見ていると、音はまだ鳴っているのにタイマーが先に「終わり」と判断し、フェードの途中で急に切れてしまうのです。

観点壁時計 setInterval再生位置ドリブン
基準にする時間Date.now()実際の再生位置 positionMillis
JSが重いとき間隔が伸びて音量がガタつく次の更新で正しい音量に追従
割り込み復帰後音と時間がズレて途中で切れる音の位置に同期し続ける
フェードの決定性累積誤差で毎回ぶれる位置から一意に決まる
✦

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

この記事の続きを読む

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

この記事で得られること
✦Rorkが吐くsetIntervalフェードがなぜ実際の音とズレるかを理解し、再生位置で駆動する正しい実装に置き換えられる
✦線形に音量を下げると不自然に速く消えて聞こえる理由と、対数カーブで自然なフェードにする具体的な計算を手に入れられる
✦画面ロック中はJSのフェードが止まるという限界を正しく知り、事前フェード済みのテール音源で確実に消す現実解を選べる
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

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

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

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

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

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

関連記事

⬡ 開発ツール2026-05-05
Rork のポッドキャストアプリを本番運用へ — expo-audio への移行・割り込みからの復帰・ダウンロード再開
Rork のポッドキャストアプリを実運用に耐えさせるための実装メモ。SDK 55 で消える expo-av からの移行、着信割り込みからの復帰、中断されたダウンロードの再開、iCloud バックアップと容量予算の設計まで、動くコードで扱います。
⬡ 開発ツール2026-09-04
EAS の secret は「アプリに入れない」設定ではありません — 接頭辞と可視性を別々に決める
EXPO_PUBLIC_ の接頭辞は成果物に入るかを決め、EAS の可視性は誰が読めるかを決めます。二つを重ねたときに OTA 更新でだけ値が消える筋道と、出荷前に自分の手で確かめる手順をまとめました。
⬡ 開発ツール2026-08-22
一括置換は全ファイル成功しました。壊れたのは、消さなかった行のほうです
生成コードに一括置換をかけると、一致した行ではなく隣の行が壊れます。運用中のプロジェクトで実際に起きた破損と、置換の不変条件を検査する20行のガードスクリプトをまとめました。