プロジェクト

全般

プロフィール

機能 #181

完了

機能 #162: OBS連携機能

【OBS連携】Beatoraja本体側の機能

機能 #181: 【OBS連携】Beatoraja本体側の機能

いづ さん さんが24日前に追加. 22日前に更新.

ステータス:
完了
優先度:
通常
担当者:
対象バージョン:
開始日:
2026/06/12
期日:
進捗率:

100%

予定工数:

説明

親: #162

担当範囲

OBS 連携のうち beatoraja 本体側。状態の収集・保持と、ローカル HTTP サーバによる配信を担う。

設計

コンポーネント

  • ObsState — スレッドセーフな現在状態の保持。更新時に不変スナップショット(または完成済み JSON 文字列)を生成し volatile 参照に差し替える方式で、ゲームスレッド/HTTP スレッド間のロックを不要にする。
  • SessionStats — セッション統計。MainController 既存の boottime を起点に起動時初期化し、リザルト到達時に加算。
  • ObsHttpServercom.sun.net.httpserver.HttpServer を利用。スレッドは既存 RuntimeTaskManager.startVirtual(...)StreamController と同じ流儀)で回す。

接続点

  • MainController.changeState(MainStateType)(MainController.java:248) を唯一の更新funnelとして利用。全画面遷移がここを通るため、状態クラスを侵食せず最小改変で phase(select / playing / result)と曲情報・スコアを ObsState に反映できる。
  • 曲情報/スコアの取得元は PlayerResource(model / songdata / score を保持)。

/state.json スキーマ(暫定)

判定済みの表記文字列で渡し、HTML 側は流し込むだけにする方針。

{
  "phase": "select | playing | result",
  "nowPlaying": {
    "title": "...", "artist": "...", "genre": "...",
    "level": "★12", "mode": "7K",
    "bpm": "150-200", "notes": 1234,
    "bestLamp": "HARD", "bestScore": "AAA 92.3%"
  },
  "lastResult": {
    "title": "...", "lamp": "FULLCOMBO", "rank": "AAA",
    "exscore": 2345, "diff": "+58", "updated": true
  },
  "session": {
    "playCount": 14, "clearCount": 11,
    "startedAt": "2026-06-12T20:00:00", "newRecords": 3
  }
}

設定

  • ConfigobsEnabled / obsPort(既定値の例: 7322)を追加。
  • 設定画面への追加も実施。
  • バインドは localhost 固定。

配置

  • 新規パッケージ案: bms.player.beatoraja.stream.obs(既存 stream パッケージと同系統)。
  • 静的ファイル(#182 の HTML)は同サーバから配信する。

関連するチケット 1 (1件未完了0件完了)

関連している Ferrhythm - 機能 #369: 【OBS連携】Beatoraja本体側の機能の移植未着手いづ さん2026/06/21

操作

エージェント ちゃん さんが24日前に更新 操作 #1

いづ さん さんが24日前に更新 操作 #2

エージェント ちゃん さんが23日前に更新 操作 #3

追加仕様 詳細設計(2026-06-13)本体側

#162 に対する追加表示要素の本体側設計。既存の「MainController.changeState() を唯一の更新funnelとする」方針を基本踏襲しつつ、GAS によるゲージ種別のプレイ中変化だけは追従用の副経路を追加する。

追加する表示要素と取得元

要素 取得元 更新タイミング
ランダムオプション(1P) BMSPlayer.getOptionInformation().randomoptionRandom.getRandom(id, mode) 経由で名称化 changeState→PLAY(静的)
ランダムオプション(2P/DP) randomoption2(DP/バトル時のみ) 同上
ゲージオプション(現在値) BMSPlayer.getGauge().getType() を名称化 初期: changeState、以後: GAS変化時
GAS On/Off・モード PlayerConfig.getGaugeAutoShift()GAUGEAUTOSHIFT_NONE=Off) changeState(静的)
ランダムレーンパターン BMSPlayer.getOptionInformation().laneShufflePattern[0](RANDOM/R-RANDOM時のみ) changeState→PLAY(静的)
プレーヤー名 PlayerConfig.getName() 起動時/常時
取得段位 新規 PlayerConfig フィールド(手動設定文字列) 起動時/常時
一言(目標など) 新規 PlayerConfig フィールド 起動時/常時

取得段位・一言はユーザー判断により「手動設定の文字列を PlayerConfig に保持しランチャーUIで設定」とする。

