コンテンツにスキップ
ドキュメント
ダウンロード
日本語

Sound Show 2 に接続する

独自のスクリプト、ハードウェアコントローラー、連携機能から、起動中の Sound Show 2 を操作できます。WebSocket API では、再生項目の起動、プレイヤーや出力の操作、キューの実行、コントローラーへのリアルタイム状態通知が可能です。

このガイドは プロトコル v2 を扱います。コンピューターまたはローカルネットワーク上の Sound Show に直接接続します。ウェブサイトの API キーやアカウントのアクセストークンは不要です。プロジェクトの編集とファイルのアップロードは、この API の対象外です。

  • まずここから接続し、サンプルを実行してください。
  • 操作を追加するにはコマンドリファレンスを参照してください。
  • ID の取得や現在の公演状態の表示には状態とイベントを参照してください。

  1. デスクトップコンピューターで Sound Show 2 を開き、プロジェクトを読み込みます。ホストには、有効な Pro 体験版を含む Pro アクセスが必要です。Free と Proを参照してください。
  2. オプション → 連携 → リモートサーバーを開きます。リモートパネルの設定ボタンからも開けます。
  3. ポート(既定値は 8080)と、6 文字以上のパスワードを指定して保存します。この接続専用のパスワードを使ってください。
  4. 接続ボタンでサーバーを起動します。パスワードを空欄にする場合は、パスワードなしのアクセスに関する警告を確認してください。
  5. Sound Show に表示されるアドレスをコピーします。内蔵ブラウザーリモコンも使う場合は、ウェブページを配信を有効にしてください。この設定が無効でも WebSocket エンドポイントは利用できます。

ブラウザーリモコンと WebSocket API は、同じホストとポートを使います。

用途 Sound Show を実行するコンピューターでの例
WebSocket 接続 ws://127.0.0.1:8080/ws
内蔵ブラウザーリモコン http://127.0.0.1:8080/
サーバーの稼働確認 http://127.0.0.1:8080/health

別の端末から接続する場合、127.0.0.1 を Sound Show のコンピューターのアドレスに置き換えます。例:ws://192.168.1.20:8080/ws。設定したポートが 8080 と異なる場合は、そのポートを使います。localhost は常に、クライアントを実行している端末を指します。

稼働確認エンドポイントは {"status":"ok","protocol_version":2} のようなオブジェクトを返します。サーバーに到達できることを確認するもので、クライアントを認証するものではありません。

既定では、Sound Show はブラウザーの Origin ヘッダーを確認します。サーバーのポートを使う HTTP オリジンで、ホストが localhost、検出された LAN アドレス、または設定済みの通知ホストである必要があります。Sound Show が表示するアドレスで内蔵リモコンを開くと、許可されたオリジンになります。

別の端末、ウェブサイト、開発サーバーのポートで配信する独自ページには、以下の外部オリジンを許可設定が必要です。このページの JavaScript ターミナル例など、ネイティブクライアントは Origin を省略でき、この設定は不要です。

独自のブラウザーリモコンを Sound Show とは別に配信する場合に有効にします。

  1. オプション → 連携 → リモートサーバー、またはリモートパネルの設定を開きます。
  2. リモート用のパスワードを設定し、外部オリジンを許可を有効にして適用します。既定では無効です。項目がない場合は、対応する Sound Show のバージョンに更新してください。
  3. Sound Show のコンピューターに到達できる端末で独自ページを開きます。ページの配信元ではなく、Sound Show のアドレスに WebSocket 接続し、パスワードを含む通常の hello メッセージを送ります。

たとえば http://192.168.1.30:3000 で配信するコントローラーから、ws://192.168.1.20:8080/ws の Sound Show に接続できます。ブラウザースクリプトは次のように開始できます。

const remotePassword = window.prompt("Sound Show remote password");
if (remotePassword !== null) {
const socket = new WebSocket("ws://192.168.1.20:8080/ws");
socket.addEventListener("open", () => {
socket.send(JSON.stringify({
type: "hello", version: 2, password: remotePassword,
features: ["runtime_snapshots"],
}));
});
socket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
console.log(message); // Handle hello_rejected, errors, and snapshots here.
});
}

