Upgrade to Pro — share decks privately, control downloads, hide ads and more …

複雑なライブ配信ロジックをステートマシンでシンプルにした話

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 複雑なライブ配信ロジックをステートマシンでシンプルにした話

Avatar for Naka

Naka

May 25, 2026

Other Decks in Programming

Transcript

  1. © DMM 自己紹介 2 仲 純平 合同会社DMM.com オンラインサロン開発部 iOSエンジニア •

    DMMオンラインサロン や SALON LIVE の開発 • 完全初心者からピアノを始めました
  2. © DMM SALON LIVE とは Protocol RTMP Library HaishinKit (Swift製

    OSS) 複雑な状態遷移が特徴 接続・切断・再接続・品質変更・バックグラウンド 復帰など、状態が頻繁に切り替わる 4 SALON LIVE DMMオンラインサロンのオーナー専用ライブ配信アプリ トーク レッスン Q&A オーナーから会員に配信 など
  3. © DMM 配信レイヤーの構造 UI LAYER StreamViewController (配信画面) LOGIC LAYER RTMPCoordinator

    RTMPClient StreamConfigurator CORE LAYER HaishinKit 旧 RTMPManager RTMPCoordinator 配信開始ボタンひとつの裏で 膨大な状態を制御する司令塔  複雑なロジックがここに集約されます 5 旧 RTMPManager
  4. © DMM きっかけ: HaishinKit メジャーアップデート 2.x 主要な変更点 ライブラリの抜本的刷新 • モジュールの分割

    HaishinKit / RTMPHaishinKit • 中核クラスの actor 化 RTMPConnection / RTMPStream 等 • Swift Concurrency 前提 非同期処理の安全性が必須に 配信制御の暗黙の前提が次々と露わに ライブラリの形が変わることで、 自分たちのコードの複雑さが浮き彫りに。 6
  5. © DMM 既存コードの "つらさ":旧 RTMPManager 536行 / 1クラスに集中 3つのつらさ ①

    状態が暗黙的 ブール値の組合せで状態が散らばり、現在のフェーズが判別不能 connection.connected + host/key + previousObserverStatus + reconnectTimer の組み合わせでしか分からない ② 並行イベントが読めない 複数の経路から再接続が走り、どの処理が本流か分からない 再接続が最低4経路から走る(ネットワーク復帰 / 3秒Timer / IOエラー / publish リトライ) ③ 責務の過剰な混在 UI通知、配信操作、トラッキング、解放処理が密結合 7
  6. © DMM Router の設計 Router (純粋関数) Logic Formula (Event, State)

    -> State? • 遷移の「表」として定義 どのイベントで次にどの状態へ行くか のみを決定 • 副作用の分離 実際の処理(API実行等)はここには 書かない 12
  7. © DMM 副作用 13 // RTMPCoordinator — 状態に応じた副作用 func executeTask(state:

    State, emit: @escaping (Event) -> Void) async { switch state { case .preparing: await prepare(emit: emit) // カメラ・ミキサー初期化 case .liveStreaming(_, _, _, .connecting(.handshaking)): await handshake(emit: emit) // RTMP接続 case .liveStreaming(_, _, _, .publishing): await withTaskGroup(of: Void.self) { group in group.addTask { await self.observeNetwork(emit: emit) } // ネットワーク状態監視 group.addTask { await self.observeRTMPConnection(emit: emit) } // RTMP接続状態監視 // … } case .liveStreaming(_, _, _, .disconnecting): await disconnect(emit: emit) // 切断 default: break } }
  8. © DMM Before / After:何が変わったか フラグベースの暗黙的な管理 • ブール値の組合せによる状態散在 • 散在したif文/switch文の遷移分岐

    • 処理全体に分散した副作用の実行 • コールバック地獄による並行処理 • 状態遷移のユニットテストが困難 ステートマシンによる一意な管理 • 階層 enum による状態の一意な定義 • ルーターの表として読める遷移フロー • 副作用を一つのメソッドに完全集約 • withTaskGroup による明示的並行処理 • 純粋関数化により 500行超のテストを完備 14 Before After
  9. © DMM 学び・今後の展望 Learn 01 破壊的変更を 好機と捉える ライブラリの破壊的変更は、既存設 計の負債を解消し、抜本的に作り直 す絶好のきっかけとなります。

    Learn 02 enum による 状態の可視化 状態を enum で明示的に定義するこ とで、議論やレビューが図解ベース で進められるようになり、開発効率 が向上します。 Learn 03 純粋関数と テストの親和性 ルーターを純粋関数として切り出す ことで、ユニットテストによる品質担 保が容易になり、堅牢な基盤を構築 できます。 今回の再設計を活かし、「読める・テストできる・壊れにくい」強固な配信レイヤーを追求していきます。 15
  10. © DMM まとめ HaishinKit のメジャーアップデートを機に、 配信ロジックを State・Event・Router と 汎用 StateMachine

    で再設計した 結果として、 「読める・テストできる・壊れにくい」 強固な配信レイヤーを実現 16