From e4eb9ef88f93f4d1f3f06c57374847959c2f604c Mon Sep 17 00:00:00 2001 From: voluntas Date: Sat, 11 Jan 2025 15:29:05 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- recvonly/index.html | 28 +++++++++---- recvonly/main.ts | 86 +++++++++++++++++--------------------- sendonly/index.html | 25 +++++++---- sendonly/main.ts | 84 ++++++++++++++++--------------------- sendrecv/main.ts | 33 ++++++--------- simulcast/main.ts | 27 ++++++------ spotlight_sendrecv/main.ts | 21 ++++------ tests/sendrecv.test.ts | 10 ++++- 8 files changed, 153 insertions(+), 161 deletions(-) diff --git a/recvonly/index.html b/recvonly/index.html index b2780cc..ea40b74 100644 --- a/recvonly/index.html +++ b/recvonly/index.html @@ -3,21 +3,33 @@ - Recvonly サンプル + 受信のみサンプル
-

Recvonly サンプル

- - -
-
+

受信のみサンプル

+

+ + +

+

+ channel_id: + +
+ session_id: + +
+ connection_id: + +

-
-
+

+ +


+    

diff --git a/recvonly/main.ts b/recvonly/main.ts index 5a39bf1..97b203e 100644 --- a/recvonly/main.ts +++ b/recvonly/main.ts @@ -2,23 +2,24 @@ import Sora, { type SoraConnection, type SignalingNotifyMessage, type ConnectionSubscriber, + type ConnectionOptions, } from "sora-js-sdk"; import { generateJwt } from "../src/misc"; document.addEventListener("DOMContentLoaded", () => { // 環境変数の読み込み const signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL; - const channelIdPrefix = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX; - const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX; - const secretKey = import.meta.env.VITE_SECRET_KEY; + const channelIdPrefix = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || ""; + const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || ""; + const secretKey = import.meta.env.VITE_SECRET_KEY || ""; + + // パラメータから channelName を取得 + const urlParams = new URLSearchParams(window.location.search); + const channelName = urlParams.get("channelName") || ""; + const channelId = `${channelIdPrefix}:${channelName}:${channelIdSuffix}`; // Sora クライアントの初期化 - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ); + const client = new SoraClient(signalingUrl, channelId, secretKey); document.querySelector("#connect")?.addEventListener("click", async () => { await client.connect(); @@ -30,29 +31,18 @@ document.addEventListener("DOMContentLoaded", () => { document.querySelector("#get-stats")?.addEventListener("click", async () => { const statsReport = await client.getStats(); - const statsDiv = document.querySelector("#stats-report") as HTMLElement; - const statsReportJsonDiv = document.querySelector("#stats-report-json"); - if (statsDiv && statsReportJsonDiv) { - let statsHtml = ""; - const statsReportJson: Record[] = []; - for (const report of statsReport.values()) { - statsHtml += `

Type: ${report.type}

"; - statsReportJson.push(reportJson); - } - statsDiv.innerHTML = statsHtml; - // データ属性としても保存(オプション) - statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson); + const statsReportJson: Record[] = []; + for (const report of statsReport.values()) { + statsReportJson.push(report); + } + const statsReportJsonElement = + document.querySelector("#stats-report-json"); + if (statsReportJsonElement) { + statsReportJsonElement.textContent = JSON.stringify( + statsReportJson, + null, + 2, + ); } }); }); @@ -61,24 +51,19 @@ class SoraClient { private debug = false; private channelId: string; - private options: object = {}; + private options: ConnectionOptions; private secretKey: string; private sora: SoraConnection; private connection: ConnectionSubscriber; - constructor( - signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, - secretKey: string, - ) { - this.sora = Sora.connection(signalingUrl, this.debug); - - this.channelId = `${channelIdPrefix}:recvonly:${channelIdSuffix}`; + constructor(signalingUrl: string, channelId: string, secretKey: string) { + this.channelId = channelId; this.secretKey = secretKey; + this.options = {}; + this.sora = Sora.connection(signalingUrl, this.debug); this.connection = this.sora.recvonly(this.channelId, null, this.options); this.connection.on("notify", this.onnotify.bind(this)); this.connection.on("track", this.ontrack.bind(this)); @@ -104,9 +89,9 @@ class SoraClient { } } - getStats(): Promise { + async getStats(): Promise { if (this.connection.pc === null) { - return Promise.reject(new Error("PeerConnection is not ready")); + throw new Error("PeerConnection is not ready"); } return this.connection.pc.getStats(); } @@ -117,10 +102,17 @@ class SoraClient { event.event_type === "connection.created" && this.connection.connectionId === event.connection_id ) { - const connectionIdElement = - document.querySelector("#connection-id"); + const channelIdElement = document.querySelector("#channel-id"); + if (channelIdElement) { + channelIdElement.textContent = this.channelId; + } + const sessionIdElement = document.querySelector("#session-id"); + if (sessionIdElement) { + sessionIdElement.textContent = this.connection.sessionId; + } + const connectionIdElement = document.querySelector("#connection-id"); if (connectionIdElement) { - connectionIdElement.textContent = event.connection_id; + connectionIdElement.textContent = this.connection.connectionId; } } } diff --git a/sendonly/index.html b/sendonly/index.html index e1eade0..b651be3 100644 --- a/sendonly/index.html +++ b/sendonly/index.html @@ -3,20 +3,29 @@ - Sendonly サンプル + 送信のみサンプル
-

Sendonly サンプル

- - -
-
+

送信のみサンプル

+

+ + +

+

+ session_id: + +
+ connection_id: + +

-
-
+

+ +


+    

diff --git a/sendonly/main.ts b/sendonly/main.ts index a8abd5b..c6c74dc 100644 --- a/sendonly/main.ts +++ b/sendonly/main.ts @@ -13,12 +13,12 @@ document.addEventListener("DOMContentLoaded", async () => { const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || ""; const secretKey = import.meta.env.VITE_SECRET_KEY || ""; - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ); + // URL から channelName パラメータを取得 + const urlParams = new URLSearchParams(window.location.search); + const channelName = urlParams.get("channelName") || ""; + const channelId = `${channelIdPrefix}:${channelName}:${channelIdSuffix}`; + + const client = new SoraClient(signalingUrl, channelId, secretKey); document.querySelector("#connect")?.addEventListener("click", async () => { const stream = await navigator.mediaDevices.getUserMedia({ @@ -34,29 +34,18 @@ document.addEventListener("DOMContentLoaded", async () => { document.querySelector("#get-stats")?.addEventListener("click", async () => { const statsReport = await client.getStats(); - const statsDiv = document.querySelector("#stats-report") as HTMLElement; - const statsReportJsonDiv = document.querySelector("#stats-report-json"); - if (statsDiv && statsReportJsonDiv) { - let statsHtml = ""; - const statsReportJson: Record[] = []; - for (const report of statsReport.values()) { - statsHtml += `

Type: ${report.type}

    `; - const reportJson: Record = { - id: report.id, - type: report.type, - }; - for (const [key, value] of Object.entries(report)) { - if (key !== "type" && key !== "id") { - statsHtml += `
  • ${key}: ${value}
  • `; - reportJson[key] = value; - } - } - statsHtml += "
"; - statsReportJson.push(reportJson); - } - statsDiv.innerHTML = statsHtml; - // データ属性としても保存(オプション) - statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson); + const statsReportJson: Record[] = []; + for (const report of statsReport.values()) { + statsReportJson.push(report); + } + const statsReportJsonElement = + document.querySelector("#stats-report-json"); + if (statsReportJsonElement) { + statsReportJsonElement.textContent = JSON.stringify( + statsReportJson, + null, + 2, + ); } }); }); @@ -74,21 +63,17 @@ class SoraClient { constructor( signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, + channelId: string, secretKey: string, options: ConnectionOptions = {}, ) { - this.channelId = `${channelIdPrefix}:sendonly:${channelIdSuffix}`; + this.channelId = channelId; this.secretKey = secretKey; this.options = options; this.sora = Sora.connection(signalingUrl, this.debug); this.connection = this.sora.sendonly(this.channelId, null, this.options); - this.connection.on("notify", this.onNotify.bind(this)); - - // E2E テスト用のコード - this.connection.on("signaling", this.onSignaling.bind(this)); + this.connection.on("notify", this.onnotify.bind(this)); } async connect(stream: MediaStream): Promise { @@ -103,7 +88,7 @@ class SoraClient { const videoElement = document.querySelector("#local-video"); - if (videoElement !== null) { + if (videoElement) { videoElement.srcObject = stream; } } @@ -113,34 +98,35 @@ class SoraClient { const videoElement = document.querySelector("#local-video"); - if (videoElement !== null) { + if (videoElement) { videoElement.srcObject = null; } } - getStats(): Promise { + async getStats(): Promise { if (this.connection.pc === null) { - return Promise.reject(new Error("PeerConnection is not ready")); + throw new Error("PeerConnection is not ready"); } return this.connection.pc.getStats(); } - private onNotify(event: SignalingNotifyMessage): void { + private onnotify(event: SignalingNotifyMessage): void { if ( event.event_type === "connection.created" && this.connection.connectionId === event.connection_id ) { + const channelIdElement = document.querySelector("#channel-id"); + if (channelIdElement) { + channelIdElement.textContent = this.channelId; + } + const sessionIdElement = document.querySelector("#session-id"); + if (sessionIdElement) { + sessionIdElement.textContent = this.connection.sessionId; + } const connectionIdElement = document.querySelector("#connection-id"); if (connectionIdElement) { - connectionIdElement.textContent = event.connection_id; + connectionIdElement.textContent = this.connection.connectionId; } } } - - // E2E テスト用のコード - private onSignaling(event: SignalingEvent): void { - if (event.type === "onmessage-switched") { - console.log("[signaling]", event.type, event.transportType); - } - } } diff --git a/sendrecv/main.ts b/sendrecv/main.ts index 8979973..13fbee7 100644 --- a/sendrecv/main.ts +++ b/sendrecv/main.ts @@ -23,19 +23,14 @@ document.addEventListener("DOMContentLoaded", async () => { const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || ""; const secretKey = import.meta.env.VITE_SECRET_KEY || ""; - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ); + // パラメータから channelName を取得 + const urlParams = new URLSearchParams(window.location.search); + const channelName = urlParams.get("channelName") || ""; + const channelId = `${channelIdPrefix}:${channelName}:${channelIdSuffix}`; - document.querySelector("#connect")?.addEventListener("click", async () => { - const videoCodecType = getVideoCodecType(); - if (videoCodecType !== undefined) { - client.setOptions({ videoCodecType: videoCodecType }); - } + const client = new SoraClient(signalingUrl, channelId, secretKey); + document.querySelector("#connect")?.addEventListener("click", async () => { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true, @@ -75,16 +70,10 @@ class SoraClient { private sora: SoraConnection; private connection: ConnectionPublisher; - constructor( - signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, - secretKey: string, - options: ConnectionOptions = {}, - ) { - this.channelId = `${channelIdPrefix}:sendrecv:${channelIdSuffix}`; + constructor(signalingUrl: string, channelId: string, secretKey: string) { + this.channelId = channelId; this.secretKey = secretKey; - this.options = options; + this.options = {}; this.sora = Sora.connection(signalingUrl, this.debug); this.connection = this.sora.sendrecv( @@ -143,6 +132,10 @@ class SoraClient { event.event_type === "connection.created" && this.connection.connectionId === event.connection_id ) { + const channelIdElement = document.querySelector("#channel-id"); + if (channelIdElement) { + channelIdElement.textContent = this.channelId; + } const sessionIdElement = document.querySelector("#session-id"); if (sessionIdElement) { sessionIdElement.textContent = this.connection.sessionId; diff --git a/simulcast/main.ts b/simulcast/main.ts index 1dc5e1f..ecc4ba6 100644 --- a/simulcast/main.ts +++ b/simulcast/main.ts @@ -14,10 +14,14 @@ document.addEventListener("DOMContentLoaded", () => { const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || ""; const secretKey = import.meta.env.VITE_SECRET_KEY || ""; + // URL から channelName パラメータを取得 + const urlParams = new URLSearchParams(window.location.search); + const channelName = urlParams.get("channelName") || ""; + const channelId = `${channelIdPrefix}:${channelName}:${channelIdSuffix}`; + const sendonly = new SimulcastSendonlySoraClient( signalingUrl, - channelIdPrefix, - channelIdSuffix, + channelId, secretKey, { audio: false, @@ -29,8 +33,7 @@ document.addEventListener("DOMContentLoaded", () => { ); const recvonlyR0 = new SimulcastRecvonlySoraClient( signalingUrl, - channelIdPrefix, - channelIdSuffix, + channelId, secretKey, { simulcast: true, @@ -39,8 +42,7 @@ document.addEventListener("DOMContentLoaded", () => { ); const recvonlyR1 = new SimulcastRecvonlySoraClient( signalingUrl, - channelIdPrefix, - channelIdSuffix, + channelId, secretKey, { simulcast: true, @@ -49,8 +51,7 @@ document.addEventListener("DOMContentLoaded", () => { ); const recvonlyR2 = new SimulcastRecvonlySoraClient( signalingUrl, - channelIdPrefix, - channelIdSuffix, + channelId, secretKey, { simulcast: true, @@ -127,12 +128,11 @@ class SimulcastSendonlySoraClient { constructor( signaling_url: string, - channelIdPrefix: string, - channelIdSuffix: string, + channelId: string, secretKey: string, options: ConnectionOptions, ) { - this.channelId = `${channelIdPrefix}:simulcast:${channelIdSuffix}`; + this.channelId = channelId; this.secretKey = secretKey; this.options = options; @@ -204,12 +204,11 @@ class SimulcastRecvonlySoraClient { constructor( signaling_url: string, - channelIdPrefix: string, - channelIdSuffix: string, + channelId: string, secretKey: string, options: ConnectionOptions, ) { - this.channelId = `${channelIdPrefix}__simulcast__${channelIdSuffix}`; + this.channelId = channelId; this.secretKey = secretKey; this.options = options; diff --git a/spotlight_sendrecv/main.ts b/spotlight_sendrecv/main.ts index b95e911..5895c8b 100644 --- a/spotlight_sendrecv/main.ts +++ b/spotlight_sendrecv/main.ts @@ -12,12 +12,12 @@ document.addEventListener("DOMContentLoaded", async () => { const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || ""; const secretKey = import.meta.env.VITE_SECRET_KEY || ""; - const sendrecv = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ); + // URL から channelName パラメータを取得 + const urlParams = new URLSearchParams(window.location.search); + const channelName = urlParams.get("channelName") || ""; + const channelId = `${channelIdPrefix}:${channelName}:${channelIdSuffix}`; + + const sendrecv = new SoraClient(signalingUrl, channelId, secretKey); document.querySelector("#connect")?.addEventListener("click", async () => { const stream = await navigator.mediaDevices.getUserMedia({ @@ -42,16 +42,11 @@ class SoraClient { private sora: SoraConnection; private connection: ConnectionPublisher; - constructor( - signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, - secretKey: string, - ) { + constructor(signalingUrl: string, channelId: string, secretKey: string) { this.secretKey = secretKey; this.sora = Sora.connection(signalingUrl, this.debug); - this.channelId = `${channelIdPrefix}:spotlight_sendrecv:${channelIdSuffix}`; + this.channelId = channelId; this.options = { audio: true, video: true, diff --git a/tests/sendrecv.test.ts b/tests/sendrecv.test.ts index 5da7863..0ad07d8 100644 --- a/tests/sendrecv.test.ts +++ b/tests/sendrecv.test.ts @@ -4,8 +4,14 @@ test("sendrecv x2", async ({ browser }) => { const sendrecv1 = await browser.newPage(); const sendrecv2 = await browser.newPage(); - await sendrecv1.goto("http://localhost:9000/sendrecv/"); - await sendrecv2.goto("http://localhost:9000/sendrecv/"); + // テストごとに異なる channelName を生成 + const channelName = crypto.randomUUID(); + await sendrecv1.goto( + `http://localhost:9000/sendrecv/?channelName=${channelName}`, + ); + await sendrecv2.goto( + `http://localhost:9000/sendrecv/?channelName=${channelName}`, + ); await sendrecv1.click("#connect"); await sendrecv2.click("#connect");