From b3572afd3480cf5e1b820fade85912c73cd03016 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 16:25:10 +0900 Subject: [PATCH] =?UTF-8?q?examples=20=E3=81=AE=20spotlight=20sendonly=20/?= =?UTF-8?q?=20recvonly=20=E3=82=92=20class=20=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/sendonly/index.html | 1 - examples/sendonly/main.mts | 18 ++-- examples/spotlight_recvonly/index.html | 8 +- examples/spotlight_recvonly/main.mjs | 58 ----------- examples/spotlight_recvonly/main.mts | 120 ++++++++++++++++++++++ examples/spotlight_sendonly/index.html | 11 +- examples/spotlight_sendonly/main.mjs | 35 ------- examples/spotlight_sendonly/main.mts | 96 +++++++++++++++++ tests/spotlight_sendonly_recvonly.spec.ts | 28 ++--- 9 files changed, 250 insertions(+), 125 deletions(-) delete mode 100644 examples/spotlight_recvonly/main.mjs create mode 100644 examples/spotlight_recvonly/main.mts delete mode 100644 examples/spotlight_sendonly/main.mjs create mode 100644 examples/spotlight_sendonly/main.mts diff --git a/examples/sendonly/index.html b/examples/sendonly/index.html index 358f8e37..42bcd6f9 100644 --- a/examples/sendonly/index.html +++ b/examples/sendonly/index.html @@ -16,7 +16,6 @@

Sendonly test

- \ No newline at end of file diff --git a/examples/sendonly/main.mts b/examples/sendonly/main.mts index cca31336..93c76b90 100644 --- a/examples/sendonly/main.mts +++ b/examples/sendonly/main.mts @@ -19,19 +19,11 @@ document.addEventListener('DOMContentLoaded', async () => { document.querySelector('#start')?.addEventListener('click', async () => { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) - const videoElement = document.querySelector('#local-video') - if (videoElement !== null) { - videoElement.srcObject = stream - } await client.connect(stream) }) document.querySelector('#stop')?.addEventListener('click', async () => { await client.disconnect() - const videoElement = document.querySelector('#local-video') - if (videoElement !== null) { - videoElement.srcObject = null - } }) }) @@ -63,10 +55,20 @@ class SoraClient { async connect(stream: MediaStream): Promise { await this.connection.connect(stream) + + const videoElement = document.querySelector('#local-video') + if (videoElement !== null) { + videoElement.srcObject = stream + } } async disconnect(): Promise { await this.connection.disconnect() + + const videoElement = document.querySelector('#local-video') + if (videoElement !== null) { + videoElement.srcObject = null + } } private onnotify(event: SignalingNotifyMessage): void { diff --git a/examples/spotlight_recvonly/index.html b/examples/spotlight_recvonly/index.html index f4153b6c..d6da3bd6 100644 --- a/examples/spotlight_recvonly/index.html +++ b/examples/spotlight_recvonly/index.html @@ -8,15 +8,15 @@

Spotlight Recvonly test

- -
-
+ +
+
- + \ No newline at end of file diff --git a/examples/spotlight_recvonly/main.mjs b/examples/spotlight_recvonly/main.mjs deleted file mode 100644 index d2be592b..00000000 --- a/examples/spotlight_recvonly/main.mjs +++ /dev/null @@ -1,58 +0,0 @@ -import Sora from '../../dist/sora.mjs' - -const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL -const SORA_CHANNEL_ID_PREFIX = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' -const SORA_CHANNEL_ID_SUFFIX = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' -const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN || '' - -const channelId = `${SORA_CHANNEL_ID_PREFIX}spotlight_sendonly_recvonly${SORA_CHANNEL_ID_SUFFIX}` -const debug = false -const sora = Sora.connection(SORA_SIGNALING_URL, debug) -const metadata = { access_token: ACCESS_TOKEN } -const options = { - multistream: true, - simulcast: true, - spotlight: true, -} -const recvonly = sora.recvonly(channelId, metadata, options) - -recvonly.on('notify', (event) => { - if (event.event_type === 'connection.created' && recvonly.connectionId === event.connection_id) { - const connectionIdElement = document.querySelector('#recvonly-connection-id') - connectionIdElement.textContent = event.connection_id - } -}) - -recvonly.on('track', (event) => { - const stream = event.streams[0] - if (!stream) return - const remoteVideoId = `remotevideo-${stream.id}` - const remoteVideos = document.querySelector('#remote-videos') - if (!remoteVideos.querySelector(`#${remoteVideoId}`)) { - const remoteVideo = document.createElement('video') - remoteVideo.id = remoteVideoId - remoteVideo.style.border = '1px solid red' - remoteVideo.autoplay = true - remoteVideo.playsinline = true - remoteVideo.controls = true - remoteVideo.srcObject = stream - remoteVideos.appendChild(remoteVideo) - } -}) - -recvonly.on('removetrack', (event) => { - const remoteVideo = document.querySelector(`#remotevideo-${event.target.id}`) - if (remoteVideo) { - remoteVideo.srcObject = null - document.querySelector('#remote-videos').removeChild(remoteVideo) - } -}) - -document.querySelector('#start-recvonly').addEventListener('click', async () => { - await recvonly.connect() -}) - -document.querySelector('#stop-recvonly').addEventListener('click', async () => { - await recvonly.disconnect() - document.querySelector('#remote-videos').innerHTML = null -}) diff --git a/examples/spotlight_recvonly/main.mts b/examples/spotlight_recvonly/main.mts new file mode 100644 index 00000000..47264399 --- /dev/null +++ b/examples/spotlight_recvonly/main.mts @@ -0,0 +1,120 @@ +import Sora, { + type SoraConnection, + type SignalingNotifyMessage, + type ConnectionSubscriber, +} from '../../dist/sora' + +document.addEventListener('DOMContentLoaded', () => { + // 環境変数の読み込み + const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL + const SORA_CHANNEL_ID_PREFIX = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' + const SORA_CHANNEL_ID_SUFFIX = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' + const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN || '' + + // Sora クライアントの初期化 + const client = new SoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + document.querySelector('#start')?.addEventListener('click', async () => { + await client.connect() + }) + + document.querySelector('#stop')?.addEventListener('click', async () => { + await client.disconnect() + }) +}) + +class SoraClient { + private debug = false + private channelId: string + private metadata: { access_token: string } + private options: object = {} + + private sora: SoraConnection + private connection: ConnectionSubscriber + + constructor( + signaling_url: string, + channel_id_prefix: string, + channel_id_suffix: string, + access_token: string, + ) { + this.sora = Sora.connection(signaling_url, this.debug) + + this.options = { + multistream: true, + simulcast: true, + spotlight: true, + } + + // channel_id の生成 + this.channelId = `${channel_id_prefix}spotlight_sendonly_recvonly${channel_id_suffix}` + // access_token を指定する metadata の生成 + this.metadata = { access_token: access_token } + + this.connection = this.sora.recvonly(this.channelId, this.metadata, this.options) + this.connection.on('notify', this.onnotify.bind(this)) + this.connection.on('track', this.ontrack.bind(this)) + this.connection.on('removetrack', this.onremovetrack.bind(this)) + } + + async connect(): Promise { + await this.connection.connect() + } + + async disconnect(): Promise { + await this.connection.disconnect() + const remoteVideos = document.querySelector('#remote-videos') + if (remoteVideos) { + remoteVideos.innerHTML = '' + } + + const connectionIdElement = document.querySelector('#connection-id') + if (connectionIdElement) { + connectionIdElement.textContent = null + } + } + + private onnotify(event: SignalingNotifyMessage) { + // 自分の connection_id を取得する + if ( + event.event_type === 'connection.created' && + this.connection.connectionId === event.connection_id + ) { + const connectionIdElement = document.querySelector('#connection-id') + if (connectionIdElement) { + connectionIdElement.textContent = event.connection_id + } + } + } + + private ontrack(event: RTCTrackEvent) { + // Sora の場合、event.streams には MediaStream が 1 つだけ含まれる + const stream = event.streams[0] + const remoteVideoId = `remotevideo-${stream.id}` + const remoteVideos = document.querySelector('#remote-videos') + if (remoteVideos && !remoteVideos.querySelector(`#${remoteVideoId}`)) { + const remoteVideo = document.createElement('video') + remoteVideo.id = remoteVideoId + remoteVideo.style.border = '1px solid red' + remoteVideo.autoplay = true + remoteVideo.playsInline = true + remoteVideo.controls = true + remoteVideo.srcObject = stream + remoteVideos.appendChild(remoteVideo) + } + } + + private onremovetrack(event: MediaStreamTrackEvent) { + // このトラックが属している MediaStream の id を取得する + const stream = event.target as MediaStream + const remoteVideo = document.querySelector(`#remotevideo-${stream.id}`) + if (remoteVideo) { + document.querySelector('#remote-videos')?.removeChild(remoteVideo) + } + } +} diff --git a/examples/spotlight_sendonly/index.html b/examples/spotlight_sendonly/index.html index 299f9db0..6e0ecc41 100644 --- a/examples/spotlight_sendonly/index.html +++ b/examples/spotlight_sendonly/index.html @@ -8,14 +8,15 @@

Spotlight Sendonly test

- -
-
- + + \ No newline at end of file diff --git a/examples/spotlight_sendonly/main.mjs b/examples/spotlight_sendonly/main.mjs deleted file mode 100644 index 09134ef9..00000000 --- a/examples/spotlight_sendonly/main.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import Sora from '../../dist/sora.mjs' - -const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL -const SORA_CHANNEL_ID_PREFIX = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' -const SORA_CHANNEL_ID_SUFFIX = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' -const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN || '' - -const channelId = `${SORA_CHANNEL_ID_PREFIX}spotlight_sendonly_recvonly${SORA_CHANNEL_ID_SUFFIX}` -const debug = false -const sora = Sora.connection(SORA_SIGNALING_URL, debug) -const metadata = { access_token: ACCESS_TOKEN } -const options = { - multistream: true, - simulcast: true, - spotlight: true, -} -const sendonly = sora.sendonly(channelId, metadata, options) - -sendonly.on('notify', (event) => { - if (event.event_type === 'connection.created' && sendonly.connectionId === event.connection_id) { - const connectionIdElement = document.querySelector('#sendonly-connection-id') - connectionIdElement.textContent = event.connection_id - } -}) - -document.querySelector('#start-sendonly').addEventListener('click', async () => { - const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - document.querySelector('#sendonly-local-video').srcObject = mediaStream - await sendonly.connect(mediaStream) -}) - -document.querySelector('#stop-sendonly').addEventListener('click', async () => { - await sendonly.disconnect() - document.querySelector('#sendonly-local-video').srcObject = null -}) diff --git a/examples/spotlight_sendonly/main.mts b/examples/spotlight_sendonly/main.mts new file mode 100644 index 00000000..bf4c955f --- /dev/null +++ b/examples/spotlight_sendonly/main.mts @@ -0,0 +1,96 @@ +import Sora, { + type SignalingNotifyMessage, + type ConnectionPublisher, + type SoraConnection, +} from '../../dist/sora' + +document.addEventListener('DOMContentLoaded', async () => { + const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL + const SORA_CHANNEL_ID_PREFIX = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' + const SORA_CHANNEL_ID_SUFFIX = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' + const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN || '' + + const client = new SoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + document.querySelector('#start')?.addEventListener('click', async () => { + const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) + await client.connect(stream) + }) + + document.querySelector('#stop')?.addEventListener('click', async () => { + await client.disconnect() + }) +}) + +class SoraClient { + private debug = false + private channelId: string + private metadata: { access_token: string } + private options: object = {} + + private sora: SoraConnection + private connection: ConnectionPublisher + + constructor( + signaling_url: string, + channel_id_prefix: string, + channel_id_suffix: string, + access_token: string, + ) { + this.sora = Sora.connection(signaling_url, this.debug) + + this.options = { + multistream: true, + simulcast: true, + spotlight: true, + } + + // channel_id の生成 + this.channelId = `${channel_id_prefix}spotlight_sendonly_recvonly${channel_id_suffix}` + // access_token を指定する metadata の生成 + this.metadata = { access_token: access_token } + + this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) + this.connection.on('notify', this.onnotify.bind(this)) + } + + async connect(stream: MediaStream): Promise { + await this.connection.connect(stream) + + const videoElement = document.querySelector('#local-video') + if (videoElement !== null) { + videoElement.srcObject = stream + } + } + + async disconnect(): Promise { + await this.connection.disconnect() + + const videoElement = document.querySelector('#local-video') + if (videoElement !== null) { + videoElement.srcObject = null + } + + const connectionIdElement = document.querySelector('#connection-id') + if (connectionIdElement) { + connectionIdElement.textContent = null + } + } + + private onnotify(event: SignalingNotifyMessage): void { + if ( + event.event_type === 'connection.created' && + this.connection.connectionId === event.connection_id + ) { + const connectionIdElement = document.querySelector('#connection-id') + if (connectionIdElement) { + connectionIdElement.textContent = event.connection_id + } + } + } +} diff --git a/tests/spotlight_sendonly_recvonly.spec.ts b/tests/spotlight_sendonly_recvonly.spec.ts index e480260b..ca9c3851 100644 --- a/tests/spotlight_sendonly_recvonly.spec.ts +++ b/tests/spotlight_sendonly_recvonly.spec.ts @@ -2,33 +2,33 @@ import { test } from '@playwright/test' test('spotlight sendonly/recvonly pages', async ({ browser }) => { // 新しいページを2つ作成 - const page1 = await browser.newPage() - const page2 = await browser.newPage() + const sendonly = await browser.newPage() + const recvonly = await browser.newPage() // それぞれのページに対して操作を行う - await page1.goto('http://localhost:9000/spotlight_sendonly/') - await page2.goto('http://localhost:9000/spotlight_recvonly/') + await sendonly.goto('http://localhost:9000/spotlight_sendonly/') + await recvonly.goto('http://localhost:9000/spotlight_recvonly/') - await page1.click('#start-sendonly') - await page2.click('#start-recvonly') + await sendonly.click('#start') + await recvonly.click('#start') // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await page1.waitForSelector('#sendonly-connection-id:not(:empty)') + await sendonly.waitForSelector('#connection-id:not(:empty)') // #sendonly-connection-id 要素の内容を取得 - const sendonlyConnectionId = await page1.$eval('#sendonly-connection-id', (el) => el.textContent) + const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) console.log(`sendonly connectionId=${sendonlyConnectionId}`) // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await page2.waitForSelector('#recvonly-connection-id:not(:empty)') + await recvonly.waitForSelector('#connection-id:not(:empty)') // #sendrecv1-connection-id 要素の内容を取得 - const recvonlyConnectionId = await page2.$eval('#recvonly-connection-id', (el) => el.textContent) + const recvonlyConnectionId = await recvonly.$eval('#connection-id', (el) => el.textContent) console.log(`recvonly connectionId=${recvonlyConnectionId}`) - await page1.click('#stop-sendonly') - await page2.click('#stop-recvonly') + await sendonly.click('#stop') + await recvonly.click('#stop') - await page1.close() - await page2.close() + await sendonly.close() + await recvonly.close() })