例のアドレスを自分のホストとポートに置き換えます。最初の state を受信してから再生コントロールを有効にし、完全な JavaScript の例のスナップショット処理を使用してください。パスワードは公開ページに埋め込まず、コントローラーのセッション内に保持します。

この設定は、ホストやポートが異なる HTTP・HTTPS ページのオリジンを許可します。パスワードのハンドシェイクと Pro の要件は引き続き適用されます。変更の保存は即時に反映され、接続中のクライアントを切断するため、再接続して認証し直してください。無効にした場合も既存の外部セッションは終了します。内蔵ブラウザーリモコンは無効のまま利用できます。

ブラウザーの制限は引き続き適用されます。 単純な LAN 構成では独自ページを HTTP で配信してください。HTTPS ページは平文の ws:// 接続をブロックする場合があります。この設定で安全な wss:// エンドポイントが追加されるわけではありません。HTTPS のコントローラーには、別途設定する TLS プロキシなど、ブラウザーに対応した安全な接続が必要です。ブラウザーはローカルネットワークの許可を求めたり、他の制限を課したりする場合もあります。この設定は HTTP エンドポイントに CORS ヘッダーを追加しません。file:// ページと不透明な null オリジンは引き続き拒否されます。ローカル HTML ファイルは HTTP サーバーで配信してください。

ソケットを開いたら直ちに、10 秒以内に JSON テキストメッセージを 1 件送信します。Sound Show に設定したリモートパスワードを含めます。

{
"type": "hello",
"version": 2,
"password": "your-remote-password",
"features": ["runtime_snapshots"]
}

パスワードなしのアクセスを確認済みのサーバーでは、password を省略するか空文字列にします。features は任意です。runtime_snapshots を要求すると、操作状態、指示、プレイリスト、再生項目の動作について個別のライブ更新を受信できます。

成功するとサーバーは次を送信します。

{
"type": "hello_ok",
"protocol_version": 2,
"requested_version": 2,
"state_mode": "full_snapshot",
"features": ["ping", "runtime_snapshots"]
}

続いて完全な state メッセージが自動的に届きます。対象の選択や公演コマンドの送信は、それを受信してから行ってください。以後のメッセージは、状態全体または名前付きの一部分を置き換えます。購読コマンドやコマンドごとの成功応答はありません。結果の状態が基準です。

失敗時は reason と message を含む hello_rejected が届き、その後切断されます。理由には invalid_password、unsupported_version、rate_limited があります。エラーと制限を参照してください。

これを soundshow-client.mjs として保存し、Node.js 22.12 以降で実行してください。内蔵 WebSocket クライアントを使うため、パッケージは不要です。プロジェクトの再生項目を一覧表示し、ライブ状態を追跡します。コマンドライン引数に再生項目 ID を渡した場合のみ再生を開始します。

const url = process.env.SOUNDSHOW_REMOTE_URL ?? "ws://127.0.0.1:8080/ws";
const password = process.env.SOUNDSHOW_REMOTE_PASSWORD ?? "";
const playableId = process.argv[2];
const ws = new WebSocket(url);
let state = null;
let firstSnapshot = true;
const domains = {
control_snapshot: "control",
players_snapshot: "players",
instructions_snapshot: "instructions",
playlists_snapshot: "playlists",
playable_runtime_snapshot: "playable_runtime",
logical_devices_snapshot: "logical_devices",
timers_snapshot: "timers",
audio_controls_snapshot: "audio_controls",
lights_snapshot: "lights",
};
function send(command) {
if (!state || ws.readyState !== WebSocket.OPEN) {
throw new Error("Wait for the first state snapshot before sending commands.");
}
ws.send(JSON.stringify(command));
}
ws.addEventListener("open", () => {
ws.send(JSON.stringify({
type: "hello", version: 2, password, features: ["runtime_snapshots"],
}));
});
ws.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.type === "hello_rejected" || message.type === "error") {
console.error(message.reason ?? message.code ?? "error", message.message);
return;
}
if (message.type === "state") {
state = message.state; // Replace the entire state, even after a project change.
} else {
const domain = domains[message.type];
if (!state || !domain) return; // Ignore unknown additive message types.
if ((domain === "audio_controls" || domain === "lights") &&
message.revision < (state[domain]?.revision ?? -1)) return;
state = { ...state, [domain]: message[domain] };
}
if (firstSnapshot) {
firstSnapshot = false;
const playables = (state.project?.categories ?? []).flatMap((category) =>
category.playables.map((p) => ({
id: p.id, name: p.name, kind: p.kind, category: category.name,
}))
);
console.table(playables);
if (playableId) {
if (playables.some((p) => p.id === playableId)) {
send({ type: "play_playable", playable_id: playableId, action: "start" });
} else {
console.error("That playable ID is not in the current project.");
}
}
console.log("Connected. Press Ctrl+C to disconnect.");
}
// Use `state` here to update your controller after every snapshot.
});
ws.addEventListener("error", () => console.error("Connection failed. Check the server address and settings."));
ws.addEventListener("close", (event) => {
state = null;
console.log("Disconnected:", event.code, event.reason);
});
process.on("SIGINT", () => ws.close());

