콘텐츠로 이동
문서
다운로드
한국어

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 텍스트 메시지 하나를 보냅니다. 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가 있습니다. 오류와 제한을 참고하세요.

JavaScript 클라이언트 사용해 보기

“JavaScript 클라이언트 사용해 보기” 섹션

이를 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 메시지와 도메인 스냅샷을 모두 처리합니다. 메시지가 없다는 이유만으로 연결이 끊긴 것은 아닙니다.

Sound Show 1에서 이전하기

“Sound Show 1에서 이전하기” 섹션

/SoundShow 대신 /ws을, $type 및 .NET 클래스 이름 대신 소문자 type 필드를 사용합니다. v2 hello 핸드셰이크를 보냅니다. 숫자 플레이어·출력 인덱스 대신 플레이어 ID와 논리 장치 ID를 사용하세요. 아이콘은 HTTP icon_url 리소스이며, 상태는 전체 또는 도메인 스냅샷으로 전달됩니다.