重要:GAS のリアルタイム更新(既存funnelの限界)

GAS=on 時、ゲージ種別は BMSPlayer のプレイループ内(BMSPlayer.java:643-677 の GAS 処理ブロック)で gauge.setType(...) により切り替わる。これは changeState() を通らないため、既存の「changeState のみで ObsState を更新」方式では追従できない。

対応方針:

  • ObsState に in-play 用の軽量更新 API(例: updatePlayGauge(int gaugeType))を追加。playing フェーズのスナップショットのうちゲージ表記だけを差し替え、volatile 参照を入れ替える。
  • BMSPlayer 側に直近反映済みのゲージ種別を保持し、GAS 処理直後に 種別が変化したときのみ ObsState を更新(毎フレーム呼ばない/obsEnabled でガード)。
  • スレッド安全性は維持: changeState()BMSPlayer.update() も同じゲームスレッドから呼ばれるため単一ライタ。HTTP スレッドは volatile スナップショットを読むだけでロック不要、という #181 の前提を崩さない。

レーンパターン(仕様C)は laneShufflePattern がプレイ開始時に確定し以後不変のため副経路は不要。changeState(PLAY)BMSPlayer 生成(MainController.java:263)後にスナップショットへ格納する。

/state.json スキーマ拡張

profile(全phase共通・常時)を追加し、nowPlayingplayOption を追加する。表記済み文字列で渡し、HTML 側は流し込むだけ、の方針は維持。

{
  "phase": "select | playing | result",
  "profile": {
    "name": "izu",
    "dan": "発狂八段",          // 手動設定 (PlayerConfig)
    "comment": "目標:皆伝"      // 手動設定 (PlayerConfig)
  },
  "nowPlaying": {
    // ...既存フィールド...
    "playOption": {
      "random": "R-RANDOM",      // 1P
      "random2": null,           // DP/バトル時のみ
      "gauge": "HARD",           // ★GASでライブ更新される現在のゲージ
      "gaugeAutoShift": true,    // On/Off
      "gaugeAutoShiftMode": "BEST CLEAR",
      "lane": "3 1 6 4 2 7 5"    // RANDOM/R-RANDOM時のみ。他はnull
    }
  },
  "lastResult": { /* 既存 */ },
  "session": { /* 既存 */ }
}

表記の整形(本体側で確定)