PowerShell ではアドレスとリモートパスワードを設定し、再生項目を一覧表示します。

ターミナルウィンドウ
$env:SOUNDSHOW_REMOTE_URL = 'ws://127.0.0.1:8080/ws'
$env:SOUNDSHOW_REMOTE_PASSWORD = 'your-remote-password'
node soundshow-client.mjs

macOS または Linux のターミナルでは次のように実行します。

ターミナルウィンドウ
export SOUNDSHOW_REMOTE_URL='ws://127.0.0.1:8080/ws'
export SOUNDSHOW_REMOTE_PASSWORD='your-remote-password'
node soundshow-client.mjs

共有するスクリプトに実際のパスワードを書かないでください。パスワードなしのサーバーでは空の値を使います。Ctrl+C でスクリプトを停止し、表から再生項目 ID をコピーして再実行すると、その項目を一度だけ起動できます。

ターミナルウィンドウ
node soundshow-client.mjs "paste-a-playable-id-here"

この例は自動再接続やコマンドの再送を行いません。常時動作するコントローラーを作る前に、接続復旧処理を追加し、リファレンスからコマンドを選んでください。

症状 確認すること
接続拒否またはタイムアウト Sound Show を開いたままサーバーを起動します。ポート、ホストアドレス、ファイアウォール、端末間のネットワーク到達性を確認します。
サーバーが起動しない Pro アクセス、接続ログ、他のアプリによるポート使用を確認します。
接続時に HTTP 403 Pro アクセスとブラウザーのオリジンを確認します。別の端末、ポート、サイトのページには、Sound Show で外部オリジンを許可を有効にします。
外部オリジンを許可しても独自リモコンが接続できない Sound Show のホストアドレス、ファイアウォール、ブラウザーコンソールを確認します。混在コンテンツやローカルネットワークの制限は回避されません。上記の HTTP LAN 構成を使ってください。
invalid_password Sound Show アカウントではなく、リモートサーバーのパスワードを使います。
unsupported_version version: 2 を送信し、/ws を使います。Sound Show 1 のメッセージには互換性がありません。
HTTP 429 または rate_limited 短い間隔での再試行やコマンド送信を止め、再試行の指針に従います。
接続できたが状態が来ない まず hello を送信します。拒否や不正な JSON がないか確認します。
コマンドが効かない 最新の状態から ID を取得し、ロック、対象の可用性、ライセンス、error メッセージを確認します。
コントローラーの表示が古い 完全な state メッセージとドメインのスナップショットを両方処理します。無通信だけでは切断とは限りません。

/SoundShow の代わりに /ws を使い、$type や .NET クラス名の代わりに小文字の type フィールドを使います。v2 の hello ハンドシェイクを送信します。プレイヤーや出力の数値インデックスではなく、プレイヤー ID と論理デバイス ID を使用します。アイコンは HTTP の icon_url リソースで、状態は完全なスナップショットまたはドメイン別スナップショットとして届きます。