何を解決するものか
作業中や就寝前に「とりあえず流しておける音」がほしいとき、YouTube を開くと広告が入り、関連動画に気を取られます。Felody はカテゴリと時間を選ぶだけで、自作のBGMを指定時間だけ流し切るサービスです。
relax(lofi / nature / ambient / sleepy)と upbeat(jazz / funk / pop rock / edm)の2ムード・8カテゴリを用意し、Basic は1日30分、Premium は無制限で利用できます。
担当範囲
企画・UI設計・実装・DB設計・決済導線・インフラ・運用のすべて。
技術的に難しかった点と、どう解いたか
1. 音声ファイルを「直リンクさせずに」配信する
課金コンテンツなので、Storage の公開URLをそのまま渡すわけにはいきません。Supabase Edge Function 経由で都度 Presigned URL を発行し、クライアントには期限付きURLだけを渡す形にしました。
発行が失敗すると無音になって体験が壊れるため、1s → 2s → 3s の間隔で3回リトライしています。
2. ブラウザによって音が鳴らない
Howler.js は既定で Web Audio API を使いますが、環境によっては初期化に失敗します。失敗を検知したら HTML5 Audio へフォールバックする二段構えにして、鳴らない端末をなくしました。
加えて、UI上の状態(再生中/停止中)と実際の再生状態がずれることがあったため、5秒間隔で両者を突き合わせて補正するチェックを入れています。「押したのに反応しない」は離脱に直結するので、ここは厚めに作りました。
3. 「1日30分」をどう正確に数えるか
再生のたびにサーバーへ書くと負荷もコストも無駄になります。60秒間隔でまとめて記録し、30秒のスロットルで重複送信を防ぐ方式にしました。日付が変わったかどうかは last_reset_date を見て、ログイン時に判定してリセットします。
4. 決済とアプリ側の状態を食い違わせない
Stripe の checkout.session.completed と customer.subscription.deleted を Webhook で受け、プラン状態を DB に同期しています。DB 書き込みはすべてサーバー側(Service Role Key)に閉じ、クライアントには Anon Key しか渡していません。
学んだこと / 次にやること
課金がある以上、「失敗したときにどう振る舞うか」がそのまま品質になります。リトライ・フォールバック・状態の突き合わせは、機能そのものより時間をかける価値がありました。
次は Player コンポーネント(約1,300行)の分割と、テストの導入を進めています。