From c764f5f151d2f77f2fa4ba775152373b67fa75d0 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 16:01:46 +0900 Subject: [PATCH 01/16] =?UTF-8?q?examples=20=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- e2e-tests/index.html | 3 - examples/README.md | 24 ++ .../check_stereo/index.html | 0 {e2e-tests => examples}/check_stereo/main.mts | 0 .../check_stereo_multi/index.html | 0 .../check_stereo_multi/main.mts | 0 examples/index.html | 29 +++ examples/messaging/index.html | 48 ++++ examples/messaging/main.mts | 213 ++++++++++++++++ examples/package.json | 11 + examples/recvonly/index.html | 26 ++ examples/recvonly/main.mts | 147 +++++++++++ .../replace_track/index.html | 0 .../replace_track/main.mts | 0 examples/sendonly/index.html | 25 ++ examples/sendonly/main.mts | 134 ++++++++++ examples/sendrecv/index.html | 41 ++++ examples/sendrecv/main.mts | 184 ++++++++++++++ examples/simulcast/index.html | 43 ++++ examples/simulcast/main.mts | 230 ++++++++++++++++++ examples/spotlight_sendrecv/index.html | 36 +++ examples/spotlight_sendrecv/main.mts | 148 +++++++++++ examples/tests/helper.ts | 13 + examples/tests/message_header.spec.ts | 184 ++++++++++++++ examples/tests/messaging.spec.ts | 197 +++++++++++++++ examples/tests/sendonly_audio.spec.ts | 75 ++++++ examples/tests/sendonly_recvonly.spec.ts | 125 ++++++++++ examples/tests/sendrecv.spec.ts | 135 ++++++++++ examples/tests/simulcast.spec.ts | 78 ++++++ .../tests/spotlight_sendonly_recvonly.spec.ts | 34 +++ examples/tests/spotlight_sendrecv.spec.ts | 25 ++ examples/tests/type_close.spec.ts | 60 +++++ examples/tests/type_switched.spec.ts | 56 +++++ examples/tests/whip_whep.spec.ts | 42 ++++ examples/tsconfig.json | 23 ++ examples/vite-env.d.ts | 16 ++ examples/vite.config.mjs | 30 +++ package.json | 3 +- 38 files changed, 2434 insertions(+), 4 deletions(-) create mode 100644 examples/README.md rename {e2e-tests => examples}/check_stereo/index.html (100%) rename {e2e-tests => examples}/check_stereo/main.mts (100%) rename {e2e-tests => examples}/check_stereo_multi/index.html (100%) rename {e2e-tests => examples}/check_stereo_multi/main.mts (100%) create mode 100644 examples/index.html create mode 100644 examples/messaging/index.html create mode 100644 examples/messaging/main.mts create mode 100644 examples/package.json create mode 100644 examples/recvonly/index.html create mode 100644 examples/recvonly/main.mts rename {e2e-tests => examples}/replace_track/index.html (100%) rename {e2e-tests => examples}/replace_track/main.mts (100%) create mode 100644 examples/sendonly/index.html create mode 100644 examples/sendonly/main.mts create mode 100644 examples/sendrecv/index.html create mode 100644 examples/sendrecv/main.mts create mode 100644 examples/simulcast/index.html create mode 100644 examples/simulcast/main.mts create mode 100644 examples/spotlight_sendrecv/index.html create mode 100644 examples/spotlight_sendrecv/main.mts create mode 100644 examples/tests/helper.ts create mode 100644 examples/tests/message_header.spec.ts create mode 100644 examples/tests/messaging.spec.ts create mode 100644 examples/tests/sendonly_audio.spec.ts create mode 100644 examples/tests/sendonly_recvonly.spec.ts create mode 100644 examples/tests/sendrecv.spec.ts create mode 100644 examples/tests/simulcast.spec.ts create mode 100644 examples/tests/spotlight_sendonly_recvonly.spec.ts create mode 100644 examples/tests/spotlight_sendrecv.spec.ts create mode 100644 examples/tests/type_close.spec.ts create mode 100644 examples/tests/type_switched.spec.ts create mode 100644 examples/tests/whip_whep.spec.ts create mode 100644 examples/tsconfig.json create mode 100644 examples/vite-env.d.ts create mode 100644 examples/vite.config.mjs diff --git a/e2e-tests/index.html b/e2e-tests/index.html index 6062d99e..a91a7c61 100644 --- a/e2e-tests/index.html +++ b/e2e-tests/index.html @@ -11,9 +11,6 @@
  • 配信視聴サンプル
  • 配信サンプル
  • 視聴サンプル
  • -
  • ステレオチェックサンプル
  • -
  • ステレオチェックサンプル(マルチストリーム)
  • -
  • track 入れ替えサンプル
  • スポットライト配信視聴サンプル
  • スポットライト配信サンプル
  • スポットライト視聴サンプル
  • diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..a9739058 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,24 @@ +# Sora JavaScript SDK E2E テスト + +## 使い方 + +```bash +$ git clone git@github.com:shiguredo/sora-js-sdk.git +$ cd sora-js-sdk +# .env.local を作成して適切な値を設定してください +$ cp .env.template .env.local +$ pnpm install +$ pnpm build +$ pnpm e2e-test +``` + +## WHIP/WHEP + +SDK では対応していないブラウザレベルでの WHIP/WHEP の E2E テストを用意しています。 + +このサンプルは Chrome / Edge でのみ動作します。 + +WHIP/WHEP を利用するには whip/whep が有効になっており、 +whip_turn/whep_turn も有効になっている必要があります。 + +さらにブラウザから fetch する場合は CORS の設定が必要です。 diff --git a/e2e-tests/check_stereo/index.html b/examples/check_stereo/index.html similarity index 100% rename from e2e-tests/check_stereo/index.html rename to examples/check_stereo/index.html diff --git a/e2e-tests/check_stereo/main.mts b/examples/check_stereo/main.mts similarity index 100% rename from e2e-tests/check_stereo/main.mts rename to examples/check_stereo/main.mts diff --git a/e2e-tests/check_stereo_multi/index.html b/examples/check_stereo_multi/index.html similarity index 100% rename from e2e-tests/check_stereo_multi/index.html rename to examples/check_stereo_multi/index.html diff --git a/e2e-tests/check_stereo_multi/main.mts b/examples/check_stereo_multi/main.mts similarity index 100% rename from e2e-tests/check_stereo_multi/main.mts rename to examples/check_stereo_multi/main.mts diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 00000000..6062d99e --- /dev/null +++ b/examples/index.html @@ -0,0 +1,29 @@ + + + + + Sora JavaScript SDK example + + + +
    + +
    + + + \ No newline at end of file diff --git a/examples/messaging/index.html b/examples/messaging/index.html new file mode 100644 index 00000000..ce77e563 --- /dev/null +++ b/examples/messaging/index.html @@ -0,0 +1,48 @@ + + + + + Messaging + + + +
    +

    DataChannel messaging test

    +

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

    +

    sora-js-sdk version:

    + +
    +
    +
    + +
    +
    +
    + + +
    +
    + + +
    + + +
    +
    +

    messaging

    +
      +
    +
    +
    +

    messages

    +
      +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/examples/messaging/main.mts b/examples/messaging/main.mts new file mode 100644 index 00000000..411418ca --- /dev/null +++ b/examples/messaging/main.mts @@ -0,0 +1,213 @@ +import Sora, { + type SoraConnection, + type ConnectionMessaging, + type SignalingNotifyMessage, + type DataChannelMessageEvent, + type DataChannelEvent, +} from 'sora-js-sdk' + +const getChannelName = (): string => { + const channelNameElement = document.querySelector('#channel-name') + const channelName = channelNameElement?.value + if (channelName === '' || channelName === undefined) { + throw new Error('channelName is empty') + } + return channelName +} + +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 soraJsSdkVersion = Sora.version() + const soraJsSdkVersionElement = document.getElementById('sora-js-sdk-version') + if (soraJsSdkVersionElement) { + soraJsSdkVersionElement.textContent = soraJsSdkVersion + } + + let client: SoraClient + + document.querySelector('#connect')?.addEventListener('click', async () => { + const channelName = getChannelName() + client = new SoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + channelName, + ) + const checkCompress = document.getElementById('check-compress') as HTMLInputElement + const compress = checkCompress.checked + const checkHeader = document.getElementById('check-header') as HTMLInputElement + const header = checkHeader.checked + + await client.connect(compress, header) + }) + document.querySelector('#disconnect')?.addEventListener('click', async () => { + await client.disconnect() + }) + document.querySelector('#send-message')?.addEventListener('click', async () => { + const value = document.querySelector('input[name=message]')?.value + if (value !== undefined && value !== '') { + await client.sendMessage(value) + } + }) + + document.querySelector('#get-stats')?.addEventListener('click', async () => { + const statsReport = await client.getStats() + const statsDiv = document.querySelector('#stats-report') as HTMLElement + const statsReportJsonDiv = document.querySelector('#stats-report-json') + if (statsDiv && statsReportJsonDiv) { + let statsHtml = '' + const statsReportJson: Record[] = [] + for (const report of statsReport.values()) { + statsHtml += `

    Type: ${report.type}

      ` + const reportJson: Record = { id: report.id, type: report.type } + for (const [key, value] of Object.entries(report)) { + if (key !== 'type' && key !== 'id') { + statsHtml += `
    • ${key}: ${value}
    • ` + reportJson[key] = value + } + } + statsHtml += '
    ' + statsReportJson.push(reportJson) + } + statsDiv.innerHTML = statsHtml + // データ属性としても保存(オプション) + statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson) + } + }) +}) + +class SoraClient { + private debug = false + + private channelId: string + private metadata: { access_token: string } + private options: object + + private sora: SoraConnection + private connection: ConnectionMessaging + constructor( + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + accessToken: string, + channelName: string, + ) { + this.sora = Sora.connection(signalingUrl, this.debug) + this.channelId = `${channelIdPrefix}${channelName}${channelIdSuffix}` + this.metadata = { access_token: accessToken } + + this.options = { + dataChannelSignaling: true, + dataChannels: [ + { + label: '#example', + direction: 'sendrecv', + compress: true, + }, + ], + } + + this.connection = this.sora.messaging(this.channelId, this.metadata, this.options) + + this.connection.on('notify', this.onnotify.bind(this)) + this.connection.on('datachannel', this.ondatachannel.bind(this)) + this.connection.on('message', this.onmessage.bind(this)) + } + + async connect(compress: boolean, header: boolean) { + // connect ボタンを無効にする + const connectButton = document.querySelector('#connect') + if (connectButton) { + connectButton.disabled = true + } + + // dataChannels の compress の設定を上書きする + this.connection.options.dataChannels = [ + { + label: '#example', + direction: 'sendrecv', + compress: compress, + // header が true の場合は sender_connection_id を追加 + header: header ? [{ type: 'sender_connection_id' }] : undefined, + }, + ] + await this.connection.connect() + + // disconnect ボタンを有効にする + const disconnectButton = document.querySelector('#disconnect') + if (disconnectButton) { + disconnectButton.disabled = false + } + } + + async disconnect() { + await this.connection.disconnect() + + // connect ボタンを有効にする + const connectButton = document.querySelector('#connect') + if (connectButton) { + connectButton.disabled = false + } + + // disconnect ボタンを無効にする + const disconnectButton = document.querySelector('#disconnect') + if (disconnectButton) { + disconnectButton.disabled = true + } + + const receivedMessagesElement = document.querySelector('#received-messages') + if (receivedMessagesElement) { + receivedMessagesElement.innerHTML = '' + } + } + + getStats(): Promise { + if (this.connection.pc === null) { + return Promise.reject(new Error('PeerConnection is not ready')) + } + return this.connection.pc.getStats() + } + + async sendMessage(message: string) { + if (message !== '') { + await 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 ondatachannel(event: DataChannelEvent) { + const openDataChannel = document.createElement('li') + openDataChannel.textContent = new TextDecoder().decode( + new TextEncoder().encode(event.datachannel.label), + ) + document.querySelector('#messaging')?.appendChild(openDataChannel) + } + + 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/examples/package.json b/examples/package.json new file mode 100644 index 00000000..97f6e2a5 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,11 @@ +{ + "name": "sora-js-sdk-examples", + "scripts": { + "lint": "biome lint .", + "fmt": "biome format --write .", + "check": "tsc --noEmit" + }, + "dependencies": { + "sora-js-sdk": "workspace:*" + } +} diff --git a/examples/recvonly/index.html b/examples/recvonly/index.html new file mode 100644 index 00000000..efa98655 --- /dev/null +++ b/examples/recvonly/index.html @@ -0,0 +1,26 @@ + + + + + Recvonly test + + + +
    +

    Recvonly test

    +

    + + +
    +
    +
    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/examples/recvonly/main.mts b/examples/recvonly/main.mts new file mode 100644 index 00000000..5193a186 --- /dev/null +++ b/examples/recvonly/main.mts @@ -0,0 +1,147 @@ +import Sora, { + type SoraConnection, + type SignalingNotifyMessage, + type ConnectionSubscriber, +} from 'sora-js-sdk' + +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, + ) + + // SDK バージョンの表示 + const sdkVersionElement = document.querySelector('#sdk-version') + if (sdkVersionElement) { + sdkVersionElement.textContent = `${Sora.version()}` + } + + document.querySelector('#connect')?.addEventListener('click', async () => { + await client.connect() + }) + + document.querySelector('#disconnect')?.addEventListener('click', async () => { + await client.disconnect() + }) + + document.querySelector('#get-stats')?.addEventListener('click', async () => { + const statsReport = await client.getStats() + const statsDiv = document.querySelector('#stats-report') as HTMLElement + const statsReportJsonDiv = document.querySelector('#stats-report-json') + if (statsDiv && statsReportJsonDiv) { + let statsHtml = '' + const statsReportJson: Record[] = [] + for (const report of statsReport.values()) { + statsHtml += `

    Type: ${report.type}

      ` + const reportJson: Record = { id: report.id, type: report.type } + for (const [key, value] of Object.entries(report)) { + if (key !== 'type' && key !== 'id') { + statsHtml += `
    • ${key}: ${value}
    • ` + reportJson[key] = value + } + } + statsHtml += '
    ' + statsReportJson.push(reportJson) + } + statsDiv.innerHTML = statsHtml + // データ属性としても保存(オプション) + statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson) + } + }) +}) + +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 = '' + } + } + + getStats(): Promise { + if (this.connection.pc === null) { + return Promise.reject(new Error('PeerConnection is not ready')) + } + return this.connection.pc.getStats() + } + + 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/e2e-tests/replace_track/index.html b/examples/replace_track/index.html similarity index 100% rename from e2e-tests/replace_track/index.html rename to examples/replace_track/index.html diff --git a/e2e-tests/replace_track/main.mts b/examples/replace_track/main.mts similarity index 100% rename from e2e-tests/replace_track/main.mts rename to examples/replace_track/main.mts diff --git a/examples/sendonly/index.html b/examples/sendonly/index.html new file mode 100644 index 00000000..883b7018 --- /dev/null +++ b/examples/sendonly/index.html @@ -0,0 +1,25 @@ + + + + + Sendonly test + + + +
    +

    Sendonly test

    +

    + + +
    +
    + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/examples/sendonly/main.mts b/examples/sendonly/main.mts new file mode 100644 index 00000000..a4ca9a4e --- /dev/null +++ b/examples/sendonly/main.mts @@ -0,0 +1,134 @@ +import Sora, { + type SignalingNotifyMessage, + type SignalingEvent, + type ConnectionPublisher, + type SoraConnection, +} from 'sora-js-sdk' + +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, + ) + + // SDK バージョンの表示 + const sdkVersionElement = document.querySelector('#sdk-version') + if (sdkVersionElement) { + sdkVersionElement.textContent = `${Sora.version()}` + } + + document.querySelector('#connect')?.addEventListener('click', async () => { + const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) + await client.connect(stream) + }) + + document.querySelector('#disconnect')?.addEventListener('click', async () => { + await client.disconnect() + }) + + document.querySelector('#get-stats')?.addEventListener('click', async () => { + const statsReport = await client.getStats() + const statsDiv = document.querySelector('#stats-report') as HTMLElement + const statsReportJsonDiv = document.querySelector('#stats-report-json') + if (statsDiv && statsReportJsonDiv) { + let statsHtml = '' + const statsReportJson: Record[] = [] + for (const report of statsReport.values()) { + statsHtml += `

    Type: ${report.type}

      ` + const reportJson: Record = { id: report.id, type: report.type } + for (const [key, value] of Object.entries(report)) { + if (key !== 'type' && key !== 'id') { + statsHtml += `
    • ${key}: ${value}
    • ` + reportJson[key] = value + } + } + statsHtml += '
    ' + statsReportJson.push(reportJson) + } + statsDiv.innerHTML = statsHtml + // データ属性としても保存(オプション) + statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson) + } + }) +}) + +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)) + + // E2E テスト用のコード + this.connection.on('signaling', this.onSignaling.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 + } + } + + getStats(): Promise { + if (this.connection.pc === null) { + return Promise.reject(new Error('PeerConnection is not ready')) + } + return this.connection.pc.getStats() + } + + 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 + } + } + } + + // E2E テスト用のコード + private onSignaling(event: SignalingEvent): void { + if (event.type === 'onmessage-switched') { + console.log('[signaling]', event.type, event.transportType) + } + } +} diff --git a/examples/sendrecv/index.html b/examples/sendrecv/index.html new file mode 100644 index 00000000..ad144a5a --- /dev/null +++ b/examples/sendrecv/index.html @@ -0,0 +1,41 @@ + + + + + Sendrecv test + + + +
    +

    Sendrecv test

    +

    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/examples/sendrecv/main.mts b/examples/sendrecv/main.mts new file mode 100644 index 00000000..ca7f2c6d --- /dev/null +++ b/examples/sendrecv/main.mts @@ -0,0 +1,184 @@ +import Sora, { + type SoraConnection, + type SignalingNotifyMessage, + type ConnectionPublisher, + type VideoCodecType, + type ConnectionOptions, +} from 'sora-js-sdk' + +const getChannelName = (): string => { + const channelNameElement = document.querySelector('#channel-name') + const channelName = channelNameElement?.value + if (channelName === '' || channelName === undefined) { + throw new Error('channelName is empty') + } + return channelName +} + +const getVideoCodecType = (): VideoCodecType | undefined => { + const videoCodecTypeElement = document.querySelector('#video-codec-type') + const videoCodecType = videoCodecTypeElement?.value + if (videoCodecType === '') { + return undefined + } + return videoCodecType as VideoCodecType +} + +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 || '' + + let client: SoraClient + + document.querySelector('#connect')?.addEventListener('click', async () => { + const channelName = getChannelName() + const videoCodecType = getVideoCodecType() + + client = new SoraClient( + SORA_SIGNALING_URL, + SORA_CHANNEL_ID_PREFIX, + SORA_CHANNEL_ID_SUFFIX, + ACCESS_TOKEN, + channelName, + videoCodecType, + ) + + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await client.connect(stream) + }) + document.querySelector('#disconnect')?.addEventListener('click', async () => { + await client.disconnect() + }) + + document.querySelector('#get-stats')?.addEventListener('click', async () => { + const statsReport = await client.getStats() + const statsDiv = document.querySelector('#stats-report') as HTMLElement + const statsReportJsonDiv = document.querySelector('#stats-report-json') + if (statsDiv && statsReportJsonDiv) { + let statsHtml = '' + const statsReportJson: Record[] = [] + for (const report of statsReport.values()) { + statsHtml += `

    Type: ${report.type}

      ` + const reportJson: Record = { id: report.id, type: report.type } + for (const [key, value] of Object.entries(report)) { + if (key !== 'type' && key !== 'id') { + statsHtml += `
    • ${key}: ${value}
    • ` + reportJson[key] = value + } + } + statsHtml += '
    ' + statsReportJson.push(reportJson) + } + statsDiv.innerHTML = statsHtml + // データ属性としても保存(オプション) + statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson) + } + }) +}) + +class SoraClient { + private debug = false + + private channelId: string + private metadata: { access_token: string } + private options: ConnectionOptions + + private sora: SoraConnection + private connection: ConnectionPublisher + + constructor( + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + accessToken: string, + channelName: string, + videoCodecType: VideoCodecType | undefined, + ) { + this.sora = Sora.connection(signalingUrl, this.debug) + + this.channelId = `${channelIdPrefix}${channelName}${channelIdSuffix}` + + this.metadata = { access_token: accessToken } + this.options = {} + + if (videoCodecType !== undefined) { + this.options = { ...this.options, videoCodecType: videoCodecType } + } + + 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 remoteVideos = document.querySelector('#remote-videos') + if (remoteVideos) { + remoteVideos.innerHTML = '' + } + } + + getStats(): Promise { + if (this.connection.pc === null) { + return Promise.reject(new Error('PeerConnection is not ready')) + } + return this.connection.pc.getStats() + } + + 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 + } + } + } + + private ontrack(event: RTCTrackEvent): void { + const stream = event.streams[0] + const remoteVideoId = `remote-video-${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.width = 320 + remoteVideo.height = 240 + remoteVideo.srcObject = stream + remoteVideos.appendChild(remoteVideo) + } + } + + private onremovetrack(event: MediaStreamTrackEvent): void { + const target = event.target as MediaStream + const remoteVideo = document.querySelector(`#remote-video-${target.id}`) + if (remoteVideo) { + document.querySelector('#remote-videos')?.removeChild(remoteVideo) + } + } +} diff --git a/examples/simulcast/index.html b/examples/simulcast/index.html new file mode 100644 index 00000000..e39a21eb --- /dev/null +++ b/examples/simulcast/index.html @@ -0,0 +1,43 @@ + + + + + Simulcast test + + + +
    +

    Simulcast test

    + + +
    +
    +

    sendonly

    +
    + +
    +
    +

    recvonly r0

    +
    + +
    +
    +

    recvonly r1

    +
    + +
    +
    +

    recvonly r2

    +
    + +
    + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/examples/simulcast/main.mts b/examples/simulcast/main.mts new file mode 100644 index 00000000..b34ed3ea --- /dev/null +++ b/examples/simulcast/main.mts @@ -0,0 +1,230 @@ +import Sora, { + type SoraConnection, + type ConnectionPublisher, + type SignalingNotifyMessage, + type ConnectionSubscriber, + type SimulcastRid, +} from 'sora-js-sdk' + +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('#connect')?.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('#disconnect')?.addEventListener('click', async () => { + await sendonly.disconnect() + + // recvonly r0 + await recvonlyR0.disconnect() + // recvonly r1 + await recvonlyR1.disconnect() + // recvonly r2 + await recvonlyR2.disconnect() + }) + + document.querySelector('#get-stats')?.addEventListener('click', async () => { + const statsReport = await sendonly.getStats() + const statsDiv = document.querySelector('#stats-report') as HTMLElement + const statsReportJsonDiv = document.querySelector('#stats-report-json') + if (statsDiv && statsReportJsonDiv) { + let statsHtml = '' + const statsReportJson: Record[] = [] + for (const report of statsReport.values()) { + statsHtml += `

    Type: ${report.type}

      ` + const reportJson: Record = { id: report.id, type: report.type } + for (const [key, value] of Object.entries(report)) { + if (key !== 'type' && key !== 'id') { + statsHtml += `
    • ${key}: ${value}
    • ` + reportJson[key] = value + } + } + statsHtml += '
    ' + statsReportJson.push(reportJson) + } + statsDiv.innerHTML = statsHtml + // データ属性としても保存(オプション) + statsDiv.dataset.statsReportJson = JSON.stringify(statsReportJson) + } + }) +}) + +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 + } + } + + getStats(): Promise { + if (this.connection.pc === null) { + return Promise.reject(new Error('PeerConnection is not ready')) + } + return this.connection.pc.getStats() + } + + 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 + } + } +} diff --git a/examples/spotlight_sendrecv/index.html b/examples/spotlight_sendrecv/index.html new file mode 100644 index 00000000..0ca6b928 --- /dev/null +++ b/examples/spotlight_sendrecv/index.html @@ -0,0 +1,36 @@ + + + + + Spotlight Sendrecv test + + + +
    +

    Spotlight Sendrecv test

    +
    +
    +

    sendrecv1

    + +
    + +
    +
    +
    +
    +

    sendrecv2

    + +
    + +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/examples/spotlight_sendrecv/main.mts b/examples/spotlight_sendrecv/main.mts new file mode 100644 index 00000000..e9ee49ca --- /dev/null +++ b/examples/spotlight_sendrecv/main.mts @@ -0,0 +1,148 @@ +import Sora, { + type SoraConnection, + type ConnectionPublisher, + type SignalingNotifyMessage, +} from 'sora-js-sdk' + +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-connect')?.addEventListener('click', async () => { + // sendrecv1 + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await sendrecv1.connect(stream) + }) + document.querySelector('#sendrecv1-disconnect')?.addEventListener('click', async () => { + await sendrecv1.disconnect() + }) + + document.querySelector('#sendrecv2-connect')?.addEventListener('click', async () => { + // sendrecv2 + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) + await sendrecv2.connect(stream) + }) + document.querySelector('#sendrecv2-disconnect')?.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}spotlight_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/examples/tests/helper.ts b/examples/tests/helper.ts new file mode 100644 index 00000000..db9a229f --- /dev/null +++ b/examples/tests/helper.ts @@ -0,0 +1,13 @@ +// バージョン比較用のヘルパー関数を追加 +export const isVersionGreaterThanOrEqual = (packageVersion: string, version: string): boolean => { + const v1Parts = packageVersion.split('.').map(Number) + const v2Parts = version.split('.').map(Number) + + for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { + const v1 = v1Parts[i] || 0 + const v2 = v2Parts[i] || 0 + if (v1 > v2) return true + if (v1 < v2) return false + } + return true +} diff --git a/examples/tests/message_header.spec.ts b/examples/tests/message_header.spec.ts new file mode 100644 index 00000000..bf18ea9b --- /dev/null +++ b/examples/tests/message_header.spec.ts @@ -0,0 +1,184 @@ +import { expect, test } from '@playwright/test' +import { isVersionGreaterThanOrEqual } from './helper' + +test('messaging pages with header', async ({ browser }) => { + // 新しいページを2つ作成 + const page1 = await browser.newPage() + const page2 = await browser.newPage() + + // それぞれのページに対して操作を行う + await page1.goto('http://localhost:9000/messaging/') + await page2.goto('http://localhost:9000/messaging/') + + // sora js sdk のバージョンを取得する + await page1.waitForSelector('#sora-js-sdk-version') + const page1SoraJsSdkVersion = await page1.$eval('#sora-js-sdk-version', (el) => el.textContent) + if (page1SoraJsSdkVersion === null) { + throw new Error('page1SoraJsSdkVersion is null') + } + // sora-js-sdk のバージョンが 2024.2.0 以上であるか確認して、2024.2.0 未満の場合はテストをスキップする + test.skip( + !isVersionGreaterThanOrEqual(page1SoraJsSdkVersion, '2024.2.0'), + 'sora-js-sdk のバージョンが 2024.2.0 以上である必要があります', + ) + + // チャネル名を設定する + await page1.fill('input[name="channel-name"]', 'message-header') + await page2.fill('input[name="channel-name"]', 'message-header') + + // header を有効にする + await page1.check('#check-header') + await page2.check('#check-header') + + // connect ボタンを押して接続開始 + await page1.click('#connect') + await page2.click('#connect') + + 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('#connection-id:not(:empty)') + const page2ConnectionId = await page2.$eval('#connection-id', (el) => el.textContent) + console.log(`page2 connectionId=${page2ConnectionId}`) + + // page1 で #example の DataChannel が open したことを確認 + await page1.waitForSelector('#messaging li', { state: 'attached' }) + + // page2 で #example の DataChannel が open したことを確認 + await page2.waitForSelector('#messaging li', { state: 'attached' }) + + // page1からpage2へメッセージを送信 + 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) + + // 受信したメッセージが期待したものであるか検証 + // receivedMessage の先頭 26 バイトには sender_connection_id が含まれている + test.expect(receivedMessage1?.slice(0, 26)).toBe(page1ConnectionId) + // 27 文字目からは page1 のメッセージがそのまま + test.expect(receivedMessage1).toContain(page1Message) + + // page2からpage1へメッセージを送信 + const page2Message = 'Hello from page2' + await page2.fill('input[name="message"]', page2Message) + await page2.click('#send-message') + + // page1でメッセージが受信されたことを確認 + await page1.waitForSelector('#received-messages li', { state: 'attached' }) + const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) + + // 受信したメッセージが期待したものであるか検証 + console.log(`Received message on page1: ${receivedMessage2}`) + // receivedMessage の先頭 26 バイトには sender_connection_id が含まれている + test.expect(receivedMessage2?.slice(0, 26)).toBe(page2ConnectionId) + // 27 文字目からは page2 のメッセージがそのまま + test.expect(receivedMessage2).toContain(page2Message) + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await page1.click('#get-stats') + await page2.click('#get-stats') + + // 統計情報が表示されるまで待機 + await page1.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const page1StatsReportJson: Record[] = await page1.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // page1 stats report + const page1DataChannelStats = page1StatsReportJson.filter( + (report) => report.type === 'data-channel', + ) + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'signaling' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'push' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'notify' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'stats' && stats.state === 'open' + }), + ).toBeDefined() + + const page1ExampleStats = page1DataChannelStats.find((stats) => { + return stats.label === '#example' && stats.state === 'open' + }) + // ここで undefined ではないことを確認してる + expect(page1ExampleStats).toBeDefined() + expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) + expect(page1ExampleStats?.bytesSent).toBeGreaterThan(0) + expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) + + // 統計情報が表示されるまで待機 + await page2.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const page2StatsReportJson: Record[] = await page2.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // page2 stats report + const page2DataChannelStats = page2StatsReportJson.filter( + (report) => report.type === 'data-channel', + ) + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'signaling' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'push' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'notify' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'stats' && stats.state === 'open' + }), + ).toBeDefined() + + const page2ExampleStats = page2DataChannelStats.find((stats) => { + return stats.label === '#example' && stats.state === 'open' + }) + // ここで undefined ではないことを確認してる + expect(page2ExampleStats).toBeDefined() + expect(page2ExampleStats?.bytesReceived).toBeGreaterThan(0) + expect(page2ExampleStats?.messagesReceived).toBeGreaterThan(0) + expect(page2ExampleStats?.bytesSent).toBeGreaterThan(0) + expect(page2ExampleStats?.messagesSent).toBeGreaterThan(0) + + await page1.click('#disconnect') + await page2.click('#disconnect') + + await page1.close() + await page2.close() +}) diff --git a/examples/tests/messaging.spec.ts b/examples/tests/messaging.spec.ts new file mode 100644 index 00000000..7f47fa03 --- /dev/null +++ b/examples/tests/messaging.spec.ts @@ -0,0 +1,197 @@ +import { expect, test } from '@playwright/test' +import { isVersionGreaterThanOrEqual } from './helper' + +// sora-js-sdk のバージョンが 2024.2.0 以降の場合のみ実行したい +test('messaging pages', async ({ browser }) => { + // 新しいページを2つ作成 + const page1 = await browser.newPage() + const page2 = await browser.newPage() + + // それぞれのページに対して操作を行う + await page1.goto('http://localhost:9000/messaging/') + await page2.goto('http://localhost:9000/messaging/') + + // sora js sdk のバージョンを取得する + await page1.waitForSelector('#sora-js-sdk-version') + const page1SoraJsSdkVersion = await page1.$eval('#sora-js-sdk-version', (el) => el.textContent) + if (page1SoraJsSdkVersion === null) { + throw new Error('page1SoraJsSdkVersion is null') + } + // sora-js-sdk のバージョンが 2024.2.0 以上であるか確認して、2024.2.0 未満の場合はテストをスキップする + test.skip( + !isVersionGreaterThanOrEqual(page1SoraJsSdkVersion, '2024.2.0'), + 'sora-js-sdk のバージョンが 2024.2.0 以上である必要があります', + ) + + // Compress のTrue/Falseをランダムで設定する + const selectedCompress1 = await page1.evaluate(() => { + const checkCompress = document.getElementById('check-compress') as HTMLInputElement + const getRandomBoolean = (): boolean => Math.random() >= 0.5 + const randomCompress: boolean = getRandomBoolean() + checkCompress.checked = randomCompress + return randomCompress + }) + const selectedCompress2 = await page2.evaluate(() => { + const checkCompress = document.getElementById('check-compress') as HTMLInputElement + const getRandomBoolean = (): boolean => Math.random() >= 0.5 + const randomCompress: boolean = getRandomBoolean() + checkCompress.checked = randomCompress + return randomCompress + }) + // 設定した Compress の True/False をログに流す + console.log(`page1 Compress = ${selectedCompress1}`) + console.log(`page2 Compress = ${selectedCompress2}`) + + // connect ボタンを押して接続開始 + await page1.click('#connect') + await page2.click('#connect') + + 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('#connection-id:not(:empty)') + const page2ConnectionId = await page2.$eval('#connection-id', (el) => el.textContent) + console.log(`page2 connectionId=${page2ConnectionId}`) + + // page1 で #example の DataChannel が open したことを確認 + await page1.waitForSelector('#messaging li', { state: 'attached' }) + + // page2 で #example の DataChannel が open したことを確認 + await page2.waitForSelector('#messaging li', { state: 'attached' }) + + // page1からpage2へメッセージを送信 + 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) + + // 受信したメッセージが期待したものであるか検証 + console.log(`Received message on page2: ${receivedMessage1}`) + test.expect(receivedMessage1).toBe(page1Message) + + // page2からpage1へメッセージを送信 + const page2Message = 'Hello from page2' + await page2.fill('input[name="message"]', page2Message) + await page2.click('#send-message') + + // page1でメッセージが受信されたことを確認 + await page1.waitForSelector('#received-messages 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(page2Message) + + // Compress で圧縮されているかを確認する(圧縮されていると Equal にならない) + if (selectedCompress1 !== selectedCompress2) { + test.expect(receivedMessage1).toEqual(page1Message) + test.expect(receivedMessage2).toEqual(page2Message) + } + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await page1.click('#get-stats') + await page2.click('#get-stats') + + // 統計情報が表示されるまで待機 + await page1.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const page1StatsReportJson: Record[] = await page1.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // page1 stats report + const page1DataChannelStats = page1StatsReportJson.filter( + (report) => report.type === 'data-channel', + ) + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'signaling' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'push' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'notify' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page1DataChannelStats.find((stats) => { + return stats.label === 'stats' && stats.state === 'open' + }), + ).toBeDefined() + + const page1ExampleStats = page1DataChannelStats.find((stats) => { + return stats.label === '#example' && stats.state === 'open' + }) + // ここで undefined ではないことを確認してる + expect(page1ExampleStats).toBeDefined() + expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) + expect(page1ExampleStats?.bytesSent).toBeGreaterThan(0) + expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) + + // 統計情報が表示されるまで待機 + await page2.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const page2StatsReportJson: Record[] = await page2.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // page2 stats report + const page2DataChannelStats = page2StatsReportJson.filter( + (report) => report.type === 'data-channel', + ) + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'signaling' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'push' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'notify' && stats.state === 'open' + }), + ).toBeDefined() + + expect( + page2DataChannelStats.find((stats) => { + return stats.label === 'stats' && stats.state === 'open' + }), + ).toBeDefined() + + const page2ExampleStats = page2DataChannelStats.find((stats) => { + return stats.label === '#example' && stats.state === 'open' + }) + // ここで undefined ではないことを確認してる + expect(page2ExampleStats).toBeDefined() + expect(page2ExampleStats?.bytesReceived).toBeGreaterThan(0) + expect(page2ExampleStats?.messagesReceived).toBeGreaterThan(0) + expect(page2ExampleStats?.bytesSent).toBeGreaterThan(0) + expect(page2ExampleStats?.messagesSent).toBeGreaterThan(0) + + await page1.click('#disconnect') + await page2.click('#disconnect') + + await page1.close() + await page2.close() +}) diff --git a/examples/tests/sendonly_audio.spec.ts b/examples/tests/sendonly_audio.spec.ts new file mode 100644 index 00000000..a89b79c4 --- /dev/null +++ b/examples/tests/sendonly_audio.spec.ts @@ -0,0 +1,75 @@ +import { expect, test } from '@playwright/test' + +test('sendonly audio pages', async ({ browser }) => { + // 新しいページを作成 + const sendonly = await browser.newPage() + // ページに対して操作を行う + await sendonly.goto('http://localhost:9000/sendonly_audio/') + + // select 要素から直接オプションを取得してランダムに選択する + // 音声コーデック + const selectedAudioCodec = await sendonly.evaluate(() => { + const select = document.querySelector('#audio-codec-type') as HTMLSelectElement + const options = Array.from(select.options) + const randomOption = options[Math.floor(Math.random() * options.length)] + select.value = randomOption.value + return randomOption.value + }) + // 音声ビットレート + const selectedBitRate = await sendonly.evaluate(() => { + const select = document.querySelector('#audio-bit-rate') as HTMLSelectElement + const options = Array.from(select.options).filter((option) => option.value !== '') // 未指定を除外 + const randomOption = options[Math.floor(Math.random() * options.length)] + select.value = randomOption.value + return randomOption.value + }) + + // ランダムで選択した音声コーデック・音声ビットレートをログに表示する + console.log(`Selected AudioCodec: ${selectedAudioCodec}`) + console.log(`Selected BitRate: ${selectedBitRate} kbps`) + + // 'connect' ボタンをクリックして音声の送信を開始する + await sendonly.click('#connect') + // #connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await sendonly.waitForSelector('#connection-id:not(:empty)') + // #connection-id 要素の内容を取得 + const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) + console.log(`sendonly connectionId=${sendonlyConnectionId}`) + + // レース対策 + await sendonly.waitForTimeout(3000) + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await sendonly.click('#get-stats') + // 統計情報が表示されるまで待機 + await sendonly.waitForSelector('#stats-report') + + // データセットから統計情報を取得 + const sendonlyStatsReportJson: Record[] = await sendonly.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // 音声コーデックを確認する : 今は指定してもしなくても OPUS のみ + const sendonlyAudioCodecStats = sendonlyStatsReportJson.find( + (report) => report.type === 'codec' && report.mimeType === 'audio/opus', + ) + expect(sendonlyAudioCodecStats).toBeDefined() + + // 音声ビットレートを確認する:音声を送れているかと targetBitrate の確認 + const sendonlyAudioOutboundRtp = sendonlyStatsReportJson.find( + (report) => report.type === 'outbound-rtp' && report.kind === 'audio', + ) + expect(sendonlyAudioOutboundRtp).toBeDefined() + + // 音声が正常に送れているかを確認する + expect(sendonlyAudioOutboundRtp?.bytesSent).toBeGreaterThan(0) + expect(sendonlyAudioOutboundRtp?.packetsSent).toBeGreaterThan(0) + + // 音声ビットレートの選択に基づいて期待値を設定し一致するかを確認する + const expectedBitRate = Number.parseInt(selectedBitRate) * 1000 + expect(sendonlyAudioOutboundRtp?.targetBitrate).toEqual(expectedBitRate) + + await sendonly.click('#disconnect') + await sendonly.close() +}) diff --git a/examples/tests/sendonly_recvonly.spec.ts b/examples/tests/sendonly_recvonly.spec.ts new file mode 100644 index 00000000..c296db2a --- /dev/null +++ b/examples/tests/sendonly_recvonly.spec.ts @@ -0,0 +1,125 @@ +import { expect, test } from '@playwright/test' + +test('sendonly/recvonly pages', async ({ browser }) => { + // 新しいページを2つ作成 + const sendonly = await browser.newPage() + const recvonly = await browser.newPage() + + // それぞれのページに対して操作を行う + await sendonly.goto('http://localhost:9000/sendonly/') + await recvonly.goto('http://localhost:9000/recvonly/') + + // SDK バージョンの表示 + await sendonly.waitForSelector('#sdk-version') + const sendonlySdkVersion = await sendonly.$eval('#sdk-version', (el) => el.textContent) + console.log(`sendonly sdkVersion=${sendonlySdkVersion}`) + + await recvonly.waitForSelector('#sdk-version') + const recvonlySdkVersion = await recvonly.$eval('#sdk-version', (el) => el.textContent) + console.log(`recvonly sdkVersion=${recvonlySdkVersion}`) + + await sendonly.click('#connect') + await recvonly.click('#connect') + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await sendonly.waitForSelector('#connection-id:not(:empty)') + + // #sendonly-connection-id 要素の内容を取得 + const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) + console.log(`sendonly connectionId=${sendonlyConnectionId}`) + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await recvonly.waitForSelector('#connection-id:not(:empty)') + + // #sendrecv1-connection-id 要素の内容を取得 + const recvonlyConnectionId = await recvonly.$eval('#connection-id', (el) => el.textContent) + console.log(`recvonly connectionId=${recvonlyConnectionId}`) + + // レース対策 + await sendonly.waitForTimeout(3000) + await recvonly.waitForTimeout(3000) + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await sendonly.click('#get-stats') + + // 統計情報が表示されるまで待機 + await sendonly.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const sendonlyStatsReportJson: Record[] = await sendonly.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await recvonly.click('#get-stats') + + // 統計情報が表示されるまで待機 + await recvonly.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const recvonlyStatsReportJson: Record[] = await recvonly.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // sendonly audio codec + const sendonlyAudioCodecStats = sendonlyStatsReportJson.find( + (report) => report.type === 'codec' && report.mimeType === 'audio/opus', + ) + expect(sendonlyAudioCodecStats).toBeDefined() + + // sendonly audio outbound-rtp + const sendonlyAudioOutboundRtp = sendonlyStatsReportJson.find( + (report) => report.type === 'outbound-rtp' && report.kind === 'audio', + ) + expect(sendonlyAudioOutboundRtp).toBeDefined() + expect(sendonlyAudioOutboundRtp?.bytesSent).toBeGreaterThan(0) + expect(sendonlyAudioOutboundRtp?.packetsSent).toBeGreaterThan(0) + + // sendonly video codec + const sendonlyVideoCodecStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP9', + ) + expect(sendonlyVideoCodecStats).toBeDefined() + + // sendonly video outbound-rtp + const sendonlyVideoOutboundRtpStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', + ) + expect(sendonlyVideoOutboundRtpStats).toBeDefined() + expect(sendonlyVideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendonlyVideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) + + // recvonly audio codec + const recvonlyAudioCodecStats = recvonlyStatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === 'audio/opus', + ) + expect(recvonlyAudioCodecStats).toBeDefined() + + // recvonly audio inbound-rtp + const recvonlyAudioInboundRtpStats = recvonlyStatsReportJson.find( + (stats) => stats.type === 'inbound-rtp' && stats.kind === 'audio', + ) + expect(recvonlyAudioInboundRtpStats).toBeDefined() + expect(recvonlyAudioInboundRtpStats?.bytesReceived).toBeGreaterThan(0) + expect(recvonlyAudioInboundRtpStats?.packetsReceived).toBeGreaterThan(0) + + // recvonly video codec + const recvonlyVideoCodecStats = recvonlyStatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP9', + ) + expect(recvonlyVideoCodecStats).toBeDefined() + + // recvonly video inbound-rtp + const recvonlyVideoInboundRtpStats = recvonlyStatsReportJson.find( + (stats) => stats.type === 'inbound-rtp' && stats.kind === 'video', + ) + expect(recvonlyVideoInboundRtpStats).toBeDefined() + expect(recvonlyVideoInboundRtpStats?.bytesReceived).toBeGreaterThan(0) + expect(recvonlyVideoInboundRtpStats?.packetsReceived).toBeGreaterThan(0) + + await sendonly.click('#disconnect') + await recvonly.click('#disconnect') + + await sendonly.close() + await recvonly.close() +}) diff --git a/examples/tests/sendrecv.spec.ts b/examples/tests/sendrecv.spec.ts new file mode 100644 index 00000000..f04f6909 --- /dev/null +++ b/examples/tests/sendrecv.spec.ts @@ -0,0 +1,135 @@ +import { expect, test } from '@playwright/test' + +test('sendrecv x2', async ({ browser }) => { + const sendrecv1 = await browser.newPage() + const sendrecv2 = await browser.newPage() + + await sendrecv1.goto('http://localhost:9000/sendrecv/') + await sendrecv2.goto('http://localhost:9000/sendrecv/') + + // チャンネル名を設定 + await sendrecv1.fill('#channel-name', 'sendrecv-different-video-codec-type') + await sendrecv2.fill('#channel-name', 'sendrecv-different-video-codec-type') + + console.log('sendrecv1 channelName: sendrecv-different-video-codec-type') + console.log('sendrecv2 channelName: sendrecv-different-video-codec-type') + + // sendrecv1 のビデオコーデックをランダムに選択 + await sendrecv1.evaluate(() => { + const videoCodecTypeSelect = document.getElementById('video-codec-type') as HTMLSelectElement + const options = Array.from(videoCodecTypeSelect.options).filter((option) => option.value !== '') + const randomIndex = Math.floor(Math.random() * options.length) + videoCodecTypeSelect.value = options[randomIndex].value + }) + + // sendrecv2 のビデオコーデックをランダムに選択 + await sendrecv2.evaluate(() => { + const videoCodecTypeSelect = document.getElementById('video-codec-type') as HTMLSelectElement + const options = Array.from(videoCodecTypeSelect.options).filter((option) => option.value !== '') + const randomIndex = Math.floor(Math.random() * options.length) + videoCodecTypeSelect.value = options[randomIndex].value + }) + + // 選択されたコーデックをログに出力 + const sendrecv1VideoCodecType = await sendrecv1.$eval( + '#video-codec-type', + (el) => (el as HTMLSelectElement).value, + ) + const sendrecv2VideoCodecType = await sendrecv2.$eval( + '#video-codec-type', + (el) => (el as HTMLSelectElement).value, + ) + console.log(`sendrecv1 videoCodecType: ${sendrecv1VideoCodecType}`) + console.log(`sendrecv2 videoCodecType: ${sendrecv2VideoCodecType}`) + + await sendrecv1.click('#connect') + await sendrecv2.click('#connect') + + // #connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await sendrecv1.waitForSelector('#connection-id:not(:empty)') + + // #connection-id 要素の内容を取得 + const sendrecv1ConnectionId = await sendrecv1.$eval('#connection-id', (el) => el.textContent) + console.log(`sendrecv1 connectionId=${sendrecv1ConnectionId}`) + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await sendrecv2.waitForSelector('#connection-id:not(:empty)') + + // #sendrecv1-connection-id 要素の内容を取得 + const sendrecv2ConnectionId = await sendrecv2.$eval('#connection-id', (el) => el.textContent) + console.log(`sendrecv2 connectionId=${sendrecv2ConnectionId}`) + + // レース対策 + await sendrecv1.waitForTimeout(3000) + await sendrecv2.waitForTimeout(3000) + + // page1 stats report + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await sendrecv1.click('#get-stats') + + // 統計情報が表示されるまで待機 + await sendrecv1.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const sendrecv1StatsReportJson: Record[] = await sendrecv1.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + const sendrecv1VideoCodecStats = sendrecv1StatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === `video/${sendrecv1VideoCodecType}`, + ) + expect(sendrecv1VideoCodecStats).toBeDefined() + + const sendrecv1VideoOutboundRtpStats = sendrecv1StatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', + ) + expect(sendrecv1VideoOutboundRtpStats).toBeDefined() + expect(sendrecv1VideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendrecv1VideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) + + const sendrecv1VideoInboundRtpStats = sendrecv1StatsReportJson.find( + (stats) => stats.type === 'inbound-rtp' && stats.kind === 'video', + ) + expect(sendrecv1VideoInboundRtpStats).toBeDefined() + expect(sendrecv1VideoInboundRtpStats?.bytesReceived).toBeGreaterThan(0) + expect(sendrecv1VideoInboundRtpStats?.packetsReceived).toBeGreaterThan(0) + + // page2 stats report + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await sendrecv2.click('#get-stats') + + // 統計情報が表示されるまで待機 + await sendrecv2.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const sendrecv2StatsReportJson: Record[] = await sendrecv2.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + const sendrecv2VideoCodecStats = sendrecv2StatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === `video/${sendrecv2VideoCodecType}`, + ) + expect(sendrecv2VideoCodecStats).toBeDefined() + + const sendrecv2VideoOutboundRtpStats = sendrecv2StatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', + ) + expect(sendrecv2VideoOutboundRtpStats).toBeDefined() + expect(sendrecv2VideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendrecv2VideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) + + const sendrecv2VideoInboundRtpStats = sendrecv2StatsReportJson.find( + (stats) => stats.type === 'inbound-rtp' && stats.kind === 'video', + ) + expect(sendrecv2VideoInboundRtpStats).toBeDefined() + expect(sendrecv2VideoInboundRtpStats?.bytesReceived).toBeGreaterThan(0) + expect(sendrecv2VideoInboundRtpStats?.packetsReceived).toBeGreaterThan(0) + + await sendrecv1.click('#disconnect') + await sendrecv2.click('#disconnect') + + await sendrecv1.close() + await sendrecv2.close() +}) diff --git a/examples/tests/simulcast.spec.ts b/examples/tests/simulcast.spec.ts new file mode 100644 index 00000000..d1488b4f --- /dev/null +++ b/examples/tests/simulcast.spec.ts @@ -0,0 +1,78 @@ +import { expect, test } from '@playwright/test' + +test('simulcast sendonly/recvonly pages', async ({ page }) => { + await page.goto('http://localhost:9000/simulcast/') + + await page.click('#connect') + + // 安全によせて 5 秒待つ + await page.waitForTimeout(5000) + + await page.waitForSelector('#local-video-connection-id:not(:empty)') + const localConnectionId = await page.$eval('#local-video-connection-id', (el) => el.textContent) + console.log(`local connectionId=${localConnectionId}`) + + await page.waitForSelector('#remote-video-connection-id-r0:not(:empty)') + const remoteR0ConnectionId = await page.$eval( + '#remote-video-connection-id-r0', + (el) => el.textContent, + ) + console.log(`remote | rid=r0, connectionId=${remoteR0ConnectionId}`) + + await page.waitForSelector('#remote-video-connection-id-r1:not(:empty)') + const remoteR1ConnectionId = await page.$eval( + '#remote-video-connection-id-r1', + (el) => el.textContent, + ) + console.log(`remote | rid=r1, connectionId=${remoteR1ConnectionId}`) + + await page.waitForSelector('#remote-video-connection-id-r2:not(:empty)') + const remoteR2ConnectionId = await page.$eval( + '#remote-video-connection-id-r2', + (el) => el.textContent, + ) + console.log(`remote | rid=r2, connectionId=${remoteR2ConnectionId}`) + + // 'Get Stats' ボタンをクリックして統計情報を取得 + await page.click('#get-stats') + + // 統計情報が表示されるまで待機 + await page.waitForSelector('#stats-report') + // データセットから統計情報を取得 + const sendonlyStatsReportJson: Record[] = await page.evaluate(() => { + const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement + return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] + }) + + // sendonly stats report + const sendonlyVideoCodecStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP8', + ) + expect(sendonlyVideoCodecStats).toBeDefined() + + const sendonlyVideoR0OutboundRtpStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video' && stats.rid === 'r0', + ) + expect(sendonlyVideoR0OutboundRtpStats).toBeDefined() + expect(sendonlyVideoR0OutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendonlyVideoR0OutboundRtpStats?.packetsSent).toBeGreaterThan(0) + expect(sendonlyVideoR0OutboundRtpStats?.scalabilityMode).toEqual('L1T1') + + const sendonlyVideoR1OutboundRtpStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video' && stats.rid === 'r1', + ) + expect(sendonlyVideoR1OutboundRtpStats).toBeDefined() + expect(sendonlyVideoR1OutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendonlyVideoR1OutboundRtpStats?.packetsSent).toBeGreaterThan(0) + expect(sendonlyVideoR1OutboundRtpStats?.scalabilityMode).toEqual('L1T1') + + const sendonlyVideoR2OutboundRtpStats = sendonlyStatsReportJson.find( + (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video' && stats.rid === 'r2', + ) + expect(sendonlyVideoR2OutboundRtpStats).toBeDefined() + expect(sendonlyVideoR2OutboundRtpStats?.bytesSent).toBeGreaterThan(0) + expect(sendonlyVideoR2OutboundRtpStats?.packetsSent).toBeGreaterThan(0) + expect(sendonlyVideoR2OutboundRtpStats?.scalabilityMode).toEqual('L1T1') + + await page.click('#disconnect') +}) diff --git a/examples/tests/spotlight_sendonly_recvonly.spec.ts b/examples/tests/spotlight_sendonly_recvonly.spec.ts new file mode 100644 index 00000000..89c09921 --- /dev/null +++ b/examples/tests/spotlight_sendonly_recvonly.spec.ts @@ -0,0 +1,34 @@ +import { test } from '@playwright/test' + +test('spotlight sendonly/recvonly pages', async ({ browser }) => { + // 新しいページを2つ作成 + const sendonly = await browser.newPage() + const recvonly = await browser.newPage() + + // それぞれのページに対して操作を行う + await sendonly.goto('http://localhost:9000/spotlight_sendonly/') + await recvonly.goto('http://localhost:9000/spotlight_recvonly/') + + await sendonly.click('#connect') + await recvonly.click('#connect') + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await sendonly.waitForSelector('#connection-id:not(:empty)') + + // #sendonly-connection-id 要素の内容を取得 + const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) + console.log(`sendonly connectionId=${sendonlyConnectionId}`) + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await recvonly.waitForSelector('#connection-id:not(:empty)') + + // #sendrecv1-connection-id 要素の内容を取得 + const recvonlyConnectionId = await recvonly.$eval('#connection-id', (el) => el.textContent) + console.log(`recvonly connectionId=${recvonlyConnectionId}`) + + await sendonly.click('#disconnect') + await recvonly.click('#disconnect') + + await sendonly.close() + await recvonly.close() +}) diff --git a/examples/tests/spotlight_sendrecv.spec.ts b/examples/tests/spotlight_sendrecv.spec.ts new file mode 100644 index 00000000..550d2d41 --- /dev/null +++ b/examples/tests/spotlight_sendrecv.spec.ts @@ -0,0 +1,25 @@ +import { test } from '@playwright/test' + +test('spotlight sendrecv x2', async ({ page }) => { + await page.goto('http://localhost:9000/spotlight_sendrecv/') + + await page.click('#sendrecv1-connect') + await page.click('#sendrecv2-connect') + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await page.waitForSelector('#sendrecv1-connection-id:not(:empty)') + + // #sendrecv1-connection-id 要素の内容を取得 + const sendrecv1ConnectionId = await page.$eval('#sendrecv1-connection-id', (el) => el.textContent) + console.log(`sendrecv1 connectionId=${sendrecv1ConnectionId}`) + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await page.waitForSelector('#sendrecv1-connection-id:not(:empty)') + + // #sendrecv1-connection-id 要素の内容を取得 + const sendrecv2ConnectionId = await page.$eval('#sendrecv2-connection-id', (el) => el.textContent) + console.log(`sendrecv2 connectionId=${sendrecv2ConnectionId}`) + + await page.click('#sendrecv1-disconnect') + await page.click('#sendrecv2-disconnect') +}) diff --git a/examples/tests/type_close.spec.ts b/examples/tests/type_close.spec.ts new file mode 100644 index 00000000..3d3b5ce4 --- /dev/null +++ b/examples/tests/type_close.spec.ts @@ -0,0 +1,60 @@ +import { expect, test } from '@playwright/test' + +test('data_channel_signaling_only type:close pages', async ({ browser }) => { + // 新しいページを2つ作成 + const dataChannelSignalingOnly = await browser.newPage() + + // デバッグ用 + dataChannelSignalingOnly.on('console', (msg) => { + console.log(msg.type(), msg.text()) + }) + + // それぞれのページに対して操作を行う + await dataChannelSignalingOnly.goto('http://localhost:9000/data_channel_signaling_only/') + + // SDK バージョンの表示 + await dataChannelSignalingOnly.waitForSelector('#sdk-version') + const dataChannelSignalingOnlySdkVersion = await dataChannelSignalingOnly.$eval( + '#sdk-version', + (el) => el.textContent, + ) + console.log(`dataChannelSignalingOnly sdkVersion=${dataChannelSignalingOnlySdkVersion}`) + + await dataChannelSignalingOnly.click('#connect') + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await dataChannelSignalingOnly.waitForSelector('#connection-id:not(:empty)') + + // #sendonly-connection-id 要素の内容を取得 + const dataChannelSignalingOnlyConnectionId = await dataChannelSignalingOnly.$eval( + '#connection-id', + (el) => el.textContent, + ) + console.log(`dataChannelSignalingOnly connectionId=${dataChannelSignalingOnlyConnectionId}`) + + // レース対策 + await dataChannelSignalingOnly.waitForTimeout(3000) + + // API で切断 + await dataChannelSignalingOnly.click('#disconnect-api') + // console.log に [signaling] switched が出力されるまで待機するための Promise を作成する + const consolePromise = dataChannelSignalingOnly.waitForEvent('console') + + // レース対策 + await dataChannelSignalingOnly.waitForTimeout(3000) + + // Console log の Promise が解決されるまで待機する + const msg = await consolePromise + // log [signaling] onmessage-close websocket が出力されるので、args 0/1/2 をそれぞれチェックする + // [signaling] + const value1 = await msg.args()[0].jsonValue() + expect(value1).toBe('[signaling]') + // onmessage-close + const value2 = await msg.args()[1].jsonValue() + expect(value2).toBe('onmessage-close') + // websocket + const value3 = await msg.args()[2].jsonValue() + expect(value3).toBe('datachannel') + + await dataChannelSignalingOnly.close() +}) diff --git a/examples/tests/type_switched.spec.ts b/examples/tests/type_switched.spec.ts new file mode 100644 index 00000000..f1a6b699 --- /dev/null +++ b/examples/tests/type_switched.spec.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test' + +test('data_channel_signaling_only type:switched pages', async ({ browser }) => { + // 新しいページを2つ作成 + const dataChannelSignalingOnly = await browser.newPage() + + // デバッグ用 + // dataChannelSignalingOnly.on('console', (msg) => { + // console.log(msg.type(), msg.text()) + // }) + + // それぞれのページに対して操作を行う + await dataChannelSignalingOnly.goto('http://localhost:9000/data_channel_signaling_only/') + + // SDK バージョンの表示 + await dataChannelSignalingOnly.waitForSelector('#sdk-version') + const dataChannelSignalingOnlySdkVersion = await dataChannelSignalingOnly.$eval( + '#sdk-version', + (el) => el.textContent, + ) + console.log(`dataChannelSignalingOnly sdkVersion=${dataChannelSignalingOnlySdkVersion}`) + + await dataChannelSignalingOnly.click('#connect') + // console.log に [signaling] switched が出力されるまで待機するための Promise を作成する + const consolePromise = dataChannelSignalingOnly.waitForEvent('console') + + // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ + await dataChannelSignalingOnly.waitForSelector('#connection-id:not(:empty)') + + // #sendonly-connection-id 要素の内容を取得 + const dataChannelSignalingOnlyConnectionId = await dataChannelSignalingOnly.$eval( + '#connection-id', + (el) => el.textContent, + ) + console.log(`dataChannelSignalingOnly connectionId=${dataChannelSignalingOnlyConnectionId}`) + + // レース対策 + await dataChannelSignalingOnly.waitForTimeout(3000) + + // Console log の Promise が解決されるまで待機する + const msg = await consolePromise + // log [signaling] onmessage-switched websocket が出力されるので、args 0/1/2 をそれぞれチェックする + // [signaling] + const value1 = await msg.args()[0].jsonValue() + expect(value1).toBe('[signaling]') + // onmessage-switched + const value2 = await msg.args()[1].jsonValue() + expect(value2).toBe('onmessage-switched') + // websocket + const value3 = await msg.args()[2].jsonValue() + expect(value3).toBe('websocket') + + await dataChannelSignalingOnly.click('#disconnect') + + await dataChannelSignalingOnly.close() +}) diff --git a/examples/tests/whip_whep.spec.ts b/examples/tests/whip_whep.spec.ts new file mode 100644 index 00000000..d211de97 --- /dev/null +++ b/examples/tests/whip_whep.spec.ts @@ -0,0 +1,42 @@ +import { expect, test } from '@playwright/test' + +test('whip/whep', async ({ browser }) => { + const whip = await browser.newPage() + const whep = await browser.newPage() + + await whip.goto('http://localhost:9000/whip/') + await whep.goto('http://localhost:9000/whep/') + + // コーデックの取得 + const whipVideoCodecType = await whip.evaluate(() => { + const videoElement = document.querySelector('#video-codec-type') as HTMLSelectElement + return videoElement.value + }) + console.log(`whipVideoCodecType=${whipVideoCodecType}`) + + const whepVideoCodecType = await whep.evaluate(() => { + const videoElement = document.querySelector('#video-codec-type') as HTMLSelectElement + return videoElement.value + }) + console.log(`whepVideoCodecType=${whepVideoCodecType}`) + + await whip.click('#connect') + await whep.click('#connect') + + // connection-stateが"connected"になるまで待つ + await whip.waitForSelector('#connection-state:has-text("connected")') + await whep.waitForSelector('#connection-state:has-text("connected")') + + // connection-stateの値を取得して確認 + const whipConnectionState = await whip.$eval('#connection-state', (el) => el.textContent) + console.log(`whip connectionState=${whipConnectionState}`) + + const whepConnectionState = await whep.$eval('#connection-state', (el) => el.textContent) + console.log(`whep connectionState=${whepConnectionState}`) + + await whip.click('#disconnect') + await whep.click('#disconnect') + + await whip.close() + await whep.close() +}) diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 00000000..d5a1cb80 --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "strict": true, + "declaration": true, + "strictNullChecks": true, + "importHelpers": true, + "moduleResolution": "Bundler", + "experimentalDecorators": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "stripInternal": true, + "newLine": "LF", + "types": [], + "paths": { + "sora-js-sdk": ["../dist/sora.d.ts"] + }, + "lib": ["esnext", "dom", "dom.iterable", "scripthost"] + }, + "exclude": ["node_modules"] +} diff --git a/examples/vite-env.d.ts b/examples/vite-env.d.ts new file mode 100644 index 00000000..e71a892a --- /dev/null +++ b/examples/vite-env.d.ts @@ -0,0 +1,16 @@ +/// + +interface ImportMetaEnv { + VITE_SORA_SIGNALING_URL: string + VITE_SORA_API_URL: string + VITE_SORA_CHANNEL_ID_PREFIX: string + VITE_SORA_CHANNEL_ID_SUFFIX: string + VITE_ACCESS_TOKEN: string + + VITE_SORA_WHIP_ENDPOINT_URL: string + VITE_SORA_WHEP_ENDPOINT_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/examples/vite.config.mjs b/examples/vite.config.mjs new file mode 100644 index 00000000..8d34490f --- /dev/null +++ b/examples/vite.config.mjs @@ -0,0 +1,30 @@ +import { resolve } from 'node:path' +import { defineConfig } from 'vite' + +// root が examples なので examples/dist にビルドされる + +export default defineConfig({ + root: resolve(__dirname), + resolve: { + alias: { + 'sora-js-sdk': resolve(__dirname, '../dist/sora.mjs'), + }, + }, + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'index.html'), + sendrecv: resolve(__dirname, 'sendrecv/index.html'), + sendonly: resolve(__dirname, 'sendonly/index.html'), + recvonly: resolve(__dirname, 'recvonly/index.html'), + check_stereo: resolve(__dirname, 'check_stereo/index.html'), + check_stereo_multi: resolve(__dirname, 'check_stereo_multi/index.html'), + replace_track: resolve(__dirname, 'replace_track/index.html'), + simulcast: resolve(__dirname, 'simulcast/index.html'), + spotlight_sendrecv: resolve(__dirname, 'spotlight_sendrecv/index.html'), + messaging: resolve(__dirname, 'messaging/index.html'), + }, + }, + }, + envDir: resolve(__dirname, '..'), +}) diff --git a/package.json b/package.json index a420611c..6b06aa1c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "scripts": { "build": "vite build", "watch": "vite build --watch", - "dev": "vite --config e2e-tests/vite.config.mjs", + "dev": "vite --config examples/vite.config.mjs", + "e2e-dev": "vite --config e2e-tests/vite.config.mjs", "e2e-test": "pnpm build && playwright test --project=chromium", "lint": "biome lint", "fmt": "biome format --write", From d1c029ee484bf4e72fa4b79c3dcfd7a971a68211 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 16:06:27 +0900 Subject: [PATCH 02/16] =?UTF-8?q?examples=20=E3=82=92=E6=96=B0=E3=81=97?= =?UTF-8?q?=E3=81=8F=E6=BA=96=E5=82=99=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- e2e-tests/index.html | 23 ++++++++++++----------- examples/README.md | 15 ++------------- examples/index.html | 5 ----- playwright.config.mts | 2 +- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/e2e-tests/index.html b/e2e-tests/index.html index a91a7c61..653e4f3b 100644 --- a/e2e-tests/index.html +++ b/e2e-tests/index.html @@ -8,17 +8,18 @@ diff --git a/examples/README.md b/examples/README.md index a9739058..14732d02 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,4 +1,4 @@ -# Sora JavaScript SDK E2E テスト +# Sora JavaScript SDK サンプル ## 使い方 @@ -9,16 +9,5 @@ $ cd sora-js-sdk $ cp .env.template .env.local $ pnpm install $ pnpm build -$ pnpm e2e-test +$ pnpm dev ``` - -## WHIP/WHEP - -SDK では対応していないブラウザレベルでの WHIP/WHEP の E2E テストを用意しています。 - -このサンプルは Chrome / Edge でのみ動作します。 - -WHIP/WHEP を利用するには whip/whep が有効になっており、 -whip_turn/whep_turn も有効になっている必要があります。 - -さらにブラウザから fetch する場合は CORS の設定が必要です。 diff --git a/examples/index.html b/examples/index.html index 6062d99e..fa8017b8 100644 --- a/examples/index.html +++ b/examples/index.html @@ -15,13 +15,8 @@
  • ステレオチェックサンプル(マルチストリーム)
  • track 入れ替えサンプル
  • スポットライト配信視聴サンプル
  • -
  • スポットライト配信サンプル
  • -
  • スポットライト視聴サンプル
  • サイマルキャスト配信/視聴サンプル
  • -
  • 音声のみ配信サンプル
  • メッセージングサンプル
  • -
  • WHIP サンプル
  • -
  • WHEP サンプル
  • diff --git a/playwright.config.mts b/playwright.config.mts index b748c960..daa62beb 100644 --- a/playwright.config.mts +++ b/playwright.config.mts @@ -36,7 +36,7 @@ export default defineConfig({ // }, ], webServer: { - command: 'pnpm run dev --port 9000', + command: 'pnpm run e2e-dev --port 9000', url: 'http://localhost:9000/', reuseExistingServer: !process.env.CI, stdout: 'pipe', From 1c376fc508790312c3364f920f3dbba1d1f1a4c7 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 16:08:20 +0900 Subject: [PATCH 03/16] =?UTF-8?q?workspace=20=E3=82=92=E3=81=A1=E3=82=83?= =?UTF-8?q?=E3=82=93=E3=81=A8=E6=95=B4=E5=82=99=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 28 ++++++++++++++++++++-------- pnpm-workspace.yaml | 1 + 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46444810..2308683d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,18 @@ importers: specifier: 2.1.8 version: 2.1.8(@types/node@22.8.4)(jsdom@25.0.1) + e2e-tests: + dependencies: + sora-js-sdk: + specifier: workspace:* + version: link:.. + + examples: + dependencies: + sora-js-sdk: + specifier: workspace:* + version: link:.. + packages: '@babel/helper-string-parser@7.25.9': @@ -1044,11 +1056,11 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tldts-core@6.1.69: - resolution: {integrity: sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==} + tldts-core@6.1.70: + resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==} - tldts@6.1.69: - resolution: {integrity: sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==} + tldts@6.1.70: + resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==} hasBin: true tough-cookie@5.0.0: @@ -2149,15 +2161,15 @@ snapshots: tinyspy@3.0.2: {} - tldts-core@6.1.69: {} + tldts-core@6.1.70: {} - tldts@6.1.69: + tldts@6.1.70: dependencies: - tldts-core: 6.1.69 + tldts-core: 6.1.70 tough-cookie@5.0.0: dependencies: - tldts: 6.1.69 + tldts: 6.1.70 tr46@5.0.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd5136d2..d3e686fa 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - "examples" + - "e2e-tests" From 28f6c35517d9fcfba2c9796e96f65963161c9694 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 16:08:41 +0900 Subject: [PATCH 04/16] =?UTF-8?q?=E5=90=8D=E5=89=8D=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- e2e-tests/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 97f6e2a5..ba67e375 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -1,5 +1,5 @@ { - "name": "sora-js-sdk-examples", + "name": "sora-js-sdk-e2e-tests", "scripts": { "lint": "biome lint .", "fmt": "biome format --write .", @@ -8,4 +8,4 @@ "dependencies": { "sora-js-sdk": "workspace:*" } -} +} \ No newline at end of file From ee96dee05dbb43127a22fb1a8d44ad8a87e8a664 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 17:04:21 +0900 Subject: [PATCH 05/16] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E7=92=B0=E5=A2=83=E5=A4=89=E6=95=B0=E3=82=92=20TEST=5F=20?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.template | 20 +++--- .github/workflows/e2e-test.yml | 14 ++-- .github/workflows/npm-pkg-e2e-test.yml | 14 ++-- .../data_channel_signaling_only/main.mts | 30 ++++---- e2e-tests/messaging/main.mts | 20 +++--- e2e-tests/recvonly/main.mts | 16 ++--- e2e-tests/sendonly/main.mts | 30 ++++---- e2e-tests/sendonly_audio/main.mts | 30 ++++---- e2e-tests/sendrecv/main.mts | 20 +++--- e2e-tests/simulcast/main.mts | 68 +++++++++---------- e2e-tests/spotlight_recvonly/main.mts | 30 ++++---- e2e-tests/spotlight_sendonly/main.mts | 30 ++++---- e2e-tests/spotlight_sendrecv/main.mts | 28 ++++---- e2e-tests/vite-env.d.ts | 14 ++-- e2e-tests/whep/main.mts | 20 +++--- e2e-tests/whip/main.mts | 20 +++--- 16 files changed, 203 insertions(+), 201 deletions(-) diff --git a/.env.template b/.env.template index 772ed632..649cdfc0 100644 --- a/.env.template +++ b/.env.template @@ -1,14 +1,16 @@ -# サンプルやテストに利用する Sora の Signaling URL を指定してください +# サンプルに利用する Sora の Signaling URL を指定してください VITE_SORA_SIGNALING_URL=wss://sora.example.com/signaling -# サンプルやテストに利用する Sora の ChannelID を指定してください +# サンプルに利用する Sora の ChannelID を指定してください VITE_SORA_CHANNEL_ID=sora-js-sdk -# サンプルやテストに利用するアクセストークンを指定してください、不要であれば空欄で大丈夫です +# サンプルに利用するアクセストークンを指定してください、不要であれば空欄で大丈夫です VITE_ACCESS_TOKEN=access_token -# サンプルやテストに利用する Sora の API URL を指定ください、不要であれば空欄で大丈夫です -VITE_SORA_API_URL=https://sora.example.com/api -# サンプルやテストに利用する Sora の WHIP のエンドポイント URL を指定してください、不要であれば空欄で大丈夫です -VITE_SORA_WHIP_ENDPOINT_URL=https://sora.example.com/whip -# サンプルやテストに利用する Sora の WHEP のエンドポイント URL を指定してください、不要であれば空欄で大丈夫です -VITE_SORA_WHEP_ENDPOINT_URL=https://sora.example.com/whep \ No newline at end of file +# テストに利用する Sora の設定 +VITE_TEST_SIGNALING_URL= +VITE_TEST_CHANNEL_ID_PREFIX= +VITE_TEST_CHANNEL_ID_SUFFIX= +VITE_TEST_API_URL= +VITE_TEST_WHIP_ENDPOINT_URL= +VITE_TEST_WHEP_ENDPOINT_URL= +VITE_TEST_SECRET_KEY= \ No newline at end of file diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 6770a991..a35876ee 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -23,12 +23,12 @@ jobs: # browser: ["chromium", "firefox", "webkit"] browser: ["chromium"] env: - VITE_SORA_SIGNALING_URL: ${{ secrets.TEST_SIGNALING_URL }} - VITE_SORA_WHIP_ENDPOINT_URL: ${{ secrets.TEST_WHIP_ENDPOINT_URL }} - VITE_SORA_WHEP_ENDPOINT_URL: ${{ secrets.TEST_WHEP_ENDPOINT_URL }} - VITE_SORA_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }} - VITE_SORA_API_URL: ${{ secrets.TEST_API_URL }} - VITE_ACCESS_TOKEN: ${{ secrets.TEST_SECRET_KEY }} + TEST_SIGNALING_URL: ${{ secrets.TEST_SIGNALING_URL }} + TEST_WHIP_ENDPOINT_URL: ${{ secrets.TEST_WHIP_ENDPOINT_URL }} + TEST_WHEP_ENDPOINT_URL: ${{ secrets.TEST_WHEP_ENDPOINT_URL }} + TEST_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }} + TEST_API_URL: ${{ secrets.TEST_API_URL }} + TEST_SECRET_KEY: ${{ secrets.TEST_SECRET_KEY }} steps: - uses: actions/checkout@v4 - uses: tailscale/github-action@v2 @@ -46,7 +46,7 @@ jobs: - run: pnpm exec playwright install ${{ matrix.browser }} --with-deps - run: pnpm exec playwright test --project=${{ matrix.browser }} env: - VITE_SORA_CHANNEL_ID_SUFFIX: _${{ matrix.node }} + VITE_TEST_CHANNEL_ID_SUFFIX: _${{ matrix.node }} # - uses: actions/upload-artifact@v4 # if: always() # with: diff --git a/.github/workflows/npm-pkg-e2e-test.yml b/.github/workflows/npm-pkg-e2e-test.yml index 37564990..473202c6 100644 --- a/.github/workflows/npm-pkg-e2e-test.yml +++ b/.github/workflows/npm-pkg-e2e-test.yml @@ -35,12 +35,12 @@ jobs: # browser: ["chromium", "firefox", "webkit"] browser: ["chromium"] env: - VITE_SORA_SIGNALING_URL: ${{ secrets.TEST_SIGNALING_URL }} - VITE_SORA_WHIP_ENDPOINT_URL: ${{ secrets.TEST_WHIP_ENDPOINT_URL }} - VITE_SORA_WHEP_ENDPOINT_URL: ${{ secrets.TEST_WHEP_ENDPOINT_URL }} - VITE_SORA_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }} - VITE_SORA_API_URL: ${{ secrets.TEST_API_URL }} - VITE_ACCESS_TOKEN: ${{ secrets.TEST_SECRET_KEY }} + VITE_TEST_SIGNALING_URL: ${{ secrets.TEST_SIGNALING_URL }} + VITE_TEST_WHIP_ENDPOINT_URL: ${{ secrets.TEST_WHIP_ENDPOINT_URL }} + VITE_TEST_WHEP_ENDPOINT_URL: ${{ secrets.TEST_WHEP_ENDPOINT_URL }} + VITE_TEST_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }} + VITE_TEST_API_URL: ${{ secrets.TEST_API_URL }} + VITE_TEST_SECRET_KEY: ${{ secrets.TEST_SECRET_KEY }} NPM_PKG_E2E_TEST: true steps: - uses: actions/checkout@v4 @@ -62,7 +62,7 @@ jobs: - run: pnpm exec playwright install ${{ matrix.browser }} --with-deps - run: pnpm exec playwright test --project=${{ matrix.browser }} env: - VITE_SORA_CHANNEL_ID_SUFFIX: _${{ matrix.node }}_${{ matrix.sdk_version }} + VITE_TEST_CHANNEL_ID_SUFFIX: _${{ matrix.node }}_${{ matrix.sdk_version }} # - uses: actions/upload-artifact@v4 # if: always() # with: diff --git a/e2e-tests/data_channel_signaling_only/main.mts b/e2e-tests/data_channel_signaling_only/main.mts index 95c0da32..4abb3977 100644 --- a/e2e-tests/data_channel_signaling_only/main.mts +++ b/e2e-tests/data_channel_signaling_only/main.mts @@ -7,16 +7,16 @@ import Sora, { } from 'sora-js-sdk' 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) // SDK バージョンの表示 @@ -77,17 +77,17 @@ class SoraClient { private connection: ConnectionPublisher constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, ) { - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) // channel_id の生成 - this.channelId = `${channel_id_prefix}sendonly_recvonly${channel_id_suffix}` + this.channelId = `${channelIdPrefix}sendonly_recvonly${channelIdSuffix}` // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: secretKey } this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onNotify.bind(this)) diff --git a/e2e-tests/messaging/main.mts b/e2e-tests/messaging/main.mts index 411418ca..dfdd868f 100644 --- a/e2e-tests/messaging/main.mts +++ b/e2e-tests/messaging/main.mts @@ -16,10 +16,10 @@ const getChannelName = (): string => { } 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const soraJsSdkVersion = Sora.version() const soraJsSdkVersionElement = document.getElementById('sora-js-sdk-version') @@ -32,10 +32,10 @@ document.addEventListener('DOMContentLoaded', async () => { document.querySelector('#connect')?.addEventListener('click', async () => { const channelName = getChannelName() client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, channelName, ) const checkCompress = document.getElementById('check-compress') as HTMLInputElement @@ -94,12 +94,12 @@ class SoraClient { signalingUrl: string, channelIdPrefix: string, channelIdSuffix: string, - accessToken: string, + secretKey: string, channelName: string, ) { this.sora = Sora.connection(signalingUrl, this.debug) this.channelId = `${channelIdPrefix}${channelName}${channelIdSuffix}` - this.metadata = { access_token: accessToken } + this.metadata = { access_token: secretKey } this.options = { dataChannelSignaling: true, diff --git a/e2e-tests/recvonly/main.mts b/e2e-tests/recvonly/main.mts index 5193a186..77410767 100644 --- a/e2e-tests/recvonly/main.mts +++ b/e2e-tests/recvonly/main.mts @@ -6,17 +6,17 @@ import 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY // Sora クライアントの初期化 const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) // SDK バージョンの表示 diff --git a/e2e-tests/sendonly/main.mts b/e2e-tests/sendonly/main.mts index a4ca9a4e..0f883cf0 100644 --- a/e2e-tests/sendonly/main.mts +++ b/e2e-tests/sendonly/main.mts @@ -6,16 +6,16 @@ import Sora, { } from 'sora-js-sdk' 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) // SDK バージョンの表示 @@ -69,17 +69,17 @@ class SoraClient { private connection: ConnectionPublisher constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, ) { - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) // channel_id の生成 - this.channelId = `${channel_id_prefix}sendonly_recvonly${channel_id_suffix}` + this.channelId = `${channelIdPrefix}sendonly_recvonly${channelIdSuffix}` // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: secretKey } this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onNotify.bind(this)) diff --git a/e2e-tests/sendonly_audio/main.mts b/e2e-tests/sendonly_audio/main.mts index 9305e06b..d7b772ed 100644 --- a/e2e-tests/sendonly_audio/main.mts +++ b/e2e-tests/sendonly_audio/main.mts @@ -5,16 +5,16 @@ import Sora, { } from 'sora-js-sdk' 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) document.querySelector('#connect')?.addEventListener('click', async () => { @@ -73,17 +73,17 @@ class SoraClient { private connection: ConnectionPublisher constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, ) { - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) // channel_id の生成 - this.channelId = `${channel_id_prefix}sendonly_audio_codec${channel_id_suffix}` + this.channelId = `${channelIdPrefix}sendonly_audio_codec${channelIdSuffix}` // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: secretKey } this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onnotify.bind(this)) diff --git a/e2e-tests/sendrecv/main.mts b/e2e-tests/sendrecv/main.mts index ca7f2c6d..9cef7cb8 100644 --- a/e2e-tests/sendrecv/main.mts +++ b/e2e-tests/sendrecv/main.mts @@ -25,10 +25,10 @@ const getVideoCodecType = (): VideoCodecType | undefined => { } 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY let client: SoraClient @@ -37,10 +37,10 @@ document.addEventListener('DOMContentLoaded', async () => { const videoCodecType = getVideoCodecType() client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, channelName, videoCodecType, ) @@ -92,7 +92,7 @@ class SoraClient { signalingUrl: string, channelIdPrefix: string, channelIdSuffix: string, - accessToken: string, + secretKey: string, channelName: string, videoCodecType: VideoCodecType | undefined, ) { @@ -100,7 +100,7 @@ class SoraClient { this.channelId = `${channelIdPrefix}${channelName}${channelIdSuffix}` - this.metadata = { access_token: accessToken } + this.metadata = { access_token: secretKey } this.options = {} if (videoCodecType !== undefined) { diff --git a/e2e-tests/simulcast/main.mts b/e2e-tests/simulcast/main.mts index b34ed3ea..d195cfb3 100644 --- a/e2e-tests/simulcast/main.mts +++ b/e2e-tests/simulcast/main.mts @@ -7,39 +7,39 @@ import Sora, { } from 'sora-js-sdk' 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const sendonly = new SimulcastSendonlySoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) const recvonlyR0 = new SimulcastRecvonlySoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, 'r0', ) const recvonlyR1 = new SimulcastRecvonlySoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, 'r1', ) const recvonlyR2 = new SimulcastRecvonlySoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, 'r2', ) @@ -104,17 +104,17 @@ class SimulcastSendonlySoraClient { private connection: ConnectionPublisher constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, ) { - this.channelId = `${channel_id_prefix}simulcast${channel_id_suffix}` + this.channelId = `${channelIdPrefix}simulcast${channelIdSuffix}` - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) this.connection = this.sora.sendonly( this.channelId, - { access_token }, + { access_token: secretKey }, { audio: false, video: true, videoCodecType: 'VP8', videoBitRate: 2500, simulcast: true }, ) @@ -167,19 +167,19 @@ class SimulcastRecvonlySoraClient { private connection: ConnectionSubscriber constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, rid: SimulcastRid, ) { - this.channelId = `${channel_id_prefix}simulcast${channel_id_suffix}` + this.channelId = `${channelIdPrefix}simulcast${channelIdSuffix}` this.rid = rid - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) this.connection = this.sora.recvonly( this.channelId, - { access_token }, + { access_token: secretKey }, { simulcastRid: this.rid, simulcast: true }, ) diff --git a/e2e-tests/spotlight_recvonly/main.mts b/e2e-tests/spotlight_recvonly/main.mts index 7e00cf48..8f33c5a3 100644 --- a/e2e-tests/spotlight_recvonly/main.mts +++ b/e2e-tests/spotlight_recvonly/main.mts @@ -6,17 +6,17 @@ import 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY // Sora クライアントの初期化 const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) document.querySelector('#connect')?.addEventListener('click', async () => { @@ -38,12 +38,12 @@ class SoraClient { private connection: ConnectionSubscriber constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, ) { - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) this.options = { multistream: true, @@ -52,9 +52,9 @@ class SoraClient { } // channel_id の生成 - this.channelId = `${channel_id_prefix}spotlight_sendonly_recvonly${channel_id_suffix}` + this.channelId = `${channelIdPrefix}spotlight_recvonly${channelIdSuffix}` // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: secretKey } this.connection = this.sora.recvonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onnotify.bind(this)) diff --git a/e2e-tests/spotlight_sendonly/main.mts b/e2e-tests/spotlight_sendonly/main.mts index bc80563c..c92c6593 100644 --- a/e2e-tests/spotlight_sendonly/main.mts +++ b/e2e-tests/spotlight_sendonly/main.mts @@ -5,16 +5,16 @@ import Sora, { } from 'sora-js-sdk' 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) document.querySelector('#connect')?.addEventListener('click', async () => { @@ -37,12 +37,12 @@ class SoraClient { private connection: ConnectionPublisher constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelIdPrefix: string, + channelIdSuffix: string, + secretKey: string, ) { - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) this.options = { multistream: true, @@ -51,9 +51,9 @@ class SoraClient { } // channel_id の生成 - this.channelId = `${channel_id_prefix}spotlight_sendonly_recvonly${channel_id_suffix}` + this.channelId = `${channelIdPrefix}spotlight_sendonly_recvonly${channelIdSuffix}` // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: secretKey } this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onnotify.bind(this)) diff --git a/e2e-tests/spotlight_sendrecv/main.mts b/e2e-tests/spotlight_sendrecv/main.mts index e9ee49ca..10d6fef0 100644 --- a/e2e-tests/spotlight_sendrecv/main.mts +++ b/e2e-tests/spotlight_sendrecv/main.mts @@ -5,25 +5,25 @@ import Sora, { } from 'sora-js-sdk' 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 signalingUrl = import.meta.env.VITE_TEST_SIGNALING_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY const sendrecv1 = new SoraClient( 'sendrecv1', - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) const sendrecv2 = new SoraClient( 'sendrecv2', - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, + signalingUrl, + channelIdPrefix, + channelIdSuffix, + secretKey, ) document.querySelector('#sendrecv1-connect')?.addEventListener('click', async () => { @@ -63,13 +63,13 @@ class SoraClient { signalingUrl: string, channelIdPrefix: string, channelIdSuffix: string, - accessToken: string, + secretKey: string, ) { this.label = label this.sora = Sora.connection(signalingUrl, this.debug) this.channelId = `${channelIdPrefix}spotlight_sendrecv${channelIdSuffix}` - this.metadata = { access_token: accessToken } + this.metadata = { access_token: secretKey } this.options = { audio: true, video: true, diff --git a/e2e-tests/vite-env.d.ts b/e2e-tests/vite-env.d.ts index e71a892a..c6e893f9 100644 --- a/e2e-tests/vite-env.d.ts +++ b/e2e-tests/vite-env.d.ts @@ -1,14 +1,14 @@ /// interface ImportMetaEnv { - VITE_SORA_SIGNALING_URL: string - VITE_SORA_API_URL: string - VITE_SORA_CHANNEL_ID_PREFIX: string - VITE_SORA_CHANNEL_ID_SUFFIX: string - VITE_ACCESS_TOKEN: string + VITE_TEST_SIGNALING_URL: string + VITE_TEST_API_URL: string + VITE_TEST_CHANNEL_ID_PREFIX: string + VITE_TEST_CHANNEL_ID_SUFFIX: string + VITE_TEST_SECRET_KEY: string - VITE_SORA_WHIP_ENDPOINT_URL: string - VITE_SORA_WHEP_ENDPOINT_URL: string + VITE_TEST_WHIP_ENDPOINT_URL: string + VITE_TEST_WHEP_ENDPOINT_URL: string } interface ImportMeta { diff --git a/e2e-tests/whep/main.mts b/e2e-tests/whep/main.mts index 4f08e240..2b59fa69 100644 --- a/e2e-tests/whep/main.mts +++ b/e2e-tests/whep/main.mts @@ -1,8 +1,8 @@ document.addEventListener('DOMContentLoaded', async () => { - const endpointUrl = import.meta.env.VITE_SORA_WHEP_ENDPOINT_URL - const channelIdPrefix = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' - const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' - const accessToken = import.meta.env.VITE_ACCESS_TOKEN + const endpointUrl = import.meta.env.VITE_TEST_WHEP_ENDPOINT_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY let whepClient: WhepClient | undefined @@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', async () => { throw new Error('Video codec type select element not found') } - whepClient = new WhepClient(endpointUrl, channelId, videoCodecTypeElement.value, accessToken) + whepClient = new WhepClient(endpointUrl, channelId, videoCodecTypeElement.value, secretKey) await whepClient.connect() }) @@ -38,14 +38,14 @@ class WhepClient { private channelId: string private videoCodecType: string - private accessToken: string + private secretKey: string private pc: RTCPeerConnection | undefined - constructor(endpointUrl: string, channelId: string, videoCodecType: string, accessToken: string) { + constructor(endpointUrl: string, channelId: string, videoCodecType: string, secretKey: string) { this.endpointUrl = endpointUrl this.channelId = channelId this.videoCodecType = videoCodecType - this.accessToken = accessToken + this.secretKey = secretKey } async connect(): Promise { @@ -112,7 +112,7 @@ class WhepClient { const response = await fetch(whepEndpointUrl, { method: 'POST', headers: { - Authorization: `Bearer ${this.accessToken}`, + Authorization: `Bearer ${this.secretKey}`, 'Content-Type': 'application/sdp', }, body: offer.sdp, @@ -165,7 +165,7 @@ class WhepClient { const response = await fetch(this.resourceUrl, { method: 'DELETE', headers: { - Authorization: `Bearer ${this.accessToken}`, + Authorization: `Bearer ${this.secretKey}`, }, }) diff --git a/e2e-tests/whip/main.mts b/e2e-tests/whip/main.mts index bc174d2c..06bec2a5 100644 --- a/e2e-tests/whip/main.mts +++ b/e2e-tests/whip/main.mts @@ -1,8 +1,8 @@ document.addEventListener('DOMContentLoaded', async () => { - const endpointUrl = import.meta.env.VITE_SORA_WHIP_ENDPOINT_URL - const channelIdPrefix = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' - const channelIdSuffix = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' - const accessToken = import.meta.env.VITE_ACCESS_TOKEN + const endpointUrl = import.meta.env.VITE_TEST_WHIP_ENDPOINT_URL + const channelIdPrefix = import.meta.env.VITE_TEST_CHANNEL_ID_PREFIX || '' + const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' + const secretKey = import.meta.env.VITE_TEST_SECRET_KEY let whipClient: WhipClient | undefined @@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', async () => { throw new Error('Video codec type select element not found') } - whipClient = new WhipClient(endpointUrl, channelId, videoCodecTypeElement.value, accessToken) + whipClient = new WhipClient(endpointUrl, channelId, videoCodecTypeElement.value, secretKey) const stream = await navigator.mediaDevices.getUserMedia({ video: true, @@ -58,16 +58,16 @@ class WhipClient { private videoCodecType: string - private accessToken: string + private secretKey: string private pc: RTCPeerConnection | undefined private stream: MediaStream | undefined - constructor(endpointUrl: string, channelId: string, videoCodecType: string, accessToken: string) { + constructor(endpointUrl: string, channelId: string, videoCodecType: string, secretKey: string) { this.endpointUrl = endpointUrl this.channelId = channelId this.videoCodecType = videoCodecType - this.accessToken = accessToken + this.secretKey = secretKey } async connect(stream: MediaStream, channelId: string): Promise { @@ -137,7 +137,7 @@ class WhipClient { method: 'POST', headers: { // 認証は Bearer Token を利用する - Authorization: `Bearer ${this.accessToken}`, + Authorization: `Bearer ${this.secretKey}`, // application/sdp を指定する 'Content-Type': 'application/sdp', }, @@ -196,7 +196,7 @@ class WhipClient { const response = await fetch(this.resourceUrl, { method: 'DELETE', headers: { - Authorization: `Bearer ${this.accessToken}`, + Authorization: `Bearer ${this.secretKey}`, }, }) From 7174abc2e19b79380fd12aeda5fcb49264529a45 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 17:33:43 +0900 Subject: [PATCH 06/16] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E6=95=B4?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 3 ++ examples/check_stereo/main.mts | 19 +++++---- examples/check_stereo_multi/main.mts | 22 +++++------ examples/messaging/main.mts | 35 +++-------------- examples/recvonly/main.mts | 27 ++++--------- examples/replace_track/main.mts | 31 +++------------ examples/sendonly/index.html | 4 +- examples/sendonly/main.mts | 29 ++++++-------- examples/sendrecv/index.html | 2 - examples/sendrecv/main.mts | 32 +++------------ examples/simulcast/main.mts | 59 +++++++--------------------- examples/spotlight_sendrecv/main.mts | 36 ++++------------- examples/vite-env.d.ts | 6 +-- 13 files changed, 85 insertions(+), 220 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1cc372d1..a9263200 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,7 +25,10 @@ - @voluntas - [ADD] .markdownlint.yaml ファイルを追加する - @voluntas +- [ADD] 新しい examples を追加する + - @voluntas - [CHANGE] examples を e2e-tests に変更する + - 環境変数の Prefix を TEST_ に切り替える - @voluntas - [CHANGE] tsconfig.json の moduleResolution を Bundler に変更する - @voluntas diff --git a/examples/check_stereo/main.mts b/examples/check_stereo/main.mts index 4f096b88..57a2c1c7 100644 --- a/examples/check_stereo/main.mts +++ b/examples/check_stereo/main.mts @@ -7,14 +7,13 @@ import Sora, { document.addEventListener('DOMContentLoaded', async () => { // 環境変数の読み込み - const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL + const signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL const uuid = crypto.randomUUID() // Sora クライアントの初期化 - const sendonly = new SendonlyClient(SORA_SIGNALING_URL, uuid) - - const recvonly = new RecvonlyClient(SORA_SIGNALING_URL, uuid) + const sendonly = new SendonlyClient(signalingUrl, uuid) + const recvonly = new RecvonlyClient(signalingUrl, uuid) // デバイスリストの取得と設定 await updateDeviceLists() @@ -76,10 +75,10 @@ class SendonlyClient { private channelCheckInterval: number | undefined - constructor(signaling_url: string, channel_id: string) { - this.sora = Sora.connection(signaling_url, this.debug) + constructor(signalingUrl: string, channelId: string) { + this.sora = Sora.connection(signalingUrl, this.debug) - this.channelId = channel_id + this.channelId = channelId this.connection = this.sora.sendonly(this.channelId, undefined, this.options) @@ -273,10 +272,10 @@ class RecvonlyClient { private canvas: HTMLCanvasElement | null = null private canvasCtx: CanvasRenderingContext2D | null = null - constructor(signaling_url: string, channel_id: string) { - this.channelId = channel_id + constructor(signalingUrl: string, channelId: string) { + this.channelId = channelId - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) this.connection = this.sora.recvonly(this.channelId, undefined, this.options) diff --git a/examples/check_stereo_multi/main.mts b/examples/check_stereo_multi/main.mts index 53274a6f..40b82b88 100644 --- a/examples/check_stereo_multi/main.mts +++ b/examples/check_stereo_multi/main.mts @@ -7,15 +7,15 @@ import Sora, { document.addEventListener('DOMContentLoaded', async () => { // 環境変数の読み込み - const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL + const signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL const uuid = crypto.randomUUID() // Sora クライアントの初期化 - const sendonly1 = new SendonlyClient(SORA_SIGNALING_URL, uuid, 1) - const sendonly2 = new SendonlyClient(SORA_SIGNALING_URL, uuid, 2) + const sendonly1 = new SendonlyClient(signalingUrl, uuid, 1) + const sendonly2 = new SendonlyClient(signalingUrl, uuid, 2) - const recvonly = new RecvonlyClient(SORA_SIGNALING_URL, uuid) + const recvonly = new RecvonlyClient(signalingUrl, uuid) // デバイスリストの取得と設定 await updateDeviceLists() @@ -101,12 +101,12 @@ class SendonlyClient { private sendonlyClientId: number - constructor(signaling_url: string, channel_id: string, sendonly_client_id: number) { - this.sora = Sora.connection(signaling_url, this.debug) + constructor(signalingUrl: string, channelId: string, sendonlyClientId: number) { + this.sora = Sora.connection(signalingUrl, this.debug) - this.channelId = channel_id + this.channelId = channelId - this.sendonlyClientId = sendonly_client_id + this.sendonlyClientId = sendonlyClientId this.connection = this.sora.sendonly(this.channelId, undefined, this.options) @@ -312,10 +312,10 @@ class RecvonlyClient { private canvases = new Map() private canvasCtxs = new Map() - constructor(signaling_url: string, channel_id: string) { - this.channelId = channel_id + constructor(signalingUrl: string, channelId: string) { + this.channelId = channelId - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) this.connection = this.sora.recvonly(this.channelId, undefined, this.options) diff --git a/examples/messaging/main.mts b/examples/messaging/main.mts index 411418ca..526ebbf2 100644 --- a/examples/messaging/main.mts +++ b/examples/messaging/main.mts @@ -6,20 +6,10 @@ import Sora, { type DataChannelEvent, } from 'sora-js-sdk' -const getChannelName = (): string => { - const channelNameElement = document.querySelector('#channel-name') - const channelName = channelNameElement?.value - if (channelName === '' || channelName === undefined) { - throw new Error('channelName is empty') - } - return channelName -} - 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 signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL + const channelId = import.meta.env.VITE_SORA_CHANNEL_ID || '' + const accessToken = import.meta.env.VITE_ACCESS_TOKEN || '' const soraJsSdkVersion = Sora.version() const soraJsSdkVersionElement = document.getElementById('sora-js-sdk-version') @@ -30,14 +20,7 @@ document.addEventListener('DOMContentLoaded', async () => { let client: SoraClient document.querySelector('#connect')?.addEventListener('click', async () => { - const channelName = getChannelName() - client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, - channelName, - ) + client = new SoraClient(signalingUrl, channelId, accessToken) const checkCompress = document.getElementById('check-compress') as HTMLInputElement const compress = checkCompress.checked const checkHeader = document.getElementById('check-header') as HTMLInputElement @@ -90,15 +73,9 @@ class SoraClient { private sora: SoraConnection private connection: ConnectionMessaging - constructor( - signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, - accessToken: string, - channelName: string, - ) { + constructor(signalingUrl: string, channelId: string, accessToken: string) { this.sora = Sora.connection(signalingUrl, this.debug) - this.channelId = `${channelIdPrefix}${channelName}${channelIdSuffix}` + this.channelId = channelId this.metadata = { access_token: accessToken } this.options = { diff --git a/examples/recvonly/main.mts b/examples/recvonly/main.mts index 5193a186..1afa8584 100644 --- a/examples/recvonly/main.mts +++ b/examples/recvonly/main.mts @@ -6,18 +6,12 @@ import 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 signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL + const channelId = import.meta.env.VITE_SORA_CHANNEL_ID + const accessToken = import.meta.env.VITE_ACCESS_TOKEN || '' // Sora クライアントの初期化 - const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, - ) + const client = new SoraClient(signalingUrl, channelId, accessToken) // SDK バージョンの表示 const sdkVersionElement = document.querySelector('#sdk-version') @@ -68,18 +62,13 @@ class SoraClient { 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) + constructor(signalingUrl: string, channelId: string, accessToken: string) { + this.sora = Sora.connection(signalingUrl, this.debug) // channel_id の生成 - this.channelId = `${channel_id_prefix}sendonly_recvonly${channel_id_suffix}` + this.channelId = channelId // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: accessToken } this.connection = this.sora.recvonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onnotify.bind(this)) diff --git a/examples/replace_track/main.mts b/examples/replace_track/main.mts index 25f374fa..4f2fb363 100644 --- a/examples/replace_track/main.mts +++ b/examples/replace_track/main.mts @@ -6,14 +6,6 @@ import Sora, { type ConnectionOptions, } from 'sora-js-sdk' -const getChannelName = (): string => { - const channelNameElement = document.querySelector('#channel-name') - const channelName = channelNameElement?.value - if (channelName === '' || channelName === undefined) { - throw new Error('channelName is empty') - } - return channelName -} const getVideoCodecType = (): VideoCodecType | undefined => { const videoCodecTypeElement = document.querySelector('#video-codec-type') @@ -25,25 +17,16 @@ const getVideoCodecType = (): VideoCodecType | undefined => { } 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 signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL + const channelId = import.meta.env.VITE_SORA_CHANNEL_ID || '' + const accessToken = import.meta.env.VITE_ACCESS_TOKEN || '' let client: SoraClient document.querySelector('#connect')?.addEventListener('click', async () => { - const channelName = getChannelName() const videoCodecType = getVideoCodecType() - client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, - channelName, - videoCodecType, - ) + client = new SoraClient(signalingUrl, channelId, accessToken, videoCodecType) await client.connect() }) @@ -98,14 +81,12 @@ class SoraClient { constructor( signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, + channelId: string, accessToken: string, - channelName: string, videoCodecType: VideoCodecType | undefined, ) { this.sora = Sora.connection(signalingUrl, this.debug) - this.channelId = `${channelIdPrefix}${channelName}${channelIdSuffix}` + this.channelId = channelId this.metadata = { access_token: accessToken } this.options = {} diff --git a/examples/sendonly/index.html b/examples/sendonly/index.html index 883b7018..add07d52 100644 --- a/examples/sendonly/index.html +++ b/examples/sendonly/index.html @@ -2,12 +2,12 @@ - Sendonly test + Sendonly サンプル
    -

    Sendonly test

    +

    Sendonly サンプル

    diff --git a/examples/sendonly/main.mts b/examples/sendonly/main.mts index a4ca9a4e..5838ebcc 100644 --- a/examples/sendonly/main.mts +++ b/examples/sendonly/main.mts @@ -6,17 +6,11 @@ import Sora, { } from 'sora-js-sdk' 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, - ) + const signalingUrl = import.meta.env.VITE_SORA_SIGNALING_URL + const channelId = import.meta.env.VITE_SORA_CHANNEL_ID || '' + const accessToken = import.meta.env.VITE_ACCESS_TOKEN || '' + + const client = new SoraClient(signalingUrl, channelId, accessToken) // SDK バージョンの表示 const sdkVersionElement = document.querySelector('#sdk-version') @@ -69,17 +63,16 @@ class SoraClient { private connection: ConnectionPublisher constructor( - signaling_url: string, - channel_id_prefix: string, - channel_id_suffix: string, - access_token: string, + signalingUrl: string, + channelId: string, + accessToken: string, ) { - this.sora = Sora.connection(signaling_url, this.debug) + this.sora = Sora.connection(signalingUrl, this.debug) // channel_id の生成 - this.channelId = `${channel_id_prefix}sendonly_recvonly${channel_id_suffix}` + this.channelId = channelId // access_token を指定する metadata の生成 - this.metadata = { access_token: access_token } + this.metadata = { access_token: accessToken } this.connection = this.sora.sendonly(this.channelId, this.metadata, this.options) this.connection.on('notify', this.onNotify.bind(this)) diff --git a/examples/sendrecv/index.html b/examples/sendrecv/index.html index ad144a5a..8e80475f 100644 --- a/examples/sendrecv/index.html +++ b/examples/sendrecv/index.html @@ -9,8 +9,6 @@

    Sendrecv test

    - -

    diff --git a/examples/simulcast/index.html b/examples/simulcast/index.html index e39a21eb..ed9afcdd 100644 --- a/examples/simulcast/index.html +++ b/examples/simulcast/index.html @@ -2,12 +2,12 @@ - Simulcast test + Simulcast サンプル
    -

    Simulcast test

    +

    Simulcast サンプル


    From 55702bc8fd04d418cc0eaeede4d4caba77ff8b3d Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 18:13:11 +0900 Subject: [PATCH 12/16] =?UTF-8?q?vite=20=E6=8A=9C=E3=81=91=E3=81=A6?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/e2e-test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index a35876ee..73fd39d8 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -23,12 +23,12 @@ jobs: # browser: ["chromium", "firefox", "webkit"] browser: ["chromium"] env: - TEST_SIGNALING_URL: ${{ secrets.TEST_SIGNALING_URL }} - TEST_WHIP_ENDPOINT_URL: ${{ secrets.TEST_WHIP_ENDPOINT_URL }} - TEST_WHEP_ENDPOINT_URL: ${{ secrets.TEST_WHEP_ENDPOINT_URL }} - TEST_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }} - TEST_API_URL: ${{ secrets.TEST_API_URL }} - TEST_SECRET_KEY: ${{ secrets.TEST_SECRET_KEY }} + VITE_TEST_SIGNALING_URL: ${{ secrets.TEST_SIGNALING_URL }} + VITE_TEST_WHIP_ENDPOINT_URL: ${{ secrets.TEST_WHIP_ENDPOINT_URL }} + VITE_TEST_WHEP_ENDPOINT_URL: ${{ secrets.TEST_WHEP_ENDPOINT_URL }} + VITE_TEST_CHANNEL_ID_PREFIX: ${{ secrets.TEST_CHANNEL_ID_PREFIX }} + VITE_TEST_API_URL: ${{ secrets.TEST_API_URL }} + VITE_TEST_SECRET_KEY: ${{ secrets.TEST_SECRET_KEY }} steps: - uses: actions/checkout@v4 - uses: tailscale/github-action@v2 From 51f78b48901e85ed001442c3359ad6ffabcf4671 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 18:17:01 +0900 Subject: [PATCH 13/16] =?UTF-8?q?TEST=5FAPI=5FURL=20=E3=81=AE=E9=96=93?= =?UTF-8?q?=E9=81=95=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- e2e-tests/data_channel_signaling_only/index.html | 4 ++-- e2e-tests/data_channel_signaling_only/main.mts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e-tests/data_channel_signaling_only/index.html b/e2e-tests/data_channel_signaling_only/index.html index c44ec923..eb2965d0 100644 --- a/e2e-tests/data_channel_signaling_only/index.html +++ b/e2e-tests/data_channel_signaling_only/index.html @@ -2,12 +2,12 @@ - Sendonly test + DataChannelSignalingOnly test
    -

    Sendonly test

    +

    DataChannelSignalingOnly test

    diff --git a/e2e-tests/data_channel_signaling_only/main.mts b/e2e-tests/data_channel_signaling_only/main.mts index 4abb3977..be310a5a 100644 --- a/e2e-tests/data_channel_signaling_only/main.mts +++ b/e2e-tests/data_channel_signaling_only/main.mts @@ -145,9 +145,9 @@ class SoraClient { // E2E テスト側で実行した方が良い気がする async apiDisconnect(): Promise { - const apiUrl = import.meta.env.VITE_SORA_API_URL + const apiUrl = import.meta.env.VITE_TEST_API_URL if (apiUrl === '') { - console.error('VITE_SORA_API_URL is not set') + console.error('VITE_TEST_API_URL is not set') } const response = await fetch(apiUrl, { method: 'POST', From cfe55b55f765e31cf8f970598b3262ededa022d0 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 18:20:29 +0900 Subject: [PATCH 14/16] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=AE=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/tests/helper.ts | 13 -- examples/tests/message_header.spec.ts | 184 ---------------- examples/tests/messaging.spec.ts | 197 ------------------ examples/tests/sendonly_audio.spec.ts | 75 ------- examples/tests/sendonly_recvonly.spec.ts | 125 ----------- examples/tests/sendrecv.spec.ts | 135 ------------ examples/tests/simulcast.spec.ts | 78 ------- .../tests/spotlight_sendonly_recvonly.spec.ts | 34 --- examples/tests/spotlight_sendrecv.spec.ts | 25 --- examples/tests/type_close.spec.ts | 60 ------ examples/tests/type_switched.spec.ts | 56 ----- examples/tests/whip_whep.spec.ts | 42 ---- 12 files changed, 1024 deletions(-) delete mode 100644 examples/tests/helper.ts delete mode 100644 examples/tests/message_header.spec.ts delete mode 100644 examples/tests/messaging.spec.ts delete mode 100644 examples/tests/sendonly_audio.spec.ts delete mode 100644 examples/tests/sendonly_recvonly.spec.ts delete mode 100644 examples/tests/sendrecv.spec.ts delete mode 100644 examples/tests/simulcast.spec.ts delete mode 100644 examples/tests/spotlight_sendonly_recvonly.spec.ts delete mode 100644 examples/tests/spotlight_sendrecv.spec.ts delete mode 100644 examples/tests/type_close.spec.ts delete mode 100644 examples/tests/type_switched.spec.ts delete mode 100644 examples/tests/whip_whep.spec.ts diff --git a/examples/tests/helper.ts b/examples/tests/helper.ts deleted file mode 100644 index db9a229f..00000000 --- a/examples/tests/helper.ts +++ /dev/null @@ -1,13 +0,0 @@ -// バージョン比較用のヘルパー関数を追加 -export const isVersionGreaterThanOrEqual = (packageVersion: string, version: string): boolean => { - const v1Parts = packageVersion.split('.').map(Number) - const v2Parts = version.split('.').map(Number) - - for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { - const v1 = v1Parts[i] || 0 - const v2 = v2Parts[i] || 0 - if (v1 > v2) return true - if (v1 < v2) return false - } - return true -} diff --git a/examples/tests/message_header.spec.ts b/examples/tests/message_header.spec.ts deleted file mode 100644 index bf18ea9b..00000000 --- a/examples/tests/message_header.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { expect, test } from '@playwright/test' -import { isVersionGreaterThanOrEqual } from './helper' - -test('messaging pages with header', async ({ browser }) => { - // 新しいページを2つ作成 - const page1 = await browser.newPage() - const page2 = await browser.newPage() - - // それぞれのページに対して操作を行う - await page1.goto('http://localhost:9000/messaging/') - await page2.goto('http://localhost:9000/messaging/') - - // sora js sdk のバージョンを取得する - await page1.waitForSelector('#sora-js-sdk-version') - const page1SoraJsSdkVersion = await page1.$eval('#sora-js-sdk-version', (el) => el.textContent) - if (page1SoraJsSdkVersion === null) { - throw new Error('page1SoraJsSdkVersion is null') - } - // sora-js-sdk のバージョンが 2024.2.0 以上であるか確認して、2024.2.0 未満の場合はテストをスキップする - test.skip( - !isVersionGreaterThanOrEqual(page1SoraJsSdkVersion, '2024.2.0'), - 'sora-js-sdk のバージョンが 2024.2.0 以上である必要があります', - ) - - // チャネル名を設定する - await page1.fill('input[name="channel-name"]', 'message-header') - await page2.fill('input[name="channel-name"]', 'message-header') - - // header を有効にする - await page1.check('#check-header') - await page2.check('#check-header') - - // connect ボタンを押して接続開始 - await page1.click('#connect') - await page2.click('#connect') - - 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('#connection-id:not(:empty)') - const page2ConnectionId = await page2.$eval('#connection-id', (el) => el.textContent) - console.log(`page2 connectionId=${page2ConnectionId}`) - - // page1 で #example の DataChannel が open したことを確認 - await page1.waitForSelector('#messaging li', { state: 'attached' }) - - // page2 で #example の DataChannel が open したことを確認 - await page2.waitForSelector('#messaging li', { state: 'attached' }) - - // page1からpage2へメッセージを送信 - 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) - - // 受信したメッセージが期待したものであるか検証 - // receivedMessage の先頭 26 バイトには sender_connection_id が含まれている - test.expect(receivedMessage1?.slice(0, 26)).toBe(page1ConnectionId) - // 27 文字目からは page1 のメッセージがそのまま - test.expect(receivedMessage1).toContain(page1Message) - - // page2からpage1へメッセージを送信 - const page2Message = 'Hello from page2' - await page2.fill('input[name="message"]', page2Message) - await page2.click('#send-message') - - // page1でメッセージが受信されたことを確認 - await page1.waitForSelector('#received-messages li', { state: 'attached' }) - const receivedMessage2 = await page1.$eval('#received-messages li', (el) => el.textContent) - - // 受信したメッセージが期待したものであるか検証 - console.log(`Received message on page1: ${receivedMessage2}`) - // receivedMessage の先頭 26 バイトには sender_connection_id が含まれている - test.expect(receivedMessage2?.slice(0, 26)).toBe(page2ConnectionId) - // 27 文字目からは page2 のメッセージがそのまま - test.expect(receivedMessage2).toContain(page2Message) - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await page1.click('#get-stats') - await page2.click('#get-stats') - - // 統計情報が表示されるまで待機 - await page1.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const page1StatsReportJson: Record[] = await page1.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // page1 stats report - const page1DataChannelStats = page1StatsReportJson.filter( - (report) => report.type === 'data-channel', - ) - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'signaling' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'push' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'notify' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'stats' && stats.state === 'open' - }), - ).toBeDefined() - - const page1ExampleStats = page1DataChannelStats.find((stats) => { - return stats.label === '#example' && stats.state === 'open' - }) - // ここで undefined ではないことを確認してる - expect(page1ExampleStats).toBeDefined() - expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) - expect(page1ExampleStats?.bytesSent).toBeGreaterThan(0) - expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) - - // 統計情報が表示されるまで待機 - await page2.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const page2StatsReportJson: Record[] = await page2.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // page2 stats report - const page2DataChannelStats = page2StatsReportJson.filter( - (report) => report.type === 'data-channel', - ) - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'signaling' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'push' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'notify' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'stats' && stats.state === 'open' - }), - ).toBeDefined() - - const page2ExampleStats = page2DataChannelStats.find((stats) => { - return stats.label === '#example' && stats.state === 'open' - }) - // ここで undefined ではないことを確認してる - expect(page2ExampleStats).toBeDefined() - expect(page2ExampleStats?.bytesReceived).toBeGreaterThan(0) - expect(page2ExampleStats?.messagesReceived).toBeGreaterThan(0) - expect(page2ExampleStats?.bytesSent).toBeGreaterThan(0) - expect(page2ExampleStats?.messagesSent).toBeGreaterThan(0) - - await page1.click('#disconnect') - await page2.click('#disconnect') - - await page1.close() - await page2.close() -}) diff --git a/examples/tests/messaging.spec.ts b/examples/tests/messaging.spec.ts deleted file mode 100644 index 7f47fa03..00000000 --- a/examples/tests/messaging.spec.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { expect, test } from '@playwright/test' -import { isVersionGreaterThanOrEqual } from './helper' - -// sora-js-sdk のバージョンが 2024.2.0 以降の場合のみ実行したい -test('messaging pages', async ({ browser }) => { - // 新しいページを2つ作成 - const page1 = await browser.newPage() - const page2 = await browser.newPage() - - // それぞれのページに対して操作を行う - await page1.goto('http://localhost:9000/messaging/') - await page2.goto('http://localhost:9000/messaging/') - - // sora js sdk のバージョンを取得する - await page1.waitForSelector('#sora-js-sdk-version') - const page1SoraJsSdkVersion = await page1.$eval('#sora-js-sdk-version', (el) => el.textContent) - if (page1SoraJsSdkVersion === null) { - throw new Error('page1SoraJsSdkVersion is null') - } - // sora-js-sdk のバージョンが 2024.2.0 以上であるか確認して、2024.2.0 未満の場合はテストをスキップする - test.skip( - !isVersionGreaterThanOrEqual(page1SoraJsSdkVersion, '2024.2.0'), - 'sora-js-sdk のバージョンが 2024.2.0 以上である必要があります', - ) - - // Compress のTrue/Falseをランダムで設定する - const selectedCompress1 = await page1.evaluate(() => { - const checkCompress = document.getElementById('check-compress') as HTMLInputElement - const getRandomBoolean = (): boolean => Math.random() >= 0.5 - const randomCompress: boolean = getRandomBoolean() - checkCompress.checked = randomCompress - return randomCompress - }) - const selectedCompress2 = await page2.evaluate(() => { - const checkCompress = document.getElementById('check-compress') as HTMLInputElement - const getRandomBoolean = (): boolean => Math.random() >= 0.5 - const randomCompress: boolean = getRandomBoolean() - checkCompress.checked = randomCompress - return randomCompress - }) - // 設定した Compress の True/False をログに流す - console.log(`page1 Compress = ${selectedCompress1}`) - console.log(`page2 Compress = ${selectedCompress2}`) - - // connect ボタンを押して接続開始 - await page1.click('#connect') - await page2.click('#connect') - - 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('#connection-id:not(:empty)') - const page2ConnectionId = await page2.$eval('#connection-id', (el) => el.textContent) - console.log(`page2 connectionId=${page2ConnectionId}`) - - // page1 で #example の DataChannel が open したことを確認 - await page1.waitForSelector('#messaging li', { state: 'attached' }) - - // page2 で #example の DataChannel が open したことを確認 - await page2.waitForSelector('#messaging li', { state: 'attached' }) - - // page1からpage2へメッセージを送信 - 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) - - // 受信したメッセージが期待したものであるか検証 - console.log(`Received message on page2: ${receivedMessage1}`) - test.expect(receivedMessage1).toBe(page1Message) - - // page2からpage1へメッセージを送信 - const page2Message = 'Hello from page2' - await page2.fill('input[name="message"]', page2Message) - await page2.click('#send-message') - - // page1でメッセージが受信されたことを確認 - await page1.waitForSelector('#received-messages 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(page2Message) - - // Compress で圧縮されているかを確認する(圧縮されていると Equal にならない) - if (selectedCompress1 !== selectedCompress2) { - test.expect(receivedMessage1).toEqual(page1Message) - test.expect(receivedMessage2).toEqual(page2Message) - } - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await page1.click('#get-stats') - await page2.click('#get-stats') - - // 統計情報が表示されるまで待機 - await page1.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const page1StatsReportJson: Record[] = await page1.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // page1 stats report - const page1DataChannelStats = page1StatsReportJson.filter( - (report) => report.type === 'data-channel', - ) - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'signaling' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'push' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'notify' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page1DataChannelStats.find((stats) => { - return stats.label === 'stats' && stats.state === 'open' - }), - ).toBeDefined() - - const page1ExampleStats = page1DataChannelStats.find((stats) => { - return stats.label === '#example' && stats.state === 'open' - }) - // ここで undefined ではないことを確認してる - expect(page1ExampleStats).toBeDefined() - expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) - expect(page1ExampleStats?.bytesSent).toBeGreaterThan(0) - expect(page1ExampleStats?.messagesSent).toBeGreaterThan(0) - - // 統計情報が表示されるまで待機 - await page2.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const page2StatsReportJson: Record[] = await page2.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // page2 stats report - const page2DataChannelStats = page2StatsReportJson.filter( - (report) => report.type === 'data-channel', - ) - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'signaling' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'push' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'notify' && stats.state === 'open' - }), - ).toBeDefined() - - expect( - page2DataChannelStats.find((stats) => { - return stats.label === 'stats' && stats.state === 'open' - }), - ).toBeDefined() - - const page2ExampleStats = page2DataChannelStats.find((stats) => { - return stats.label === '#example' && stats.state === 'open' - }) - // ここで undefined ではないことを確認してる - expect(page2ExampleStats).toBeDefined() - expect(page2ExampleStats?.bytesReceived).toBeGreaterThan(0) - expect(page2ExampleStats?.messagesReceived).toBeGreaterThan(0) - expect(page2ExampleStats?.bytesSent).toBeGreaterThan(0) - expect(page2ExampleStats?.messagesSent).toBeGreaterThan(0) - - await page1.click('#disconnect') - await page2.click('#disconnect') - - await page1.close() - await page2.close() -}) diff --git a/examples/tests/sendonly_audio.spec.ts b/examples/tests/sendonly_audio.spec.ts deleted file mode 100644 index a89b79c4..00000000 --- a/examples/tests/sendonly_audio.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('sendonly audio pages', async ({ browser }) => { - // 新しいページを作成 - const sendonly = await browser.newPage() - // ページに対して操作を行う - await sendonly.goto('http://localhost:9000/sendonly_audio/') - - // select 要素から直接オプションを取得してランダムに選択する - // 音声コーデック - const selectedAudioCodec = await sendonly.evaluate(() => { - const select = document.querySelector('#audio-codec-type') as HTMLSelectElement - const options = Array.from(select.options) - const randomOption = options[Math.floor(Math.random() * options.length)] - select.value = randomOption.value - return randomOption.value - }) - // 音声ビットレート - const selectedBitRate = await sendonly.evaluate(() => { - const select = document.querySelector('#audio-bit-rate') as HTMLSelectElement - const options = Array.from(select.options).filter((option) => option.value !== '') // 未指定を除外 - const randomOption = options[Math.floor(Math.random() * options.length)] - select.value = randomOption.value - return randomOption.value - }) - - // ランダムで選択した音声コーデック・音声ビットレートをログに表示する - console.log(`Selected AudioCodec: ${selectedAudioCodec}`) - console.log(`Selected BitRate: ${selectedBitRate} kbps`) - - // 'connect' ボタンをクリックして音声の送信を開始する - await sendonly.click('#connect') - // #connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await sendonly.waitForSelector('#connection-id:not(:empty)') - // #connection-id 要素の内容を取得 - const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) - console.log(`sendonly connectionId=${sendonlyConnectionId}`) - - // レース対策 - await sendonly.waitForTimeout(3000) - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await sendonly.click('#get-stats') - // 統計情報が表示されるまで待機 - await sendonly.waitForSelector('#stats-report') - - // データセットから統計情報を取得 - const sendonlyStatsReportJson: Record[] = await sendonly.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // 音声コーデックを確認する : 今は指定してもしなくても OPUS のみ - const sendonlyAudioCodecStats = sendonlyStatsReportJson.find( - (report) => report.type === 'codec' && report.mimeType === 'audio/opus', - ) - expect(sendonlyAudioCodecStats).toBeDefined() - - // 音声ビットレートを確認する:音声を送れているかと targetBitrate の確認 - const sendonlyAudioOutboundRtp = sendonlyStatsReportJson.find( - (report) => report.type === 'outbound-rtp' && report.kind === 'audio', - ) - expect(sendonlyAudioOutboundRtp).toBeDefined() - - // 音声が正常に送れているかを確認する - expect(sendonlyAudioOutboundRtp?.bytesSent).toBeGreaterThan(0) - expect(sendonlyAudioOutboundRtp?.packetsSent).toBeGreaterThan(0) - - // 音声ビットレートの選択に基づいて期待値を設定し一致するかを確認する - const expectedBitRate = Number.parseInt(selectedBitRate) * 1000 - expect(sendonlyAudioOutboundRtp?.targetBitrate).toEqual(expectedBitRate) - - await sendonly.click('#disconnect') - await sendonly.close() -}) diff --git a/examples/tests/sendonly_recvonly.spec.ts b/examples/tests/sendonly_recvonly.spec.ts deleted file mode 100644 index c296db2a..00000000 --- a/examples/tests/sendonly_recvonly.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('sendonly/recvonly pages', async ({ browser }) => { - // 新しいページを2つ作成 - const sendonly = await browser.newPage() - const recvonly = await browser.newPage() - - // それぞれのページに対して操作を行う - await sendonly.goto('http://localhost:9000/sendonly/') - await recvonly.goto('http://localhost:9000/recvonly/') - - // SDK バージョンの表示 - await sendonly.waitForSelector('#sdk-version') - const sendonlySdkVersion = await sendonly.$eval('#sdk-version', (el) => el.textContent) - console.log(`sendonly sdkVersion=${sendonlySdkVersion}`) - - await recvonly.waitForSelector('#sdk-version') - const recvonlySdkVersion = await recvonly.$eval('#sdk-version', (el) => el.textContent) - console.log(`recvonly sdkVersion=${recvonlySdkVersion}`) - - await sendonly.click('#connect') - await recvonly.click('#connect') - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await sendonly.waitForSelector('#connection-id:not(:empty)') - - // #sendonly-connection-id 要素の内容を取得 - const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) - console.log(`sendonly connectionId=${sendonlyConnectionId}`) - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await recvonly.waitForSelector('#connection-id:not(:empty)') - - // #sendrecv1-connection-id 要素の内容を取得 - const recvonlyConnectionId = await recvonly.$eval('#connection-id', (el) => el.textContent) - console.log(`recvonly connectionId=${recvonlyConnectionId}`) - - // レース対策 - await sendonly.waitForTimeout(3000) - await recvonly.waitForTimeout(3000) - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await sendonly.click('#get-stats') - - // 統計情報が表示されるまで待機 - await sendonly.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const sendonlyStatsReportJson: Record[] = await sendonly.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await recvonly.click('#get-stats') - - // 統計情報が表示されるまで待機 - await recvonly.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const recvonlyStatsReportJson: Record[] = await recvonly.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // sendonly audio codec - const sendonlyAudioCodecStats = sendonlyStatsReportJson.find( - (report) => report.type === 'codec' && report.mimeType === 'audio/opus', - ) - expect(sendonlyAudioCodecStats).toBeDefined() - - // sendonly audio outbound-rtp - const sendonlyAudioOutboundRtp = sendonlyStatsReportJson.find( - (report) => report.type === 'outbound-rtp' && report.kind === 'audio', - ) - expect(sendonlyAudioOutboundRtp).toBeDefined() - expect(sendonlyAudioOutboundRtp?.bytesSent).toBeGreaterThan(0) - expect(sendonlyAudioOutboundRtp?.packetsSent).toBeGreaterThan(0) - - // sendonly video codec - const sendonlyVideoCodecStats = sendonlyStatsReportJson.find( - (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP9', - ) - expect(sendonlyVideoCodecStats).toBeDefined() - - // sendonly video outbound-rtp - const sendonlyVideoOutboundRtpStats = sendonlyStatsReportJson.find( - (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', - ) - expect(sendonlyVideoOutboundRtpStats).toBeDefined() - expect(sendonlyVideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) - expect(sendonlyVideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) - - // recvonly audio codec - const recvonlyAudioCodecStats = recvonlyStatsReportJson.find( - (stats) => stats.type === 'codec' && stats.mimeType === 'audio/opus', - ) - expect(recvonlyAudioCodecStats).toBeDefined() - - // recvonly audio inbound-rtp - const recvonlyAudioInboundRtpStats = recvonlyStatsReportJson.find( - (stats) => stats.type === 'inbound-rtp' && stats.kind === 'audio', - ) - expect(recvonlyAudioInboundRtpStats).toBeDefined() - expect(recvonlyAudioInboundRtpStats?.bytesReceived).toBeGreaterThan(0) - expect(recvonlyAudioInboundRtpStats?.packetsReceived).toBeGreaterThan(0) - - // recvonly video codec - const recvonlyVideoCodecStats = recvonlyStatsReportJson.find( - (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP9', - ) - expect(recvonlyVideoCodecStats).toBeDefined() - - // recvonly video inbound-rtp - const recvonlyVideoInboundRtpStats = recvonlyStatsReportJson.find( - (stats) => stats.type === 'inbound-rtp' && stats.kind === 'video', - ) - expect(recvonlyVideoInboundRtpStats).toBeDefined() - expect(recvonlyVideoInboundRtpStats?.bytesReceived).toBeGreaterThan(0) - expect(recvonlyVideoInboundRtpStats?.packetsReceived).toBeGreaterThan(0) - - await sendonly.click('#disconnect') - await recvonly.click('#disconnect') - - await sendonly.close() - await recvonly.close() -}) diff --git a/examples/tests/sendrecv.spec.ts b/examples/tests/sendrecv.spec.ts deleted file mode 100644 index f04f6909..00000000 --- a/examples/tests/sendrecv.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('sendrecv x2', async ({ browser }) => { - const sendrecv1 = await browser.newPage() - const sendrecv2 = await browser.newPage() - - await sendrecv1.goto('http://localhost:9000/sendrecv/') - await sendrecv2.goto('http://localhost:9000/sendrecv/') - - // チャンネル名を設定 - await sendrecv1.fill('#channel-name', 'sendrecv-different-video-codec-type') - await sendrecv2.fill('#channel-name', 'sendrecv-different-video-codec-type') - - console.log('sendrecv1 channelName: sendrecv-different-video-codec-type') - console.log('sendrecv2 channelName: sendrecv-different-video-codec-type') - - // sendrecv1 のビデオコーデックをランダムに選択 - await sendrecv1.evaluate(() => { - const videoCodecTypeSelect = document.getElementById('video-codec-type') as HTMLSelectElement - const options = Array.from(videoCodecTypeSelect.options).filter((option) => option.value !== '') - const randomIndex = Math.floor(Math.random() * options.length) - videoCodecTypeSelect.value = options[randomIndex].value - }) - - // sendrecv2 のビデオコーデックをランダムに選択 - await sendrecv2.evaluate(() => { - const videoCodecTypeSelect = document.getElementById('video-codec-type') as HTMLSelectElement - const options = Array.from(videoCodecTypeSelect.options).filter((option) => option.value !== '') - const randomIndex = Math.floor(Math.random() * options.length) - videoCodecTypeSelect.value = options[randomIndex].value - }) - - // 選択されたコーデックをログに出力 - const sendrecv1VideoCodecType = await sendrecv1.$eval( - '#video-codec-type', - (el) => (el as HTMLSelectElement).value, - ) - const sendrecv2VideoCodecType = await sendrecv2.$eval( - '#video-codec-type', - (el) => (el as HTMLSelectElement).value, - ) - console.log(`sendrecv1 videoCodecType: ${sendrecv1VideoCodecType}`) - console.log(`sendrecv2 videoCodecType: ${sendrecv2VideoCodecType}`) - - await sendrecv1.click('#connect') - await sendrecv2.click('#connect') - - // #connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await sendrecv1.waitForSelector('#connection-id:not(:empty)') - - // #connection-id 要素の内容を取得 - const sendrecv1ConnectionId = await sendrecv1.$eval('#connection-id', (el) => el.textContent) - console.log(`sendrecv1 connectionId=${sendrecv1ConnectionId}`) - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await sendrecv2.waitForSelector('#connection-id:not(:empty)') - - // #sendrecv1-connection-id 要素の内容を取得 - const sendrecv2ConnectionId = await sendrecv2.$eval('#connection-id', (el) => el.textContent) - console.log(`sendrecv2 connectionId=${sendrecv2ConnectionId}`) - - // レース対策 - await sendrecv1.waitForTimeout(3000) - await sendrecv2.waitForTimeout(3000) - - // page1 stats report - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await sendrecv1.click('#get-stats') - - // 統計情報が表示されるまで待機 - await sendrecv1.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const sendrecv1StatsReportJson: Record[] = await sendrecv1.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - const sendrecv1VideoCodecStats = sendrecv1StatsReportJson.find( - (stats) => stats.type === 'codec' && stats.mimeType === `video/${sendrecv1VideoCodecType}`, - ) - expect(sendrecv1VideoCodecStats).toBeDefined() - - const sendrecv1VideoOutboundRtpStats = sendrecv1StatsReportJson.find( - (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', - ) - expect(sendrecv1VideoOutboundRtpStats).toBeDefined() - expect(sendrecv1VideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) - expect(sendrecv1VideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) - - const sendrecv1VideoInboundRtpStats = sendrecv1StatsReportJson.find( - (stats) => stats.type === 'inbound-rtp' && stats.kind === 'video', - ) - expect(sendrecv1VideoInboundRtpStats).toBeDefined() - expect(sendrecv1VideoInboundRtpStats?.bytesReceived).toBeGreaterThan(0) - expect(sendrecv1VideoInboundRtpStats?.packetsReceived).toBeGreaterThan(0) - - // page2 stats report - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await sendrecv2.click('#get-stats') - - // 統計情報が表示されるまで待機 - await sendrecv2.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const sendrecv2StatsReportJson: Record[] = await sendrecv2.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - const sendrecv2VideoCodecStats = sendrecv2StatsReportJson.find( - (stats) => stats.type === 'codec' && stats.mimeType === `video/${sendrecv2VideoCodecType}`, - ) - expect(sendrecv2VideoCodecStats).toBeDefined() - - const sendrecv2VideoOutboundRtpStats = sendrecv2StatsReportJson.find( - (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video', - ) - expect(sendrecv2VideoOutboundRtpStats).toBeDefined() - expect(sendrecv2VideoOutboundRtpStats?.bytesSent).toBeGreaterThan(0) - expect(sendrecv2VideoOutboundRtpStats?.packetsSent).toBeGreaterThan(0) - - const sendrecv2VideoInboundRtpStats = sendrecv2StatsReportJson.find( - (stats) => stats.type === 'inbound-rtp' && stats.kind === 'video', - ) - expect(sendrecv2VideoInboundRtpStats).toBeDefined() - expect(sendrecv2VideoInboundRtpStats?.bytesReceived).toBeGreaterThan(0) - expect(sendrecv2VideoInboundRtpStats?.packetsReceived).toBeGreaterThan(0) - - await sendrecv1.click('#disconnect') - await sendrecv2.click('#disconnect') - - await sendrecv1.close() - await sendrecv2.close() -}) diff --git a/examples/tests/simulcast.spec.ts b/examples/tests/simulcast.spec.ts deleted file mode 100644 index d1488b4f..00000000 --- a/examples/tests/simulcast.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('simulcast sendonly/recvonly pages', async ({ page }) => { - await page.goto('http://localhost:9000/simulcast/') - - await page.click('#connect') - - // 安全によせて 5 秒待つ - await page.waitForTimeout(5000) - - await page.waitForSelector('#local-video-connection-id:not(:empty)') - const localConnectionId = await page.$eval('#local-video-connection-id', (el) => el.textContent) - console.log(`local connectionId=${localConnectionId}`) - - await page.waitForSelector('#remote-video-connection-id-r0:not(:empty)') - const remoteR0ConnectionId = await page.$eval( - '#remote-video-connection-id-r0', - (el) => el.textContent, - ) - console.log(`remote | rid=r0, connectionId=${remoteR0ConnectionId}`) - - await page.waitForSelector('#remote-video-connection-id-r1:not(:empty)') - const remoteR1ConnectionId = await page.$eval( - '#remote-video-connection-id-r1', - (el) => el.textContent, - ) - console.log(`remote | rid=r1, connectionId=${remoteR1ConnectionId}`) - - await page.waitForSelector('#remote-video-connection-id-r2:not(:empty)') - const remoteR2ConnectionId = await page.$eval( - '#remote-video-connection-id-r2', - (el) => el.textContent, - ) - console.log(`remote | rid=r2, connectionId=${remoteR2ConnectionId}`) - - // 'Get Stats' ボタンをクリックして統計情報を取得 - await page.click('#get-stats') - - // 統計情報が表示されるまで待機 - await page.waitForSelector('#stats-report') - // データセットから統計情報を取得 - const sendonlyStatsReportJson: Record[] = await page.evaluate(() => { - const statsReportDiv = document.querySelector('#stats-report') as HTMLDivElement - return statsReportDiv ? JSON.parse(statsReportDiv.dataset.statsReportJson || '[]') : [] - }) - - // sendonly stats report - const sendonlyVideoCodecStats = sendonlyStatsReportJson.find( - (stats) => stats.type === 'codec' && stats.mimeType === 'video/VP8', - ) - expect(sendonlyVideoCodecStats).toBeDefined() - - const sendonlyVideoR0OutboundRtpStats = sendonlyStatsReportJson.find( - (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video' && stats.rid === 'r0', - ) - expect(sendonlyVideoR0OutboundRtpStats).toBeDefined() - expect(sendonlyVideoR0OutboundRtpStats?.bytesSent).toBeGreaterThan(0) - expect(sendonlyVideoR0OutboundRtpStats?.packetsSent).toBeGreaterThan(0) - expect(sendonlyVideoR0OutboundRtpStats?.scalabilityMode).toEqual('L1T1') - - const sendonlyVideoR1OutboundRtpStats = sendonlyStatsReportJson.find( - (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video' && stats.rid === 'r1', - ) - expect(sendonlyVideoR1OutboundRtpStats).toBeDefined() - expect(sendonlyVideoR1OutboundRtpStats?.bytesSent).toBeGreaterThan(0) - expect(sendonlyVideoR1OutboundRtpStats?.packetsSent).toBeGreaterThan(0) - expect(sendonlyVideoR1OutboundRtpStats?.scalabilityMode).toEqual('L1T1') - - const sendonlyVideoR2OutboundRtpStats = sendonlyStatsReportJson.find( - (stats) => stats.type === 'outbound-rtp' && stats.kind === 'video' && stats.rid === 'r2', - ) - expect(sendonlyVideoR2OutboundRtpStats).toBeDefined() - expect(sendonlyVideoR2OutboundRtpStats?.bytesSent).toBeGreaterThan(0) - expect(sendonlyVideoR2OutboundRtpStats?.packetsSent).toBeGreaterThan(0) - expect(sendonlyVideoR2OutboundRtpStats?.scalabilityMode).toEqual('L1T1') - - await page.click('#disconnect') -}) diff --git a/examples/tests/spotlight_sendonly_recvonly.spec.ts b/examples/tests/spotlight_sendonly_recvonly.spec.ts deleted file mode 100644 index 89c09921..00000000 --- a/examples/tests/spotlight_sendonly_recvonly.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { test } from '@playwright/test' - -test('spotlight sendonly/recvonly pages', async ({ browser }) => { - // 新しいページを2つ作成 - const sendonly = await browser.newPage() - const recvonly = await browser.newPage() - - // それぞれのページに対して操作を行う - await sendonly.goto('http://localhost:9000/spotlight_sendonly/') - await recvonly.goto('http://localhost:9000/spotlight_recvonly/') - - await sendonly.click('#connect') - await recvonly.click('#connect') - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await sendonly.waitForSelector('#connection-id:not(:empty)') - - // #sendonly-connection-id 要素の内容を取得 - const sendonlyConnectionId = await sendonly.$eval('#connection-id', (el) => el.textContent) - console.log(`sendonly connectionId=${sendonlyConnectionId}`) - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await recvonly.waitForSelector('#connection-id:not(:empty)') - - // #sendrecv1-connection-id 要素の内容を取得 - const recvonlyConnectionId = await recvonly.$eval('#connection-id', (el) => el.textContent) - console.log(`recvonly connectionId=${recvonlyConnectionId}`) - - await sendonly.click('#disconnect') - await recvonly.click('#disconnect') - - await sendonly.close() - await recvonly.close() -}) diff --git a/examples/tests/spotlight_sendrecv.spec.ts b/examples/tests/spotlight_sendrecv.spec.ts deleted file mode 100644 index 550d2d41..00000000 --- a/examples/tests/spotlight_sendrecv.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test } from '@playwright/test' - -test('spotlight sendrecv x2', async ({ page }) => { - await page.goto('http://localhost:9000/spotlight_sendrecv/') - - await page.click('#sendrecv1-connect') - await page.click('#sendrecv2-connect') - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await page.waitForSelector('#sendrecv1-connection-id:not(:empty)') - - // #sendrecv1-connection-id 要素の内容を取得 - const sendrecv1ConnectionId = await page.$eval('#sendrecv1-connection-id', (el) => el.textContent) - console.log(`sendrecv1 connectionId=${sendrecv1ConnectionId}`) - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await page.waitForSelector('#sendrecv1-connection-id:not(:empty)') - - // #sendrecv1-connection-id 要素の内容を取得 - const sendrecv2ConnectionId = await page.$eval('#sendrecv2-connection-id', (el) => el.textContent) - console.log(`sendrecv2 connectionId=${sendrecv2ConnectionId}`) - - await page.click('#sendrecv1-disconnect') - await page.click('#sendrecv2-disconnect') -}) diff --git a/examples/tests/type_close.spec.ts b/examples/tests/type_close.spec.ts deleted file mode 100644 index 3d3b5ce4..00000000 --- a/examples/tests/type_close.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('data_channel_signaling_only type:close pages', async ({ browser }) => { - // 新しいページを2つ作成 - const dataChannelSignalingOnly = await browser.newPage() - - // デバッグ用 - dataChannelSignalingOnly.on('console', (msg) => { - console.log(msg.type(), msg.text()) - }) - - // それぞれのページに対して操作を行う - await dataChannelSignalingOnly.goto('http://localhost:9000/data_channel_signaling_only/') - - // SDK バージョンの表示 - await dataChannelSignalingOnly.waitForSelector('#sdk-version') - const dataChannelSignalingOnlySdkVersion = await dataChannelSignalingOnly.$eval( - '#sdk-version', - (el) => el.textContent, - ) - console.log(`dataChannelSignalingOnly sdkVersion=${dataChannelSignalingOnlySdkVersion}`) - - await dataChannelSignalingOnly.click('#connect') - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await dataChannelSignalingOnly.waitForSelector('#connection-id:not(:empty)') - - // #sendonly-connection-id 要素の内容を取得 - const dataChannelSignalingOnlyConnectionId = await dataChannelSignalingOnly.$eval( - '#connection-id', - (el) => el.textContent, - ) - console.log(`dataChannelSignalingOnly connectionId=${dataChannelSignalingOnlyConnectionId}`) - - // レース対策 - await dataChannelSignalingOnly.waitForTimeout(3000) - - // API で切断 - await dataChannelSignalingOnly.click('#disconnect-api') - // console.log に [signaling] switched が出力されるまで待機するための Promise を作成する - const consolePromise = dataChannelSignalingOnly.waitForEvent('console') - - // レース対策 - await dataChannelSignalingOnly.waitForTimeout(3000) - - // Console log の Promise が解決されるまで待機する - const msg = await consolePromise - // log [signaling] onmessage-close websocket が出力されるので、args 0/1/2 をそれぞれチェックする - // [signaling] - const value1 = await msg.args()[0].jsonValue() - expect(value1).toBe('[signaling]') - // onmessage-close - const value2 = await msg.args()[1].jsonValue() - expect(value2).toBe('onmessage-close') - // websocket - const value3 = await msg.args()[2].jsonValue() - expect(value3).toBe('datachannel') - - await dataChannelSignalingOnly.close() -}) diff --git a/examples/tests/type_switched.spec.ts b/examples/tests/type_switched.spec.ts deleted file mode 100644 index f1a6b699..00000000 --- a/examples/tests/type_switched.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('data_channel_signaling_only type:switched pages', async ({ browser }) => { - // 新しいページを2つ作成 - const dataChannelSignalingOnly = await browser.newPage() - - // デバッグ用 - // dataChannelSignalingOnly.on('console', (msg) => { - // console.log(msg.type(), msg.text()) - // }) - - // それぞれのページに対して操作を行う - await dataChannelSignalingOnly.goto('http://localhost:9000/data_channel_signaling_only/') - - // SDK バージョンの表示 - await dataChannelSignalingOnly.waitForSelector('#sdk-version') - const dataChannelSignalingOnlySdkVersion = await dataChannelSignalingOnly.$eval( - '#sdk-version', - (el) => el.textContent, - ) - console.log(`dataChannelSignalingOnly sdkVersion=${dataChannelSignalingOnlySdkVersion}`) - - await dataChannelSignalingOnly.click('#connect') - // console.log に [signaling] switched が出力されるまで待機するための Promise を作成する - const consolePromise = dataChannelSignalingOnly.waitForEvent('console') - - // #sendrecv1-connection-id 要素が存在し、その内容が空でないことを確認するまで待つ - await dataChannelSignalingOnly.waitForSelector('#connection-id:not(:empty)') - - // #sendonly-connection-id 要素の内容を取得 - const dataChannelSignalingOnlyConnectionId = await dataChannelSignalingOnly.$eval( - '#connection-id', - (el) => el.textContent, - ) - console.log(`dataChannelSignalingOnly connectionId=${dataChannelSignalingOnlyConnectionId}`) - - // レース対策 - await dataChannelSignalingOnly.waitForTimeout(3000) - - // Console log の Promise が解決されるまで待機する - const msg = await consolePromise - // log [signaling] onmessage-switched websocket が出力されるので、args 0/1/2 をそれぞれチェックする - // [signaling] - const value1 = await msg.args()[0].jsonValue() - expect(value1).toBe('[signaling]') - // onmessage-switched - const value2 = await msg.args()[1].jsonValue() - expect(value2).toBe('onmessage-switched') - // websocket - const value3 = await msg.args()[2].jsonValue() - expect(value3).toBe('websocket') - - await dataChannelSignalingOnly.click('#disconnect') - - await dataChannelSignalingOnly.close() -}) diff --git a/examples/tests/whip_whep.spec.ts b/examples/tests/whip_whep.spec.ts deleted file mode 100644 index d211de97..00000000 --- a/examples/tests/whip_whep.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('whip/whep', async ({ browser }) => { - const whip = await browser.newPage() - const whep = await browser.newPage() - - await whip.goto('http://localhost:9000/whip/') - await whep.goto('http://localhost:9000/whep/') - - // コーデックの取得 - const whipVideoCodecType = await whip.evaluate(() => { - const videoElement = document.querySelector('#video-codec-type') as HTMLSelectElement - return videoElement.value - }) - console.log(`whipVideoCodecType=${whipVideoCodecType}`) - - const whepVideoCodecType = await whep.evaluate(() => { - const videoElement = document.querySelector('#video-codec-type') as HTMLSelectElement - return videoElement.value - }) - console.log(`whepVideoCodecType=${whepVideoCodecType}`) - - await whip.click('#connect') - await whep.click('#connect') - - // connection-stateが"connected"になるまで待つ - await whip.waitForSelector('#connection-state:has-text("connected")') - await whep.waitForSelector('#connection-state:has-text("connected")') - - // connection-stateの値を取得して確認 - const whipConnectionState = await whip.$eval('#connection-state', (el) => el.textContent) - console.log(`whip connectionState=${whipConnectionState}`) - - const whepConnectionState = await whep.$eval('#connection-state', (el) => el.textContent) - console.log(`whep connectionState=${whepConnectionState}`) - - await whip.click('#disconnect') - await whep.click('#disconnect') - - await whip.close() - await whep.close() -}) From 721ea5d68e90c500e2aab79db2f1b199fe9e1577 Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 18:21:04 +0900 Subject: [PATCH 15/16] pnpm run fmt --- e2e-tests/package.json | 2 +- examples/replace_track/main.mts | 1 - examples/sendonly/main.mts | 6 +----- package.json | 6 ++---- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/e2e-tests/package.json b/e2e-tests/package.json index ba67e375..d027db46 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -8,4 +8,4 @@ "dependencies": { "sora-js-sdk": "workspace:*" } -} \ No newline at end of file +} diff --git a/examples/replace_track/main.mts b/examples/replace_track/main.mts index 4f2fb363..719227dc 100644 --- a/examples/replace_track/main.mts +++ b/examples/replace_track/main.mts @@ -6,7 +6,6 @@ import Sora, { type ConnectionOptions, } from 'sora-js-sdk' - const getVideoCodecType = (): VideoCodecType | undefined => { const videoCodecTypeElement = document.querySelector('#video-codec-type') const videoCodecType = videoCodecTypeElement?.value diff --git a/examples/sendonly/main.mts b/examples/sendonly/main.mts index 5838ebcc..f678cd5d 100644 --- a/examples/sendonly/main.mts +++ b/examples/sendonly/main.mts @@ -62,11 +62,7 @@ class SoraClient { private sora: SoraConnection private connection: ConnectionPublisher - constructor( - signalingUrl: string, - channelId: string, - accessToken: string, - ) { + constructor(signalingUrl: string, channelId: string, accessToken: string) { this.sora = Sora.connection(signalingUrl, this.debug) // channel_id の生成 diff --git a/package.json b/package.json index 6b06aa1c..fb416c20 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,7 @@ "url": "https://discord.gg/shiguredo" }, "homepage": "https://github.com/shiguredo/sora-js-sdk#readme", - "files": [ - "dist" - ], + "files": ["dist"], "devDependencies": { "@biomejs/biome": "1.9.4", "@playwright/test": "1.49.1", @@ -51,4 +49,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} From 37ccd4b5a35bb0ca7970d74330aac24d814599bd Mon Sep 17 00:00:00 2001 From: voluntas Date: Wed, 25 Dec 2024 18:21:37 +0900 Subject: [PATCH 16/16] pnpm run fmt --- biome.jsonc | 10 +++++++++- e2e-tests/data_channel_signaling_only/main.mts | 7 +------ e2e-tests/messaging/main.mts | 8 +------- e2e-tests/recvonly/main.mts | 7 +------ e2e-tests/sendonly/main.mts | 7 +------ e2e-tests/sendonly_audio/main.mts | 7 +------ e2e-tests/spotlight_recvonly/main.mts | 7 +------ e2e-tests/spotlight_sendonly/main.mts | 7 +------ 8 files changed, 16 insertions(+), 44 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 144a0739..198592e1 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -4,7 +4,15 @@ "enabled": true }, "files": { - "include": ["src/**", "examples/**", "tests/**", "*.config.mjs", "package.json", "biome.jsonc"] + "include": [ + "src/**", + "examples/**", + "tests/**", + "e2e-tests/**", + "*.config.mjs", + "package.json", + "biome.jsonc" + ] }, "linter": { "enabled": true, diff --git a/e2e-tests/data_channel_signaling_only/main.mts b/e2e-tests/data_channel_signaling_only/main.mts index be310a5a..26f4c907 100644 --- a/e2e-tests/data_channel_signaling_only/main.mts +++ b/e2e-tests/data_channel_signaling_only/main.mts @@ -12,12 +12,7 @@ document.addEventListener('DOMContentLoaded', async () => { const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' const secretKey = import.meta.env.VITE_TEST_SECRET_KEY - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ) + const client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey) // SDK バージョンの表示 const sdkVersionElement = document.querySelector('#sdk-version') diff --git a/e2e-tests/messaging/main.mts b/e2e-tests/messaging/main.mts index dfdd868f..81a8ef17 100644 --- a/e2e-tests/messaging/main.mts +++ b/e2e-tests/messaging/main.mts @@ -31,13 +31,7 @@ document.addEventListener('DOMContentLoaded', async () => { document.querySelector('#connect')?.addEventListener('click', async () => { const channelName = getChannelName() - client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - channelName, - ) + client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey, channelName) const checkCompress = document.getElementById('check-compress') as HTMLInputElement const compress = checkCompress.checked const checkHeader = document.getElementById('check-header') as HTMLInputElement diff --git a/e2e-tests/recvonly/main.mts b/e2e-tests/recvonly/main.mts index 77410767..c19bfd25 100644 --- a/e2e-tests/recvonly/main.mts +++ b/e2e-tests/recvonly/main.mts @@ -12,12 +12,7 @@ document.addEventListener('DOMContentLoaded', () => { const secretKey = import.meta.env.VITE_TEST_SECRET_KEY // Sora クライアントの初期化 - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ) + const client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey) // SDK バージョンの表示 const sdkVersionElement = document.querySelector('#sdk-version') diff --git a/e2e-tests/sendonly/main.mts b/e2e-tests/sendonly/main.mts index 0f883cf0..c8732509 100644 --- a/e2e-tests/sendonly/main.mts +++ b/e2e-tests/sendonly/main.mts @@ -11,12 +11,7 @@ document.addEventListener('DOMContentLoaded', async () => { const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' const secretKey = import.meta.env.VITE_TEST_SECRET_KEY - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ) + const client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey) // SDK バージョンの表示 const sdkVersionElement = document.querySelector('#sdk-version') diff --git a/e2e-tests/sendonly_audio/main.mts b/e2e-tests/sendonly_audio/main.mts index d7b772ed..dd8305e8 100644 --- a/e2e-tests/sendonly_audio/main.mts +++ b/e2e-tests/sendonly_audio/main.mts @@ -10,12 +10,7 @@ document.addEventListener('DOMContentLoaded', async () => { const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' const secretKey = import.meta.env.VITE_TEST_SECRET_KEY - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ) + const client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey) document.querySelector('#connect')?.addEventListener('click', async () => { const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }) diff --git a/e2e-tests/spotlight_recvonly/main.mts b/e2e-tests/spotlight_recvonly/main.mts index 8f33c5a3..d3f343c3 100644 --- a/e2e-tests/spotlight_recvonly/main.mts +++ b/e2e-tests/spotlight_recvonly/main.mts @@ -12,12 +12,7 @@ document.addEventListener('DOMContentLoaded', () => { const secretKey = import.meta.env.VITE_TEST_SECRET_KEY // Sora クライアントの初期化 - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ) + const client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey) document.querySelector('#connect')?.addEventListener('click', async () => { await client.connect() diff --git a/e2e-tests/spotlight_sendonly/main.mts b/e2e-tests/spotlight_sendonly/main.mts index c92c6593..d93272e0 100644 --- a/e2e-tests/spotlight_sendonly/main.mts +++ b/e2e-tests/spotlight_sendonly/main.mts @@ -10,12 +10,7 @@ document.addEventListener('DOMContentLoaded', async () => { const channelIdSuffix = import.meta.env.VITE_TEST_CHANNEL_ID_SUFFIX || '' const secretKey = import.meta.env.VITE_TEST_SECRET_KEY - const client = new SoraClient( - signalingUrl, - channelIdPrefix, - channelIdSuffix, - secretKey, - ) + const client = new SoraClient(signalingUrl, channelIdPrefix, channelIdSuffix, secretKey) document.querySelector('#connect')?.addEventListener('click', async () => { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })