From f3ab1ae6d1c5ad926d799fe930d3c381b3164ee8 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 09:39:31 +0900 Subject: [PATCH 01/19] =?UTF-8?q?examples=20=E3=81=AE=20class=20=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/recvonly/index.html | 8 +-- examples/recvonly/main.mjs | 55 ---------------- examples/recvonly/main.mts | 109 ++++++++++++++++++++++++++++++++ examples/sendonly/index.html | 10 +-- examples/sendonly/main.mjs | 32 ---------- examples/sendonly/main.mts | 83 ++++++++++++++++++++++++ examples/vite-env.d.ts | 11 ++++ tests/sendonly_recvonly.spec.ts | 31 +++++---- 8 files changed, 229 insertions(+), 110 deletions(-) delete mode 100644 examples/recvonly/main.mjs create mode 100644 examples/recvonly/main.mts delete mode 100644 examples/sendonly/main.mjs create mode 100644 examples/sendonly/main.mts create mode 100644 examples/vite-env.d.ts diff --git a/examples/recvonly/index.html b/examples/recvonly/index.html index 7af002a9..3a4738ed 100644 --- a/examples/recvonly/index.html +++ b/examples/recvonly/index.html @@ -8,15 +8,15 @@

Recvonly test

- -
-
+ +
+
- + \ No newline at end of file diff --git a/examples/recvonly/main.mjs b/examples/recvonly/main.mjs deleted file mode 100644 index 5647e8aa..00000000 --- a/examples/recvonly/main.mjs +++ /dev/null @@ -1,55 +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 debug = false -const sora = Sora.connection(SORA_SIGNALING_URL, debug) - -const channelId = `${SORA_CHANNEL_ID_PREFIX}sendonly_recvonly${SORA_CHANNEL_ID_SUFFIX}` -const metadata = { access_token: ACCESS_TOKEN } -const options = {} - -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) { - 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/recvonly/main.mts b/examples/recvonly/main.mts new file mode 100644 index 00000000..4698e8e3 --- /dev/null +++ b/examples/recvonly/main.mts @@ -0,0 +1,109 @@ +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) + + // channel_id の生成 + this.channelId = `${channel_id_prefix}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 = '' + } + } + + 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/sendonly/index.html b/examples/sendonly/index.html index e09dcf8a..358f8e37 100644 --- a/examples/sendonly/index.html +++ b/examples/sendonly/index.html @@ -8,14 +8,14 @@

Sendonly test

- -
-
- + diff --git a/examples/sendonly/main.mjs b/examples/sendonly/main.mjs deleted file mode 100644 index 05c90ece..00000000 --- a/examples/sendonly/main.mjs +++ /dev/null @@ -1,32 +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}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 = {} - -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 }) - await sendonly.connect(mediaStream) - document.querySelector('#sendonly-local-video').srcObject = mediaStream -}) - -document.querySelector('#stop-sendonly').addEventListener('click', async () => { - await sendonly.disconnect() - document.querySelector('#sendonly-local-video').srcObject = null -}) diff --git a/examples/sendonly/main.mts b/examples/sendonly/main.mts new file mode 100644 index 00000000..cca31336 --- /dev/null +++ b/examples/sendonly/main.mts @@ -0,0 +1,83 @@ +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 }) + 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 + } + }) +}) + +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) + + // channel_id の生成 + this.channelId = `${channel_id_prefix}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) + } + + async disconnect(): Promise { + await this.connection.disconnect() + } + + 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/examples/vite-env.d.ts b/examples/vite-env.d.ts new file mode 100644 index 00000000..9fa69035 --- /dev/null +++ b/examples/vite-env.d.ts @@ -0,0 +1,11 @@ +/// + +interface ImportMetaEnv { + VITE_SORA_SIGNALING_URL: string + VITE_SORA_CHANNEL_ID: string + VITE_ACCESS_TOKEN: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/tests/sendonly_recvonly.spec.ts b/tests/sendonly_recvonly.spec.ts index a9dfc32e..30e45373 100644 --- a/tests/sendonly_recvonly.spec.ts +++ b/tests/sendonly_recvonly.spec.ts @@ -2,33 +2,36 @@ import { test } from '@playwright/test' test('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/sendonly/') - await page2.goto('http://localhost:9000/recvonly/') + await sendonly.goto('http://localhost:9000/sendonly/') + await recvonly.goto('http://localhost:9000/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() }) From a21fb76ac019b2fdd885824957e0956a8aa0d88b Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 13:51:53 +0900 Subject: [PATCH 02/19] =?UTF-8?q?sendrecv=20=E3=82=92=20class=20=E5=8C=96?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/sendrecv/index.html | 10 +-- examples/sendrecv/main.mjs | 108 -------------------------- examples/sendrecv/main.mts | 142 +++++++++++++++++++++++++++++++++++ tests/sendrecv.spec.ts | 8 +- 4 files changed, 151 insertions(+), 117 deletions(-) delete mode 100644 examples/sendrecv/main.mjs create mode 100644 examples/sendrecv/main.mts diff --git a/examples/sendrecv/index.html b/examples/sendrecv/index.html index 17411a81..e6ea1050 100644 --- a/examples/sendrecv/index.html +++ b/examples/sendrecv/index.html @@ -11,8 +11,8 @@

Sendrecv test

sendrecv1

- -
+ +
@@ -20,8 +20,8 @@

sendrecv1

sendrecv2

- -
+ +
@@ -30,7 +30,7 @@

sendrecv2

- + \ No newline at end of file diff --git a/examples/sendrecv/main.mjs b/examples/sendrecv/main.mjs deleted file mode 100644 index 8f51e842..00000000 --- a/examples/sendrecv/main.mjs +++ /dev/null @@ -1,108 +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}sendrecv${SORA_CHANNEL_ID_SUFFIX}` -const debug = false -const sora = Sora.connection(SORA_SIGNALING_URL, debug) -const metadata = { access_token: ACCESS_TOKEN } -const options = {} - -const sendrecv1 = sora.sendrecv(channelId, metadata, options) - -sendrecv1.on('notify', (event) => { - if (event.event_type === 'connection.created' && sendrecv1.connectionId === event.connection_id) { - const connectionIdElement = document.querySelector('#sendrecv1-connection-id') - connectionIdElement.textContent = event.connection_id - } -}) - -sendrecv1.on('track', (event) => { - const stream = event.streams[0] - if (!stream) return - const remoteVideoId = `sendrecv1-remotevideo-${stream.id}` - const remoteVideos = document.querySelector('#sendrecv1-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.width = '160' - remoteVideo.height = '120' - remoteVideo.srcObject = stream - remoteVideos.appendChild(remoteVideo) - } -}) - -sendrecv1.on('removetrack', (event) => { - const remoteVideo = document.querySelector(`#sendrecv1-remotevideo-${event.target.id}`) - if (remoteVideo) { - document.querySelector('#sendrecv1-remote-videos').removeChild(remoteVideo) - } -}) - -const sendrecv2 = sora.sendrecv(channelId, metadata, options) - -sendrecv2.on('notify', (event) => { - if (event.event_type === 'connection.created' && sendrecv2.connectionId === event.connection_id) { - const connectionIdElement = document.querySelector('#sendrecv2-connection-id') - connectionIdElement.textContent = event.connection_id - } -}) - -sendrecv2.on('track', (event) => { - const stream = event.streams[0] - if (!stream) return - const remoteVideoId = `sendrecv2-remotevideo-${stream.id}` - const remoteVideos = document.querySelector('#sendrecv2-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.width = '160' - remoteVideo.height = '120' - remoteVideo.srcObject = stream - remoteVideos.appendChild(remoteVideo) - } -}) - -sendrecv2.on('removetrack', (event) => { - const remoteVideo = document.querySelector(`#sendrecv2-remotevideo-${event.target.id}`) - if (remoteVideo) { - document.querySelector('#sendrecv2-remote-videos').removeChild(remoteVideo) - } -}) - -document.querySelector('#start-sendrecv1').addEventListener('click', async () => { - // sendrecv1 - const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - await sendrecv1.connect(mediaStream) - document.querySelector('#sendrecv1-local-video').srcObject = mediaStream -}) - -document.querySelector('#start-sendrecv2').addEventListener('click', async () => { - // sendrecv2 - const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - await sendrecv2.connect(mediaStream) - document.querySelector('#sendrecv2-local-video').srcObject = mediaStream -}) - -document.querySelector('#stop-sendrecv1').addEventListener('click', async () => { - await sendrecv1.disconnect() - document.querySelector('#sendrecv1-local-video').srcObject = null - document.querySelector('#sendrecv1-remote-videos').innerHTML = null -}) - -document.querySelector('#stop-sendrecv2').addEventListener('click', async () => { - await sendrecv2.disconnect() - document.querySelector('#sendrecv2-local-video').srcObject = null - document.querySelector('#sendrecv2-remote-videos').innerHTML = null -}) diff --git a/examples/sendrecv/main.mts b/examples/sendrecv/main.mts new file mode 100644 index 00000000..4b54bf74 --- /dev/null +++ b/examples/sendrecv/main.mts @@ -0,0 +1,142 @@ +import Sora, { + type SoraConnection, + type SignalingNotifyMessage, + ConnectionPublisher, +} 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 sendrecv1 = new SoraClient( + 'sendrecv1', + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + const sendrecv2 = new SoraClient( + 'sendrecv2', + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + document.querySelector('#sendrecv1-start')?.addEventListener('click', async () => { + // sendrecv1 + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await sendrecv1.connect(stream) + }) + document.querySelector('#sendrecv1-stop')?.addEventListener('click', async () => { + await sendrecv1.disconnect() + }) + + document.querySelector('#sendrecv2-start')?.addEventListener('click', async () => { + // sendrecv2 + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await sendrecv2.connect(stream) + }) + document.querySelector('#sendrecv2-stop')?.addEventListener('click', async () => { + await sendrecv2.disconnect() + }) +}) + +class SoraClient { + // sendrecv1 or sendrecv2 + private label: string + + private debug = false + + private channelId: string + private metadata: { access_token: string } + private options: object + + private sora: SoraConnection + private connection: ConnectionPublisher + + constructor( + label: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + accessToken: string, + ) { + this.label = label + + this.sora = Sora.connection(signalingUrl, this.debug) + this.channelId = `${channelIdPrefix}sendrecv${channelIdSuffix}` + this.metadata = { access_token: accessToken } + this.options = {} + + this.connection = this.sora.sendrecv(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(stream: MediaStream) { + await this.connection.connect(stream) + const localVideo = document.querySelector(`#${this.label}-local-video`) + if (localVideo) { + localVideo.srcObject = stream + } + } + + async disconnect() { + await this.connection.disconnect() + + // お掃除 + const localVideo = document.querySelector(`#${this.label}-local-video`) + if (localVideo) { + localVideo.srcObject = null + } + // お掃除 + const remoteVideos = document.querySelector(`#${this.label}-remote-videos`) + if (remoteVideos) { + remoteVideos.innerHTML = '' + } + } + + private onnotify(event: SignalingNotifyMessage): void { + if ( + event.event_type === 'connection.created' && + this.connection.connectionId === event.connection_id + ) { + const connectionIdElement = document.querySelector(`#${this.label}-connection-id`) + if (connectionIdElement) { + connectionIdElement.textContent = event.connection_id + } + } + } + + private ontrack(event: RTCTrackEvent): void { + const stream = event.streams[0] + const remoteVideoId = `${this.label}-remotevideo-${stream.id}` + const remoteVideos = document.querySelector(`#${this.label}-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.width = 160 + remoteVideo.height = 120 + remoteVideo.srcObject = stream + remoteVideos.appendChild(remoteVideo) + } + } + + private onremovetrack(event: MediaStreamTrackEvent): void { + const target = event.target as MediaStream + const remoteVideo = document.querySelector(`#${this.label}-remotevideo-${target.id}`) + if (remoteVideo) { + document.querySelector(`#${this.label}-remote-videos`)?.removeChild(remoteVideo) + } + } +} diff --git a/tests/sendrecv.spec.ts b/tests/sendrecv.spec.ts index 1116ebd3..d8e62447 100644 --- a/tests/sendrecv.spec.ts +++ b/tests/sendrecv.spec.ts @@ -3,8 +3,8 @@ import { test } from '@playwright/test' test('sendrecv x2', async ({ page }) => { await page.goto('http://localhost:9000/sendrecv/') - await page.click('#start-sendrecv1') - await page.click('#start-sendrecv2') + await page.click('#sendrecv1-start') + await page.click('#sendrecv2-start') // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ await page.waitForSelector('#sendrecv1-connection-id:not(:empty)') @@ -20,6 +20,6 @@ test('sendrecv x2', async ({ page }) => { const sendrecv2ConnectionId = await page.$eval('#sendrecv2-connection-id', (el) => el.textContent) console.log(`sendrecv2 connectionId=${sendrecv2ConnectionId}`) - await page.click('#stop-sendrecv1') - await page.click('#stop-sendrecv2') + await page.click('#sendrecv1-stop') + await page.click('#sendrecv2-stop') }) From 8276030e3fbe574585cb007230b5c15b0d05b41f Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 13:54:13 +0900 Subject: [PATCH 03/19] =?UTF-8?q?id=20=E3=82=92=20remote-video=20=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/sendrecv/main.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sendrecv/main.mts b/examples/sendrecv/main.mts index 4b54bf74..29bdf6fb 100644 --- a/examples/sendrecv/main.mts +++ b/examples/sendrecv/main.mts @@ -116,7 +116,7 @@ class SoraClient { private ontrack(event: RTCTrackEvent): void { const stream = event.streams[0] - const remoteVideoId = `${this.label}-remotevideo-${stream.id}` + const remoteVideoId = `${this.label}-remote-video-${stream.id}` const remoteVideos = document.querySelector(`#${this.label}-remote-videos`) if (remoteVideos && !remoteVideos.querySelector(`#${remoteVideoId}`)) { const remoteVideo = document.createElement('video') @@ -134,7 +134,7 @@ class SoraClient { private onremovetrack(event: MediaStreamTrackEvent): void { const target = event.target as MediaStream - const remoteVideo = document.querySelector(`#${this.label}-remotevideo-${target.id}`) + const remoteVideo = document.querySelector(`#${this.label}-remote-video-${target.id}`) if (remoteVideo) { document.querySelector(`#${this.label}-remote-videos`)?.removeChild(remoteVideo) } From 1913acab1d5e6fad8807303333ebe5f4b6f81e98 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 13:58:07 +0900 Subject: [PATCH 04/19] =?UTF-8?q?=E5=A4=A7=E6=96=87=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index cb99bb53..dc0d54bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "es2020", - "module": "es2020", + "target": "ES2020", + "module": "ES2020", "strict": true, "declaration": true, "strictNullChecks": true, From d4b00c1a06497d145422daaab16b6a59f2898232 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 15:18:33 +0900 Subject: [PATCH 05/19] =?UTF-8?q?examples=20=E3=81=AE=20simulcast=20?= =?UTF-8?q?=E3=82=92=20class=20=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/simulcast/index.html | 2 +- examples/simulcast/main.mjs | 110 ------------------- examples/simulcast/main.mts | 200 ++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 111 deletions(-) delete mode 100644 examples/simulcast/main.mjs create mode 100644 examples/simulcast/main.mts diff --git a/examples/simulcast/index.html b/examples/simulcast/index.html index 2818acf5..f2431120 100644 --- a/examples/simulcast/index.html +++ b/examples/simulcast/index.html @@ -33,7 +33,7 @@

recvonly r2

- + \ No newline at end of file diff --git a/examples/simulcast/main.mjs b/examples/simulcast/main.mjs deleted file mode 100644 index c992a0f3..00000000 --- a/examples/simulcast/main.mjs +++ /dev/null @@ -1,110 +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 debug = false -const sora = Sora.connection(SORA_SIGNALING_URL, debug) - -const channelId = `${SORA_CHANNEL_ID_PREFIX}simulcast${SORA_CHANNEL_ID_SUFFIX}` -const metadata = { access_token: ACCESS_TOKEN } - -const sendonly = sora.sendonly(channelId, metadata, { - audio: false, - videoCodecType: 'VP8', - videoBitRate: 2500, - simulcast: true, -}) -sendonly.on('notify', (event) => { - console.log(event) - if (event.event_type === 'connection.created' && event.connection_id === sendonly.connectionId) { - document.querySelector('#local-video-connection-id').textContent = `${event.connection_id}` - } -}) - -const recvonlyR0 = sora.recvonly(channelId, metadata, { - simulcast: true, - simulcastRid: 'r0', -}) -recvonlyR0.on('notify', (event) => { - if ( - event.event_type === 'connection.created' && - event.connection_id === recvonlyR0.connectionId - ) { - console.log(`recvonly-r0: ${event.connection_id}`) - document.querySelector('#remote-video-connection-id-r0').textContent = `${event.connection_id}` - } -}) -recvonlyR0.on('track', (event) => { - document.querySelector('#remote-video-r0').srcObject = event.streams[0] -}) - -const recvonlyR1 = sora.recvonly(channelId, metadata, { - simulcast: true, - simulcastRid: 'r1', -}) -recvonlyR1.on('notify', (event) => { - if ( - event.event_type === 'connection.created' && - event.connection_id === recvonlyR1.connectionId - ) { - console.log(`recvonly-r1: ${event.connection_id}`) - document.querySelector('#remote-video-connection-id-r1').textContent = `${event.connection_id}` - } -}) -recvonlyR1.on('track', (event) => { - document.querySelector('#remote-video-r1').srcObject = event.streams[0] -}) - -const recvonlyR2 = sora.recvonly(channelId, metadata, { - simulcast: true, - simulcastRid: 'r2', -}) -recvonlyR2.on('notify', (event) => { - if ( - event.event_type === 'connection.created' && - event.connection_id === recvonlyR2.connectionId - ) { - console.log(`recvonly-r2: ${event.connection_id}`) - document.querySelector('#remote-video-connection-id-r2').textContent = `${event.connection_id}` - } -}) -recvonlyR2.on('track', (event) => { - document.querySelector('#remote-video-r2').srcObject = event.streams[0] -}) - -document.querySelector('#start').addEventListener('click', async () => { - // sendonly - const mediaStream = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: { width: { exact: 1280 }, height: { exact: 720 } }, - }) - document.querySelector('#local-video').srcObject = mediaStream - await sendonly.connect(mediaStream) - - // recvonly r0 - await recvonlyR0.connect() - // recvonly r1 - await recvonlyR1.connect() - // recvonly r2 - await recvonlyR2.connect() -}) - -document.querySelector('#stop').addEventListener('click', async () => { - await sendonly.disconnect() - document.querySelector('#local-video').srcObject = null - - // recvonly r0 - await recvonlyR0.disconnect() - document.querySelector('#remote-video-r0').srcObject = null - - // recvonly r1 - await recvonlyR1.disconnect() - document.querySelector('#remote-video-r1').srcObject = null - - // recvonly r2 - await recvonlyR2.disconnect() - document.querySelector('#remote-video-r2').srcObject = null -}) diff --git a/examples/simulcast/main.mts b/examples/simulcast/main.mts new file mode 100644 index 00000000..505815b1 --- /dev/null +++ b/examples/simulcast/main.mts @@ -0,0 +1,200 @@ +import Sora, { + type SoraConnection, + type ConnectionPublisher, + type SignalingNotifyMessage, + ConnectionSubscriber, + SimulcastRid, +} 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 || '' + + const sendonly = new SimulcastSendonlySoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + const recvonlyR0 = new SimulcastRecvonlySoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + 'r0', + ) + + const recvonlyR1 = new SimulcastRecvonlySoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + 'r1', + ) + + const recvonlyR2 = new SimulcastRecvonlySoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + 'r2', + ) + + document.querySelector('#start')?.addEventListener('click', async () => { + // sendonly + const stream = await navigator.mediaDevices.getUserMedia({ + audio: false, + video: { width: { exact: 1280 }, height: { exact: 720 } }, + }) + await sendonly.connect(stream) + + // recvonly r0 + await recvonlyR0.connect() + // recvonly r1 + await recvonlyR1.connect() + // recvonly r2 + await recvonlyR2.connect() + }) + + document.querySelector('#stop')?.addEventListener('click', async () => { + await sendonly.disconnect() + + // recvonly r0 + await recvonlyR0.disconnect() + + // recvonly r1 + await recvonlyR1.disconnect() + + // recvonly r2 + await recvonlyR2.disconnect() + }) +}) + +class SimulcastSendonlySoraClient { + private debug = false + private channelId: string + + private sora: SoraConnection + private connection: ConnectionPublisher + + constructor( + signaling_url: string, + channel_id_prefix: string, + channel_id_suffix: string, + access_token: string, + ) { + this.channelId = `${channel_id_prefix}simulcast${channel_id_suffix}` + + this.sora = Sora.connection(signaling_url, this.debug) + this.connection = this.sora.sendonly( + this.channelId, + { access_token }, + { audio: false, video: true, videoCodecType: 'VP8', videoBitRate: 2500, simulcast: true }, + ) + + this.connection.on('notify', this.onnotify.bind(this)) + } + + async connect(stream: MediaStream) { + await this.connection.connect(stream) + const localVideo = document.querySelector('#local-video') + if (localVideo) { + localVideo.srcObject = stream + } + } + + async disconnect() { + await this.connection.disconnect() + const localVideo = document.querySelector('#local-video') + if (localVideo) { + localVideo.srcObject = null + } + } + + private onnotify(event: SignalingNotifyMessage) { + if ( + event.event_type === 'connection.created' && + event.connection_id === this.connection.connectionId + ) { + const localVideoConnectionId = document.querySelector('#local-video-connection-id') + if (localVideoConnectionId) { + localVideoConnectionId.textContent = `${event.connection_id}` + } + } + } +} + +class SimulcastRecvonlySoraClient { + private debug = false + + private channelId: string + private rid: SimulcastRid + + private sora: SoraConnection + private connection: ConnectionSubscriber + + constructor( + signaling_url: string, + channel_id_prefix: string, + channel_id_suffix: string, + access_token: string, + rid: SimulcastRid, + ) { + this.channelId = `${channel_id_prefix}simulcast${channel_id_suffix}` + this.rid = rid + + this.sora = Sora.connection(signaling_url, this.debug) + this.connection = this.sora.recvonly( + this.channelId, + { access_token }, + { simulcastRid: this.rid, simulcast: true }, + ) + + 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() { + await this.connection.connect() + } + + async disconnect() { + await this.connection.disconnect() + const remoteVideo = document.querySelector(`#remote-video-${this.rid}`) + if (remoteVideo) { + remoteVideo.srcObject = null + } + } + + private onnotify(event: SignalingNotifyMessage) { + if ( + event.event_type === 'connection.created' && + event.connection_id === this.connection.connectionId + ) { + const localVideoConnectionId = document.querySelector( + `#remote-video-connection-id-${this.rid}`, + ) + if (localVideoConnectionId) { + localVideoConnectionId.textContent = `${event.connection_id}` + } + } + } + + private ontrack(event: RTCTrackEvent) { + const remoteVideo = document.querySelector(`#remote-video-${this.rid}`) + if (remoteVideo) { + remoteVideo.srcObject = event.streams[0] + } + } + + private onremovetrack(event: MediaStreamTrackEvent) { + const remoteVideo = document.querySelector(`#remote-video-${this.rid}`) + if (remoteVideo) { + remoteVideo.srcObject = null + } + } +} From 8f659c88083a3e18c5a0557a757dc3ec3f5846d0 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 15:27:22 +0900 Subject: [PATCH 06/19] =?UTF-8?q?=E3=82=B3=E3=82=B9=E3=83=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/simulcast/main.mts | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/simulcast/main.mts b/examples/simulcast/main.mts index 505815b1..0e217789 100644 --- a/examples/simulcast/main.mts +++ b/examples/simulcast/main.mts @@ -64,10 +64,8 @@ document.addEventListener('DOMContentLoaded', () => { // recvonly r0 await recvonlyR0.disconnect() - // recvonly r1 await recvonlyR1.disconnect() - // recvonly r2 await recvonlyR2.disconnect() }) From fba715a1774787473af822a7de3c0e153b35c921 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 15:44:15 +0900 Subject: [PATCH 07/19] =?UTF-8?q?examples=20spotlight=20sendrecv=20?= =?UTF-8?q?=E3=81=AE=20class=20=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/spotlight_sendrecv/index.html | 10 +- examples/spotlight_sendrecv/main.mjs | 107 ------------------ examples/spotlight_sendrecv/main.mts | 148 +++++++++++++++++++++++++ tests/spotlight_sendrecv.spec.ts | 8 +- 4 files changed, 157 insertions(+), 116 deletions(-) delete mode 100644 examples/spotlight_sendrecv/main.mjs create mode 100644 examples/spotlight_sendrecv/main.mts diff --git a/examples/spotlight_sendrecv/index.html b/examples/spotlight_sendrecv/index.html index d8e8a82a..8378c140 100644 --- a/examples/spotlight_sendrecv/index.html +++ b/examples/spotlight_sendrecv/index.html @@ -11,8 +11,8 @@

Spotlight Sendrecv test

sendrecv1

- -
+ +
@@ -20,8 +20,8 @@

sendrecv1

sendrecv2

- -
+ +
@@ -30,7 +30,7 @@

sendrecv2

- + \ No newline at end of file diff --git a/examples/spotlight_sendrecv/main.mjs b/examples/spotlight_sendrecv/main.mjs deleted file mode 100644 index 7989e281..00000000 --- a/examples/spotlight_sendrecv/main.mjs +++ /dev/null @@ -1,107 +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${SORA_CHANNEL_ID_SUFFIX}` -const debug = false -const sora = Sora.connection(SORA_SIGNALING_URL, debug) -const metadata = { access_token: ACCESS_TOKEN } -const options = { - audio: true, - video: true, - simulcast: true, - spotlight: true, - spotlightNumber: 1, -} - -const sendrecv1 = sora.sendrecv(channelId, metadata, options) - -sendrecv1.on('notify', (event) => { - if (event.event_type === 'connection.created' && sendrecv1.connectionId === event.connection_id) { - const connectionIdElement = document.querySelector('#sendrecv1-connection-id') - connectionIdElement.textContent = event.connection_id - } -}) - -sendrecv1.on('track', (event) => { - const stream = event.streams[0] - if (!stream) return - const remoteVideoId = `sendrecv1-remotevideo-${stream.id}` - const remoteVideos = document.querySelector('#sendrecv1-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) - } -}) - -sendrecv1.on('removetrack', (event) => { - const remoteVideo = document.querySelector(`#sendrecv1-remotevideo-${event.target.id}`) - if (remoteVideo) { - document.querySelector('#sendrecv1-remote-videos').removeChild(remoteVideo) - } -}) - -const sendrecv2 = sora.sendrecv(channelId, metadata, options) -sendrecv2.on('notify', (event) => { - if (event.event_type === 'connection.created' && sendrecv2.connectionId === event.connection_id) { - const connectionIdElement = document.querySelector('#sendrecv2-connection-id') - connectionIdElement.textContent = event.connection_id - } -}) - -sendrecv2.on('track', (event) => { - const stream = event.streams[0] - if (!stream) return - const remoteVideoId = `sendrecv2-remotevideo-${stream.id}` - const remoteVideos = document.querySelector('#sendrecv2-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) - } -}) -sendrecv2.on('removetrack', (event) => { - const remoteVideo = document.querySelector(`#sendrecv2-remotevideo-${event.target.id}`) - if (remoteVideo) { - remoteVideo.srcObject = null - document.querySelector('#sendrecv2-remote-videos').removeChild(remoteVideo) - } -}) - -document.querySelector('#start-sendrecv1').addEventListener('click', async () => { - // sendrecv1 - const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - - document.querySelector('#sendrecv1-local-video').srcObject = mediaStream - await sendrecv1.connect(mediaStream) -}) -document.querySelector('#start-sendrecv2').addEventListener('click', async () => { - // sendrecv2 - const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - document.querySelector('#sendrecv2-local-video').srcObject = mediaStream - await sendrecv2.connect(mediaStream) -}) -document.querySelector('#stop-sendrecv1').addEventListener('click', async () => { - await sendrecv1.disconnect() - document.querySelector('#sendrecv1-local-video').srcObject = null - document.querySelector('#sendrecv1-remote-videos').innerHTML = null -}) -document.querySelector('#stop-sendrecv2').addEventListener('click', async () => { - sendrecv2.disconnect() - document.querySelector('#sendrecv2-local-video').srcObject = null - document.querySelector('#sendrecv2-remote-videos').innerHTML = null -}) diff --git a/examples/spotlight_sendrecv/main.mts b/examples/spotlight_sendrecv/main.mts new file mode 100644 index 00000000..ba149236 --- /dev/null +++ b/examples/spotlight_sendrecv/main.mts @@ -0,0 +1,148 @@ +import Sora, { + type SoraConnection, + type ConnectionPublisher, + type SignalingNotifyMessage, +} 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 sendrecv1 = new SoraClient( + 'sendrecv1', + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + const sendrecv2 = new SoraClient( + 'sendrecv2', + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + ) + + document.querySelector('#sendrecv1-start')?.addEventListener('click', async () => { + // sendrecv1 + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await sendrecv1.connect(stream) + }) + document.querySelector('#sendrecv1-stop')?.addEventListener('click', async () => { + await sendrecv1.disconnect() + }) + + document.querySelector('#sendrecv2-start')?.addEventListener('click', async () => { + // sendrecv2 + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await sendrecv2.connect(stream) + }) + document.querySelector('#sendrecv2-stop')?.addEventListener('click', async () => { + await sendrecv2.disconnect() + }) +}) + +class SoraClient { + // sendrecv1 or sendrecv2 + private label: string + + private debug = false + + private channelId: string + private metadata: { access_token: string } + private options: object + + private sora: SoraConnection + private connection: ConnectionPublisher + + constructor( + label: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + accessToken: string, + ) { + this.label = label + + this.sora = Sora.connection(signalingUrl, this.debug) + this.channelId = `${channelIdPrefix}sendrecv${channelIdSuffix}` + this.metadata = { access_token: accessToken } + this.options = { + audio: true, + video: true, + simulcast: true, + spotlight: true, + spotlightNumber: 1, + } + + this.connection = this.sora.sendrecv(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(stream: MediaStream) { + await this.connection.connect(stream) + const localVideo = document.querySelector(`#${this.label}-local-video`) + if (localVideo) { + localVideo.srcObject = stream + } + } + + async disconnect() { + await this.connection.disconnect() + + // お掃除 + const localVideo = document.querySelector(`#${this.label}-local-video`) + if (localVideo) { + localVideo.srcObject = null + } + // お掃除 + const remoteVideos = document.querySelector(`#${this.label}-remote-videos`) + if (remoteVideos) { + remoteVideos.innerHTML = '' + } + } + + private onnotify(event: SignalingNotifyMessage): void { + if ( + event.event_type === 'connection.created' && + this.connection.connectionId === event.connection_id + ) { + const connectionIdElement = document.querySelector(`#${this.label}-connection-id`) + if (connectionIdElement) { + connectionIdElement.textContent = event.connection_id + } + } + } + + private ontrack(event: RTCTrackEvent): void { + const stream = event.streams[0] + const remoteVideoId = `${this.label}-remote-video-${stream.id}` + const remoteVideos = document.querySelector(`#${this.label}-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.width = 160 + remoteVideo.height = 120 + remoteVideo.srcObject = stream + remoteVideos.appendChild(remoteVideo) + } + } + + private onremovetrack(event: MediaStreamTrackEvent): void { + const target = event.target as MediaStream + const remoteVideo = document.querySelector(`#${this.label}-remote-video-${target.id}`) + if (remoteVideo) { + document.querySelector(`#${this.label}-remote-videos`)?.removeChild(remoteVideo) + } + } +} diff --git a/tests/spotlight_sendrecv.spec.ts b/tests/spotlight_sendrecv.spec.ts index e002a38f..ad7e5470 100644 --- a/tests/spotlight_sendrecv.spec.ts +++ b/tests/spotlight_sendrecv.spec.ts @@ -3,8 +3,8 @@ import { test } from '@playwright/test' test('spotlight sendrecv x2', async ({ page }) => { await page.goto('http://localhost:9000/spotlight_sendrecv/') - await page.click('#start-sendrecv1') - await page.click('#start-sendrecv2') + await page.click('#sendrecv1-start') + await page.click('#sendrecv2-start') // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ await page.waitForSelector('#sendrecv1-connection-id:not(:empty)') @@ -20,6 +20,6 @@ test('spotlight sendrecv x2', async ({ page }) => { const sendrecv2ConnectionId = await page.$eval('#sendrecv2-connection-id', (el) => el.textContent) console.log(`sendrecv2 connectionId=${sendrecv2ConnectionId}`) - await page.click('#stop-sendrecv1') - await page.click('#stop-sendrecv2') + await page.click('#sendrecv1-stop') + await page.click('#sendrecv2-stop') }) From 421820dc428de807a5362b5b9e127ddd21f88e95 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 15:46:03 +0900 Subject: [PATCH 08/19] =?UTF-8?q?channel=5Fid=20=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/spotlight_sendrecv/main.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/spotlight_sendrecv/main.mts b/examples/spotlight_sendrecv/main.mts index ba149236..b4ff81f7 100644 --- a/examples/spotlight_sendrecv/main.mts +++ b/examples/spotlight_sendrecv/main.mts @@ -68,7 +68,7 @@ class SoraClient { this.label = label this.sora = Sora.connection(signalingUrl, this.debug) - this.channelId = `${channelIdPrefix}sendrecv${channelIdSuffix}` + this.channelId = `${channelIdPrefix}spotlight_sendrecv${channelIdSuffix}` this.metadata = { access_token: accessToken } this.options = { audio: true, From b3572afd3480cf5e1b820fade85912c73cd03016 Mon Sep 17 00:00:00 2001 From: voluntas Date: Thu, 29 Feb 2024 16:25:10 +0900 Subject: [PATCH 09/19] =?UTF-8?q?examples=20=E3=81=AE=20spotlight=20sendon?= =?UTF-8?q?ly=20/=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() }) From 61b2d7cb8d4f8b138da1734f92c710dd13392516 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:11:59 +0900 Subject: [PATCH 10/19] =?UTF-8?q?messaging=20=E3=82=92=20class=20=E3=81=AB?= =?UTF-8?q?=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/messaging/index.html | 6 +- examples/messaging/main.mjs | 53 ------------- examples/messaging/main.mts | 137 ++++++++++++++++++++++++++++++++++ tests/messaging.spec.ts | 10 +-- 4 files changed, 145 insertions(+), 61 deletions(-) delete mode 100644 examples/messaging/main.mjs create mode 100644 examples/messaging/main.mts diff --git a/examples/messaging/index.html b/examples/messaging/index.html index 17a63dee..2ed28cef 100644 --- a/examples/messaging/index.html +++ b/examples/messaging/index.html @@ -11,13 +11,13 @@

DataChannel messaging test

複数のブラウザで開いて sendMessage することで動作確認できます

-
+

-
+

messages

    @@ -27,7 +27,7 @@

    messages

- + \ No newline at end of file diff --git a/examples/messaging/main.mjs b/examples/messaging/main.mjs deleted file mode 100644 index c7b7daf7..00000000 --- a/examples/messaging/main.mjs +++ /dev/null @@ -1,53 +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 debug = true -const sora = Sora.connection(SORA_SIGNALING_URL, debug) - -const channelId = `${SORA_CHANNEL_ID_PREFIX}messaging${SORA_CHANNEL_ID_SUFFIX}` -const metadata = { access_token: ACCESS_TOKEN } -const options = { - dataChannelSignaling: true, - dataChannels: [ - { - label: '#example', - direction: 'sendrecv', - compress: true, - }, - ], -} -const recvonly = sora.recvonly(channelId, metadata, options) - -recvonly.on('notify', (event) => { - if (event.event_type === 'connection.created' && event.connection_id === recvonly.connectionId) { - document.querySelector('#local-connection-id').textContent = `${event.connection_id}` - } -}) - -recvonly.on('message', (event) => { - const message = document.createElement('li') - message.textContent = new TextDecoder().decode(event.data) - document.querySelector('#received-messages').appendChild(message) -}) -recvonly.on('disconnect', (event) => { - document.querySelector('#send-message').disabled = true -}) - -document.querySelector('#start').addEventListener('click', async () => { - await recvonly.connect() - document.querySelector('#send-message').disabled = false -}) -document.querySelector('#stop').addEventListener('click', async () => { - await recvonly.disconnect() - document.querySelector('#received-messages').innerHTML = null -}) -document.querySelector('#send-message').addEventListener('click', async () => { - const value = document.querySelector('input[name=message]').value - if (value !== '') { - recvonly.sendMessage('#example', new TextEncoder().encode(value)) - } -}) diff --git a/examples/messaging/main.mts b/examples/messaging/main.mts new file mode 100644 index 00000000..8156283a --- /dev/null +++ b/examples/messaging/main.mts @@ -0,0 +1,137 @@ +import Sora, { + type SoraConnection, + type ConnectionSubscriber, + type SignalingNotifyMessage, + DataChannelMessageEvent, +} 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 () => { + await client.connect() + }) + document.querySelector('#stop')?.addEventListener('click', async () => { + await client.disconnect() + }) + document.querySelector('#send-message')?.addEventListener('click', () => { + const value = document.querySelector('input[name=message]')?.value + if (value !== undefined && value !== '') { + client.send_message(value) + } + }) +}) + +class SoraClient { + private debug = false + + private channelId: string + private metadata: { access_token: string } + private options: object + + private sora: SoraConnection + private connection: ConnectionSubscriber + constructor( + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + accessToken: string, + ) { + this.sora = Sora.connection(signalingUrl, this.debug) + this.channelId = `${channelIdPrefix}messaging${channelIdSuffix}` + this.metadata = { access_token: accessToken } + + this.options = { + dataChannelSignaling: true, + dataChannels: [ + { + label: '#example', + direction: 'sendrecv', + compress: true, + }, + ], + } + + this.connection = this.sora.recvonly(this.channelId, this.metadata, this.options) + + this.connection.on('notify', this.onnotify.bind(this)) + this.connection.on('message', this.onmessage.bind(this)) + } + + async connect() { + // start ボタンを無効にする + const startButton = document.querySelector('#start') + if (startButton) { + startButton.disabled = true + } + + await this.connection.connect() + + // stop ボタンを有効にする + const stopButton = document.querySelector('#stop') + if (stopButton) { + stopButton.disabled = false + } + } + + async disconnect() { + await this.connection.disconnect() + + // start ボタンを有効にする + const startButton = document.querySelector('#start') + if (startButton) { + startButton.disabled = false + } + + // stop ボタンを無効にする + const stopButton = document.querySelector('#stop') + if (stopButton) { + stopButton.disabled = true + } + + const receivedMessagesElement = document.querySelector('#received-messages') + if (receivedMessagesElement) { + receivedMessagesElement.innerHTML = '' + } + } + + send_message(message: string) { + if (message !== '') { + this.connection.sendMessage('#example', new TextEncoder().encode(message)) + } + } + + 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 + } + + // 送信ボタンを有効にする + const sendMessageButton = document.querySelector('#send-message') + if (sendMessageButton) { + sendMessageButton.disabled = false + } + } + } + + private onmessage(event: DataChannelMessageEvent) { + const message = document.createElement('li') + message.textContent = new TextDecoder().decode(event.data) + document.querySelector('#received-messages')?.appendChild(message) + } +} diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index a4455161..9a02ec3f 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test' -test.skip('messaging pages', async ({ browser }) => { +test('messaging pages', async ({ browser }) => { // 新しいページを2つ作成 const page1 = await browser.newPage() const page2 = await browser.newPage() @@ -12,12 +12,12 @@ test.skip('messaging pages', async ({ browser }) => { await page1.click('#start') await page2.click('#start') - await page1.waitForSelector('#local-connection-id:not(:empty)') - const page1ConnectionId = await page1.$eval('#local-connection-id', (el) => el.textContent) + await page1.waitForSelector('#connection-id:not(:empty)') + const page1ConnectionId = await page1.$eval('#connection-id', (el) => el.textContent) console.log(`page1 connectionId=${page1ConnectionId}`) - await page2.waitForSelector('#local-connection-id:not(:empty)') - const page2ConnectionId = await page2.$eval('#local-connection-id', (el) => el.textContent) + await page2.waitForSelector('#connection-id:not(:empty)') + const page2ConnectionId = await page2.$eval('#connection-id', (el) => el.textContent) console.log(`page2 connectionId=${page2ConnectionId}`) // page1からpage2へメッセージを送信 From b2d84db628763a8ebd20d18d6e7ca12669905f02 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:14:02 +0900 Subject: [PATCH 11/19] =?UTF-8?q?github=20actions=20pnpm=20v3=20=E3=81=AB?= =?UTF-8?q?=E4=B8=8A=E3=81=92=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d37107c1..ad4f6492 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v3 with: version: 8 - run: pnpm --version From d36e49abc2de28ecd000c7294cc27665669e9983 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:22:26 +0900 Subject: [PATCH 12/19] =?UTF-8?q?=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB?= =?UTF-8?q?=E3=82=92class=E3=83=99=E3=83=BC=E3=82=B9=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=97=E3=80=81.env.template=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c4e44803..30fd6c84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,11 +14,6 @@ - [CHANGE] stopAudioTrack と stopVideoTrack を非推奨にする - 代わりに名前を変えただけの removeAudioTrack と removeVideoTrack を用意する - @voluntas - -## 2023.2.1 - -**テストとサンプルのみの変更であり、SDK の変更はありません** - - [CHANGE] examples を Vite を利用して動かすように変更する - serve を削除 - Vite を追加 @@ -27,6 +22,10 @@ - [CHANGE] deploy-pages.yml を削除する - E2E テストで実行できるようになったので - @voluntas +- [CHANGE] サンプルを class ベースに変更する + - @voluntas +- [ADD] サンプル用の .env.template を用意する + - @voluntas - [ADD] tests に Playwright を利用した E2E テストを追加する - e2ee と messaging は一旦 skip で追加 - @voluntas From dc8200e1c3f74a90df1e3619d850ad28ca8c1f20 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:33:49 +0900 Subject: [PATCH 13/19] =?UTF-8?q?=E9=80=81=E4=BF=A1=E3=81=BE=E3=81=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/messaging.spec.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index 9a02ec3f..a9fa32cc 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -24,25 +24,25 @@ test('messaging pages', async ({ browser }) => { await page1.fill('input[name="message"]', 'Hello from page1') await page1.click('#send-message') - // page2でメッセージが受信されたことを確認 - await page2.waitForSelector('#received-messages li', { state: 'attached' }) - const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) + // // page2でメッセージが受信されたことを確認 + // await page2.waitForSelector('#received-messages li', { state: 'attached' }) + // const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) - // 受信したメッセージが期待したものであるか検証 - console.log(`Received message on page2: ${receivedMessage1}`) - test.expect(receivedMessage1).toBe('Hello from page1') + // // 受信したメッセージが期待したものであるか検証 + // console.log(`Received message on page2: ${receivedMessage1}`) + // test.expect(receivedMessage1).toBe('Hello from page1') - // page2からpage1へメッセージを送信 - await page2.fill('input[name="message"]', 'Hello from page2') - await page2.click('#send-message') + // // page2からpage1へメッセージを送信 + // await page2.fill('input[name="message"]', 'Hello from page2') + // await page2.click('#send-message') - // page1でメッセージが受信されたことを確認 - await page1.waitForSelector('li', { state: 'attached' }) - const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) + // // page1でメッセージが受信されたことを確認 + // await page1.waitForSelector('li', { state: 'attached' }) + // const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) - // 受信したメッセージが期待したものであるか検証 - console.log(`Received message on page1: ${receivedMessage2}`) - test.expect(receivedMessage2).toBe('Hello from page2') + // // 受信したメッセージが期待したものであるか検証 + // console.log(`Received message on page1: ${receivedMessage2}`) + // test.expect(receivedMessage2).toBe('Hello from page2') await page1.click('#stop') await page2.click('#stop') From 2fb9e5059c8a1649f7e17e9a57b1ef3d227957ec Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:43:25 +0900 Subject: [PATCH 14/19] =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=81=BE=E3=81=A7=E5=AE=9F=E8=A3=85=E3=81=97=E3=81=A6=E3=81=BF?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/messaging.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index a9fa32cc..db6d1db8 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -24,9 +24,9 @@ test('messaging pages', async ({ browser }) => { await page1.fill('input[name="message"]', 'Hello from page1') await page1.click('#send-message') - // // page2でメッセージが受信されたことを確認 - // await page2.waitForSelector('#received-messages li', { state: 'attached' }) - // const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) + // page2でメッセージが受信されたことを確認 + await page2.waitForSelector('#received-messages li', { state: 'attached' }) + const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page2: ${receivedMessage1}`) From ca330235563ea91715dfe8c0b56aa35fbf570a21 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:46:31 +0900 Subject: [PATCH 15/19] =?UTF-8?q?page2=20=E3=81=8B=E3=82=89=E3=82=82?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E9=80=81?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=BF=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/messaging.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index db6d1db8..c0014e4c 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -32,13 +32,13 @@ test('messaging pages', async ({ browser }) => { // console.log(`Received message on page2: ${receivedMessage1}`) // test.expect(receivedMessage1).toBe('Hello from page1') - // // page2からpage1へメッセージを送信 - // await page2.fill('input[name="message"]', 'Hello from page2') - // await page2.click('#send-message') + // page2からpage1へメッセージを送信 + await page2.fill('input[name="message"]', 'Hello from page2') + await page2.click('#send-message') - // // page1でメッセージが受信されたことを確認 - // await page1.waitForSelector('li', { state: 'attached' }) - // const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) + // page1でメッセージが受信されたことを確認 + await page1.waitForSelector('li', { state: 'attached' }) + const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page1: ${receivedMessage2}`) From e8abaded6094def5de5fe74a64a6b4fcc02f20af Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:51:22 +0900 Subject: [PATCH 16/19] =?UTF-8?q?waitForFunction=20=E4=BD=BF=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=BF=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/messaging.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index c0014e4c..eb0ff85c 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -25,20 +25,20 @@ test('messaging pages', async ({ browser }) => { await page1.click('#send-message') // page2でメッセージが受信されたことを確認 - await page2.waitForSelector('#received-messages li', { state: 'attached' }) - const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) + await page2.waitForFunction(() => document.querySelectorAll('#received-messages li').length > 0) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page2: ${receivedMessage1}`) // test.expect(receivedMessage1).toBe('Hello from page1') - // page2からpage1へメッセージを送信 - await page2.fill('input[name="message"]', 'Hello from page2') - await page2.click('#send-message') + // // page2からpage1へメッセージを送信 + // await page2.fill('input[name="message"]', 'Hello from page2') + // await page2.click('#send-message') - // page1でメッセージが受信されたことを確認 - await page1.waitForSelector('li', { state: 'attached' }) - const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) + // // page1でメッセージが受信されたことを確認 + await page1.waitForFunction(() => document.querySelectorAll('#received-messages li').length > 0) + // await page1.waitForSelector('li', { state: 'attached' }) + // const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page1: ${receivedMessage2}`) From 4b84bfaef274053b6f535cbc79fcfe7d0b21afb8 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 11:59:36 +0900 Subject: [PATCH 17/19] =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E4=BB=A5=E5=A4=96=E3=82=92=E8=A1=8C=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/messaging.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index eb0ff85c..ca1db36f 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -25,20 +25,20 @@ test('messaging pages', async ({ browser }) => { await page1.click('#send-message') // page2でメッセージが受信されたことを確認 - await page2.waitForFunction(() => document.querySelectorAll('#received-messages li').length > 0) + await page2.waitForSelector('#received-messages li', { state: 'attached' }) + const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page2: ${receivedMessage1}`) // test.expect(receivedMessage1).toBe('Hello from page1') // // page2からpage1へメッセージを送信 - // await page2.fill('input[name="message"]', 'Hello from page2') - // await page2.click('#send-message') + await page2.fill('input[name="message"]', 'Hello from page2') + await page2.click('#send-message') - // // page1でメッセージが受信されたことを確認 - await page1.waitForFunction(() => document.querySelectorAll('#received-messages li').length > 0) - // await page1.waitForSelector('li', { state: 'attached' }) - // const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) + // page1でメッセージが受信されたことを確認 + await page1.waitForSelector('li', { state: 'attached' }) + const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page1: ${receivedMessage2}`) From 365c8032e7c55357dfe84be1aada1e94f9337825 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 12:10:34 +0900 Subject: [PATCH 18/19] =?UTF-8?q?skip=20=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/messaging.spec.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts index ca1db36f..db1e9876 100644 --- a/tests/messaging.spec.ts +++ b/tests/messaging.spec.ts @@ -1,6 +1,6 @@ import { test } from '@playwright/test' -test('messaging pages', async ({ browser }) => { +test.skip('messaging pages', async ({ browser }) => { // 新しいページを2つ作成 const page1 = await browser.newPage() const page2 = await browser.newPage() @@ -21,28 +21,30 @@ test('messaging pages', async ({ browser }) => { console.log(`page2 connectionId=${page2ConnectionId}`) // page1からpage2へメッセージを送信 - await page1.fill('input[name="message"]', 'Hello from page1') + const page1Message = 'Hello from page1' + await page1.fill('input[name="message"]', page1Message) await page1.click('#send-message') // page2でメッセージが受信されたことを確認 - await page2.waitForSelector('#received-messages li', { state: 'attached' }) - const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) + // await page2.waitForSelector('#received-messages li', { state: 'attached' }) + // const receivedMessage1 = await page2.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page2: ${receivedMessage1}`) - // test.expect(receivedMessage1).toBe('Hello from page1') + // test.expect(receivedMessage1).toBe(page1Message) // // page2からpage1へメッセージを送信 - await page2.fill('input[name="message"]', 'Hello from page2') - await page2.click('#send-message') + // const page2Message = 'Hello from page2' + // await page2.fill('input[name="message"]', page2Message) + // await page2.click('#send-message') - // page1でメッセージが受信されたことを確認 - await page1.waitForSelector('li', { state: 'attached' }) - const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) + // // page1でメッセージが受信されたことを確認 + // await page1.waitForSelector('li', { state: 'attached' }) + // const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) // // 受信したメッセージが期待したものであるか検証 // console.log(`Received message on page1: ${receivedMessage2}`) - // test.expect(receivedMessage2).toBe('Hello from page2') + // test.expect(receivedMessage2).toBe(page2Message) await page1.click('#stop') await page2.click('#stop') From 2da8907cc4db07a0cc98c9a0d12402ed76fed4e1 Mon Sep 17 00:00:00 2001 From: voluntas Date: Fri, 1 Mar 2024 18:15:49 +0900 Subject: [PATCH 19/19] =?UTF-8?q?e2ee=20=E3=82=82=20class=20=E5=8C=96?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/e2ee/index.html | 10 +-- examples/e2ee/main.mjs | 88 --------------------- examples/e2ee/main.mts | 167 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 93 deletions(-) delete mode 100644 examples/e2ee/main.mjs create mode 100644 examples/e2ee/main.mts diff --git a/examples/e2ee/index.html b/examples/e2ee/index.html index b88a4a64..35d6d8ac 100644 --- a/examples/e2ee/index.html +++ b/examples/e2ee/index.html @@ -12,20 +12,20 @@

E2EE test

local

- -
+ +
-

remote

-
+
- + \ No newline at end of file diff --git a/examples/e2ee/main.mjs b/examples/e2ee/main.mjs deleted file mode 100644 index 6ccf00e8..00000000 --- a/examples/e2ee/main.mjs +++ /dev/null @@ -1,88 +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 debug = false -Sora.initE2EE('https://sora-e2ee-wasm.shiguredo.app/2020.2/wasm.wasm').catch((e) => { - document.querySelector('#error-message').textContent = - 'E2EE用 wasm ファイルの読み込みに失敗しました' -}) - -const sora = Sora.connection(SORA_SIGNALING_URL, debug) - -const channelId = `${SORA_CHANNEL_ID_PREFIX}e2ee${SORA_CHANNEL_ID_SUFFIX}` -const metadata = { access_token: ACCESS_TOKEN } -const options = { - e2ee: true, -} - -const sendrecv = sora.sendrecv(channelId, metadata, options) - -sendrecv.on('track', (event) => { - const stream = event.streams[0] - if (!stream) { - return - } - const remoteVideoBoxId = `remote-video-box-${stream.id}` - const remoteVideos = document.querySelector('#sendrecv-remote-videos') - if (!remoteVideos.querySelector(`#${remoteVideoBoxId}`)) { - const remoteVideoBox = document.createElement('div') - remoteVideoBox.id = remoteVideoBoxId - const connectionIdElement = document.createElement('div') - connectionIdElement.id = `${remoteVideoBoxId}-connection-id` - connectionIdElement.textContent = `connectionId: ${stream.id}` - remoteVideoBox.appendChild(connectionIdElement) - const fingerprintElement = document.createElement('div') - fingerprintElement.id = `${remoteVideoBoxId}-fingerprint` - remoteVideoBox.appendChild(fingerprintElement) - const remoteVideo = document.createElement('video') - remoteVideo.style.border = '1px solid red' - remoteVideo.autoplay = true - remoteVideo.playsinline = true - remoteVideo.controls = true - remoteVideo.srcObject = stream - remoteVideoBox.appendChild(remoteVideo) - remoteVideos.appendChild(remoteVideoBox) - } -}) -sendrecv.on('removetrack', (event) => { - const remoteVideo = document.querySelector(`#remote-video-box-${event.target.id}`) - if (remoteVideo) { - document.querySelector('#sendrecv-remote-videos').removeChild(remoteVideo) - } -}) -sendrecv.on('notify', (event) => { - console.log(event) - if (event.event_type === 'connection.created' && event.connection_id === sendrecv.connectionId) { - document.querySelector('#local-connection-id').textContent = `${sendrecv.connectionId}` - document.querySelector('#local-fingerprint').textContent = `${sendrecv.e2eeSelfFingerprint}` - } - - if (event.event_type === 'connection.created') { - const remoteFingerprints = sendrecv.e2eeRemoteFingerprints - // biome-ignore lint/complexity/noForEach: - Object.keys(remoteFingerprints).forEach((connectionId) => { - const fingerprintElement = document.querySelector( - `#remote-video-box-${connectionId}-fingerprint`, - ) - if (fingerprintElement) { - fingerprintElement.textContent = `fingerprint: ${remoteFingerprints[connectionId]}` - } - }) - } -}) - -document.querySelector('#start-sendrecv').addEventListener('click', async () => { - const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true }) - await sendrecv.connect(mediaStream) - document.querySelector('#sendrecv-local-video').srcObject = mediaStream -}) - -document.querySelector('#stop-sendrecv').addEventListener('click', async () => { - await sendrecv.disconnect() - document.querySelector('#sendrecv-local-video').srcObject = null - document.querySelector('#sendrecv-remote-videos').innerHTML = null -}) diff --git a/examples/e2ee/main.mts b/examples/e2ee/main.mts new file mode 100644 index 00000000..40aaabf8 --- /dev/null +++ b/examples/e2ee/main.mts @@ -0,0 +1,167 @@ +import Sora, { + type SoraConnection, + type SignalingNotifyMessage, + ConnectionPublisher, +} 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 || '' + + await Sora.initE2EE('https://sora-e2ee-wasm.shiguredo.app/2020.2/wasm.wasm').catch((e) => { + const errorMessageElement = document.querySelector('#error-message') + if (errorMessageElement) { + errorMessageElement.textContent = 'E2EE用 wasm ファイルの読み込みに失敗しました' + } + }) + + 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({ audio: true, video: 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( + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + accessToken: string, + ) { + this.sora = Sora.connection(signalingUrl, this.debug) + this.channelId = `${channelIdPrefix}e2ee${channelIdSuffix}` + this.metadata = { access_token: accessToken } + this.options = { + e2ee: true, + } + + this.connection = this.sora.sendrecv(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(stream: MediaStream) { + await this.connection.connect(stream) + const localVideo = document.querySelector('#local-video') + if (localVideo) { + localVideo.srcObject = stream + } + } + + async disconnect() { + await this.connection.disconnect() + + // お掃除 + const localVideo = document.querySelector('#local-video') + if (localVideo) { + localVideo.srcObject = null + } + + const localConnectionId = document.querySelector('#local-connection-id') + if (localConnectionId) { + localConnectionId.textContent = '' + } + + const remoteVideos = document.querySelector('#remote-videos') + if (remoteVideos) { + remoteVideos.innerHTML = '' + } + } + + private onnotify(event: SignalingNotifyMessage): void { + if ( + event.event_type === 'connection.created' && + event.connection_id === this.connection.connectionId + ) { + const localConnectionId = document.querySelector('#local-connection-id') + if (localConnectionId) { + localConnectionId.textContent = this.connection.connectionId + } + + const localFingerprint = document.querySelector('#local-fingerprint') + if (localFingerprint) { + localFingerprint.textContent = this.connection.e2eeSelfFingerprint || null + } + } + + if (event.event_type === 'connection.created') { + const remoteFingerprints = this.connection.e2eeRemoteFingerprints || {} + Object.keys(remoteFingerprints).filter((connectionId) => { + const fingerprintElement = document.querySelector( + `#remote-video-box-${connectionId}-fingerprint`, + ) + if (fingerprintElement) { + fingerprintElement.textContent = `fingerprint: ${remoteFingerprints[connectionId]}` + } + }) + } + } + + private ontrack(event: RTCTrackEvent): void { + const stream = event.streams[0] + /* +
+
connectionId: ${stream.id} +
fingerprint: ${stream.id} + +
+ */ + + const remoteVideoBoxId = `remote-video-box-${stream.id}` + const remoteVideos = document.querySelector('#remote-videos') + if (remoteVideos && !remoteVideos.querySelector(`#${remoteVideoBoxId}`)) { + //
を作る + const remoteVideoBox = document.createElement('div') + remoteVideoBox.id = remoteVideoBoxId + //
を作る + const connectionIdElement = document.createElement('div') + connectionIdElement.id = `${remoteVideoBoxId}-connection-id` + connectionIdElement.textContent = `connectionId: ${stream.id}` + remoteVideoBox.appendChild(connectionIdElement) + //
を作る + const fingerprintElement = document.createElement('div') + fingerprintElement.id = `${remoteVideoBoxId}-fingerprint` + remoteVideoBox.appendChild(fingerprintElement) + //