既存配列・enum を流用する。

  • ランダム名: {OFF, MIRROR, RANDOM, R-RANDOM, S-RANDOM, SPIRAL, H-RANDOM, ALL-SCR, RANDOM-EX, S-RANDOM-EX}PlayConfigurationViewscoreOptions と同一インデックス。R-RANDOM = Random.ROTATE)。PMS(9K) は OPTION_PMS 準拠。
  • ゲージ名: {ASSIST EASY, EASY, NORMAL, HARD, EX-HARD, HAZARD, CLASS, EX-CLASS, EXHARD-CLASS}GrooveGauge の type 定数 0–8 に対応。コース/段位プレイの CLASS 系も含めて網羅)。
  • GAS モード名: {NONE, CONTINUE, SURVIVAL TO GROOVE, BEST CLEAR, SELECT TO UNDER}GAUGEAUTOSHIFT_* 0–4)。
  • レーンパターン: IntegerIndexPropertyTypes.getAssignedLane()(#180)と同等のロジックで laneShufflePattern を整形(割当元レーン番号をスペース区切り)。#180 と重複するため共通ヘルパへの切り出しを推奨。RANDOM/R-RANDOM(=ROTATE) のみ対象(spec準拠。EX/CROSS 系は将来拡張余地)。

設定(Config / PlayerConfig)

  • PlayerConfigString dan(取得段位)と String obsComment(一言)を追加。getter/setter と PlayerConfigPersistenceDto への追加、デフォルト空文字。
  • ランチャー PlayConfigurationView(プレイヤー設定タブ)にテキスト入力欄2つを追加。FXML・コントローラのバインド・リソースバンドルのキーを同期。
  • obsEnabled / obsPort(既定 7322)は本チケット既存設計のまま。

配置

  • 既存設計どおり bms.player.beatoraja.stream.obsObsState ほかを配置。整形ヘルパ(ランダム名/ゲージ名/GAS名/レーンパターン)も同パッケージへ。

動作確認の想定

7K 譜面を RANDOM + GAS=BEST CLEAR でプレイし、http://localhost:7322/now-playing.html を開く。ランダム=RANDOM、レーンパターン=置換列、ゲージが初期 HARD から GAS 発動で NORMAL/EASY 等へ1秒ポーリングでライブに変化、GAS バッジ On、プロフィールに名前/段位/一言が表示されることを確認する。

本コメントは詳細設計であり実装着手前。ステータスは変更していない。

エージェント ちゃん さんが23日前に更新 操作 #4

補足(2026-06-13):HTML側の単一集約に伴う影響

#182 の方針を「用途別分割 → 単一 overlay.html に集約」へ変更したことに伴う本体側の確認事項。

  • /state.json スキーマ(profile / nowPlaying.playOption)は変更不要。元から全ブロックを1本で返す設計のため単一 HTML と整合する。
  • HTTP サーバの静的配信は overlay.html 1枚(+CSS/JS)を配る構成になる。profile.html など用途別テンプレートの配信は不要。
  • ポーリングが3ソース→1ソースに集約されるため、/state.json 生成・配信の負荷面でもむしろ軽くなる。GAS のライブ更新経路(ObsState.updatePlayGauge)の設計は前コメントのまま。

エージェント ちゃん さんが23日前に更新 操作 #5

  • ステータス未着手 から 実装中 に変更
  • 進捗率0 から 10 に変更

実装着手。作業ブランチ codex/162-obs-integration(develop 起点)にて本体側を実装する。コードベース調査で設計前提(changeState=MainController.java:248 / BMSPlayer 生成=263、RuntimeTaskManager.startVirtual、ReplayData.randomoption/randomoption2/laneShufflePattern、GrooveGauge 定数 0–8、GAS 処理=BMSPlayer.java:643-677 の gauge.setType、Config/PlayerConfig の Json DTO 永続化、PlayConfigurationView/FXML/UIResources.properties、IntegerIndexPropertyTypes.getAssignedLane=396-447)を実コードで確認済み。

エージェント ちゃん さんが23日前に更新 操作 #6

実装完了・MR !24 にて develop へ。https://gitlab.izu-san.com/games/beatoraja/-/merge_requests/24

本体側:新規 bms.player.beatoraja.stream.obs(ObsHttpServer / ObsState / SessionStats / ObsController / ObsFormat)。MainController.changeState() を funnel に select/nowPlaying/play/result/courseResult を反映、BMSPlayer の GAS 処理直後に種別変化時のみゲージをライブ更新。/state.json(phase/profile/nowPlaying(+playOption)/lastResult/session/history)、難易度表表記、選曲履歴(最大5件)。設定:Config.obsEnabled/obsPort、PlayerConfig.dan/obsComment+永続化、ランチャー Stream タブ UI。スレッド安全(単一ライタ+volatile)。xvfb-run -a ./gradlew test 成功。

エージェント ちゃん さんが23日前に更新 操作 #7

  • ステータス実装中 から 完了 に変更
  • 進捗率10 から 100 に変更

MR !24 承認・ develop へマージ完了につき完了。

実装サマリ(本体側)

  • 新規 bms.player.beatoraja.stream.obs:ObsHttpServer(localhost 固定・/state.json./obs 静的配信・パストラバーサル防止・仮想スレッド)、ObsState(volatile 不変スナップショット+キャッシュ済 JSON、ゲームスレッド単一ライタ)、SessionStats、ObsController、ObsFormat。
  • MainController.changeState() を funnel に select/nowPlaying/play/result/courseResult を反映。BMSPlayer の GAS 処理直後に種別変化時のみゲージをライブ更新。
  • /state.json:phase / profile / nowPlaying(+playOption) / lastResult / session / history。難易度は難易度表表記(sl9 / ★13)、表外は ★+PLAYLEVEL。選曲履歴最大5件。
  • 設定:Config.obsEnabled/obsPort、PlayerConfig.dan/obsComment+永続化、ランチャー Stream タブ UI。obsEnabled=false 時ゼロ影響。
  • テスト:xvfb-run -a ./gradlew test 成功。

いづ さん さんが22日前に更新 操作 #8

  • 対象バージョン1.0.1 にセット

いづ さん さんが15日前に更新 操作 #9

  • 関連している 機能 #369: 【OBS連携】Beatoraja本体側の機能の移植 を追加

他の形式にエクスポート: PDF Atom