# SkyWay SDK Documentation - Full Content > This file contains full documentation content for SkyWay SDK. > > SkyWay is a WebRTC platform that enables real-time communication > for web and mobile applications. This file includes the complete content of all documentation files for comprehensive reference. ## ユーザーガイド/はじめに Path: user-guide_introduction.md # はじめに ## SkyWayとは SkyWayは、ビデオ・音声・データ通信機能をアプリケーションに簡単に実装できるSDK & APIです。 SDKには、Webやモバイル、IoTやゲームなど様々な利用シーンに合わせたものが存在します。SDKは通信を行うために様々なAPIを利用します。SDKと複数のサーバAPIの総体がSkyWayです。 ![introduction_topology](/media/posts/docs/introduction_topology.png) ## SkyWayをご利用になるには SkyWayのご利用にあたっては、会員登録が必要となります。 まだお済みでない場合は、[こちらのリンク](https://console.skyway.ntt.com/signup)より登録をお願いします。 ## SDK アプリケーションのプラットフォームに合わせて、SDK を選択してくだい。 - [JavaScript SDK](./user-guide/javascript-sdk/overview) : Web アプリケーション用 - [iOS SDK](./user-guide/ios-sdk/overview) : iOS アプリケーション用 - [Android SDK](./user-guide/android-sdk/overview) : Android アプリケーション用 - [Linux®︎ SDK](./user-guide/linux-sdk/overview) : Linux アプリケーション用 - [Unity SDK](./user-guide/unity-sdk/overview) : Unity アプリケーション用 (ベータ版に関するお問い合わせは[こちら](https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=14615614124185)) ## 開発にあたっての基礎知識 SkyWayの通信モデルは、複数の要素から成り立っています。各要素について、以下の図を用いて説明します。 ![introduction_elements](/media/posts/docs/introduction_elements.png) - Roomという部屋のような要素があります。 - Roomの中にはMemberが複数存在できます。Room内に存在するMember同士が通信することができます。 - Member同士が通信を行う際は、まず片方のMember AがPublicationを作成します(これをpublishと言います)。 - このPublicationを、もう片方のMember BがsubscribeすることでSubscriptionが生成されます。これにより、Publication → Subscriptionへ音声・映像・データが流れ、Member Bはこれらを受信することができます。 ## 認証・認可 正規のエンドユーザーがあらかじめ許可された操作のみを実行できるようにするために、SkyWay はトークンベースの認証・認可機能を提供しています。 ユーザーは、自身が認証したエンドユーザーに対して適切な権限を付与したトークン(SkyWay Auth Token)を発行することで、SkyWay に対するエンドユーザーの操作を制限できます。 詳しくは[こちら](./user-guide/authentication) ## TURN TURNサーバーは、データを中継することで、企業ネットワークなど P2P 通信が利用できない特定のネットワーク環境での通信を可能にします。 詳しくは[こちら](./user-guide/turn) ## SFU SFUサーバーは上りの通信の数、端末のエンコード負荷、上り帯域幅・通信量を削減し、P2P 方式よりも多人数での通信を可能にします。また、サイマルキャスト機能により、クライアントごとの通信環境に応じて自動的に品質を選択することで、快適な通信を実現できます。 SFUサーバーを利用するには、SFU Bot という特殊な Member を用いる必要があります。SkyWayのライブラリには RoomライブラリとCoreライブラリの2種類があり、SFU Bot を内包しているRoomライブラリでは、 Bot の存在を意識せずともオプション1つで簡単にSFUサーバーがご利用いただけます。 詳しくは[こちら](./user-guide/sfu) ## 商標 Linux®︎は、米国およびその他の国における Linus Torvalds の登録商標です。 --- ## ユーザーガイド/認証・認可/SkyWay Auth Token(各種SDK用) Path: user-guide_authentication_skyway-auth-token.md # SkyWay Auth Token 正規のエンドユーザーがあらかじめ許可された操作のみを実行できるようにするために、SkyWay はトークンベースの認証・認可機能を提供しています。 ユーザーは、自身が認証したエンドユーザーに対して適切な権限を付与したトークン(SkyWay Auth Token)を発行することで、SkyWay に対するエンドユーザーの操作を制限できます。 次の図は、 SkyWay を利用した認証・認可のフローを示しています。 ![Flow](/media/posts/docs/00_02_authentication_flow_jp.png) 1. エンドユーザーに対応したセッショントークンやパスワードなどの認証情報を、ユーザーアプリケーションのバックエンドサーバーに送信する 2. バックエンドサーバーの認証基盤で認証する 3. エンドユーザーに応じて適切な権限を付与した SkyWay Auth Token を生成する 4. SkyWay Auth Token をフロントエンドアプリケーションに送信する 5. 取得した SkyWay Auth Token を SkyWay SDK に設定し、 SkyWay へのリクエストに用いる 悪意のある攻撃者によるトークンの改ざんを防ぐため、 SkyWay Auth Token にはシークレットキーによる署名が必要です。 シークレットキーは、 SkyWay コンソールにログイン後、アプリケーション一覧画面から取得できます。 > このドキュメントは、SkyWay Auth Tokenの最新版であるversion 3について記述されたドキュメントです。 > SkyWay Auth Tokenの `version` プロパティが `1` 、 `2` 、未指定の場合は、[旧バージョン SkyWay Auth Token(各種SDK用)](/ja/docs/user-guide/authentication/skyway-auth-token-legacy/)のページを参照してください。 > SkyWay Auth Token version 3は、各SDKのバージョンが以下のいずれかの場合に利用できます。 > > JavaScript SDK: v1.11.0以降 > > iOS SDK: v2.1.7以降 > > Android SDK: v2.4.0以降 > > Linux®︎ SDK: v2.0.0以降 ## 認証・認可サンプル GitHubで[SkyWay Auth Tokenを生成・取得するサンプルコード](https://github.com/skyway/authentication-samples)を公開しています。 このサンプルコードは、以下のような用途で利用できます。 - バックエンドサーバーにおける SkyWay Auth Token 生成の参考実装 - フロントエンド開発時の SkyWay Auth Token 払い出しサーバーのモックサーバー ## 基本仕様 SkyWay Auth Token は JWT(JSON Web Token)形式のトークンとして表されており、ヘッダー部・ペイロード部・署名部の 3 つから構成されます。ヘッダー部には署名の方式の情報、ペイロード部には発行する権限を示した情報、署名部には Base 64 URL 方式で変換されたヘッダとペイロードの署名が含まれます。 ### ヘッダー部 ヘッダー部には、署名生成に利用するアルゴリズムを記述します。 JWT の仕様では、さまざまな署名アルゴリズムを指定できるよう規定されていますが、 SkyWay Auth Token ではアルゴリズムの指定に関するセキュリティ上の問題を回避するため HS256 にのみ対応しています。 ```js { "alg" : "HS256", "typ" : "JWT" } ``` ### ペイロード部 ペイロード部には JWT の仕様で定められるクレームの他、このトークンに付与する権限を定義する `scope` クレームを記述します。 | クレーム | 必須 | 形式 | 説明 | | -------- | ---- | ------------------- | ----------------------------------------------------------------------- | | iat | ✔️ | UNIX タイムスタンプ | トークンが発行された日時 | | jti | ✔️ | string (UUID v4) | トークンのユニークな id | | exp | ✔️ | UNIX タイムスタンプ | このトークンが無効になる時間を表すタイムスタンプ | | version | ✔️ | number ( `3` ) | このトークンのバージョン。最新のバージョンである `3` を指定してください | | scope | ✔️ | Object | このトークンに付与する権限を表すクレーム | `iat` はトークンが発行された日時を示します。検証されるタイミングより後の時刻が `iat` として指定されているとエラーになります。ただし、時刻同期のズレを考慮して `+2分` まで許容されます。 `jti` は、トークンを一意に識別するための値です。トークン生成時に UUID v4 を生成し、設定する必要があります。 `exp` は、このトークンが無効になる時間を表すタイムスタンプです。この値を過ぎた時刻において、このトークンを用いたリクエストは失敗します。`iat` で示した時刻から `+3日` を超えた時刻を設定するとエラーになります。 `version` は、このトークンのバージョンです。最新のバージョンは `3` です。本ドキュメントでは、version 3 の SkyWay Auth Token について説明します。 `scope` は、このトークンに付与する権限を表すクレームです。 詳しくは「スコープによる権限の付与」を参照してください。 ### 署名部 署名部には、悪意のある攻撃者によるトークンの改ざんを防ぐために HS256 による署名を記述します。署名は Base 64 URL 方式で変換されたヘッダとペイロードに対して行います。 ユーザー独自の方法で JWT の署名を行うことも可能ですが、SkyWay が公式に提供している [@skyway-sdk/token パッケージ](https://www.npmjs.com/package/@skyway-sdk/token) を用いることで、より簡単にトークンを作成することが可能です。 ## スコープによる権限の付与 `scope` は、各リソースに対する操作の権限を定義するクレームです。 SDK のメソッド呼び出しに対応する各リソースの操作を許可するかどうかを設定します。 ### リソースの階層構造 各リソースは次に示す階層構造となっています。 - TURN リソース - Analytics リソース - NoiseCancelling リソース - Room リソース - SFU リソース - Member リソース > SkyWay では Room ライブラリの利用をお勧めしているため、SkyWay Auth Token では、Room と記述する仕様となっています。 > > Core ライブラリなど、Channel という名称を用いるライブラリを利用している場合は、本ドキュメントの説明文において Room と表記している箇所を Channel と読み替えてください。 > なお、Core ライブラリで SkyWay Auth Token を利用する場合であっても、 `scope` における指定は `rooms` である必要があります。 ### Room / Memberリソースの指定方法 Room リソースと Member リソースは、`id` と `name` のプロパティを持ちます。 Room リソースや Member リソースを指定する際は、少なくとも `id` または `name` のどちらか一方のプロパティを指定する必要があります。 SkyWayでは `name` を利用してリソースを指定することを推奨しています。詳細は各 SDK の以下のドキュメントをご確認ください。 - [JavaScript SDK セキュアな運用のためのnameの指定の推奨について](/ja/docs/cookbook/javascript-sdk/recommendation-for-using-name) - [iOS SDK セキュアな運用のためのnameの指定の推奨について](/ja/docs/cookbook/ios-sdk/recommendation-for-using-name) - [Android SDK セキュアな運用のためのnameの指定の推奨について](/ja/docs/cookbook/android-sdk/recommendation-for-using-name) #### プロパティにおけるワイルドカードの指定 `id` および `name` の各プロパティでは、ワイルドカードとして `*` を指定できます。 `*` を指定した場合、0 文字以上のあらゆる文字列にマッチします。 加えて、`name` に `*` を指定した場合は `name` を持たないリソースにもマッチします。 また、`name` のプロパティを `lesson-room-*` のように指定することにより、`lesson-room-1` や `lesson-room-a` にマッチさせることができます。 ワイルドカードは合計8個まで利用できます。 なお、nameに `*` を含む値をマッチさせたい場合は、バックスラッシュでエスケープして `\*` と記載する必要があります。 例えば、`lesson-room-\*` と指定することにより、`lesson-room-1` や `lesson-room-a` ではなく、`lesson-room-*` にマッチさせることができます。 `name` と `id` が両方指定された場合、指定された `id` と `name` の両方を持つリソースにマッチします。 片方のみが指定された場合、指定されていない方のプロパティは `*` が単体で指定されたとみなされます。 ### メソッド Room リソースと Member リソースに対する操作の権限はメソッド(`methods`)として定義します。 各リソースに対して指定可能なメソッドは以下の通りです。(各メソッドの詳細は後述します) | リソース | メソッド | | -------- | ---------------------------------- | | Room | create, close, updateMetadata | | Member | publish, subscribe, updateMetadata | Member リソースの publish メソッドは、その Member が Publisher である Publication の unpublish 、および updateMetadata の操作も許可します。 Member リソースの subscribe メソッドは、その Member が Subscriber である Subscription の unsubscribe の操作も許可します。 なお、指定したリソースのプロパティの参照(読み取り)は、メソッドの指定に関わらず、リソースの指定がなされている場合は暗黙的に許可されます。 したがって、メソッドの指定を空にした場合は、指定したリソースの読み取り専用のトークンとして利用できます。 ### Room リソースが複数指定されている場合の適用順 Room リソースの指定は、 `rooms` として複数を同時に指定することができます。 Room リソースが複数指定されている場合は、操作の対象となる Room と Member にマッチする最初の要素の指定のみが適用されます。 #### 例 `meeting-room-1` という name を持つ Room において、以下の操作を許可するための SkyWay Auth Token の例を示します。 - `manager` という name を持つ Member による publish の操作 - それ以外の Member による subscribe の操作 ```ts scope: { // ...省略 rooms: [ { id: "*", name: "meeting-room-1", methods: [], member: { id: "*", name: "manager", methods: ["publish"] } }, { id: "*", name: "meeting-room-1", methods: [], member: { id: "*", name: "*", methods: ["subscribe"] } } ] } ``` `manager` という name を持つ Member は先に 1 つ目の Room リソースの要素にマッチするため、subscribe の権限は付与されないことに注意してください。 この Member にも subscribe の権限を付与する場合は、 1 つ目の要素の Member リソースの指定において、subscribe のメソッドを明示的に指定するようにしてください。 ### SkyWay Auth Token の例 例として、すべてのリソースについて権限を指定したスコープを以下に示します。 このスコープはトークンに以下の権限を付与します。 - App(id: `sample-app-id`)において、 - TURN を有効化 - Analytics を有効化 - NoiseCancelling を有効化 - Room(name: `lesson-room-1`)の作成・削除、metadataの更新ができる。 - Room(name: `lesson-room-1`)において SFU を有効化 - SFU サーバーを介して publish されている Publication に同時に紐づけられる Subscription の数を最大 99 に制限 - Room(name: `lesson-room-1`)について、 - `alice` という名前の Member として入室できる。 - `alice` という名前の Member のmetadataの更新ができる。 - `alice` が Publisher である Publication を作成・削除できる。 - `alice` が Subscriber となる Subscription を作成・削除できる。 ```js scope: { appId: "sample-app-id", turn: { enabled: true }, analytics: { enabled: true }, noiseCancelling: { enabled: true }, rooms: [ { id: "*", name: "lesson-room-1", methods: ["create", "close", "updateMetadata"], sfu: { enabled: true, maxSubscribersLimit: 99, }, member: { id: "*", name: "alice", methods: ["publish", "subscribe", "updateMetadata"] } } ] } ``` また、Room リソースは、複数の定義を含めることができます。 例えば、 2 つの Room に同時に join し、それぞれの Room で実行可能な操作の権限を個別に設定したい場合は以下の例のようになります。 - App(id: `sample-app-id`)において、 - TURN を有効化 - Analytics を有効化 - NoiseCancelling を有効化 - Room(name: `lesson-room-1`)の作成・削除、metadataの更新ができる。 - Room(name: `lesson-room-1`)において SFU を有効化 - SFU サーバーを介して publish されている Publication に同時に紐づけられる Subscription の数を最大 99 に制限 - Room(name: `lesson-room-1`)について、 - `alice` という名前の Member として入室できる。 - `alice` という名前の Member のmetadataの更新ができる。 - `alice` が Publisher である Publication を作成・削除できる。 - `alice` が Subscriber となる Subscription を作成・削除できる。 - Room(name: `lesson-room-2`)の参照ができる。 - Room(name: `lesson-room-2`)において SFU を有効化 - SFU サーバーを介して publish されている Publication に同時に紐づけられる Subscription の数を最大 99 に制限 - Room(name: `lesson-room-2`)について、 - `bob` という名前の Member として入室できる。 - `bob` が Subscriber となる Subscription を作成・削除できる。 ```js scope: { appId: "sample-app-id", turn: { enabled: true }, analytics: { enabled: true }, noiseCancelling: { enabled: true }, rooms: [ { id: "*", name: "lesson-room-1", methods: ["create", "close", "updateMetadata"], sfu: { enabled: true, maxSubscribersLimit: 99, }, member: { id: "*", name: "alice", methods: ["publish", "subscribe", "updateMetadata"] } }, { id: "*", name: "lesson-room-2", methods: [], sfu: { enabled: true, maxSubscribersLimit: 99, }, member: { id: "*", name: "bob", methods: ["subscribe"] } } ] } ``` ### TURN リソース TURN サーバーの利用可否を設定するためのオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ------- | ---- | ---------------------------------------------------------------------------------------- | | enabled | boolean | ✔️ | TURN サーバーの利用可否。true の場合、 TURN サーバーを経由して通信することも可能となる。 | なお、TURN リソースの指定が省略された場合は、 `enabled` に `true` が指定されたものとみなされます。 ### Analytics リソース Analytics 機能の利用有無を設定するためのオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ------- | ---- | --------------------------------------------------------------------- | | enabled | boolean | ✔️ | Analytics 機能の利用有無。true の場合、通信ログをサーバーに送信する。 | なお、Analytics リソースの指定が省略された場合は、 `enabled` に `true` が指定されたものとみなされます。 > SkyWay Auth Token version 1、2 と異なり、 version 3 では `analytics` を省略した場合は `true` となります。 ### NoiseCancelling リソース AI Noise Canceller の利用有無を設定するためのオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ------- | ---- | --------------------------------------------------------------------- | | enabled | boolean | ✔️ | AI Noise Canceller の利用有無。true の場合、AI Noise Cancellerが利用可能になる。 | なお、NoiseCancelling リソースの指定が省略された場合は、 `enabled` に `true` が指定されたものとみなされます。 ### Room リソース Room を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | --------------------------------------- | ---- | ------------------------------------------------ | | id | string (UUID v4)\| `*` | ※ | 指定する Room の id。`*` を指定可能。 | | name | string \| `*` | ※ | 指定する Room の name。`*` を指定可能。 | | methods | ( create \| close \| updateMetadata )[] | ✔️ | Room メソッドを配列で指定する。 | | sfu | SFU Resource | | SFU リソースに関するオブジェクトを指定する。 | | member | Member Resource | | Member リソース に関するオブジェクトを指定する。 | ※: 上記「Room / Memberリソースの指定方法」を参照 member プロパティが指定されている場合は、そのトークンを用いた Room への入室(join)と退室(leave)が暗黙的に許可されます。 #### Room メソッド Room リソースの `methods` には、以下の値が指定可能 - create: Room の作成ができる - close: Room の削除ができる - updateMetadata: Room の metadata の編集ができる なお、Room リソースの指定がなされている場合は、メソッドの指定内容に関わらず暗黙的に参照(読み取り)が許可されます。 ### SFU リソース SFU サーバーの利用可否を設定するためのオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ------------------- | ------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | enabled | boolean | ✔️ | SFU サーバーの利用有無。true の場合、P2P での通信に加えて SFU サーバーを利用した通信も可能になる。 | | maxSubscribersLimit | number | | SFU サーバーを介して publish されている Publication に同時に紐づけられる Subscription の数の上限。値は0から99まで指定可能。publish時に引数で指定した `maxSubscribers` の値がこの値よりも大きい場合、 publish 時にエラーとなる。| なお、SFU リソースの指定が省略された場合は、 `enabled` に `true` が、 `maxSubscribersLimit` に `99` が指定されたものとみなされます。 ### Member リソース Member を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | -------------------------------------------- | ---- | ----------------------------------------- | | id | string (UUID v4)\| `*` | ※ | 指定する Member の id。`*` を指定可能。 | | name | string \| `*` | ※ | 指定する Member の name。`*` を指定可能。 | | methods | ( publish \| subscribe \| updateMetadata )[] | ✔️ | Member メソッドを配列で指定する。 | ※: 上記「Room / Memberリソースの指定方法」を参照 #### Member メソッド Member リソースの `methods` には、以下の値が指定可能 - publish: publish・unpublish、Publication の metadata の編集ができる - subscribe: subscribe・unsubscribe ができる - updateMetadata: Member の metadata の編集ができる なお、Member リソースの指定がなされている場合は、メソッドの指定内容に関わらず、その Member リソースが指定されている Room に対する参照(読み取り)、および Room への入室(join)と退室(leave)が暗黙的に許可されます。 ## 商標 Linux®︎は、米国およびその他の国における Linus Torvalds の登録商標です。 --- ## ユーザーガイド/認証・認可/旧バージョン SkyWay Auth Token(各種SDK用) Path: user-guide_authentication_skyway-auth-token-legacy.md # 旧バージョン SkyWay Auth Token > このドキュメントは、SkyWay Auth Tokenの version 1(version未指定も含む)、または version 2 について記述されたドキュメントです。 > SkyWay Auth Tokenの `version` プロパティが `3` となっているSkyWay Auth Tokenをご利用の場合は、[SkyWay Auth Token(各種SDK用)](/ja/docs/user-guide/authentication/skyway-auth-token/)のページを参照してください。 > また、現在 version 1、または 2 の SkyWay Auth Token をご利用中の方は、 version 3 への移行をご検討ください。 SkyWay は、ユーザーが認証していないエンドユーザーによる SkyWay の不正利用を防ぐため、トークンベースの認証・認可機能を提供しています。 ユーザーは、自身が認証したエンドユーザーに対して適切な権限を付与したトークン(SkyWay Auth Token)を発行することで、エンドユーザーによる SkyWay の不正な利用を防ぐことができます。 次の図は、 SkyWay を利用した認証・認可のフローを示しています。 ![Flow](/media/posts/docs/00_02_authentication_flow_jp.png) 1. エンドユーザーに対応したセッショントークンやパスワードなどの認証情報を、ユーザーアプリケーションのバックエンドサーバーに送信する 2. バックエンドサーバーの認証基盤で認証する 3. エンドユーザーに応じて適切な権限を付与した SkyWay Auth Token を生成する 4. SkyWay Auth Token をフロントエンドアプリケーションに送信する 5. 取得した SkyWay Auth Token を SkyWay SDK に設定し、 SkyWay へのリクエストに用いる 悪意のある攻撃者によるトークンの改ざんを防ぐため、 SkyWay Auth Token にはシークレットキーによる署名が必要です。 シークレットキーは、 SkyWay コンソールにログイン後、アプリケーション一覧画面から取得できます。 ## 認証・認可サンプル GitHubで[SkyWay Auth Tokenを生成・取得するサンプルコード](https://github.com/skyway/authentication-samples/tree/version-1-2)を公開しています。 このサンプルコードは、以下のような用途で利用できます。 - バックエンドサーバーにおける SkyWay Auth Token 生成の参考実装 - フロントエンド開発時の SkyWay Auth Token 払い出しサーバーのモックサーバー ## 基本仕様 SkyWay Auth Token は JWT(JSON Web Token)形式のトークンとして表されており、ヘッダー部・ペイロード部・署名部の 3 つから構成されます。ヘッダー部には署名の方式の情報、ペイロード部には発行する権限を示した情報、署名部には Base 64 URL に変換されたヘッダとペイロードの署名が含まれます。 ### ヘッダー部 ヘッダー部には、署名生成に利用するアルゴリズムを記述します。 JWT の仕様では、さまざまな署名アルゴリズムを指定できるよう規定されていますが、 SkyWay Auth Token ではアルゴリズムの指定に関するセキュリティ上の問題を回避するため HS256 にのみ対応しています。 ```js { "alg" : "HS256", "typ" : "JWT" } ``` ### ペイロード部 ペイロード部には JWT の仕様で定められるクレームの他、このトークンに付与する権限を定義する `scope` クレームを記述します。 | クレーム | 必須 | 形式 | 説明 | | -------- | ---- | ------------------- | ------------------------------------------------ | | iat | ✔️ | UNIX タイムスタンプ | トークンが発行された日時 | | jti | ✔️ | string (UUID v4) | トークンのユニークな id | | exp | ✔️ | UNIX タイムスタンプ | このトークンが無効になる時間を表すタイムスタンプ | | version | | number | このトークンのバージョン | | scope | ✔️ | Object | このトークンに付与する権限を表すクレーム | `iat` はトークンが発行された日時を示します。検証されるタイミングより後の時刻が `iat` として指定されているとエラーになります。ただし、時刻同期のズレを考慮して `+2分` まで許容されます。 `jti` は、トークンを一意に識別するための値です。トークン生成時に UUID v4 を生成し、設定する必要があります。 `exp` は、このトークンが無効になる時間を表すタイムスタンプです。この値を過ぎた時刻において、このトークンを用いたリクエストは失敗します。`iat` で示した時刻から `+3日` を超えた時刻を設定するとエラーになります。 `version` は、このトークンのバージョンです。指定していなければ`1`となります。`2`以上を指定することで、ChannelおよびMemberのNameにワイルドカードが利用できます。(後述) `scope` は、このトークンに付与する権限を表すクレームです。 詳しくは「スコープによる権限の付与」を参照してください。 ### 署名部 署名部には、悪意のある攻撃者によるトークンの改ざんを防ぐために HS256 による署名を記述します。署名は Base 64 URL エンコードに変換されたヘッダとペイロードに対して行います。 ユーザー独自の方法で JWT の署名を行うことも可能ですが、SkyWay が公式に提供している [@skyway-sdk/token](https://www.npmjs.com/package/@skyway-sdk/token) パッケージを用いることで、より簡単にトークンを作成することが可能です。 ## スコープによる権限の付与 `scope` は、各リソースに対する操作に関する権限を定義するクレームです。どのリソースについてどのような操作を可能とするか、を定義することができます。 ### リソースの階層構造 各リソースは次に示す階層構造となっております。 - App リソース - Channel リソース - Member リソース - Publication リソース - Subscription リソース - SFU Bot リソース - Forwarding リソース ### Channel / Memberリソースの指定方法 ChannelリソースとMemberリソースは、`id`と`name`を持ちます。 `scope` クレームでは、これらのリソースの指定のため少なくとも `id` または `name` のどちらか一方を指定する必要があります。 SkyWayでは `name` を利用してリソースを指定することを推奨しています。詳細は各 SDK の以下のドキュメントをご確認ください。 - [JavaScript SDK セキュアな運用のためのnameの指定の推奨について](/ja/docs/cookbook/javascript-sdk/recommendation-for-using-name) - [iOS SDK セキュアな運用のためのnameの指定の推奨について](/ja/docs/cookbook/ios-sdk/recommendation-for-using-name) - [Android SDK セキュアな運用のためのnameの指定の推奨について](/ja/docs/cookbook/android-sdk/recommendation-for-using-name) #### `*`の利用について `id`および`name`には、`*`のみを指定可能です。 `*`のみを指定した場合、あらゆる文字列にマッチします。 また、`name`に`*`のみを指定した場合、`name`を持たないリソースにもマッチします。 また、`version`に`2`以上を指定すると、nameに対して、ワイルドカードとしての`*`を利用可能です。 例えば、`lesson-room-*`と指定することにより、`lesson-room-1`や`lesson-room-a`にマッチさせることができます。 ワイルドカードは合計8個まで利用できます。 なお、version2でnameに`*`を含む値をマッチさせたい場合は、バックスラッシュでエスケープして`\*`と記載する必要があります。 例えば、`lesson-room-\*`と指定することにより、`lesson-room-1`や`lesson-room-a`ではなく、`lesson-room-*`にマッチさせることができます。 `name`と`id`が両方指定された場合、指定された`id`と`name`の両方を持つリソースにマッチします。 片方のみが指定された場合、指定されていない方が `*`のみとして扱われます。 ### アクション リソースに対する操作の権限はアクション(`actions`)として定義します。 各リソースに対して指定可能なアクションは以下の通りです。(各アクションの詳細は後述します) | リソース | アクション | | ------------ | ------------------------------------------------------ | | App | read | | Channel | write, read, create, delete, updateMetadata | | Member | write, create, delete, signal, updateMetadata | | Publication | write, create, delete, updateMetadata, enable, disable | | Subscription | write, create, delete | | SFU Bot | write, create, delete | | Forwarding | write, create, delete | ### 例 例として、すべてのリソースについて権限を指定したスコープを以下に示します。 このスコープはトークンに以下の権限を付与します。 - App(id: `sample-app-id`)において、 - Channel(Name: `lesson-room-1`)を作成・削除できる。 - Channel(Name: `lesson-room-1`)について、 - `alice` という名前の Member を作成・削除できる。 - `alice` が Publisher である Publication を作成・削除できる。 - `alice` が Subscriber となる Subscription を作成・削除できる。 - SFU Bot を Channel に作成・削除できる。 - SFU Bot に `Forwarding` の作成・削除をさせることができる。 ```js scope: { app: { id: "sample-app-id", actions: ["read"], channels: [ { name: "lesson-room-1", actions: ["create", "delete"], members: [ { name: "alice", actions: ["create", "delete"], publication: { actions: ["create", "delete"] }, subscription: { actions: ["create", "delete"] } }, ], sfuBots: [ { actions: ["write"], forwardings: [ { actions: ["create", "delete"] } ] } ] }, ] } } ``` また、一部のリソースは、複数のリソースを下位の階層に含めることができます。 例えば、 1 つの Channel に複数の Member を含め、次のようなスコープを定義できます。 - App(ID: `sample-app-id`)において、 - Channel(Name: `lesson-room-1`)を作成・削除できる。 - Channel(Name: `lesson-room-1`)について、 - `alice` という名前の Member を作成・削除できる。 - `alice` が Publisher である Publication を作成・削除できる。 - `alice` が Subscriber となる Subscription を作成・削除できる。 - (`*`を使用しているので) すべての Member を削除できる。 ```js scope: { app: { id: "sample-app-id", actions: ["read"], channels: [ { name: "lesson-room-1", actions: ["create", "delete"], members: [ { name: "alice", actions: ["create", "delete"], publication: { actions: ["create", "delete"] }, subscription: { actions: ["create", "delete"] } }, { name: "*", actions: ["delete"], publication: { actions: [] }, subscription: { actions: [] } } ], }, ] } } ``` ### App リソース あるアプリケーションを表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ----------------- | ---- | ---------------------------------------------------------------------------------------------------------------------- | | id | string (UUID v4) | ✔️ | アプリケーションの id。 `*` は指定できない。 | | turn | boolean | | TURN サーバーの利用可否。true の場合、 TURN サーバーを経由して通信することが可能となる。省略された場合は true となる。 | | analytics | boolean | | Analytics の利用有無。true の場合、通信ログをサーバーに送信する。省略された場合は false となる。 | | actions | read [] | ✔️ | アプリケーション自体に対する権限を配列で指定する。`read` のみ指定可能。 | | channels | ChannelResource[] | ✔️ | Channel リソース に関するオブジェクトを配列で指定する。 | ### Channel リソース Channel を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | --------------------------------------------------------- | ---- | ------------------------------------------------------ | | id | string (UUID v4)\| `*` | ※ | 指定する Channel の id。`*` を指定可能。 | | name | string \| `*` | ※ | 指定する Channel の name。`*` を指定可能。 | | actions | ( write \| read \| create \| delete \| updateMetadata )[] | ✔️ | Channel アクションを配列で指定する。 | | members | Member Resource[] | | Member リソース に関するオブジェクトを配列で指定する。 | | sfuBots | SFU Bot Resource[] | | SFUbot リソース に関するオブジェクトを配列で指定する。 | ※: 上記「Channel / Memberリソースの指定方法」を参照 #### Channel アクション Channel リソースの `actions` には、以下の値が指定可能 - read: Channel の取得 - write: Channel のすべての操作 - create: Channel の作成 - delete: Channel の削除 - updateMetadata: metadata の編集 ただし、書き込み権限(write | create | delete | updateMetadata)のいずれかが設定されている場合、暗黙的に読み込み権限(read)も付与される。 ### Member リソース Member を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ------------ | ----------------------------------------------------------- | ---- | ----------------------------------------------------- | | id | string (UUID v4)\| `*` | ※ | 指定する Member の id。`*` を指定可能。 | | name | string \| `*` | ※ | 指定する Member の name。`*` を指定可能。 | | actions | ( write \| create \| delete \| updateMetadata \| signal )[] | ✔️ | Member アクションを配列で指定する。 | | publication | Publication Resource | | Publication リソースに関するオブジェクトを指定する。 | | subscription | Subscription Resource | | Subscription リソースに関するオブジェクトを指定する。 | ※: 上記「Channel / Memberリソースの指定方法」を参照 #### Member アクション Member リソースの `actions` には、以下の値が指定可能 - write: Member のすべての操作 - create: Member の作成、MemberTtl の Update - delete: Member の削除 - updateMetadata: metadata の編集 - signal: シグナリング情報のやり取り ※3 ※3: Pub/Sub を行う場合に必要 ### Publication リソース 親リソースである Member リソースによって表される Member を Publisher とする Publication を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ---------------------------------------------------------------------- | ---- | ---------------------------------------- | | actions | ( write \| create \| delete \| updateMetadata \| enable \| disable )[] | ✔️ | Publication アクションを配列で指定する。 | #### Publication アクション Publication リソースの `actions` には、以下の値が指定可能 - write: Publication のすべての操作 - create: Publication の作成 - delete: Publication の削除 - updateMetadata: metadata の編集 - enable: Publication の有効化 - disable: Publication の無効化 ### Subscription リソース 親リソースである Member リソースによって表される Member を Subscriber とする Subscription を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ------------------------------- | ---- | ----------------------------------------- | | actions | ( write \| create \| delete )[] | ✔️ | Subscription アクションを配列で指定する。 | #### Subscription アクション Subscription リソースの `actions` には、以下の値が指定可能 - write: Subscription のすべての操作 - create: Subscription の作成 - delete: Subscription の削除 ### SFU Bot リソース SFU Bot を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ----------- | -------------------------------- | ---- | --------------------------------------------------------- | | actions | ( write \| create \| delete ) [] | ✔️ | SFU Bot アクション を配列で指定する。 | | forwardings | Forwarding Resource[] | | Forwarding リソースに関するオブジェクトを配列で指定する。 | #### SFU Bot アクション SFU Bot リソースの `actions` には、以下の値が指定可能 - write: SFU Bot のすべての操作 - create: SFU Bot の作成 - delete: SFU Bot の削除 ### Forwarding リソース Forwarding を表すオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | -------------------------------- | ---- | --------------------------------------- | | actions | ( write \| create \| delete ) [] | ✔️ | Forwarding アクションを配列で指定する。 | #### Forwarding アクション Forwarding リソースの `actions` には、以下の値が指定可能 - write: Forwarding のすべての操作 - create: Forwarding の作成 - delete: Forwarding の削除 --- ## ユーザーガイド/認証・認可/SkyWay Admin Auth Token(SkyWay Channel API, SkyWay Recording API用) Path: user-guide_authentication_skyway-admin-auth-token.md # SkyWay Admin Auth Token SkyWay Admin Auth Tokenは、SkyWay Channel APIやSkyWay Recording APIなどのアプリケーションの管理者(サーバー)用APIを利用する際に必要な、認証のためのトークンです。 トークン検証の際、ペイロードに記載された有効期限確認が行われます。 > 本トークンをクライアントアプリケーションに提供しないよう注意してください。管理者(サーバー)用APIを利用されてしまう恐れがあります。 ## 形式 SkyWay Admin Auth TokenはJWT形式です。 ペイロード部は以下の通りです。 | クレーム | 必須 | 形式 | 説明 | | ------- | ---- | --------------- | ------------------------------------------------- | | iat | ✔️ | UNIX タイムスタンプ | トークンが発行された日時 | | jti | ✔️ | string (UUID v4) | トークンのユニークな id | | exp | ✔️ | UNIX タイムスタンプ | このトークンが無効になる時間を表すタイムスタンプ | | appId | ✔️ | string(UUID v4) | アプリケーションID | `iat` はトークンが発行された日時を示します。検証されるタイミングより後の時刻が `iat` として指定されているとエラーになります。ただし、時刻同期のズレを考慮して `+2分` まで許容されます。 `jti` は、トークンを一意に識別するための値です。トークン生成時に UUID v4 を生成し、設定する必要があります。 `exp` は、このトークンが無効になる時間を表すタイムスタンプです。この値を過ぎた時刻において、このトークンを用いたリクエストは失敗します。`iat` で示した時刻から `+3日` を超えた時刻を設定するとエラーになります。 例 ```js { "iat": 1706754878, "jti": "ba51311f-599d-47ca-a51d-df371fa750e7", "exp": 1706854878, "appId": "ac8adbc8-a2ff-4c41-9f5e-fdaed5e1e65e" } ``` ## 作成方法の例 Node.jsでの作成方法を以下に示します。 ```js const jwt = require("jsonwebtoken"); const crypto = require("crypto"); const payload = { iat: Date.now() / 1000, jti: crypto.randomUUID(), exp: Date.now() / 1000 + 60 * 60 * 24 * 3, appId: "アプリケーションIDを記載します", }; const SECRET_KEY = "シークレットキーを記載します"; const token = jwt.sign(payload, SECRET_KEY); console.log(token) ``` --- ## ユーザーガイド/Analytics/概要 Path: user-guide_analytics_overview.md # 概要 Analyticsは、SkyWayの通信状況と品質を分析するための機能です。通信ログを収集・可視化することにより、エンドユーザーの通信品質の正確な把握が可能となり、迅速な問い合わせ対応や、ユーザーエクスペリエンスの向上に役立てることができます。 ### Analyticsの利用シーン エンドユーザーから不具合の申告があった際、対応者はエンドユーザーの利用環境(ブラウザやOSのバージョン、使用機器の名称、等)と、通信状態をAnalyticsの画面で確認します。そうすることで、ブラウザアップデートが必要、マイクがオフになっている、ネットワーク環境の改善が必要、等の適切な案内を行うことができます。 ### Analyticsが収集する通信ログ Analyticsが収集する通信ログには2種類あります。サーバ通信ログとクライアント通信ログです。クライアント通信ログは主にグラフで可視化する通信状態に関する情報です。サーバ通信ログは主にチャンネルやメンバーの基本情報ですが、SFU利用時にはSFUサーバ上の通信ログも含まれます。いずれも通話内容や個人を特定するような情報は収集しません。 ![通信ログのフロー](/media/posts/docs/analytics-log.png) > クライアント通信ログの送信は、以下のバージョンでの利用を推奨します。 > > - JavaScript SDK: v1.7.0以降 > - iOS SDK: v2.0.0以降 > - Android SDK: v2.0.0以降 > - Linux®︎ SDK: v1.0.0以降 (Linux®︎ は、米国およびその他の国における Linus Torvalds の登録商標です。) ## 通信ログの送信について クライアント通信ログはSkyWayコンソールでAnalyticsを有効化していない場合でも送信されます。これは何らかのトラブルが生じた際のサポート時に利用するためです。 もし送信しないようにする場合は、SkyWayAuthToken で analytics の enabled プロパティを `false` にします。 ```javascript { version: 3, scope: { appId: "sample-app-id", analytics: { enabled: false, // false を指定 }, rooms: [ ...以下省略 ``` SkyWayAuthToken v1, v2 を使用する場合の指定方法は [旧バージョン SkyWayAuthToken(各種SDK用)](https://skyway.ntt.com/ja/docs/user-guide/authentication/skyway-auth-token-legacy/) を参照してください。 なお、この設定を行った場合でも、サーバ通信ログは収集されます。 --- ## ユーザーガイド/Analytics/クイックスタート Path: user-guide_analytics_quickstart.md # クイックスタート ### 始め方 Analytics を始めるには、SkyWay コンソールへログインし、プロジェクト詳細画面より Analytics を有効化していただく必要があります。 まず、SkyWay コンソールへログインし、プロジェクト詳細画面を開くと以下のボタンが確認できます。 ![Analytics利用開始](/media/posts/docs/analytics-start.png) こちらを押して、Analyticsを有効化します。Enterpriseプランの場合は、料金に関する案内が表示されるので、内容をご確認いただいた上で利用開始してください。 > SkyWayAuthToken v1, v2を利用する場合、analytics の enabled プロパティを `true` に設定する必要があります。 > 詳細は [旧バージョン SkyWayAuthToken(各種SDK用)](https://skyway.ntt.com/ja/docs/user-guide/authentication/skyway-auth-token-legacy/) を参照ください。 ### コンソールの使い方 **Analytics画面の表示** アプリケーション一覧画面に「Analytics」というボタンがあります。こちらを押すと、次項のチャンネル・メンバー検索画面に移動します。または、アプリケーション詳細画面のサイドメニューから「Analytics」を選択しても同様です。 ![アプリケーション一覧画面](/media/posts/docs/analytics-console-screen-1.png) **チャンネル・メンバー検索** 通信状況を調べたいチャンネル、またはメンバーを検索する画面です。(「チャンネル」、「メンバー」については[こちら](./user-guide/introduction/#26)) 画面を表示した時点では、直近1週間の間に存在していたチャンネル一覧が表示されます。 検索条件を指定し、検索ボタンをクリックします。その後、結果リストのいずれかの項目をクリックすることで、チャンネル詳細、またはメンバー詳細の画面に移動します。 ![チャンネル・メンバー検索画面](/media/posts/docs/analytics-console-screen-2.png) **チャンネル詳細** チャンネルの詳細な情報を確認できます。 チャンネルに参加したメンバーの一覧と、メンバー数の推移が確認できます。メンバー一覧は、期間やキーワード(ID, Name)で検索することができます。メンバーのリストのいずれかをクリックすると、メンバー詳細の画面に移動します。 ![チャンネル詳細画面](/media/posts/docs/analytics-console-screen-3.png) **メンバー詳細** メンバーの詳細な情報を確認できます。 SDKプラットフォームやデバイス名などの環境情報は、SkyWayAuthTokenに`analytics: true` を加えていない場合は表示されません。 画面下の通話履歴では、SubscriptionとPublicationの一覧が確認できます。(「Subscription」、「Publication」については[こちら](./user-guide/introduction/#12)) Subscriptionのいずれかの項目を押すと、通信詳細の画面に移動します。 ![メンバー詳細画面](/media/posts/docs/analytics-console-screen-4.png) **通信詳細** 指定したPublicationとSubscriptionの間での通信について、詳細を確認する画面です。 通信品質の分析において、もっとも重要な画面です。以下、画面のエリアごとに説明します。 - 画面上段では、通信の両端となるPublicationとSubscriptionの情報が確認できます。 - 画面中段の時系列データでは、ある時点での通信の状態を確認できます。 - 上部のバー - 両端の青いピンを移動することで、画面下段のグラフで表示する範囲を指定できます。 - バーの上にある白い丸は、カメラデバイス、エンコード設定、metadataの変更のイベントです。これをクリックすると、その時刻における通信の状態が表示されます。 - Publication / Subscription の状態 - ネットワーク種別 - 有線接続、無線(モバイル)接続など - TURN利用 - TURNを経由した通信かどうか - ハードウェア アクセラレーション - ON or OFF - メディア(Publicationのみ) - ビデオ・マイクデバイスの機種名、または画面共有時にはディスプレイ、ウィンドウ、タブいずれか - コーデック(Publicationのみ) - コーデック名(H264、VP8、Opusなど) - metadata(Publicationのみ) - メタデータの内容が表示される。全文表示可能。 - 画面下段では、通信品質をグラフで確認することができます。ドラッグで拡大することができます。各グラフの見方は次の章で説明します。 ![通信詳細画面](/media/posts/docs/analytics-console-screen-5.png) --- ## ユーザーガイド/Analytics/グラフの読み取り方 Path: user-guide_analytics_graph.md # グラフの読み取り方 このページでは、通信詳細画面における各種グラフの読み取り方について説明します。 ## ネットワーク情報 ### ジッター ジッターとは、パケットの伝送時間の揺らぎ、振れ幅を表します。ネットワークが混雑すると大きくなり、映像・音声の遅延や欠損が起きやすくなります。 一般的に、ジッターは10ms未満が正常とされています。 WebRTCは自動で画質やフレームレートを下げることでネットワークの詰まりを解消するように動作するため、ジッターが10msを超えた場合、数秒程度で回復します。 回復しなかった場合、 **利用しているルータやネットワーク回線の過負荷・不具合(後述)** が予想されます。 ### ジッターバッファー遅延 ジッターバッファー遅延は、ジッターが高い場合に発生し、映像・音声の遅延が起きやすくなっていることを示します。また、乱高下するような状況では映像や音声の途切れが目立つ可能性が高いとされています。 WebRTCは自動で画質やフレームレートを下げることでジッターが10ms未満に回復するように動作するため、ジッターバッファー遅延もそれに伴い回復します。 回復しなかった場合、 **利用しているルータやネットワーク回線の負荷・不具合(後述)** が予想されます。 ### パケットロス率 パケットロス率は、publisherから送信されたパケットのうち、subscriberが受信するまでの間に欠損した割合を示します。 一般的に、パケットロス率が20%を超えると映像・音声の途切れが目立ち、50%を超えると通話が困難になるとされています。 パケットロス率が10%を超えると、WebRTCは自動で画質やフレームレートを下げてパケットロス率の回復を試みます。多くの場合、数秒程度でパケットロス率は10%未満に回復します。 回復しなかった場合、 **利用しているルータやネットワーク回線の過負荷・不具合(後述)** が予想されます。 ### ビットレート ビットレートは、映像・音声・データ通信の、1秒あたりに受信したデータ量です。 映像・音声の場合は、データ量が大きければ大きいほど、高い品質であることを示します。データ通信の場合は、データ量が大きければ大きいほど、多くのデータ通信が行われたことを示します - 音声の場合、ビットレートは一般的に32kbps前後であり、32kbpsより低い場合には、音声の途切れや品質の低下を感じる可能性があります。 - 映像の場合、ビットレートはネットワーク品質に応じて200kbps~1Mbps前後となり、200kbpsより低い場合には画質やフレームレートの低下が目立つ可能性があります。 - データ通信の場合、ビットレートは受信するデータに応じて変化します。データ通信で多くのデータを送受信(publish、subscribe)してしまうと、映像・音声で利用できる通信帯域幅が減り、映像・音声の品質が低下する可能性があります。 受信した音声・映像のビットレートが低い場合、 **利用しているルータやネットワーク回線の過負荷・不具合(後述)** が予想されます。 ### 往復遅延時間(RTT) 往復遅延時間(RTT)はRoundTripTimeの略であり、パケットがpublisherとsubscriberの間を往復するのに要した時間を示します。 一般的に、RTTが300ms以上の場合、ほとんどのユーザーが遅延を感じ、通話が困難になると言われています。 RTTが高い場合、主に **通信相手との物理的な距離が離れている(後述)** ことが予想されます。 ## メディア情報 ### 音量レベル(音声のみ) 音量レベルは、送信・受信した音声の大きさを示します。 送信側の音量レベルが0の場合、複数の原因が考えられます。 - マイクがミュート状態になっている - 別のマイクを選択している - マイクが故障している マイクがミュート状態になっていないか、マイクの選択が適切か、マイクが故障していないかを確認する必要があります。 ### フレームレート(映像のみ) フレームレートは、送信・受信した映像の滑らかさを示します。 WebRTCは幅広いフレームレートをサポートしており、ネットワーク品質に応じて自動でフレームレートを調整します。 一般的に、カメラ映像の場合は15\~30fps、画面共有の場合は5\~10fpsになります。 これらの数値より低い場合には、映像の途切れが目立つようになります。 フレームレートが低い場合、 **利用しているルータやネットワーク回線の過負荷・不具合(後述)** が予想されます。 ### 解像度(映像のみ) 解像度は、送信・受信した映像の鮮明さを示します。 WebRTCは幅広い解像度をサポートしており、ネットワーク品質に応じて自動で解像度を調整します。 映像の体感品質は、解像度だけではなく描画サイズも影響するため、解像度のみで品質を評価することは困難です。しかし、320x240(QVGA)よりも小さい画質の場合は、ボヤけて見える可能性が高くなります。 品質が低いと感じる場合には、 **利用しているルータやネットワーク回線の過負荷・不具合(後述)** が予想されます。 ### 1秒間のうちエンコード/デコードに要した時間(映像のみ) この項目は、映像を圧縮(エンコード)・復元(デコード)するのに要した時間を示します。 一般的に、1000msを超える値の場合は、デバイス負荷が高まっており、映像の送信や描画がリアルタイムに行えなくなっていることを示します。 1000msを超える場合は、 **デバイスの過負荷(後述)** が予想されます。 また、デバイスによってはハードウェアアクセラレーションがサポートされている場合もあり、この場合は専用のチップにエンコード・デコード処理を任せることで、デバイス負荷を軽減できます。ハードウェアアクセラレーションが有効になっているかは、通信詳細画面において、「 **Publication / Subscription の状態** 」で確認できます。 ## 各種グラフ共通 ### Disabled状態 各種グラフにおいて、Publicationが `Disabled` 状態になっている時間帯は、灰色の領域で表現されます。 `Disabled` 状態は、Publicationが映像や音声の送信を停止している状態を示します。 例えば下記のような状態は`Disabled` 状態とは異なるためご注意ください。 - マイクの物理的なミュートボタン等によるハードウェアミュートの状態 - **[MediaStreamTrack](https://developer.mozilla.org/ja/docs/Web/API/MediaStreamTrack)** の`enabled`プロパティが`false`の状態 ## 通話不具合に対する対処法 通話不具合のケースに応じた、対処法の例を説明します。 ### 利用しているルータやネットワーク回線の過負荷・不具合が予想されるケース - 有線LAN接続の利用(例: Wi-Fiから有線LAN接続への変更) - デバイスからWi-Fiルータへの距離が遠い場合に、有線LAN接続へ切り替えることで、ジッターの減少、パケットロスの減少、RTTの低下により、より安定した通話が行える可能性があります。 - 多くのデータを送受信(publish、subscribe)しないようにアプリケーションを修正する - 不要な映像・音声・データ通信を送受信(publish、subscribe)しないようにアプリケーションを修正することで、映像や音声一つ一つに利用できる通信帯域幅が増え、より高品質な通話が行える可能性があります。 - ルーター・モデムの再起動 - ルータ・モデムの不調が解消する可能性があります - 通信端末で動作している不要なアプリケーションの停止 - データ通信量が削減され、WebRTCで利用できる通信帯域幅が増え、より高品質な通話が行える可能性があります。 - 同一ルーターに接続する他の端末の通信を停止する - データ通信量が削減され、WebRTCで利用できる通信帯域幅が増え、より高品質な通話が行える可能性があります。 - ネットワーク回線の変更(例:テザリングの利用) - 混雑しているネットワーク回線を利用しないことで、ジッター、パケットロス率、RTTなどの品質が改善し、より安定した通話が行える可能性があります。 - テザリングを利用する場合、WebRTCでは大量のデータ通信を行うため、速度制限や追加料金にご注意ください。 ### 通信相手との物理的な距離が離れていることが予想されるケース 以下の対処によって、問題が解消する可能性があります。 - ネットワーク回線の変更(例:テザリングの利用) - 経由するネットワーク機器の数が減り、RTTの高まりを解消でき、より遅延の少ない通話が行える可能性があります。 - テザリングを利用する場合、WebRTCでは大量のデータ通信を行うため、速度制限や追加料金にご注意ください。 ### 通信端末の過負荷が予想されるケース 以下の対処によって、問題が解消する可能性があります。 - 送信・描画する映像や音声の数の削減 - エンコード・デコード処理の数を減らすことで、通信端末のCPU負荷が削減され、より安定した通話が行える可能性があります。 - バックグラウンドで動作しているアプリケーションの停止 - 動作中のアプリケーションを停止することで、通信端末のCPU負荷が削減され、より安定した通話が行える可能性があります。 --- ## ユーザーガイド/JavaScript SDK/概要 Path: user-guide_javascript-sdk_overview.md # 概要 ## JavaScript SDK について JavaScript SDK は Web アプリケーションで SkyWay を利用するための SDK です。 この SDK は複数のライブラリで構成されています。 現在提供されているライブラリは以下の通りです。 **Room** - SkyWay の全機能を利用できるライブラリ - メディア通信毎に P2P と SFU の方式を選択できます - また事前に Room 内での通信方式を定めておくことも可能です - P2P Room は少人数向け - 上限人数に制限はありませんが、ユーザーが快適に通話できる人数は 4 人までです - SFU Room は多人数向け - SFU サーバーを経由してメディア通信を行います **Token** - 認証認可のためのトークンを生成する際に利用できるユーティリティライブラリ - シークレットキーを利用するため、フロントエンドで利用する際には注意してください ## ライブラリの仕様 SkyWay を利用する上で理解する必要のある基本的な仕様について説明します。 ### Room 通話を行うグループの単位であり、ユーザーは共通の Room に参加したユーザー同士で通話を行います。 Room に参加しているユーザーのことを Member と呼びます。 ### Stream Room 上で送受信できるメディアのことを Stream といいます。以下の 3 種類の Stream を利用できます。 - AudioStream - ユーザーのマイク音声など - VideoStream - ユーザーのカメラ映像など - DataStream - 任意のメッセージ - SFU を用いた通信では利用できません ### Publish Member が Stream を Room に公開することを Publish といいます。 Stream を Publish すると Room 上に Stream に対応する Publication というリソースが作成されます。 ### Subscribe Member が Room 上の Publication を受信することを Subscribe といいます。 Subscribe をすると Room 上に Subscription というリソースが作成されます。 Publication を Subscribe した Member は Subscription を通じて Stream にアクセスし映像や音声を受信できます。 ### SkyWay Auth Token SkyWay Auth Token は、SkyWay を利用するために必要な JWT(JSON Web Token)形式のトークンです。 ユーザー毎に権限を細かく設定することでき、例えば Room ごとの入室を特定ユーザーに制限する、といったことができます。 SkyWay Auth Token を利用するためには、これを払い出すアプリケーションサーバーを構築する必要があります。SkyWay SDK を利用したクライアントアプリは、アプリケーションサーバーから SkyWay Auth Token を取得し、これを用いて SkyWay の各種機能を利用します。 なお、サーバーを構築せずにフロントエンドで SkyWay Auth Token を生成した場合、シークレットキーをエンドユーザーが取得できるため、権限の制限が機能せず注意する必要があります。 GitHub で公開している[SkyWay Auth Tokenを生成・取得するサンプルコード](https://github.com/skyway/authentication-samples)は SkyWay Auth Token 払い出しサーバーのモックサーバーとしても活用できるため、開発時はこちらの利用もご検討ください。 ## SDK のインストール方法 基本的には NPM パッケージとして提供しています。 なお、Room ライブラリはプロトタイピングや記事の執筆、学習など、手軽に試していただくために CDN にホストしたライブラリを用意していますが、SkyWay としてお客様の環境での CDN 経由での利用はおすすめしておりません。 CDN にホストされたライブラリは SkyWay の SDK バージョンアップに伴い自動的に更新されるため、お客様が意図しないところで、商用環境に更新版がリリースされてしまう可能性があるためです。 大変お手数ではございますが、NPM 経由での利用をお願い致します。 ### NPM を利用する場合 npm がインストールされている環境下で以下のコマンドを実行します。 **Room ライブラリ** ```sh npm install @skyway-sdk/room ``` **その他のプラグインやユーティリティライブラリ** ```sh npm install @skyway-sdk/token ``` ### CDN を利用する場合 CDN を介して利用できるのは Room ライブラリのみとなっています。それ以外のライブラリは NPM を利用ください。 なお、CDN にホストされているライブラリはアップデートに伴い自動的に最新版に更新されるため、商用環境での利用はおすすめしておりません。 以下のスクリプト要素を HTML に追加します。 **Room ライブラリ** ```html ``` モジュールはグローバル変数の `skyway_room` に格納されるので以下のようにモジュールを取得できます。 ```js const { SkyWayAuthToken, SkyWayContext, SkyWayStreamFactory, SkyWayRoom } = skyway_room; ``` ## リリース ### NPM - Room ライブラリ: https://www.npmjs.com/package/@skyway-sdk/room ### GitHub - リポジトリ: https://github.com/skyway/js-sdk - リリースノート: https://github.com/skyway/js-sdk/releases --- ## ユーザーガイド/JavaScript SDK/クイックスタート Path: user-guide_javascript-sdk_quickstart.md # 🚀 クイックスタート ## チュートリアルを始めるにあたって 本チュートリアルは、JavaScript の基本的な知識、及び、npm の利用経験を前提としています。 JavaScript の言語仕様や開発環境の構築手順は記載していないため、必要に応じて他の Web サイト等を参考にしてください。 ## チュートリアル SkyWay のメディア通信を体験できるシンプルなサンプルアプリケーションを作成します。 本チュートリアルでは Room ライブラリを使用します。 完成品(NPM パッケージ利用)は [https://github.com/skyway/js-sdk/tree/main/examples/tutorial](https://github.com/skyway/js-sdk/tree/main/examples/tutorial) にあります。 SDK を CDN 経由で利用する場合と、NPM 経由で利用する場合の 2 パターンの環境構築方法を紹介します。 なお、CDN にホストされているライブラリはアップデートに伴い自動的に最新版に更新されるため、商用環境での利用はおすすめしておりません。また、CDN を介して利用できるのは Room ライブラリのみとなっています。 ### 環境構築 [CDN を利用する場合] #### HTML 作成 index.html というファイルを作成し、以下の内容にします。 _index.html_ ```html SkyWay Tutorial

ID:

room name:
``` #### JavaScript ファイル作成 index.html と同じディレクトリで main.js というファイルを作成してください。 チュートリアルのプログラムはこのファイルの中に記述します。 #### 各種モジュールの取得 main.js ファイルの先頭に以下の内容を入力し、これから用いる各種モジュールをライブラリより取得します。 _main.js_ ```js const { nowInSec, SkyWayAuthToken, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } = skyway_room; ``` #### アプリケーションの起動方法 以下に、ローカルサーバーを用意するいくつかの方法を記載します。 - Visual Studio Code の拡張機能 Live Server - 各種プログラミング言語での起動コマンド ```bash # python 2.X $ python -m SimpleHTTPServer 8080 # python 3.X $ python -m http.server 8080 # ruby $ ruby -run -e httpd . -p 8080 # php $ php -S localhost:8080 # Node.js $ npx http-server -p 8080 ``` ブラウザからローカルサーバーのアドレス経由で index.html ファイルを開くとアプリケーションが起動します。 ### 環境構築 [NPM を利用する場合] #### 事前準備 1. Node.js のバージョン 20 以降をインストールする 2. 任意の作業ディレクトリに tutorial フォルダを作成する 3. tutorial フォルダ直下で次の作業をする 3-1. `npm init` を実行し、対話画面が終了するまで Enter キーをクリックする 3-2. `npm i @skyway-sdk/room` を実行する 3-3. `npm i -D parcel` を実行する 3-4. src フォルダを作成する 3-5. package.json ファイルを開き、scripts の階層に `"dev": "parcel ./src/index.html",` を追記する #### HTML 作成 src フォルダの下に index.html というファイルを作成し、以下の内容を入力します。 _index.html_ ```html SkyWay Tutorial

ID:

room name:
``` #### JavaScript ファイル作成 src フォルダの下に main.js というファイルを作成してください。 チュートリアルのプログラムはこのファイルの中に記述します。 #### 各種モジュールの取得 main.js ファイルの先頭に以下の内容を入力し、これから用いる各種モジュールをライブラリより取得します。 _main.js_ ```js import { nowInSec, SkyWayAuthToken, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } from "@skyway-sdk/room"; ``` #### アプリケーションの起動方法 tutorial ディレクトリで以下のコマンドを実行するとローカルサーバーが起動します。 ```sh npm run dev ``` ローカルサーバーのアドレスをブラウザで開くとアプリケーションが起動します。 ### アプリケーション ID とシークレットキーの取得 ※SkyWay への登録がまだの方は[こちら](https://console.skyway.ntt.com/login/)から SkyWay コンソールへログインし、以下の 3 つを行います。 1. 「アプリケーションを作成」ボタンを押す ![Peer](/media/posts/docs/tutorial_1.png) 2. アプリケーション名を入力して作成ボタンを押す 3. アプリケーション一覧からアプリケーション ID とシークレットキーをコピーする ### SkyWay Auth Token を作る SkyWay Auth Token とは、SkyWay を利用するための JWT 形式のトークンです。 トークンごとに権限を細かく設定することでき、例えば room ごとの入室を特定ユーザーに制限する、といったことができます。 ここでは細かい SkyWay Auth Token の設定方法に関する説明は省きます([SkyWay Auth Token についてはこちら](/ja/docs/user-guide/authentication))。 > 本チュートリアルでは、すぐに通信を試していただくために、トークン生成をクライアントアプリケーションで実装していますが、 本来、 **SkyWay Auth Token はサーバーアプリケーションで生成してクライアントアプリケーションに渡すようにするべきです。** クライアントアプリケーションでトークン生成を行った場合、任意の Room に入ることができるようなトークンを第三者が作成する可能性があります。 SkyWay では、[SkyWay Auth Tokenを生成するためのサーバーアプリケーションのサンプル](https://github.com/skyway/authentication-samples)を提供しています。 サーバーアプリケーションを実装する際は、こちらも参考にしてください。 --- main.js に以下の内容を入力してください。先ほど取得したアプリケーション ID とシークレットキーを置換する必要があります。 _main.js_ ```js const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId: "ここにアプリケーションIDをペーストしてください", rooms: [ { name: "*", methods: ["create", "close", "updateMetadata"], member: { name: "*", methods: ["publish", "subscribe", "updateMetadata"], }, }, ], }, }).encode("ここにシークレットキーをペーストしてください"); ``` ### カメラ映像、マイク音声の取得 main.js 内に、カメラから映像を取得して、video タグにセットするコードを追加します。 1. 即時実行の async 関数で全体を囲みます。これにより、非同期処理を await で記述できるようになります。**以降の JavaScript の処理はこの即時実行関数内に記述します。** 2. マイク音声とカメラ映像を取得し、それぞれを変数に分割代入します。 3. video 要素に映像(video)をセットします(audio は後ほど利用します)。 4. セットした映像を再生します。 _main.js_ ```js (async () => { // 1 const localVideo = document.getElementById("local-video"); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); // 2 video.attach(localVideo); // 3 await localVideo.play(); // 4 })(); // 1 ``` ここで、一度アプリケーションを起動して、カメラ映像が表示されるか確かめてみましょう。 環境構築のアプリケーションの起動方法の項目に従ってアプリケーションを起動してください。 ### 通信処理の追加 相手との通信を行うための処理を追加していきます。 #### HTML 要素の取得 後ほど利用するため、要素を JavaScript 側で変数に格納しておきます。 _main.js_ ```js const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const leaveButton = document.getElementById('leave'); ``` #### room の作成と入室 join ボタンを押した際に実行されるイベントハンドラを作成し、この中に以降の処理を記載していきます。 先ほど生成した SkyWay Auth Token を用いて、`context` を作ります。 `context` とは、グローバルな情報を管理するオブジェクトです。認証・認可や、ログの設定などの情報を管理します。 また、このとき、`roomNameInput` が空の場合には、以降の処理が実行できないため、空かどうかのチェックを入れています。 _main.js_ ```js joinButton.onclick = async () => { if (roomNameInput.value === "") return; const context = await SkyWayContext.Create(token); }; ``` 次に `SkyWayRoom.FindOrCreate` という関数の第一引数に、先ほど作成した `context` を渡して、`room` を作成します。 この関数は、もしすでに同じ name の room が存在しなければ作成し、存在する場合にはその room を取得するという関数です。 第2引数のオブジェクトで name には、ユーザーが input 要素に入力した値を用います。 _main.js_ ```js const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, }); ``` 次に、先ほど作成(or 取得)した `room` に入室します。 すると `Member` オブジェクトが返ってきます。ここでは `me` という変数名とします。 自分の ID を表示するために、me.id を span 要素に格納します。 _main.js_ ```js const me = await room.join(); myId.textContent = me.id; ``` #### 自分の映像と音声を publish する `Member` オブジェクトの `publish` 関数の引数に、先ほど取得した audio と video を渡して、音声・映像を publish します。 第2引数のオブジェクトにて通信方式を指定します。今回は P2P 方式を利用するため `type` には”p2p”を指定します。 - ”sfu”を指定すると SFU を利用した通信になり、 `type` を定義しない場合(デフォルト値)は ”p2p” となります _main.js_ ```js await me.publish(audio, { type: "p2p" }); await me.publish(video, { type: "p2p" }); ``` #### 相手の映像と音声を subscribe する 相手の映像と音声を subscribe し、audio, video 要素にセットする処理を追加します。 _main.js_ ```js const subscribeAndAttach = (publication) => { // 3 if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement("button"); // 3-1 subscribeButton.id = `subscribe-button-${publication.id}`; subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { // 3-2 const { stream } = await me.subscribe(publication.id); // 3-2-1 let newMedia; // 3-2-2 switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } newMedia.id = `media-${publication.id}`; stream.attach(newMedia); // 3-2-3 remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); // 1 room.onStreamPublished.add((e) => { // 2 subscribeAndAttach(e.publication); }); ``` 1. `room` の `publications` プロパティに、room に存在する `publication` の配列が入っています。この配列の各要素を、`subscribeAndAttach` 関数の引数に与えています。この関数については後ほど説明します。 2. `room` の `onStreamPublished` は `Event` 型のプロパティです。`Event` には、`add` という関数があります。この関数の引数にコールバック関数を渡すと、その room 内で誰かが publish された時点でコールバック関数が実行されます。コールバック関数の引数に入っているオブジェクトの `publication` プロパティに、publish された `publication` が存在していますので、これを `subscribeAndAttach` 関数に渡します。 3. `subscribeAndAttach` 関数を作成します。引数に publication を取ります。この publication が自分(me)が publish したものでない場合に、以降の処理を実行します。 3-1. publisher.id と publication の contentType(video or audio)をラベルにしたボタンを、ボタンエリアに追加します。 3-2. 3-1 で作成したボタンのイベントハンドラを作成します。 3-2-1. publication を subscribe します。すると `stream` が返却されます。 3-2-2. audio 要素 or video 要素を作成します。`newMedia` という名前の変数を作成し、 `stream.track.kind` が video であれば video 要素を、audio であれば audio 要素を作成し、それぞれ適切な属性を設定します。 3-2-3. `stream` を、先ほど作成した `newMedia`(audio 要素 or video 要素)にセットし、その後、`remoteMediaArea`(div 要素)に追加します。 #### 自分の退室処理を実装する `Member` オブジェクトの `leave` 関数で `room` から退室します。退室後は `room` についての処理を行わないため、`dispose` 関数で `room` に関連するリソースを解放・破棄します。 ```js leaveButton.onclick = async () => { await me.leave(); await room.dispose(); myId.textContent = ""; buttonArea.replaceChildren(); remoteMediaArea.replaceChildren(); }; ``` #### 相手の退室処理を実装する `onStreamPublished` と同様に、`room` の `onStreamUnublished` は `Event` 型のプロパティです。この関数の引数にコールバック関数を渡すと、その room 内で誰かの publication が unpublish された時点でコールバック関数が実行されます。相手が退室すると自動的に相手の publication が unpublish されるため、退室時もこのコールバック関数が実行されます。コールバック関数の引数に入っているオブジェクトの `publication` プロパティには unpublish された `publication` が存在しています。 ```js room.onStreamUnpublished.add((e) => { document.getElementById(`subscribe-button-${e.publication.id}`)?.remove(); document.getElementById(`media-${e.publication.id}`)?.remove(); }); ``` これで完成です。 アプリケーションの起動方法に従ってアプリケーションを起動して、複数の window で表示しましょう。 それぞれで同じルーム名を入力し `join` ボタンを押すと、room 内に存在する publication のボタンが表示されます。そのボタンを押すと、相手の映像と音声が表示されるはずです。 完成したアプリケーションを、[GitHub Pages](https://docs.github.com/ja/pages/getting-started-with-github-pages/about-github-pages)や[Netlify](https://www.netlify.com/)など、ホスティングサービスを利用してアップロードし、複数の端末からの接続を試してみましょう。 ライブラリの取得部分を除いたコード全体はこちら _main.js_ ```js const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId: "ここにアプリケーションIDをペーストしてください", rooms: [ { name: "*", methods: ["create", "close", "updateMetadata"], member: { name: "*", methods: ["publish", "subscribe", "updateMetadata"], }, }, ], }, }).encode("ここにシークレットキーをペーストしてください"); (async () => { const localVideo = document.getElementById("local-video"); const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const leaveButton = document.getElementById("leave"); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); video.attach(localVideo); await localVideo.play(); joinButton.onclick = async () => { if (roomNameInput.value === "") return; const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, }); const me = await room.join(); myId.textContent = me.id; await me.publish(audio, { type: "p2p" }); await me.publish(video, { type: "p2p" }); const subscribeAndAttach = (publication) => { if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement("button"); subscribeButton.id = `subscribe-button-${publication.id}`; subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } newMedia.id = `media-${publication.id}`; stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); leaveButton.onclick = async () => { await me.leave(); await room.dispose(); myId.textContent = ""; buttonArea.replaceChildren(); remoteMediaArea.replaceChildren(); }; room.onStreamUnpublished.add((e) => { document.getElementById(`subscribe-button-${e.publication.id}`)?.remove(); document.getElementById(`media-${e.publication.id}`)?.remove(); }); }; })(); ``` --- ## ユーザーガイド/JavaScript SDK/解放・破棄処理 Path: user-guide_javascript-sdk_release-and-dispose.md # 解放・破棄処理 Room インスタンスおよび SkyWayContext インスタンスの利用を終了してリソースを解放するには、 [Room.dispose メソッド](https://javascript-sdk.api-reference.skyway.ntt.com/room/interfaces/Room.html#dispose) および [SkyWayContext.dispose メソッド](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/SkyWayContext.html#dispose) を使用します。 Room.dispose メソッドはその Room インスタンスに関するイベントリスナーや LocalMember のインスタンス、サーバとの通信を解放・破棄し、SkyWayContext.dispose メソッドは SkyWay SDK で使用しているすべてのイベントリスナーや Room インスタンス、サーバとの通信を解放・破棄します。 ```js const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, }); // 入室 const me = await room.join(); ... // 退出 await me.leave(); // Roomを含むリソースが不要になった await room.dispose(); // SkyWay SDKに関するすべての操作が不要になった context.dispose(); ``` --- ## ユーザーガイド/JavaScript SDK/バーチャル背景と背景ぼかし処理の実装 Path: user-guide_javascript-sdk_video-processing.md # バーチャル背景と背景ぼかし処理の実装 SkyWay では、JavaScript/TypeScript を用いてブラウザ上でカメラから取得した映像の背景を加工するライブラリを提供しています。 カメラから取得した映像の背景を任意の画像に差し替えたり(以下では背景差し替え処理と呼びます)、背景へのぼかし処理(以下では背景ぼかし処理と呼びます)を行うことができます。 ## 対応ブラウザ 以下ブラウザの直近 2 バージョンに対応しています。 - Chrome - Edge ## インストール 以下のコマンドでインストールを行います。 ``` npm install skyway-video-processors ``` ## 使い方 以下の使い方の詳細は[Sample](https://github.com/skyway/skyway-video-processors/tree/main/example/simple)を参照してください。 任意の画像を利用して背景差し替え処理を行う `VirtualBackground` と、任意の強度で背景ぼかし処理を適用する `BlurBackground` の 2 つのクラスが存在します。 ### VirtualBackground の使い方 `VirtualBackground` のインスタンスを作成します。 ```ts import { VirtualBackground } from "skyway-video-processors"; const backgroundProcessor = new VirtualBackground({ image: "green.png" }); ``` インスタンスの初期化を行います。 ```ts await backgroundProcessor.initialize(); ``` `createProcessedStream` によって、デバイスからの映像に対して背景差し替え処理を行った映像の `ProcessedStream` を取得できます。 `ProcessedStream` の track を用いて、背景差し替え処理を行った映像の `MediaStream` を作成できます。 ```ts const result = await backgroundProcessor.createProcessedStream(); const stream = new MediaStream([result.track]); ``` 作成した `MediaStream` を `videoElement` の `srcObject` に割り当てることで映像を再生できます。 ```ts videoElement.srcObject = stream; await videoElement.play(); ``` ### BlurBackground の使い方 `BlurBackground` のインスタンスを作成します。 ```ts import { BlurBackground } from "skyway-video-processors"; const backgroundProcessor = new BlurBackground(); ``` インスタンスの初期化を行います。 ```ts await backgroundProcessor.initialize(); ``` `createProcessedStream` によって、デバイスからの映像に対して背景ぼかし処理を行った映像の `ProcessedStream` を取得できます。 `ProcessedStream` の `track` を用いて、背景ぼかし処理を行った映像の `MediaStream` を作成できます。 ```ts const result = await backgroundProcessor.createProcessedStream(); const stream = new MediaStream([result.track]); ``` 作成した `ProcessedStream` を `videoElement` の `srcObject` に割り当てることで映像を再生できます。 ```ts videoElement.srcObject = stream; await videoElement.play(); ``` ## JavaScript SDKとの連携方法 バーチャル背景による加工を行った映像を SkyWay で送信する映像として利用することができます。 `VirtualBackground`, もしくは `BlurBackground` の初期化を行い、そのインスタンスを JavaScript SDK に引数として渡します。 ```ts const backgroundProcessor = new BlurBackground(); await backgroundProcessor.initialize(); const video = await SkyWayStreamFactory.createCustomVideoStream(backgroundProcessor, { stopTrackWhenDisabled: true, }); const me = await room.join(); await me.publish(video); ``` ## API リファレンス こちらに API リファレンスを公開しています。 - [API リファレンス](https://github.com/skyway/skyway-video-processors/tree/main#api) ## サンプル こちらにサンプルアプリを公開しています。 - [バーチャル背景を利用したカメラ映像を取得するアプリケーション](https://github.com/skyway/skyway-video-processors/tree/main/example/simple) - [バーチャル背景を利用してSkyWayで通話するアプリケーション](https://github.com/skyway/skyway-video-processors/tree/main/example/skyway-js-sdk) --- ## ユーザーガイド/JavaScript SDK/サポートブラウザ Path: user-guide_javascript-sdk_browser.md # サポートブラウザ - PC - Chrome - Firefox - Safari - Edge - スマートフォン/タブレット - iOS/iPadOS - Safari - Chrome - Firefox - Edge - Android - Chrome - Firefox - Edge - WebView - SFSafariViewController - WKWebView - 標準ブラウザの機能外のカスタムを行う場合には必ずしも動作保証しない - AndroidWebView - 標準ブラウザの機能外のカスタムを行う場合には必ずしも動作保証しない **サポートバージョン** 上記サポートブラウザ全てについて「安定版の最新 2 メジャーバージョン」とします。 --- ## ユーザーガイド/JavaScript SDK/既知の問題 Path: user-guide_javascript-sdk_issues.md # 既知の問題 ## Bluetooth イヤホンを利用している際に Audio を扱う Publication の Disable/Enable を行うと再生中の音声が一瞬途切れる SDK では Publication の Disable をする際に Stream と紐付いているメディアデバイスを解放し、Enable する際にメディアデバイスを再取得します。 これにより Publication を Disable している間は、PC のカメラインジゲータやマイクインジゲータを消灯することが出来ます。 Bluetooth イヤホンを利用している環境では音声デバイスの取得/解放時に Bluetooth イヤホン上の音声が途切れる場合があります。 この音声の途切れが許容できない場合は、SkyWayStreamFactory で LocalAudioStream を作成する際に次のオプションを指定してください。 ただし、このオプションを指定すると Publication の Disable 時にメディアデバイスの解放処理が行われなくなるのでご注意ください。 ```ts await SkyWayStreamFactory.createMicrophoneAudioStream({ stopTrackWhenDisabled: false }); ``` ## iOS Safari でブラウザのウィンドウやタブを閉じたとき、またはページを更新したとき Member が Room から leave しない SDK ではブラウザのウィンドウやタブを閉じたとき、またはページを更新したときに [beforeunload イベント](https://developer.mozilla.org/ja/docs/Web/API/Window/beforeunload_event) を検知して Member を自動的に leave させます。しかし、iOS Safari は beforeunload イベントに対応していないため、これによる Member の leave が行われません。 代替手段として、[LocalMemberConfig](https://javascript-sdk.api-reference.skyway.ntt.com/room/types/LocalMemberConfig.html) の keepaliveIntervalSec(デフォルト値 30)と keepaliveIntervalGapSec(デフォルト値 30)を適切な値に設定することで Member が自動的に leave するタイミングをある程度コントロールできます。 Member の生存確認は keepaliveIntervalSec 秒ごとにクライアントからサーバーへの応答の有無で行われており、keepaliveIntervalSec 秒応答がないまま keepaliveIntervalGapSec 秒経過した場合 Member の leave が行われます。 そのため、例えば keepaliveIntervalSec を 30 秒、keepaliveIntervalGapSec を 5 秒とすることで、ブラウザのウィンドウを閉じたとき最短で 5 秒後、最長で 35 秒後に Member を自動的に leave させることができます。 Room ライブラリの場合 ```ts const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.FindOrCreate(context, { name: 'sample-room', }); const me = await room.join({ keepaliveIntervalSec: 30, keepaliveIntervalGapSec: 5, }); ``` --- ## ユーザーガイド/iOS SDK/概要 Path: user-guide_ios-sdk_overview.md # SkyWay iOS SDK の概要 SkyWay iOS SDK (以下、iOS SDK )は iOS デバイス用のアプリケーションから SkyWay を利用するための SDK です。 iOS のネイティブなアプリケーションに SkyWay を組み込むことで、デバイス同士やブラウザとのリアルタイム通信を実現できます。 ## 対応環境 | 項目 | iOS SDKの対応状況 | | ----------------- | ---------------------------------------- | | OS | iOS 14, iPadOS 14 以降 | | CPUアーキテクチャ | arm64, x86_64※ | | IDE | Xcode 16 以降 | ※iPhone Simulator 環境でのビルドも対応はしていますが、動作未保証です。 ## アプリケーション開発言語 Swift ## ライブラリの仕様 SkyWay を利用する上で理解する必要のある基本的な仕様について説明します。 ### Room 通話を行うグループの単位であり、ユーザーは共通の Room に参加したユーザー同士で通話を行います。 メディア通信毎に P2P と SFU の方式を選択できます。 また事前に Room 内での通信方式を定めておくことも可能です。 - P2P は少人数向け - 上限人数に制限はありませんが、ユーザーが快適に通話できる人数は 4 人までです - SFU は多人数向け - SFU サーバーを経由してメディア通信を行います ### Member Room に参加しているユーザーのことを Member と呼びます。 ### Stream Room 上で送受信できるメディアのことを Stream といいます。以下の 3 種類の Stream を利用できます。 - AudioStream - ユーザーのマイク音声など - VideoStream - ユーザーのカメラ映像など - DataStream - 任意のメッセージ - SFUを用いた通信では利用できません ### Publish Member が Stream を Room に公開することを Publish といいます。 Stream を Publish すると Room 上に Stream に対応する Publication というリソースが作成されます。 ### Subscribe Member が Room 上の Publication を受信することを Subscribe といいます。 Subscribe をすると Room 上に Subscription というリソースが作成されます。 Publication を Subscribe した Member は Subscription を通じて Stream にアクセスし映像や音声を受信できます。 ### SkyWay Auth Token SkyWay Auth Token は、SkyWay を利用するために必要な JWT(JSON Web Token)形式のトークンです。 ユーザー毎に権限を細かく設定することでき、例えば Room ごとの入室を特定ユーザーに制限する、といったことができます。 SkyWay Auth Token を利用するためには、これを払い出すアプリケーションサーバーを構築する必要があります。SkyWay SDK を利用したクライアントアプリは、アプリケーションサーバーから SkyWay Auth Token を取得し、これを用いて各種 SkyWay の機能を利用します。 なお、サーバーを構築せずにフロントエンドで SkyWay Auth Token を生成した場合、シークレットキーをエンドユーザーが取得できるため、権限の制限が機能せず注意する必要があります。 ## SDK のダウンロード Swift Package Manager と CocoaPods 、 GitHub のリリースで配布を行なっています。 ### Swift Package Manager 以下のリポジトリで公開しています。 - https://github.com/skyway/ios-sdk.git ### CocoaPods Pod Specs は独自に管理しています。 CocoaPodsがインストールされている環境でリポジトリを追加してください ``` $ pod repo add skyway-ios-sdk-specs https://github.com/skyway/skyway-ios-sdk-specs.git $ pod repo add skyway-ios-webrtc-specs https://github.com/skyway/skyway-ios-webrtc-specs.git $ pod repo update ``` `Podfile` に以下のソースを追加してください。 ``` source 'https://github.com/skyway/skyway-ios-sdk-specs.git' source 'https://github.com/skyway/skyway-ios-webrtc-specs.git' source 'https://github.com/CocoaPods/Specs.git' ``` インストールするライブラリを以下のように記述します。 ``` pod 'SkyWayRoom' ``` `Podfile` の全体は以下のようになります。 ``` # Uncomment the next line to define a global platform for your project # platform :ios, '9.0' source 'https://github.com/skyway/skyway-ios-sdk-specs.git' source 'https://github.com/skyway/skyway-ios-webrtc-specs.git' source 'https://github.com/CocoaPods/Specs.git' target 'CocoaPodSample' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for CocoaPodSample pod 'SkyWayRoom' end ``` `pod install` でインストールします。 ``` $ pod install ``` ### GitHub のリリース 以下のリポジトリでリリースノートと xcframework を公開しています。 - https://github.com/skyway/ios-sdk/releases/latest ダウンロード後、 zip ファイルを解凍し、 xcframework をプロジェクトに配置してください。 ## 旧iOS SDK(SkyWay.framework)との互換性と共存 互換性はありません。 1つのアプリで新旧 SDK を共存(リンク)することは v1.2.1 から可能です。 --- ## ユーザーガイド/iOS SDK/クイックスタート Path: user-guide_ios-sdk_quickstart.md # 🚀 クイックスタート SkyWay のメディア通信を体験できるシンプルなサンプルアプリケーションを作成します。 本チュートリアルでは SFU でのメディア通信を使用します。 ここでは、iPhone のマイク音源とカメラ映像を SFU サーバーに送信し、それぞれを自分が受信して音声を再生と映像の描画するというアプリケーションを作ります。 このクイックスタートの実行には iPhone 実機が必要です。 完成品は https://github.com/skyway/ios-sdk/tree/main/Tutorial にあります。 ## 開発環境 - Xcode 26.0.1 - iOS 26.0.1 ## アプリケーション ID とシークレットキーの取得 ※SkyWay への登録がまだの方は[こちら](https://console.skyway.ntt.com/login/)から SkyWay コンソールへログインし、以下の 3 つを行います。 1. 「アプリケーションを作成」ボタンを押す ![Peer](/media/posts/docs/tutorial_1.png) 2. アプリケーション名を入力して作成ボタンを押す 3. アプリケーション一覧からアプリケーション ID とシークレットキーをコピーする ## SkyWay Auth Token の作成 SkyWay を利用するためには、初めに JWT(JSON Web Token)を用いて `Context` を初期化します。 SkyWay Auth Token は本来サーバーサイドで生成するため、 iOS SDK にはトークンの生成機能はございません。 クイックスタートでは、 Dev 環境専用の API である `Context.setupForDev(withAppId:secretKey:options:completion:)` を用いて初期化するため、 SkyWay Auth Token の作成は省略します。 認証認可について、詳しくは[こちら](/ja/docs/user-guide/authentication/)をご覧ください。 ## XcodeProjectの作成・フレームワークの設定 新規で Xcodeproject を作成してください。 作成時、Interface は `Storyboard` を選択してください。 ![SelectStoryboard](/media/posts/docs/ios-sdk/quick/interface_storyboard.png) ## パーミッションの設定 `info.plist` の `Privacy - Microphone Usage Description` と `Privacy - Camera Usage Description` を追加して、value にはユーザーに利用許可を確認するプロンプトのメッセージを登録してください。 ここでは、`マイクを利用します` と `カメラを利用します` と登録します。 ![InfoPlist](/media/posts/docs/ios-sdk/quick/info_plist.png) ## SDKのダウンロード 今回は Swift Package Manager にてダウンロードします。 Xcode から Project を選択し、 Package Dependencies を選択します。 左下 `+` ボタンからパッケージ検索のモーダルを表示させ、右上の URL 検索ボックスに `https://github.com/skyway/ios-sdk.git` と入力します。 Package Product `SkyWayRoom` がチェックされていることを確認し、 Add Package を押下します。 ## 映像ビューのセットアップ 今回は、カメラからキャプチャしている Local の View (プレビュー)と SFU サーバーから受信した Remote の View を描画します。 Storyboard を使って解説します。 まず、View コンポーネントを作成し、ViewController 直下の View に追加します。 ![StoryBoardView](/media/posts/docs/ios-sdk/quick/storyboard_view.png) View の名前を `Local View` にリネームします。 ![StoryBoardLocalView](/media/posts/docs/ios-sdk/quick/storyboard_local_view.png) この View の Custom Class の設定で Class 名を `SKWCameraPreviewView` に設定します。 この View を複製して、`Remote View` にリネームします。 ![StoryBoardRemoteView](/media/posts/docs/ios-sdk/quick/storyboard_remote_view.png) この View の Custom Class の設定で Class 名を `SKWVideoView` に設定します。 適宜 Auto Layout を利用して View の constraint を設定します。 上側を LocalView, 下側を RemoteView に配置します。 ![StoryBoardConstraints](/media/posts/docs/ios-sdk/quick/storyboard_constraints.png) IBOutlet を利用して `Local View` と `Remote View` をそれぞれ変数宣言します。この時、Type は `SKW` を消した `CameraPreviewView` と `VideoView` 宣言してください。 ``` デフォルトでは`SKW`プレフィックスがついているのでご注意ください。 ``` ![IBOutletLocalView](/media/posts/docs/ios-sdk/quick/iboutlet_local_view.png) ![IBOutletRemoteView](/media/posts/docs/ios-sdk/quick/iboutlet_remote_view.png) ## Contextのセットアップ ここでは、簡易的に Initial ViewController の `viewDidLoad` に SkyWay のロジックを記述していきます。 ```swift class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Impl here } } ``` `import SkyWayRoom` でフレームワークを import してください。 ```swift import UIKit import SkyWayRoom class ViewController: UIViewController { ``` アプリケーション ID とシークレットキーをメンバー変数で宣言します。 `Context.setupForDev(withAppId:secretKey:options:completion:)` で SkyWay のセットアップを行います。 `completion` にて失敗した場合は Error が返ります。ここでは async/await 形式で記述します。 > `Context.setupForDev(withAppId:secretKey:options:completion:)` は Dev 環境での利用が想定されている API です。 > 本番環境では、SecretKeyを秘匿するため `Context.setup(withToken:options:completion:)` をご利用ください。 ```swift override func viewDidLoad() { super.viewDidLoad() Task { let appId = "アプリケーションIDを入力してください" let secretKey = "シークレットキーを入力してください" // SkyWayのセットアップ try? await Context.setupForDev(withAppId: appId, secretKey: secretKey, options: contextOpt) ``` ## Roomの作成 SkyWay のセットアップが完了したら Room を作成します。 `Room.create(with:completion:)` で Room を作成できます。 ```swift let roomInit: Room.InitOptions = .init() guard let room: Room = try? await .create(with: roomInit) else { print("[Tutorial] Creating room failed.") return } ``` ## Roomへの参加 `Room.join(with:completion:)` でルームに参加し、メンバーを作成します。 ```swift let memberInit: Room.MemberInitOptions = .init() memberInit.name = "Alice" // Memberに名前を付けることができます guard let member = try? await room.join(with: memberInit) else { print("[Tutorial] Join failed.") return } ``` ## マイク音源のAudioStreamの作成とRoomへのPublish マイクを音声入力として Stream を作成します。 Publish をするための LocalStream を作成するためには、Source が必要です。 `MicrophoneAudioSource` のイニシャライザから Source を作成し、`createStream()` で LocalStream を作成します。 通信方式は P2P とメディアサーバーを介する SFU がありますが、今回は SFU を利用します。 通信方式は `RoomPublicationOptions` の `type` プロパティで指定できます。 `LocalRoomMember.publish(_:options:completion:)` で Room に Publish します。 ```swift // AudioStreamの作成 let audioSource: MicrophoneAudioSource = .init() let audioStream = audioSource.createStream() let audioPublicationOptions: RoomPublicationOptions = .init() audioPublicationOptions.type = .SFU guard let audioPublication = try? await member.publish(audioStream, options: audioPublicationOptions) else { print("[Tutorial] Publishing failed.") return } ``` ## AudioStreamのSubscribe 先ほど Publish した Stream を `LocalRoomMember.subscribe(publicationId:options:completion:)` を利用して、Subscribe して Stream を受け取ります。 PublicationID は `RoomPublication` の `id` で取得できます。 ``` ※P2P方式では、自分のPublishしたStreamをSubscribeできませんのでご注意ください ``` ```swift // Audioの場合、subscribeした時から音声が流れます guard let _ = try? await member.subscribe(publicationId: audioPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing audio stream successfully.") ``` ## カメラの設定とプレビューの描画 カメラ映像ソースの場合は**カメラの設定とキャプチャ操作が必要です。** `CameraVideoSource.supportedCameras()` で SkyWay がサポートしているカメラの一覧を取得できます。 今回は前面カメラデバイスを取得します。 ```swift // Cameraの設定 guard let camera = CameraVideoSource.supportedCameras().first(where: { $0.position == .front }) else { print("Supported cameras is not found."); return } ``` `CameraVideoSource.shared().startCapturing(with:options:completion:)` でキャプチャーを開始します。 ```swift // キャプチャーの開始 try! await CameraVideoSource.shared().startCapturing(with: camera, options: nil) ``` 確認用に `CameraVideoSource.shared().attach(localView)` でキャプチャリングをしているカメラ映像を描画します。 ```swift // Previewの描画 CameraVideoSource.shared().attach(localView) ``` ## カメラ映像ソースのVideoStreamの作成とRoomへのPublish Stream は `CameraVideoSource.shared().createStream()` で作成できます。 AudioStream 同様、`publish(_:options:completion:)` で Publish を行います。 ```swift // VideoStreamの作成 let localVideoStream = CameraVideoSource.shared().createStream() let videoPublicationOptions: RoomPublicationOptions = .init() videoPublicationOptions.type = .SFU guard let videoPublication = try? await member.publish(localVideoStream, options: videoPublicationOptions) else { print("[Tutorial] Publishing failed.") return } ``` ## VideoStreamのSubscribe AudioStream 同様、`subscribe(publicationId:completion:)` を利用して、Subscribe して Stream を受け取ります。 ```swift guard let videoSubscription = try? await member.subscribe(publicationId: videoPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing video stream successfully.") ``` ## RemoteViewの描画 Subscription の `stream` を取得し、`RemoteVideoStream` にキャストします。 `attach(_)` で `remoteView` を指定することで、SFU から受信した映像を描画できます。 ```swift let remoteVideoStream = videoSubscription.stream as! RemoteVideoStream remoteVideoStream.attach(remoteView) ``` ## Tutorial完成コード ```swift import UIKit import SkyWayRoom class ViewController: UIViewController { @IBOutlet weak var localView: CameraPreviewView! @IBOutlet weak var remoteView: VideoView! override func viewDidLoad() { super.viewDidLoad() Task { let appId = "アプリケーションIDを入力してください" let secretKey = "シークレットキーを入力してください" // SkyWayのセットアップ let contextOpt: ContextOptions = .init() contextOpt.logLevel = .trace try? await Context.setupForDev(withAppId: appId, secretKey: secretKey, options: contextOpt) let roomInit: Room.InitOptions = .init() guard let room: Room = try? await .create(with: roomInit) else { print("[Tutorial] Creating room failed.") return } let memberInit: Room.MemberInitOptions = .init() memberInit.name = "Alice" // Memberに名前を付けることができます guard let member = try? await room.join(with: memberInit) else { print("[Tutorial] Join failed.") return } // AudioStreamの作成 let auidoSource: MicrophoneAudioSource = .init() let audioStream = auidoSource.createStream() let audioPublicationOptions: RoomPublicationOptions = .init() audioPublicationOptions.type = .SFU guard let audioPublication = try? await member.publish(audioStream, options: audioPublicationOptions) else { print("[Tutorial] Publishing failed.") return } // Audioの場合、subscribeした時から音声が流れます guard let _ = try? await member.subscribe(publicationId: audioPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing audio stream successfully.") // Cameraの設定 guard let camera = CameraVideoSource.supportedCameras().first(where: { $0.position == .front }) else { print("Supported cameras is not found."); return } // キャプチャーの開始 try! await CameraVideoSource.shared().startCapturing(with: camera, options: nil) // Previewの描画 CameraVideoSource.shared().attach(localView) // VideoStreamの作成 let localVideoStream = CameraVideoSource.shared().createStream() let videoPublicationOptions: RoomPublicationOptions = .init() videoPublicationOptions.type = .SFU guard let videoPublication = try? await member.publish(localVideoStream, options: videoPublicationOptions) else { print("[Tutorial] Publishing failed.") return } guard let videoSubscription = try? await member.subscribe(publicationId: videoPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing video stream successfully.") let remoteVideoStream = videoSubscription.stream as! RemoteVideoStream remoteVideoStream.attach(remoteView) } } } ``` ## 実行 iPhone(実機)にて Run します。 ``` iPhone Simulatorではカメラが利用できないので実機にて実行してください。 ``` iPhone 実機に声をかけたり音声を鳴らしてみてください。音声が出力され、LocalView と RemoteView に映像が描画されれば成功です。 ## 次のステップ 今回は SFU サーバーを介して自分が Publish したメディアを Subscribe するというシンプルなものでした。 次は、他のクライアントと映像・音声・データをやりとりしてみましょう。他のクライアントと疎通できるサンプルアプリケーションを用意しています。 [サンプルコード](/ja/docs/sample-code/ios-sdk/) また、開発の前に開発ドキュメントもご一読ください。 [iOS SDKの開発ドキュメント](/ja/docs/user-guide/ios-sdk/) --- ## ユーザーガイド/iOS SDK/音声・映像入力ソースと LocalStream の作成方法 Path: user-guide_ios-sdk_media-source-and-stream.md # 音声・映像入力ソースと LocalStream の作成方法 Publish をするためには、音声・映像入力ソースまたはデータソースと紐付いた LocalStream が必要です。 ここでは、音声・映像入力ソースに対して LocalStream をどのように作るか説明します。 ## 概要 LocalStream を作る操作は以下の流れで行います。 1. ソースの作成 2. (一部ソースのみ)ソースのキャプチャ処理開始 3. ソースから LocalStream を作成 ## 音声ソース ### マイク入力ソース 現在、マイク入力ソースのみ対応しています。 マイクを利用する場合は `info.plist` の `Privacy - Microphone Usage Description` を追加して、value にはユーザーにマイク利用許可を確認するプロンプトのメッセージを登録してください。 マイク入力ソースは `MicrophoneAudioSource.init()` で作成できます。 このソースから `createStream()` で LocalStream を作成できます。 ```swift let audioSource: MicrophoneAudioSource = .init() let localSteam: LocalAudioStream = audioSource.createStream() ``` **後述の映像ソースで必要なキャプチャの操作は不要です。** Subscribe され次第、マイクから音声がキャプチャされます。 ## 映像ソース ### カメラソース 本体のカメラ(前面・背面)入力から LocalStream を作成します。 カメラを利用する場合は `info.plist` の `Privacy - Camera Usage Description` を追加して、value にはユーザーにカメラ利用許可を確認するプロンプトのメッセージを登録してください。 カメラ入力ソースは、`CameraVideoSource.shared()` で取得できます。 このソースはキャプチャが同時に1つしかできないため、シングルトンインスタンスで管理しています。 次に、カメラの設定を行います。 SkyWay SDK がサポートしているカメラの一覧から、キャプチャを行うカメラを設定できます。 `supportedCameras()` から一覧取得できます。 例えば、前面カメラの場合は以下のようなコードで取得できます。 ```swift let frontCam: AVCaptureDevice? = CameraVideoSource.supportedCameras().first(where: { $0.position == .front }) ``` ``` ※iPhone Simulatorでは利用できるカメラが存在しないことに注意してください。 ``` `startCapturing(with:options:completion:)` でキャプチャを開始する必要があります。 ```swift try? await CameraVideoSource.shared().startCapturing(with:frontCam, options:nil) ``` このソースから `createStream()` で LocalStream を作成できます。 ```swift let stream = CameraVideoSource.shared().createStream() ``` ``` ※キャプチャ開始は、`createStream()`の後に任意のタイミングで開始させることもできますが、キャプチャを開始し忘れた場合、SubscriberがStreamを受け取っても映像が描画されませんので注意してください。 ``` #### カメラデバイスの変更による映像切り替え カメラデバイスの変更は Unpublish->新しいソースの LocalStream の作成->Subscribe といったことをさせず、ソースのみ切り替えることができます。 `CameraVideoSource` の `change(_:)` をコールすることで、映像描画中でもカメラデバイス変更できます。 #### カメラ映像のプレビュー 自分自身がカメラ映像を Publish する前にプレビューを確認したいユースケースの場合などの場合は、`CameraVideoSource.shared().attach(_:)` で `CameraPreviewView` に描画できます。 ### 動画ファイルソース アプリ内のアセットにある動画ファイルの映像から、LocalStream を作成します。 `FileVideoSource.init(filename:)` から拡張子まで含めたファイル名でインスタンスを生成します。 ```swift let fileSource: FileVideoSource = .init(filename: "hoge.mp4") ``` `startCapturing(onError:)` でキャプチャを開始する必要があります。 ```swift fileSource.startCapturing(onError: nil) ``` このソースから `createStream()` で LocalStream を作成できます。 ```swift let stream = fileSource.createStream() ``` ### 任意の画像フレームソース `CMSampleBuffer` 型の画像データを連続的に更新し、その映像ソースから LocalStream を作成します。 `CustomFrameVideoSource.init()` でインスタンスを作成します。 ```swift let frameSource: CustomFrameVideoSource = .init() ``` このソースから `createStream()` で LocalStream を作成できます。 ```swift let stream = frameSource.createStream() ``` キャプチャループ内などで `updateFrame(with:)` をコールして画像を更新します。 ``` { // フレーム更新ループ内などで frameSource.updateFrame(with:buffer) } ``` #### 画面共有 ReplayKit と組み合わせることで、アプリ内の画面共有を行うことができます。 ```swift let source: CustomFrameVideoSource = .init() RPScreenRecorder.shared().startCapture { buffer, _, err in guard err == nil else { return } source.updateFrame(with: buffer) } completionHandler: { _ in } stream = source.createStream() ``` --- ## ユーザーガイド/iOS SDK/解放・破棄処理 Path: user-guide_ios-sdk_release-and-dispose.md # 解放・破棄処理 リソースとは、SDK で生成される全てのインスタンスおよびそのメモリを指します。 ## Room の管理 Room インスタンスは操作不要な状態になるまでアプリケーションで管理してください。 OK ```swift let room: Room? = try? await .find(by: query) // 入室 let member: LocalRoomMember = try? await room?.join(with: nil) ... // 退出 try? await member.leave() // Roomを含むリソースが不要になった await room.dispose() room = nil ``` NG ```swift let room: Room? = try? await .find(by: query) // 入室 let member: LocalRoomMember = try? await room?.join(with: nil) ... // 破棄 room = nil // 危険: クラッシュする可能性があります try? await member.publsh(stream, options: nil) ``` ### Room を閉じた後の挙動 誰かが明示的に Room を閉じたときに Member が入室していた場合、 Publish と Subscribe を中止して退出します。 Room を閉じた後にそこで生成されたリソースの操作はエラーになります。 ### SkyWayを終了する アプリケーションにおいて SkyWay の機能を利用しなくなった場合、`Context.dispose()` をコールすることで SkyWay サーバーとの通信を切断し、SDK で管理している全てのリソースを破棄します。 事前に、Member の Room 退出処理を行ってください。 また、`Context.dispose()` をコールした後、それまで生成したリソース(インスタンス)にアクセスしないでください。 アクセスした場合の挙動は未定義でアプリケーションがクラッシュする可能性があります。 `dispose()` 後に再度 `setup(withToken:options:completion:)` することで再度 SkyWay サーバーと接続できます。 --- ## ユーザーガイド/iOS SDK/Tips Path: user-guide_ios-sdk_tips.md # Tips 開発に関するヒント、注意点を掲載しています。 ## SKWプレフィックスについて 一部 API は Objective-C++ 言語で実装されているためコーディング規約として `SKW` プレフィックスを付与しています。 Swift アプリケーションからは `NS_SWIFT_NAME` で `SKW` プレフィックスなしで設定していますが、Storyboard や Interface Builder などをご利用いただく際などに `SKW` プレフィックスで設定するケースがあります。 --- ## ユーザーガイド/iOS SDK/既知の問題 Path: user-guide_ios-sdk_issues.md # 既知の問題 ## Xcode14以上において、Context.setup(withToken:options:completion) をするとThread Performance Checkerのログが表示される ``` v1.2.1 にて修正されました。 ``` 依存ライブラリである Socket Rocket によるものです。 https://github.com/facebookincubator/SocketRocket/issues/648 ## Class XXX is implemented in both...のログが表示される ``` v1.2.0 にて修正されました。 ``` 内部で利用している WebRTC ライブラリのシンボルが衝突していることによるものです。 動作には影響ありません。 --- ## ユーザーガイド/Android SDK/概要 Path: user-guide_android-sdk_overview.md # SkyWay Android SDK の概要 SkyWay Android SDK(以下、Android SDK)は Android デバイス用のアプリケーションから SkyWay を利用するための SDK です。 Android のネイティブなアプリケーションに SkyWay を組み込むことで、デバイス同士やブラウザとのリアルタイム通信を実現できます。 このセクションでは、Android SDK の動作環境や入手方法を掲載しています。 Android SDK の採用を検討する際の参考情報としてください。 ## 対応環境 | 項目 | SkyWay Android SDKの対応状況 | | ----------------- | ---------------------------------------- | | OS | Android 5.0 Lollipop(API Level 21)以降 | | CPUアーキテクチャ | arm64-v8a、armeabi-v7a、x86_64、x86 | | 推奨するIDE | Android Studio | `Android SDK v2.2.1 から 64-bit アーキテクチャ(arm64v8a & x86_64) における 16KB Page Sizeに対応済です。` ※原則として、一般のスマートフォン上での動作、またはエミュレータによる動作検証を想定しています。 ## アプリケーション開発言語 - Kotlin Kotlin は Android アプリケーション開発の公式でサポート言語であり(2023年現在)、Android Studio のような開発環境で利用できます。 ## ライブラリの仕様 SkyWay を利用する上で理解する必要のある基本的な仕様について説明します。 ### Room 通話を行うグループの単位であり、ユーザーは共通の Room に参加したユーザー同士で通話を行います。 メディア通信毎に P2P と SFU の方式を選択できます。 また事前に Room 内での通信方式を定めておくことも可能です。 - P2P は少人数向け - 上限人数に制限はありませんが、ユーザーが快適に通話できる人数は 4 人までです - SFU は多人数向け - SFU サーバーを経由してメディア通信を行います ### Member Room に参加しているユーザーのことを Member と呼びます。 ### Stream Room 上で送受信できるメディアのことを Stream といいます。以下の 3 種類の Stream を利用できます。 - AudioStream - ユーザーのマイク音声など - VideoStream - ユーザーのカメラ映像など - DataStream - 任意のメッセージ - SFU を用いた通信では利用できません ### Publish Member が Stream を Room に公開することを Publish といいます。 Stream を Publish すると Room 上に Stream に対応する Publication というリソースが作成されます。 ### Subscribe Member が Room 上の Publication を受信することを Subscribe といいます。 Subscribe をすると Room 上に Subscription というリソースが作成されます。 Publication を Subscribe した Member は Subscription を通じて Stream にアクセスし映像や音声を受信できます。 ### SkyWay Auth Token SkyWay Auth Token は、SkyWay を利用するために必要な JWT(JSON Web Token)形式のトークンです。 ユーザー毎に権限を細かく設定することでき、例えば Room ごとの入室を特定ユーザーに制限する、といったことができます。 SkyWay Auth Token を利用するためには、これを払い出すアプリケーションサーバーを構築する必要があります。SkyWay SDK を利用したクライアントアプリは、アプリケーションサーバーから SkyWay Auth Token を取得し、これを用いて各種 SkyWay の機能を利用します。 なお、サーバーを構築せずにフロントエンドで SkyWay Auth Token を生成した場合、シークレットキーをエンドユーザーが取得できるため、権限の制限が機能せず注意する必要があります。 ## SDK のダウンロード Maven Central Repository にて配布を行なっています。 導入先の app/build.gradle にて、以下の依存関係を追記してください。 ``` // 最新の SDK バージョンに差し替えてください // 最新の SDK バージョン情報は以下のリンクより、Maven Central Repository にて確認できます // https://central.sonatype.com/search?q=skyway def skywayVersion = 'x.x.x' dependencies { implementation "com.ntt.skyway:room:$skywayVersion" } ``` ## 旧Android SDK(skyway.aar)との互換性と共存 互換性はありません。 1つのアプリで新旧 SDK を共存することは v4.0.2 から可能です。 --- ## ユーザーガイド/Android SDK/クイックスタート(Android View) Path: user-guide_android-sdk_quickstart.md # 🚀 クイックスタート(Android View) このセクションでは、SkyWay Android SDK を利用した最小限の Android View をベースとするアプリケーションを開発する方法について掲載しています。 はじめて Android SDK を利用する方はこちらを参考に導入してください。 完成品は [公式リポジトリ](https://github.com/skyway/android-sdk/tree/main/examples/AndroidView/QuickStart) にて公開しています。 尚、この記事は以下を前提に構成しています。 - Activity を用いたアプリ開発経験があること - Kotlin の文法(関数、変数定義、呼び出し、コルーチンなど)がわかること 上記については Android Developers などを参考にしてください。 ## アプリの概要 このアプリには以下のシンプルな通話機能をP2Pによって実装します。 1) ユーザーはルーム名を指定して入室できる 2) ユーザーがパーミッションの要求を許可すれば、入室と同時に同じルームのメンバーに対してカメラ映像とマイク音声を配信する 3) ユーザーは入室時から同じルームに入室しているユーザーのマイク音声とカメラ映像を視聴できる ## Android プロジェクト の作成 Android Developers の公式ウェブサイトを参考に、Android プロジェクトを作成します。 https://developer.android.com/training/basics/firstapp/creating-project?hl=ja 「Language」は kotlin を選択してください。また、「Minimum SDK」は Android 6.0(API Level: 23) 以降を選択してください。 ## Android SDK のインストール `app/build.gradle` に以下の依存関係を追加するだけでSDKのインストールは完了です。 > このセクションではgradleファイルをGroovyによって記述しています。 > Kotlin DSLによる記述方法は[クイックスタート(JetPack Compose)](https://skyway.ntt.com/ja/docs/user-guide/android-sdk/quickstart-compose/)をご参照ください。 ```java // 最新の SDK バージョンに差し替えてください // 最新の SDK バージョン情報は以下のリンクより、Maven Central Repository にて確認できます // https://central.sonatype.com/search?q=skyway def skywayVersion = 'x.x.x' dependencies { // ... 省略 implementation 'com.ntt.skyway:room:$skywayVersion' } ``` ## パーミッションの設定 Android SDK の動作に必要なパーミッションを `app/src/main/AndroidManifest.xml` に記述します。 以下に、映像・音声の通信をする際に必要なパーミッションの例を示します。 ```xml ``` ## レイアウトの設定 `res/layout/activity_main.xml` に表示したいコンポーネントを記述します。 ### Video 表示コンポーネントの配置する "ローカルのビデオ"には自身の映像、"リモートのビデオ"には通話相手の映像を映します。 初期状態から以下のように変更します。 ```xml ``` ### ルーム名の表示枠と参加ボタンを作成する Video 表示コンポーネントの下にルーム名の表示枠と参加ボタンを追加します。 ```xml
``` 次にスクリプトファイルを用意します。 以下を参照して、`tutorial/client/main.js` ファイルを作成してください。 _tutorial/client/main.js_ ```js import { SkyWayContext, SkyWayRoom, SkyWayStreamFactory } from '@skyway-sdk/room'; (async () => { const localVideo = document.getElementById('local-video'); const buttonArea = document.getElementById('button-area'); const remoteMediaArea = document.getElementById('remote-media-area'); const roomNameInput = document.getElementById('room-name'); const myId = document.getElementById('my-id'); const joinButton = document.getElementById('join'); const startRecordingButton = document.getElementById('startRecording'); const stopRecordingButton = document.getElementById('stopRecording'); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); video.attach(localVideo); await localVideo.play(); joinButton.onclick = async () => { const roomName = roomNameInput.value; if (roomName === '') return; const response = await fetch(`http://localhost:9090/channels/${roomName}/join`, { method: 'POST', }); const { token } = await response.json(); const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.Find(context, { name: roomName }, { type: 'sfu' }); const me = await room.join(); myId.textContent = me.id; await me.publish(audio); await me.publish(video); const subscribeAndAttach = (publication) => { if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement('button'); subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case 'video': newMedia = document.createElement('video'); newMedia.playsInline = true; newMedia.autoplay = true; break; case 'audio': newMedia = document.createElement('audio'); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); startRecordingButton.onclick = async () => { await fetch(`http://localhost:9090/channels/${room.name}/start`, { method: 'POST', headers: { Authorization: context.authTokenString, }, }); }; stopRecordingButton.onclick = async () => { await fetch(`http://localhost:9090/channels/${room.name}/stop`, { method: 'DELETE', headers: { Authorization: context.authTokenString, }, }); }; }; })().catch((e) => console.error('main error', e)); ``` ## チュートリアルアプリの実行 ### アプリケーションの起動 ターミナルで `npm run server` を実行してサーバーを起動します。 別のターミナルで `npm run client` を実行してクライアントを起動します。なお、この際にターミナルにローカルアドレスが表示されるので、そのアドレスをブラウザで開いてください。 Room に join したあと、startRecording ボタンを押すと録音・録画が始まります。stopRecording ボタンを押すと録音・録画が終了します。 ### 保存されたファイルの確認 stopRecording ボタンを押すと、サーバー側の標準出力にクラウドストレージにアップロードされたファイルのパスが出力されます。 クラウドストレージのコンソールにアクセスし、ファイルがアップロードされていることを確認してください。 保存されるファイルの詳細な仕様については概要の[録音・録画ファイル](/ja/docs/user-guide/recording/recording-overview/#92)の項目を参照してください。 --- ## ユーザーガイド/録音・録画/開発ガイド Path: user-guide_recording_recording-development-guide.md # 開発ガイド ## P2P での通話内容を録音・録画する方法 ユーザー間の通信を行う P2P 通信とは別に、録音・録画機能を利用するための SFU 通信を同時に利用することで、ユーザー間の通信は P2P 通信を利用しながら録音・録画機能を利用できます。 ![ユーザー間の通信に P2P 通信を利用しながら録音・録画機能を利用する際の概要図](/media/posts/docs/recording/development/p2p-rec-overview.png) ### SFU 通信料と SFU リソース確保料について P2P 通信での通話を録音・録画する場合、SkyWay Auth Token に次のような設定を行った上で `maxSubscribers` の設定を `0` として publish を行うことで、録音・録画に伴う[SFU 通信料と SFU リソース確保料](/ja/pricing/)が発生しなくなります。 本設定は SkyWay Auth Token のバージョン 3 から対応しております。 ※ [SkyWay Auth Token / SFU リソースについて](/ja/docs/user-guide/authentication/skyway-auth-token/#265) ```js { // ...省略 version: 3, scope: { // ...省略 rooms: [ { // ...省略 sfu: { enabled: true, maxSubscribersLimit: 0, }, // ...省略 } ] } } ``` publish 時の `maxSubscribers` オプションを `0` に設定すると、その Publication は subscribe ができなくなり、録音・録画専用の Publication として扱われます。 ### 注意事項 SkyWay の 各種 SDK では一度 Publish した Stream を再度 Publish することができません。 P2P 通信と SFU 通信を併用する際に同一のマイク、カメラをそれぞれ「ユーザー間通信」と「録音・録画」で用いる場合は、 Stream を 2 回ずつ取得する必要があります。 ### JavaScript SDK での実装例 ```js // トークンはサーバーサイドで生成してください const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId: 'ここにアプリケーションIDをペーストしてください', rooms: [ { id: '*', name: 'recordingRoom', methods: ['create', 'close', 'updateMetadata'], sfu: { enabled: true, maxSubscribersLimit: 0, }, member: { id: '*', name: '*', methods: ['publish', 'subscribe', 'updateMetadata'], }, }, ], }, }).encode('ここにシークレットキーをペーストしてください'); const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.FindOrCreate(context, { name: 'recordingRoom', }); const me = await room.join(); const { audio: communicationAudio, video: communicationVideo } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); // ユーザー間の通信にはP2P通信を利用します await me.publish(communicationAudio, { type: 'p2p' }); await me.publish(communicationVideo, { type: 'p2p' }); // 一度PublishしたStreamは再度Publishすることができないので、改めて録音・録画用のStreamを取得します const { audio: recordingAudio, video: recordingVideo } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); // 録音録画機能を利用するために、SFU通信を利用します const recordingAudioPublication = await me.publish(recordingAudio, { type: 'sfu', maxSubscribers: 0 }); const recordingVideoPublication = await me.publish(recordingVideo, { type: 'sfu', maxSubscribers: 0 }); // 録音録画を開始します // startRecording関数はあくまでも例であり、実際はユーザーのアプリケーションにて実装していただく処理です await startRecording([recordingAudioPublication.id, recordingVideoPublication.id]); ``` ## 想定されるエラー 録音・録画機能を利用する際に発生する可能性のあるエラーについて説明します。 ### SkyWay Recording API のエラーレスポンス #### 400 リクエストに含まれるパラメータに問題があります。ドキュメントを参照して正しい値をパラメータに入れるように修正してください。 #### 401 有効な SkyWay Admin Auth Token が使用されていません。[SkyWay Admin Auth Token のドキュメント](/ja/docs/user-guide/authentication/skyway-admin-auth-token/)を参照して正しいトークンを使用してください。 #### 403 クラウドストレージのクレデンシャルに対応する権限が不足しており、クラウドストレージを正常に利用できません。[各クラウドストレージに必要な資格情報](/ja/docs/user-guide/recording/recording-overview/#63)の項目を参照して正しいクレデンシャルを使用してください。 #### 404 対象のリソースが存在しません。 #### 429 レートリミットもしくはリソース量の制限に違反しています。[制限と割当](/ja/docs/user-guide/commons/quotas-and-limits/)の項目に記載されている範囲内で利用してください。 #### 500 Recording サーバーにおいてエラーが発生しています。リクエストの処理が完了していない可能性があるため、リクエストを再試行してください。 特に、 DeleteRecordingSession の場合は、録音・録画の処理が停止せず、意図しない料金が発生する可能性があります。 複数回再試行を行なってもエラーが解消しない場合は、SkyWay のサポートに問い合わせてください。 ### 録音・録画中のエラー 録音・録画処理において、各種クラウドストレージに録音・録画ファイルをアップロードする際に、何らかのエラーが発生した場合、再試行して解決可能な場合は自動的に再試行を行います。再試行して解決できなかった場合、録音・録画が中断され、新しい録音・録画ファイルとして一度だけ再開されます。 再試行に失敗した場合、録音・録画ファイルのメタデータの status が FAILED になり、errors フィールドにエラーの内容が追記されます。 格納されるエラーは大きく分けて 2 種類あり、1 つは SkyWay 側のエラー、もう 1 つはクラウドストレージ側のエラーです。 #### SkyWay 側のエラー SkyWay のエラーは `Internal Server Error:` という文字列から始まります。 SkyWay のエラーが発生している場合は、録音・録画の処理が途中で終了している可能性があります。 #### クラウドストレージ側のエラー クラウドストレージのエラーは、お使いのクラウドストレージの公式ドキュメントを参照し、原因の特定と対処を行ってください。 - Google Cloud Storage [トラブルシューティング  |  Cloud Storage  |  Google Cloud](https://cloud.google.com/storage/docs/troubleshooting?hl=ja) [HTTP status and error codes for JSON  |  Cloud Storage  |  Google Cloud](https://cloud.google.com/storage/docs/json_api/v1/status-codes) - Amazon S3 [Error responses - Amazon Simple Storage Service](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html) [Amazon S3 のエラーに関するベストプラクティス - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ErrorBestPractices.html) - Wasabi [REST API Introduction](https://docs.wasabi.com/docs/rest-api-introduction) ### エラーの対応方法 #### 検知方法 録音・録画処理中のエラーはメタデータの errors フィールドに出力されます。 よって、以下のいずれかの方法でエラーの検知ができます。 - [GetRecordingSession API](https://recording.api-reference.skyway.ntt.com/#/paths/~1v1~1channels~1%7BchannelId%7D~1sessions~1%7BsessionId%7D/get) を定期的に呼び出し、レスポンスをチェックする - 録音・録画終了後にクラウドストレージ上のメタデータファイルの内容をチェックする 録音・録画が中断された場合、GetRecordingSession APIの結果とクラウドストレージ上のメタデータファイルの内容が異なる場合があります。その場合正しい内容はGetRecordingSession APIの結果となります。 #### 再試行 Recording サーバー側で可能な限り再試行を行っています。 再試行で解決ができなかったエラーに関しては、メタデータの errors フィールドや、SkyWay 並びに各種クラウドストレージの障害情報を確認し、エラーがクラウドストレージのものであればクラウドストレージのサポートに問い合わせを行ってください。 ## エラーとなった場合の録音・録画ファイルの取得 録音・録画ファイルのアップロードが何らかの理由でエラーとなった場合、録音・録画ファイルのステータスは FAILED になります。ここではクラウドストレージごとに修正方法を説明します。 ### Google Cloud Storage 録音・録画ファイルの構造はステータスが RECORDING の場合と同様になります。 そのため録音・録画ファイルを再生する場合は分割された一時保存ファイルを結合することで再生できるようになります。 手順を次に示します。 1. 対象の録音・録画ファイルの webm ファイルをすべてダウンロードします。 2. ダウンロードしたファイルを次のコマンドで結合します。 ```bash ls -v *.webm | xargs cat > output.webm ``` ### Amazon S3 録音・録画したデータを Amazon S3 に保存する際は、MultipartUpload という仕組みを利用します。MultipartUpload がエラーとなった場合は、Amazon S3 に MultipartUpload のデータが残り続ける可能性があります。 エラー発生時点までのデータをファイルとして保存したい場合は、CompleteMultipartUpload の操作を行い、MultipartUpload の処理を完了させる必要があります。 また、MultipartUpload のデータは課金対象となります。エラー発生時点までのデータが不要な場合は、バケットライフサイクルによる自動削除の設定を行うか、AbortMultipartUpload の操作を行い、MultipartUpload を完全に終了させてください。 本記事では、AWS CLI を用いて、CompleteMultipartUpload の操作を行いエラー発生時点までのデータをファイルとして保存する方法と、AbortMultipartUpload の操作を行い MultipartUpload を完全に終了させる方法について説明します。 バケットライフサイクルによる自動削除の設定については、Amazon S3 のドキュメントを参照してください。 [不完全なマルチパートアップロードを削除するためのバケットライフサイクル設定の設定 - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecycle-config.html) [不完全なマルチパートアップロードをクリーンアップするための Amazon S3 ライフサイクル設定ルールを検証する](https://repost.aws/ja/knowledge-center/s3-multipart-cleanup-lifecycle-rule) また、本記事で説明している内容は、変更されている可能性があります。 必要に応じて、Amazon S3 の公式ドキュメントを参照してください。 [マルチパートアップロードを使用したオブジェクトのアップロードとコピー - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/mpuoverview.html) #### エラー発生時点までのデータをファイルとして保存する方法 - 以下のコマンドを実行し、MultipartUpload のリストを取得します。 `aws s3api list-multipart-uploads --bucket ` - 実行結果の Uploads の中から、対象の Key と UploadId を選択します。 - 次に、以下のコマンドを実行し、MultipartUpload に含まれる Parts のリストを取得します。 `aws s3api list-parts --bucket --key --upload-id ` - 実行結果の Parts の中から、PartNumber と ETag の組を全て選択します。 - 次に、以下のコマンドを実行し、エラー発生時点までの MultipartUpload のデータをファイルとして保存します。 `aws s3api complete-multipart-upload --multipart-upload 'Parts=[{ETag="",PartNumber=},{ETag="",PartNumber=}, ... ]' --bucket --key --upload-id ` なお、`Parts` には、`list-parts` で取得した Parts を全て指定してください。 #### MultipartUpload を完全に終了させる方法 - 以下のコマンドを実行し、MultipartUpload のリストを取得します。 `aws s3api list-multipart-uploads --bucket ` - 実行結果の Uploads の中から、対象の Key と UploadId を選択します。 - 以下のコマンドを実行し、MultipartUpload を完全に終了させます。これにより、MultipartUpload のデータが削除されます。 `aws s3api abort-multipart-upload --bucket --key --upload-id ` ### Wasabi 録音・録画したデータを Wasabi に保存する際は、MultipartUpload という仕組みを利用します。MultipartUpload がエラーとなった場合は、Wasabi に MultipartUpload のデータが残り続ける可能性があります。 エラー発生時点までのデータをファイルとして保存したい場合は、CompleteMultipartUpload の操作を行い、MultipartUpload の処理を完了させる必要があります。 また、MultipartUpload のデータは課金対象となります。エラー発生時点までのデータが不要な場合は、AbortMultipartUpload の操作を行い、MultipartUpload を完全に終了させてください。 なお、Wasabi では、中断された MultipartUpload のデータは[30 日後に自動で削除](https://knowledgebase.wasabi.com/hc/en-us/articles/360030872392-How-does-Wasabi-handle-multipart-uploads)されます。 本記事では、AWS CLI を用いて、CompleteMultipartUpload の操作を行いエラー発生時点までのデータをファイルとして保存する方法と、AbortMultipartUpload の操作を行い MultipartUpload を完全に終了させる方法について説明します。 また、本記事で説明している内容は、変更されている可能性があります。 必要に応じて、Wasabi の公式ドキュメントを参照してください。 [How do I clean up my failed multipart uploads? – Wasabi Knowledge Base](https://knowledgebase.wasabi.com/hc/en-us/articles/360033859411-How-do-I-clean-up-my-failed-multipart-uploads) #### エラー発生時点までのデータをファイルとして保存する方法 - 以下のコマンドを実行し、MultipartUpload のリストを取得します。 `aws s3api list-multipart-uploads --bucket --endpoint-url=` - 実行結果の Uploads の中から、対象の Key と UploadId を選択します。 - 次に、以下のコマンドを実行し、MultipartUpload に含まれる Parts のリストを取得します。 `aws s3api list-parts --bucket --key --upload-id --endpoint-url=` - 実行結果の Parts の中から、PartNumber と ETag の組を全て選択します。 - 次に、以下のコマンドを実行し、エラー発生時点までの MultipartUpload のデータをファイルとして保存します。 `aws s3api complete-multipart-upload --multipart-upload 'Parts=[{ETag="",PartNumber=},{ETag="",PartNumber=}, ... ]' --bucket --key --upload-id --endpoint-url=` なお、`Parts` には、`list-parts` で取得した Parts を全て指定してください。 #### MultipartUpload を完全に終了させる方法 - 以下のコマンドを実行し、MultipartUpload のリストを取得します。 `aws s3api list-multipart-uploads --bucket --endpoint-url=` - 実行結果の Uploads の中から、対象の Key と UploadId を選択します。 - 以下のコマンドを実行し、MultipartUpload を完全に終了させます。これにより、MultipartUpload のデータが削除されます。 `aws s3api abort-multipart-upload --bucket --key --upload-id --endpoint-url=` ## 録音・録画ファイルの修正方法 保存先クラウドストレージとして Amazon S3、または Wasabi を利用し、保存された録音・録画ファイルを再生する際に次のような事象が発生します。 - ファイルの総再生時間が実際の録音・録画時間と大きく異なる - 総再生時間が 1 時間と表示される - シーク機能が適切に機能しない また、ファイルが破損していると表示されることがあります。 これは、録音・録画の処理に起因するもので、メディアデータ自体は正常に保存されています。 録音・録画ファイルを正常に再生するためには、ffmpeg というツールを用いて、記録された録音・録画ファイルに対して以下の処理を実行することで、正常に再生できるようになります。 ```sh ffmpeg -i original.webm -c copy -y fixed.webm ``` なお、ffmpeg のインストール方法については、[ffmpeg の公式ドキュメント](https://ffmpeg.org/download.html)を参照してください。 映像のコーデックにH264を利用している場合は、ffmpegが直接処理できないので次のように拡張子を一旦変更してから処理してください。 ```sh mv original.webm original.mkv ffmpeg -i original.mkv -c copy -y fixed.mkv mv fixed.mkv fixed.webm ``` ## 安全な実装方法 録音・録画機能を安全に利用するための注意事項について説明します。 録音・録画機能を利用する際に扱う識別子として以下があります。 - Channel ID - Publication ID - Publisher ID - Publisher Name これらの 識別子 をクライアントサイドからサーバーサイドアプリケーションへ送信して録音・録画を開始する場合、送信者が 識別子 を差し替えることで意図しない Publication が録音・録画される可能性があります。 これらの 識別子 をクライアントサイドから取得する代わりに次の手法を採用することを検討してください。なお、ここで使用する SkyWay Channel API の詳細な仕様は次の記事を参照してください。 [SkyWay Channel API](/ja/docs/user-guide/channel-api) ### サーバサイドで createChannel を行う createChannel をクライアントサイドではなくサーバーサイドアプリケーションで SkyWay Channel API を用いて実行することで常に正しい Channel ID を得る事ができます。 次のようにして SkyWay Channel API で createChannel を行うことができます。 ```bash curl -X POST https://channel.skyway.ntt.com/v1/json-rpc \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \ -d '{ "jsonrpc": "2.0", "id": "YOUR_RANDOM_UUID", "method": "createChannel", "params": { "name": "YOUR_CHANNEL_NAME" }}' ``` ### Channel Name から Channel ID を取得する サーバーサイドアプリケーションで Channel Name を払い出している場合に有効な手法です。 Channel Name はサーバーサイドアプリケーションで払い出すことができるため、 SkyWay Auth Token でその Channel Name を指定していれば、Channel Name は信用できる情報として扱うことができます。 また、SkyWay Channel API の findChannel で name として Channel Name を指定することで、Channel ID を取得できます。 次のようにして SkyWay Channel API で Channel Name から Channel ID を取得できます。 ```bash curl -X POST https://channel.skyway.ntt.com/v1/json-rpc \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \ -d '{ "jsonrpc": "2.0", "id": "YOUR_RANDOM_UUID", "method": "findChannel", "params": { "name": "YOUR_CHANNEL_NAME" }}'\ | jq -r '.result.channel.id' ``` ### サーバサイドアプリケーションで Publication のリストを取得する 録音・録画対象の Publication の ID を指定する場合はクライアントサイドからサーバーサイドアプリケーションに Publication の ID を送信するのではなく、SkyWay Channel API を用いて信用できる Publication のリストを取得することをおすすめします。 次のようにして SkyWay Channel API で Publication のリストを取得できます。 ```bash curl -X POST https://channel.skyway.ntt.com/v1/json-rpc \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \ -d '{ "jsonrpc": "2.0", "id": "YOUR_RANDOM_UUID", "method": "findChannel", "params": { "name": "YOUR_CHANNEL_NAME" }}' \ | jq -r '.result.channel.publications' ``` ## RecordingSession の管理 RecordingSession が作成されると、以下のいずれかの状況になるまでは録音・録画の処理が継続され、指定されたクラウドストレージに対して録音・録画ファイルの送信が行われます。 - RecordingSession が削除される - RecordingSession は、以下のいずれかのタイミングで削除されます - DeleteRecordingSession API を呼び出す - RecordingSession 作成から 7 日間が経過する - RecordingSession と紐づく Room(Channel)が削除される - 録音・録画対象となっている Publication が unpublish される - Publication は以下のいずれかのタイミングで unpublish されます - 明示的に SDK の unpublish の処理を実行する - 当該の Publication を publish している Member が Room(Channel) から退出する - 当該の Publication が存在する Room(Channel) が削除される - 録音・録画開始から 12 時間経過する これらのうち、サーバーサイドアプリケーションから録音・録画処理を停止させる方法は、DeleteRecordingSession API を利用し、RecordingSession の削除を行うことです。 したがって、DeleteRecordingSession API を利用するための、以下の情報は適切に管理・記録する必要があります。 - Channel ID - RecordingSession ID これらの情報を紛失してしまうと、録音・録画開始から 12 時間が経過するか、Publication が unpublish されるまでは録音・録画の処理が行われ、課金が発生し続けてしまいます。サーバーサイドアプリケーションから録音・録画処理を停止させたい場合は、上記の情報を適切に管理・記録するようにしてください。 ## 各クラウドストレージで発生するオペレーション・リクエスト SkyWay の録音・録画機能では、録音・録画ファイルのアップロード先としてユーザーのクラウドストレージを使用します。したがって、ユーザー側でクラウドストレージのオペレーション・リクエストに伴う料金が発生します。オペレーション・リクエストに伴う料金は、その種類と回数によって決まります。 本記事では、録音・録画機能を利用する際に発生するオペレーション・リクエストの種類と回数について説明します。 ### Google Cloud Storage 以下の条件で録音、及び録画処理を行った場合、以下の回数のオペレーションが実行されます。 なお、オペレーションの回数はあくまでも概算であり、実際の回数は状況によって増える可能性があります。 - 録音 - ビットレート約 32 kbps - 録画 - 1920 x 1080 (FHD) - 30 FPS - ビットレート約 5 Mbps | 録音・録画時間(時間) | 録音に伴うクラス A オペレーション回数 | 録音に伴うクラス B オペレーション回数 | 録画に伴うクラス A オペレーション回数 | 録画に伴うクラス B オペレーション回数 | | -------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------- | | 0.5 | 80 | 70 | 260 | 260 | | 1 | 130 | 130 | 510 | 500 | | 2 | 250 | 260 | 990 | 1000 | | 3 | 370 | 380 | 1480 | 1500 | | 4 | 480 | 500 | 1970 | 1990 | | 5 | 600 | 600 | 2460 | 2460 | | 6 | 710 | 720 | 2950 | 2950 | | 7 | 830 | 840 | 3440 | 3450 | | 8 | 950 | 970 | 3920 | 3940 | | 9 | 1060 | 1060 | 4410 | 4410 | | 10 | 1180 | 1190 | 4900 | 4910 | | 11 | 1300 | 1310 | 5390 | 5400 | | 12 | 1410 | 1430 | 5880 | 5900 | なお、実際に発生する料金は、バケットの設定などによって異なります。 詳しくは、[Google Cloud Storage の料金ページ](https://cloud.google.com/storage/pricing?hl=ja#operations-pricing)を参照してください。 なお、録音・録画処理では、オブジェクトの書き込みだけでなく、削除の操作も行われます。したがって、Standard ストレージ以外のストレージクラスを利用している場合は、早期削除料金が発生します。録音・録画ファイルの保存先バケットには、Standard ストレージを利用することを強くおすすめします。 ### Amazon S3 以下の条件で録音、及び録画処理を行った場合、以下の回数の「PUT、COPY、POST、LIST リクエスト」が実行されます。 なお、リクエストの回数はあくまでも概算であり、実際の回数は状況によって増える可能性があります。 - 録音 - ビットレート約 32 kbps - 録画 - 1920 x 1080 (FHD) - 30 FPS - ビットレート約 5 Mbps | 録音・録画時間(時間) | 録音に伴うリクエスト回数 | 録画に伴うリクエスト回数 | | -------------------- | ------------------------ | ------------------------ | | 0.5 | 4 | 230 | | 1 | 5 | 450 | | 2 | 8 | 890 | | 3 | 11 | 1330 | | 4 | 14 | 1770 | | 5 | 17 | 2210 | | 6 | 20 | 2650 | | 7 | 23 | 3090 | | 8 | 26 | 3530 | | 9 | 29 | 3970 | | 10 | 32 | 4410 | | 11 | 35 | 4850 | | 12 | 38 | 5290 | なお、実際に発生する料金は、バケットの設定などによって異なります。 詳しくは、[Amazon S3 の料金ページ](https://aws.amazon.com/jp/s3/pricing/)を参照してください。 ### Wasabi Wasabi では、リクエスト回数に伴う料金は発生しません。 ## 録音のホワイトノイズを軽減させる方法 Opus DTX を有効化した状態で録音を行うと、録音ファイルにホワイトノイズが含まれる場合があります。 Opus DTX は、音声が無音に近い状態の場合に、送信されるデータ量を大幅に削減するための機能です。 Recording サーバーでは、 Opus DTX が有効になっている場合でも正常にファイルを保存できるようにするための処理を行っています。 これにより、特に音声が無音に近い状態となっていた箇所でホワイトノイズが含まれる可能性があります。 Opus DTX はデフォルトで有効となっているため、Opus DTX を無効化する際は Publication の publish 時に明示的に設定する必要があります。 Opus DTX を無効化する方法は、各 SDK のドキュメントを参照してください。 - [JavaScript SDK](https://javascript-sdk.api-reference.skyway.ntt.com/core/types/CodecParameters.html) - [iOS SDK]() - [Android SDK](https://android-sdk.api-reference.skyway.ntt.com/core/core/com.ntt.skyway.core.content/-codec/-parameters/index.html) ## 録音ファイルの合成方法 複数の音声ファイルを1つのファイルに結合するには、ffmpeg等の外部のツールを利用する必要があります。ここでは、ffmpegの利用方法とその注意点を説明します。 ffmpegのダウンロードは[公式サイト](https://www.ffmpeg.org/download.html)をご参照ください。 なお、詳細な利用方法は[ffmpegの公式ドキュメント](https://www.ffmpeg.org/documentation.html)をご参照ください。 ### ffmpeg を使用した録音ファイルの基本的な合成方法 ffmpegを使用して、2つの音声ファイルを結合するには、以下のようにコマンドを実行します。 ```sh ffmpeg \ -i audio_1.webm \ -i audio_2.webm \ -filter_complex \ "[0:a][1:a]amerge=inputs=2[out]" \ -map "[out]" \ -ac 2 \ output.m4a ``` - `-i` オプションで入力ファイルを指定します。これらのファイルは1番目から順番に`[0]`, `[1]`, ... として参照できます。`[0:a]`とすると、1番目のファイルの音声を指定できます。 - `-filter_complex` オプション以下で、詳細なフィルタを指定します。 - `amerge=inputs=2` は、2つの音声を1つの音声に結合するフィルタを指定します。このフィルタの出力先には `[out]` というタグを付けます。 - `-map` オプションで、`[out]` タグの音声を出力として指定します。 - `-ac` オプションで、出力ファイルのチャンネル数を指定します。この例では、2チャンネルに設定しています。 - `output.m4a` は、出力ファイル名を指定します。 ### Opus の仕様による音ズレの修正方法 FFmpeg などのツールで録音ファイルを合成すると、 Opus の仕様により音ズレが発生する可能性があります。 次のようにリサンプリングを行うことでこの問題を回避できます。 ```sh ffmpeg \ -i audio_1.webm \ -i audio_2.webm \ -i audio_3.webm \ -filter_complex \ "[0:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a0]; \ [1:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a1]; \ [2:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a2]; \ [a0][a1][a2]amerge=inputs=3[out]" \ -map "[out]" \ -ac 2 \ output.m4a ``` - `aresample` フィルタを使用してリサンプリングを行い、音ズレを修正します。修正したファイルにはそれぞれ`[a0]`, `[a1]`, `[a2]`というタグを付けています。 - `amerge=inputs=3`フィルタを使用して修正した3つの音声`[a0]`, `[a1]`, `[a2]`を結合します。 ### 録音ファイルの開始時刻の調整方法 録音開始時刻が異なる複数の音声ファイルを合成する場合、各音声ファイルに調整が必要になります。 録音開始時刻は[metadata](https://skyway.ntt.com/ja/docs/user-guide/recording/recording-tips/#4)を参照することで、`createdAt` というパラメータから取得することができます。 `createdAt` を比較した結果 audio_2.webm の開始時刻が1秒遅れていた場合、以下のようにして調整を行います。 ```sh ffmpeg \ -i audio_1.webm \ -i audio_2.webm \ -filter_complex \ "[0:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a0]; \ [1:a]aresample=async=2000:first_pts=0, aformat=channel_layouts=stereo[a1]; \ [a1]adelay=1000:all=1[a1_delay]; \ [a0][a1_delay]amerge=inputs=2[out]" \ -map "[out]" \ -ac 2 \ output.m4a ``` - `adelay` フィルタを使用して、音声の開始時刻を調整します。この例では、`[a1]` の開始時刻を1秒遅らせ、`[a1_delay]` というタグを付けています。 - `amerge=inputs=2` フィルタを使用して、調整した2つの音声`[a0]`, `[a1_delay]`を結合します。 --- ## ユーザーガイド/録音・録画/Tips Path: user-guide_recording_recording-tips.md # Tips ## 録音・録画ファイルのメタデータの活用 録音・録画機能では保存した録音・録画ファイルと対になるメタデータを提供しています。 メタデータからは録音・録画ファイルの詳しい情報を知ることができます。 メタデータは以下の方法で取得できます。 - GetRecordingSession API のレスポンスの files フィールドを参照する - DeleteRecordingSession API のレスポンスの files フィールドを参照する - クラウドストレージの録音・録画ファイルと同一の階層に保存された同名の json ファイルを参照する メタデータは以下のような構造になっています。 ```ts { name: string; path: string; status: "RECORDING" | "SUCCEEDED" | "FAILED"; errors: { detail: string; occurredAt: string; }[]; type: "AUDIO" | "VIDEO" | "AUDIO_AND_VIDEO"; mimeType: string; createdAt: string; codecs: string[]; duration: number; sessionId?: string; publisherId: string; originPublisher: { name?: string | undefined; id: string; }; publications: { id: string; publisherId: string; originPublisher: { name?: string | undefined; id: string; }; contentType: "AUDIO" | "VIDEO"; }[]; } ``` メタデータのフィールドの詳細について説明します。 | キー名 | フィールドの詳細 | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | name | クラウドストレージにおけるファイル名 | | path | クラウドストレージにおけるパス | | status | 録音・録画ファイルの状態 | | errors | status が FAILED の場合の原因となったエラー | | type | ファイルに含まれるメディアの種類 | | mimeType | ファイルの種別 | | createdAt | ファイルの最初の部分が作られた日時(ISO 8601 形式)。このタイムスタンプを元にして、映像・音声の合成の際に再生タイミングを調整できる。 | | codecs | ファイルに含まれるメディアのコーデックのリスト | | duration | ファイルの再生時間(ミリ秒) | | sessionId | このファイルの session の ID。クラウドストレージ上の json ファイルにのみ含まれる。 | | publisherId | このファイルの元になっている Publication を forwarding している SFU Bot の ID | | originPublisher | このファイルの元になっている Publication を publish している Member の情報 | | publications | このファイルの元になっている Publication のリスト | ## 録画ファイルの解像度パラメータについて 生成された録画ファイル(WebM)において、動画の解像度のパラメータには常に幅 640px 、高さ 360px が設定される仕様となっています。この値は動画の実際の解像度を示すものではなく、固定値であることに注意してください。 ## ステレオ音声の録音 録音・録画機能は、ステレオ音声にも対応しています。 ステレオ音声の録音を行う場合は、LocalAudioStream の作成時、および publish 時に以下のパラメーターの設定が必要となります。 - JavaScript SDK の例 ```js const audio = await SkyWayStreamFactory.createMicrophoneAudioStream({ channelCount: 2, // 一部のブラウザにおいてエコーキャンセラーを有効にした状態でステレオ音声を取得するとモノラルになる問題があるため、エコーキャンセラーを無効にする echoCancellation: false, }); // publish時に、codecCapabilitiesにステレオ音声の設定を追加する const audioPublication = await me.publish(audio, { codecCapabilities: [{ mimeType: 'audio/opus', parameters: { stereo: 1 } }], }); ``` なお、 iOS SDK 、 Android SDK では、ステレオ音声の送信には対応していません。 ## サイマルキャスト利用時の録画ファイルの画質 VideoPublication の publish 時にサイマルキャストの設定をしている場合、録画ファイルには最も高画質の映像が録画されます。 ## 録音・録画ファイルの再生について 録音・録画機能で生成されるWebMファイルの再生について、SkyWayとして動作確認している環境はPC版Google Chromeです。 再生できない場合などの問題が発生した場合は、まずPC版Google Chromeで再生できるかをご確認ください。 なお、Amazon S3、またはWasabiに保存したWebMファイルは、修復処理を行わないとシークできない場合があります。 詳細は[録音・録画ファイルの修正方法](/ja/docs/user-guide/recording/recording-development-guide/#308)をご参照ください。 --- ## ユーザーガイド/録音・録画/既知の問題 Path: user-guide_recording_issues.md # 既知の問題 ## 録音・録画が一時的に中断される Recordingサーバーのエラーにより、進行中の録音・録画が一時的に中断される場合があります。その際の主な事象と対応方法は以下の通りです。 ### **主な事象** - **録音・録画の中断** Recordingサーバーのエラーにより、最大で約10秒間、録音・録画が中断される可能性があります。 - **自動再開とファイル分割** 中断後、録音・録画は自動的に再開されますが、再開後のデータは別のファイルとして保存されます。 --- ### **中断されたファイルの確認方法** - [`GetRecordingSession API`](https://recording.api-reference.skyway.ntt.com/#/paths/~1v1~1channels~1%7BchannelId%7D~1sessions~1%7BsessionId%7D/get) を使用して、該当セッションの情報を取得します。 - 中断されたファイルの`errors`フィールドに、以下のメッセージが格納されます。 `"Internal Server Error: the recording of the file will be interrupted and will resume as a separate file"` - 中断されたファイルの取得方法等については、開発ガイドの[**エラーとなった場合の録音・録画ファイルの取得**](/ja/docs/user-guide/recording/recording-development-guide/#209)をご参照ください。 --- ### **中断されたファイルの取り扱い** - 中断されたファイルのメタデータの`duration`フィールドは **0** となります。 - 正確な再生時間を知るには、録音・録画ファイル自体を直接確認する必要があります。 - 再生時間を確認する際に、録音・録画ファイルの修正が必要になるので開発ガイドの[**録音・録画ファイルの修正方法**](/ja/docs/user-guide/recording/recording-development-guide/#308)をご参照ください。 --- ## ユーザーガイド/AI Noise Canceller/概要 Path: user-guide_ai-noise-canceller_overview.md # 概要 SkyWay AI Noise Canceller は音声ノイズを抑制し、快適な通話を実現するためのライブラリです。 NTT 研究所が開発した高性能なエンジンを使用しており、単調なノイズだけではなくあらゆる種類のノイズを抑制できます。 その他の音声サンプルは、[こちらのページ](https://lp.skyway.ntt.com/noise-cancelling)でご確認いただけます。 ## 動作仕様 AI Noise Canceller は SkyWay SDK と組み合わせて使用します。 マイクから取得した音声に対してクライアントサイドでノイズを抑制し、 SkyWay の通信によりノイズが抑えられたクリアな音声を送信できます。 ![Overview](/media/posts/docs/00_13_01_ai-noise-canceller-overview.png) 本ライブラリは以下の特徴を持っています。 ### モデルタイプの選択 AI Noise Canceller が使用するモデルを選択できます。 モデルタイプは `small` , `medium` , `large` の3種類があります。 モデルのサイズが大きくなるほどノイズ抑制の精度は向上しますが、その一方で、初期化にかかる時間や CPU の消費量が増加します。 ### ノイズ抑制強度の調整 ノイズを抑制する強度を 1 〜 100 の範囲で設定でき、大きな値ほどノイズの抑制効果が強くなります。 一方、小さな値ほど原音に近い音声を保ちやすくなり、臨場感を残しつつノイズによる不快感を抑えられます。 ライブラリのダウンロードにはアプリケーション ID とシークレットキーが必要です。 ## 対応環境 AI Noise Canceller は以下の環境をサポートしています。 | 対象 SDK | サポート環境 | | ----------------- | ---------------------------------------------- | | JavaScript SDK | Chrome / Edge の「安定版の最新 2 メジャーバージョン」 | | iOS SDK | iOS 14, iPadOS 14 以降 | | Android SDK | Android 5.0 Lollipop(API Level 21)以降 | なお、JavaScript 版のスマートフォン上での利用については、 CPU 使用率が大きくなる可能性があるため動作を保証しておりません。iOS/Android スマートフォンで利用する場合は iOS/Android 版をご使用ください。 ## サンプリングレートの仕様 AI Noise Canceller で生成される Audio Stream のサンプリングレートは、通話品質および処理の最適化のために必ず 16kHz に調整されます。 ## ライセンスの注意事項 AI Noise Canceller は OSS ではありません。 ソフトウェアやそのアーカイブに対して以下のような行為を禁止しております。 - 改変 - リバースエンジニアリング - 公開リポジトリへのアップロード 詳細な取り扱い方法については[利用規約](https://skyway.ntt.com/ja/terms/)やライブラリに付属している LICENSE ファイルをご確認ください。 ## リリース - インストール用ツール: https://github.com/skyway/ai-noise-canceller - リリースノート: https://github.com/skyway/ai-noise-canceller/releases --- ## ユーザーガイド/AI Noise Canceller/JavaScript 版/クイックスタート Path: user-guide_ai-noise-canceller_javascript_quickstart.md # 🚀 クイックスタート JavaScript SDK のクイックスタートをベースに、SkyWay JavaScript SDK と AI Noise Canceller ライブラリを使った簡単なアプリケーション `tutorial` を作成します。 ## クイックスタートの環境 以下の環境で実施してください。また最新版のブラウザ利用を推奨します。 - Node: v20 以降 - 対応ブラウザ: Chrome / Edge ## SkyWay を使った通話アプリを作る [環境構築 [NPMを利用する場合]](https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/quickstart/#94) 以降の手順に従って SkyWay を使った通話アプリを作成してください。 完成した通話アプリに以下の変更を加えてください。 - `createMicrophoneAudioAndCameraStream` にオプションを追加する ```js // const { audio, video } = // await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); // を以下に置き換える const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ audio: { echoCancellation: false, } }); ``` 追加後は下記のような実装になっているはずです。 src/index.html ```html SkyWay Tutorial

ID:

room name:
``` src/main.js ```js import { nowInSec, SkyWayAuthToken, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } from "@skyway-sdk/room"; const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId: "ここにアプリケーションIDをペーストしてください", rooms: [ { id: "*", methods: ["create", "close", "updateMetadata"], member: { id: "*", methods: ["publish", "subscribe", "updateMetadata"], }, sfu: { enabled: true, }, }, ], turn: { enabled: true, }, analytics: { enabled: true, }, }, }).encode("ここにシークレットキーをペーストしてください"); (async () => { const localVideo = document.getElementById("local-video"); const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const leaveButton = document.getElementById("leave"); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ audio: { echoCancellation: false, } }); video.attach(localVideo); await localVideo.play(); joinButton.onclick = async () => { if (roomNameInput.value === "") return; const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, }); const me = await room.join(); myId.textContent = me.id; await me.publish(audio, { type: "p2p" }); await me.publish(video, { type: "p2p" }); const subscribeAndAttach = (publication) => { if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement("button"); subscribeButton.id = `subscribe-button-${publication.id}`; subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } newMedia.id = `media-${publication.id}`; stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); leaveButton.onclick = async () => { await me.leave(); await room.dispose(); myId.textContent = ""; buttonArea.replaceChildren(); remoteMediaArea.replaceChildren(); }; room.onStreamUnpublished.add((e) => { document.getElementById(`subscribe-button-${e.publication.id}`)?.remove(); document.getElementById(`media-${e.publication.id}`)?.remove(); }); }; })(); ``` この時点でのディレクトリ構造は次の通りです。 ``` tutorial ├── package-lock.json ├── package.json ├── node_modules │ └── ... └── src ├── index.html └── main.js ``` この記述をした段階で `npm run dev` してみましょう。 SkyWay の Room パッケージで通話アプリが動作しているはずです。 > 本チュートリアルでは、すぐに通信を試していただくためにトークン生成をクライアントアプリケーションで実装しています。 > 本来は SkyWay Auth Token はサーバーアプリケーションで生成してクライアントアプリケーションに渡すようにする必要があります。 > クライアントアプリケーションでトークン生成を行った場合、任意の Room に入ることができるようなトークンを第三者が作成する可能性があります。 ### 音声の確認方法 1 人で動作を確認したい場合、以下の手順で確認できます。 - ブラウザのタブを 2 つ開き、それぞれ http://localhost:1234 にアクセスします - それぞれのタブで同じ room name を入力し join ボタンを押します - どちらも join した後に「{UUID}: audio」というボタンを押すと対向のタブからの音声を受信(subscribe)できます ※ 本チュートリアルでは、 1 人で動作を確認する際に出力音声へ影響を与えないよう、ブラウザのエコーキャンセリング機能を無効化しています。音声確認時は必ずイヤホンをご利用ください。 ## AI Noise Canceller を組み込んだアプリへ改修 ### ライブラリのインストール ライブラリをインストールする前に、環境変数を設定する必要があります。 appId と secret の値を差し替えて、以下のコマンドを実行してください。 ```sh export SKYWAY_APP_ID="your-app-id" export SKYWAY_SECRET_KEY="your-app-secret" ``` 以下のコマンドを実行して、ライブラリをインストールします。 > なお、 Windows のネイティブ環境(PowerShell や CMD など)ではサポートされていません。 > Windows をご利用の場合は、WSL(Windows Subsystem for Linux)上で実行してください。 ```sh curl -fsSL https://raw.githubusercontent.com/skyway/ai-noise-canceller/refs/heads/main/tools/js/install.sh | bash ``` 上記のコマンドにより、`tmp` ディレクトリに最新版バージョンの AI Noise Canceller がダウンロードされ、 `node_modules` に追加されます。 ライブラリのインストールが完了したら、`tmp` 配下にある `tgz` ファイルは削除してしまって構いません。 > 上記で実行するシェルスクリプトは、 端末内で [SkyWay Admin Auth Token](https://skyway.ntt.com/ja/docs/user-guide/authentication/skyway-admin-auth-token/) を生成※しライブラリ取得の認証に利用しています。 > この SkyWay Admin Auth Token は、アプリケーションの管理者(サーバー)用APIを利用する際に必要なトークンであり、本トークンが流出した場合は第三者に管理者(サーバー)用APIを悪用されてしまう恐れがあります。 > 取り扱いには十分に気をつけてください。 > > ※ SkyWay Admin Auth Token の有効期限は1時間です `--download-only` の引数を付与することで、 ライブラリのみの取得も可能です。 ```sh # tmp ディレクトリに保存 curl -fsSL https://raw.githubusercontent.com/skyway/ai-noise-canceller/refs/heads/main/tools/js/install.sh | bash -s -- --download-only --dest="tmp" ``` 取得したライブラリが手元にあれば、パッケージマネージャーを利用して追加できます。 ```sh # npmを用いた場合 npm install ./tmp/skyway-ai-noise-canceller-x.x.x.tgz ``` ### ノイズ抑制の実装 次に src/main.js を編集し、ノイズ抑制の機能を組み込んでいきます。 src/main.js の先頭に以下を記載してライブラリを読み込ませます。 ```js import { SkyWayNoiseCanceller } from "skyway-ai-noise-canceller"; ``` ノイズ抑制の機能を利用する際は認可を追加する必要があります。`SkyWayAuthToken` の `scope` へ以下のように `noiseCancelling` の項目を追加します。 ```js const token = new SkyWayAuthToken({ ... scope: { ... analytics: { enabled: true }, noiseCancelling: { enabled: true } }, }).encode(secret); ``` > AI Noise Canceller は、`version` プロパティが `1` 、 `2` 、 未指定となっている旧バージョンの SkyWay Auth Token ではご利用いただけません。 > 旧バージョンの SkyWay Auth Token をご利用中の方は、 version 3 へ移行してください。 > なお、SkyWay Auth Token version 3 の詳しい仕様を知りたい方は、[SkyWay Auth Token(各種SDK用)](/ja/docs/user-guide/authentication/skyway-auth-token/)のページをご参照ください。 続いて、ブラウザが提供する `noiseSuppression` は不要なので音声取得の設定を変更して `false` にしておきます。 ```js // const { audio, video } = // await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ // audio: { // echoCancellation: false, // } // }); // を以下に置き換える const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ audio: { echoCancellation: false, // 1 人で出力音声を確認する際には出力音声に影響を与えないように false にする noiseSuppression: false, // AI Noise Canceller のノイズ抑制と競合しないように false にする } }); ``` 音声にノイズ抑制をかけて送信するためには以下のように変更します。 ```js // await me.publish(audio, { type: "p2p" }); を以下に置き換える const noiseCanceller = new SkyWayNoiseCanceller(context); // インスタンスの作成 // onReadyイベント発火時に実行する処理を登録 noiseCanceller.onReady(async () => { const noiseCancelledAudio = await noiseCanceller.connect(audio); // ノイズ抑制後の音声をnoiseCancelledAudioとして取得 await me.publish(noiseCancelledAudio, { type: "p2p" }); }); noiseCanceller.init() // インスタンスの初期化処理 ``` `SkyWayNoiseCanceller` インスタンスを生成し、 `init` で抑制処理の初期化をします。初期化が完了すると `onReady` イベントが発火します。 `onReady` イベント発火時にマイクから取得した音声ストリームを `connect` することで、ノイズ抑制が適用された音声ストリームを `noiseCancelledAudio` として受け取ることができます。 また、ノイズ抑制の機能を利用するには SkyWayAuthToken による認証および認可が必要です。SkyWayContext をインスタンス生成時に引き渡してください。 この時点で、 `npm run dev` を実行して音声を確認してみましょう。 確認方法は [先程実装した通話アプリでの確認方法](#240) と同様です。 発話しながらマウスクリックやキータイプを行うことで、これらのノイズが低減していることが確認※できます。次の手順で実装する ON/OFF 切り替え機能を利用するとより処理結果を把握しやすくなります。 ※ ノイズ抑制によって音声の出力に数十 ms の遅延が発生します。この遅延はビデオ・音声通話による遅延と比べて小さいため、通話の実現について大きな影響はございません。 ### ノイズ抑制の ON/OFF 切り替え実装 続いて、ノイズ抑制の ON/OFF を切り替えられるように実装を変更してみましょう。 src/index.html を編集し、room name の入力フォームおよびボタンの下にノイズ抑制 ON/OFF ボタンを作ります。 ```html ...
room name:
noise cancelling: false
... ``` 次に、src/main.js を編集し、ボタン操作の割り当てをするために Element を保持する変数を定義します。 ```js // const leaveButton = document.getElementById("leave"); の下に追加 const noiseCancelButton = document.getElementById("noise-cancel"); const noiseCancelling = document.getElementById("noise-cancelling"); ``` また、先ほど追加したノイズ抑制の実装を以下のように変更します。 ```js // const noiseCanceller = new SkyWayNoiseCanceller(context); // インスタンスの作成 // // onReadyイベント発火時に実行する処理を登録 // noiseCanceller.onReady(async () => { // const noiseCancelledAudio = await noiseCanceller.connect(audio); // ノイズ抑制後の音声をnoiseCancelledAudioとして取得 // await me.publish(noiseCancelledAudio, { type: "p2p" }); // }); // noiseCanceller.init() // インスタンスの初期化処理 // 上記を削除して以下に置き換える let noiseCanceller; // ボタンでインスタンスを生成/破棄できるように `let` で宣言 const myAudioPublication = await me.publish(audio, { type: "p2p" }); // ボタン操作でストリームを差し替えできるよう、変数で保持 noiseCancelButton.onclick = async () => {}; ``` ノイズ抑制が有効になっている場合とそうでない場合で処理を分けていきます。 - 有効にする際: - インスタンスの生成 - onReady イベントの登録 - audioStream の処理と Element の差し替え - ノイズ抑制が有効であることを表示 - インスタンスの初期化処理 - 無効にする際: - インスタンスの破棄 - 元々の audioStream への Element 差し替え - ノイズ抑制が無効であることを表示 ```js ... noiseCancelButton.onclick = async () => { if (noiseCancelling.textContent === "false") { // 有効時の処理 } else { // 無効時の処理 } }; ``` 有効時の処理は今回次のように記述します。 - `SkyWayNoiseCanceller` のインスタンスを生成します。 - `onReady` イベントハンドラーを登録し、ハンドラーの中で `SkyWayNoiseCanceller.connect` を利用し stream にノイズ抑制を適用します - イベントを登録した後に `SkyWayNoiseCanceller.init` で初期化処理を実施します - `SkyWayNoiseCanceller` の詳しい仕様については API リファレンスを参照ください - replaceStream 時に stream が途切れて再生が止まってしまうことがあるため、`releaseOldStream: false` オプションを付与しています - [ReplaceStreamOptions](https://javascript-sdk.api-reference.skyway.ntt.com/room/types/ReplaceStreamOptions.html) ```js // インスタンスの作成 noiseCanceller = new SkyWayNoiseCanceller(context); // onReadyイベント発火時に実行する処理を登録 noiseCanceller.onReady(async () => { // ノイズ抑制のinputとoutput const noiseCancelledAudio = await noiseCanceller.connect(audio); // ブラウザに表示(再生)しているaudioElementの差し替え myAudioPublication.replaceStream(noiseCancelledAudio, { releaseOldStream: false, }); // ノイズ抑制が有効であることを表示 noiseCancelling.textContent = 'true'; }); noiseCanceller.init(); ``` 無効時の処理は今回次のように記述します。 `SkyWayNoiseCanceller.dispose` でインスタンスを破棄します。 ```js myAudioPublication.replaceStream(audio, { releaseOldStream: false, }); // インスタンスの破棄 noiseCanceller.dispose(); // ノイズ抑制が無効であることを表示 noiseCancelling.textContent = "false"; ``` これでボタンによる ON/OFF の切り替えが実装できました。 ### ノイズ抑制の強度変更 次に、ノイズ抑制の強度を変更できるように実装を変更します。 src/index.html を編集し、強度変更に必要なスライダなどを追加します。 ```html ...
noise cancelling: false
... ``` 強度を変更するために index.html に設定した Element を保持する変数を src/main.js に定義します。 ```js // const noiseCancelling = document.getElementById("noise-cancelling"); の下に追加 const noiseCancelController = document.getElementById( "noise-cancel-controller" ); const noiseCancelStrengthRange = document.getElementById( "noise-cancel-strength-range" ); const noiseCancelStrengthValue = document.getElementById( "noise-cancel-strength-value" ); ``` ノイズ抑制が有効になっている場合のみ強度変更 UI が表示されるように変更します。 ```js // noiseCancelling.textContent = "true"; の下に以下を追加 noiseCancelController.style.display = "block"; ``` ```js // noiseCancelling.textContent = "false";の下に以下を追加 noiseCancelController.style.display = "none"; ``` スライダが操作された際に `SkyWayNoiseCanceller.changeStrength` でノイズ抑制の強度を変更できるようにします。 ```js // noiseCancelButton.onclick = async () => {...}; の後に記述 noiseCancelStrengthRange.oninput = (e) => { const strength = Number(e.target.value); // ノイズ抑制の強度変更 noiseCanceller.changeStrength(strength); noiseCancelStrengthValue.textContent = strength.toString(); }; ``` 変更が完了したら、もう一度 `npm run dev` でアプリを立ち上げて動作を確認してみましょう。 「noise cancel」ボタンを押すことでノイズ抑制の ON/OFF を切り替えることができます。処理が有効になるとスライダが表示され、このスライダを操作することでノイズ抑制の強度を変更できます。処理強度を変更しながら出力音声を確認するとノイズ抑制の効果がわかりやすくなります。 はじめに一度だけノイズ抑制強度を変更すれば良い場合、init の引数から設定する※ことができます。 詳しい使い方は API リファレンスをご参照ください。 ### 退出時にノイズ抑制を停止 room の退出時にノイズ抑制の停止処理を追加します。 ```js leaveButton.onclick = async () => { if (noiseCanceller) { noiseCanceller.dispose(); } // await me.leave(); の上に追加 ... // remoteMediaArea.replaceChildren(); の下に以下を追加 noiseCancelling.textContent = 'false'; noiseCancelStrengthRange.value = '100'; noiseCancelStrengthValue.textContent = '100'; noiseCancelController.style.display = 'none'; } ``` ### (推奨) エラーハンドリングの登録 ノイズ抑制の処理に失敗し、内部で回復不可能な状態になると `onFatalError` に渡されたコールバック関数が発火します。 このコールバック関数として、ノイズ抑制適用前の stream に戻す処理を登録します。 ```js // noiseCanceller.onReady(async () => { // ... // noiseCancelController.style.display = 'block'; // }); // onReady の後に onFatalError の処理を追加する noiseCanceller.onFatalError((event) => { const error = event.detail; if (error.type === 'ProcessError') { myAudioPublication.replaceStream(audio, { releaseOldStream: false, }); noiseCancelling.textContent = 'false'; noiseCancelController.style.display = 'none'; } }); ``` `SkyWayNoiseCanceller.connect` を利用してノイズ抑制を適用している場合、 回復不可能なエラーが発生すると音声ストリームを返さなくなり通話を継続できません。 ノイズ抑制失敗時に通話継続させるため、適用前の stream に戻してあげる必要があります。 ## 完成したコード src/index.html ```html SkyWay Tutorial

ID:

room name:
noise cancelling: false
``` src/main.js ```js import { nowInSec, SkyWayAuthToken, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } from "@skyway-sdk/room"; import { SkyWayNoiseCanceller } from "skyway-ai-noise-canceller"; const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId: "ここにアプリケーションIDをペーストしてください", rooms: [ { id: "*", methods: ["create", "close", "updateMetadata"], member: { id: "*", methods: ["publish", "subscribe", "updateMetadata"], }, sfu: { enabled: true, }, }, ], turn: { enabled: true, }, analytics: { enabled: true, }, noiseCancelling: { enabled: true }, }, }).encode("ここにシークレットキーをペーストしてください"); (async () => { const localVideo = document.getElementById("local-video"); const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const leaveButton = document.getElementById("leave"); // ノイズ抑制用のElement const noiseCancelButton = document.getElementById("noise-cancel"); const noiseCancelling = document.getElementById("noise-cancelling"); const noiseCancelController = document.getElementById( "noise-cancel-controller" ); const noiseCancelStrengthRange = document.getElementById( "noise-cancel-strength-range" ); const noiseCancelStrengthValue = document.getElementById( "noise-cancel-strength-value" ); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ audio: { echoCancellation: false, noiseSuppression: false, // SkyWayNoiseCanceller のノイズ抑制と競合しないように false にする } }); video.attach(localVideo); await localVideo.play(); joinButton.onclick = async () => { if (roomNameInput.value === "") { return; } const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, }); const me = await room.join(); myId.textContent = me.id; await me.publish(video, { type: "p2p" }); // インスタンスを保持する変数 let noiseCanceller; const myAudioPublication = await me.publish(audio, { type: "p2p" }); noiseCancelButton.onclick = async () => { if (noiseCancelling.textContent === "false") { // インスタンスの作成 noiseCanceller = new SkyWayNoiseCanceller(context); // init() 処理完了時(onReadyイベント発火時)に実行する処理を登録 noiseCanceller.onReady(async () => { // ノイズ抑制のinputとoutput const noiseCancelledAudio = await noiseCanceller.connect(audio); // ブラウザに表示(再生)しているaudioElementの差し替え myAudioPublication.replaceStream(noiseCancelledAudio, { releaseOldStream: false, }); // 各種変数の代入 noiseCancelling.textContent = 'true'; noiseCancelController.style.display = 'block'; }); noiseCanceller.onFatalError((event) => { const error = event.detail; // ノイズ抑制実行中に発生したエラーの場合 if (error.type === 'ProcessError') { // ノイズ抑制適用前の audio に戻す myAudioPublication.replaceStream(audio, { releaseOldStream: false, }); // 各種変数の代入 noiseCancelling.textContent = 'false'; noiseCancelController.style.display = 'none'; } }); // インスタンスの初期化処理 noiseCanceller.init(); } else { myAudioPublication.replaceStream(audio, { releaseOldStream: false, }); // インスタンスの破棄 noiseCanceller.dispose(); noiseCancelling.textContent = "false"; noiseCancelController.style.display = "none"; } }; noiseCancelStrengthRange.oninput = (e) => { const strength = Number(e.target.value); // ノイズ抑制強度の変更 noiseCanceller.changeStrength(strength); noiseCancelStrengthValue.textContent = strength.toString(); }; const subscribeAndAttach = (publication) => { if (publication.publisher.id === me.id) { return; } const subscribeButton = document.createElement("button"); subscribeButton.id = `subscribe-button-${publication.id}`; subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } newMedia.id = `media-${publication.id}`; stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); leaveButton.onclick = async () => { if (noiseCanceller) { noiseCanceller.dispose(); } await me.leave(); await room.dispose(); myId.textContent = ""; buttonArea.replaceChildren(); remoteMediaArea.replaceChildren(); noiseCancelling.textContent = 'false'; noiseCancelStrengthRange.value = '100'; noiseCancelStrengthValue.textContent = '100'; noiseCancelController.style.display = 'none'; }; room.onStreamUnpublished.add((e) => { document.getElementById(`subscribe-button-${e.publication.id}`)?.remove(); document.getElementById(`media-${e.publication.id}`)?.remove(); }); }; })(); ``` --- ## ユーザーガイド/AI Noise Canceller/JavaScript 版/開発ガイド Path: user-guide_ai-noise-canceller_javascript_development-guide.md # 開発ガイド ## 未対応ブラウザ利用時のハンドリング AI Noise Canceller は、現在スマートフォンを除く Chrome / Edge でのみ動作します。 それ以外のブラウザはサポートしておらず、動作保証はありません。そのため、動作するかどうかは、`isSupported` メソッドを使用して判定してください。 ```typescript if (SkyWayNoiseCanceller.isSupported() === false) { console.warn("SkyWay Noise Canceller is not supported"); return; } ``` ## エラーハンドリング AI Noise Canceller は、短時間のネットワーク切断時には自動的に再接続処理を行います。しかし、長時間のネットワーク切断など回復不可能なエラーが発生した場合には、`onFatalError` イベントが発火します。このイベントが発火した場合、一時的に AI Noise Canceller が利用できなくなる可能性が高いため、元の Audio Stream を利用するなど、アプリケーション側での対応が必要です。 ```typescript // 元のStream const audio = await SkyWayStreamFactory.createMicrophoneAudioStream({ echoCancellation: false, }); noiseCanceller.onFatalError((event: CustomEvent) => { const error = event.detail; // connect中に発生するのはProcessErrorのみ if (error.type === "ProcessError") { myAudioPublication.replaceStream(audio, { releaseOldStream: false, }); } }); ``` ## ブラウザのノイズ抑制機能の無効化 `getUserMedia()` や `SkyWayStreamFactory.createMicrophoneAudioStream` を使用して Audio Stream を取得する場合、 `noiseSuppression` を設定できます。 しかし、AI Noise Canceller を利用する際は音声ノイズを抑制する機能が競合するため、 `noiseSuppression` の設定を `false` にすることを推奨します。 ```typescript const audio = await SkyWayStreamFactory.createMicrophoneAudioStream({ noiseSuppression: false, // AI Noise Cancellerと競合しないようにfalseに設定 }); ``` また、開発中に自身で動作確認するようなユースケースで Publish した音声を Subscribe してループバックする場合は、`echoCancellation` も `false` に設定すると音声が聞き取りやすくなります。 ```typescript const audio = await SkyWayStreamFactory.createMicrophoneAudioStream({ echoCancellation: false, // ループバック用にfalseに設定 noiseSuppression: false, }); ``` ## 課金対象期間について AI Noise Canceller の課金対象期間は、`connect` を呼び出してから `dispose` を呼び出すまでの期間です。 ![Billing](/media/posts/docs/00_13_03_ai-noise-canceller-development-guide-billing.png) 現在 `mute/unmute` の機能は提供していないため、一時的に無効化する場合でも必ず以下のように `dispose` を呼び出してリソースを解放してください。 ```typescript // replaceStreamで適用前のAudio Streamにreplaceする myAudioPublication.replaceStream(audio, { releaseOldStream: false, }); noiseCanceller.dispose(); ``` また、`onFatalError` イベントが発火した際は内部で自動的に `dispose` が呼び出され課金集計処理が止まります。 ## ノイズ抑制強度(strength)の調整 `strength` はノイズ抑制の強度を設定するための値です。インスタンス生成時に設定できるほか、`changeStrength` メソッドを使用して任意のタイミングでの変更も可能です。 ```typescript // インスタンス生成時に設定する場合 noiseCanceller = new SkyWayNoiseCanceller(context, { strength: 80 }); // changeStrengthで変更する場合 const strength = 80; noiseCanceller.changeStrength(strength); ``` 何も指定しなかった場合は 100 が設定されます。 ## モデルタイプについて モデルタイプは `small` , `medium` , `large` の 3 種類があり、インスタンス生成時に設定できます。 何も指定しなかった場合は `small` が設定されます。 `small` が最も処理負荷を抑えられるため、様々なデバイスで活用されるユースケースでは `small` の利用を推奨します。 モデルタイプを変更したい場合は、インスタンスを `dispose` して新しいインスタンスを作成してください。 ```typescript // 初期化時にモデルを'medium'に設定 noiseCanceller = new SkyWayNoiseCanceller(context, { modelType: "medium" }); ``` ## AI Noise Canceller 利用時の SkyWay Auth Token に関する注意 AI Noise Canceller は、`version` プロパティが `1` 、 `2` 、 未指定となっている旧バージョンの SkyWay Auth Token ではご利用いただけません。旧バージョンの SkyWay Auth Token をご利用中の方は、 version 3 へ移行してください。なお、SkyWay Auth Token version 3 の詳しい仕様を知りたい方は、[SkyWay Auth Token(各種SDK用)](/ja/docs/user-guide/authentication/skyway-auth-token/)のページをご参照ください。 ## 開発用リポジトリでの利用 以下のようなユースケースにより開発用のリポジトリで本ライブラリを利用したい場合、インストール用スクリプトを活用したワークフローをご使用ください。 - 公開リポジトリで開発しているソフトウェアで利用したい場合 - 常に最新版のライブラリを用いてテストを実行したい場合 --- ## ユーザーガイド/AI Noise Canceller/iOS 版/クイックスタート Path: user-guide_ai-noise-canceller_ios_quickstart.md # 🚀 クイックスタート iOS SDK のクイックスタートをベースに、SkyWay iOS SDK と AI Noise Canceller ライブラリを使った簡単なアプリケーション `tutorial` を作成します。 ## 動作環境 - 対応 OS バージョン: iOS 14, iPadOS 14 以降 ## 通話アプリの作成 [クイックスタート](/ja/docs/user-guide/ios-sdk/quickstart) の手順に従って SkyWay を使った通話アプリを作成してください。 > 重要: AI Noise Canceller を利用するには SkyWay iOS SDK v3.0.0 以降 が必要です。SDK をインストールする際はバージョンを必ず確認してください。 ### 音声の確認方法 1 人で動作を確認する際に出力音声へ影響を与えないよう、イヤホンの利用を推奨します。 ## AI Noise Canceller を組み込んだアプリへ改修 ### ライブラリのインストール #### 1. 環境変数の設定 ライブラリをインストールする前に、環境変数を設定する必要があります。 appId と secret の値を差し替えて、以下のコマンドを実行してください。 ```sh export SKYWAY_APP_ID="your-app-id" export SKYWAY_SECRET_KEY="your-app-secret" ``` #### 2. ダウンロードスクリプトの実行 以下のコマンドを実行して、ライブラリをダウンロードします。 ```sh curl -fsSL https://raw.githubusercontent.com/skyway/ai-noise-canceller/refs/heads/main/tools/ios/download.sh | bash ``` 上記のコマンドにより、`tmp` ディレクトリに最新版バージョンの AI Noise Canceller がダウンロードされます。 環境に合わせて Project に追加してください。 > 上記で実行するシェルスクリプトは、 端末内で [SkyWay Admin Auth Token](/ja/docs/user-guide/authentication/skyway-admin-auth-token/) を生成※しライブラリ取得の認証に利用しています。 > この SkyWay Admin Auth Token は、アプリケーションの管理者(サーバー)用APIを利用する際に必要なトークンであり、本トークンが流出した場合は第三者に管理者(サーバー)用APIを悪用されてしまう恐れがあります。 > 取り扱いには十分に気をつけてください。 > > ※ SkyWay Admin Auth Token の有効期限は1時間です #### 3. Swift Package Manager での導入 Swift Package Manager を用いる場合、Xcode から Project を選択し、 Package Dependencies を選択します。 左下 `+` ボタンからパッケージ検索のモーダルを表示させ、画面下の `Add Local...` を押下します。 ダウンロードした skyway-ai-noise-canceller ディレクトリを選択し Add Package を押下します。 SkyWayAINoiseCanceller と voiceomnia を追加する Target を設定し、 Add Package を押下します。 ### ノイズ抑制のセットアップ #### `SkyWayAuthToken` の設定 ノイズ抑制の機能を利用する際は認可を追加する必要があります。`SkyWayAuthToken` の `scope` へ以下のように `noiseCancelling` の項目を追加します。 ```js const token = new SkyWayAuthToken({ ... scope: { ... analytics: { enabled: true }, noiseCancelling: { enabled: true } }, }).encode(secret); ``` #### サンプリングレートの設定 ノイズ抑制では、通話品質および処理の最適化のためにサンプリングレートは 16kHz にすることを推奨します。 `ViewController.swift` にて、サンプリングレートを 16kHz に設定します。 実行タイミングは音声疎通の前となりますが、今回は `Context` のセットアップ直後に設定します。 ```swift try? await Context.setupForDev(withAppId: appId, secretKey: secretKey, options: contextOpt) AudioSettings.setPreferredSampleRate(16000.0) let roomInit: Room.InitOptions = .init() ``` サンプリングレートについて、詳しくは[開発ガイド](/ja/docs/user-guide/ai-noise-canceller/ios/development-guide)を参照してください。 #### セットアップの実装 `SkyWayAINoiseCanceller` をインポートします。 ```swift import SkyWayAINoiseCanceller ``` `Context` のセットアップが成功した後に、`SkyWayNoiseCanceller.shared.setup` によりノイズ抑制処理のセットアップを行います。 `SkyWayNoiseCanceller` はシングルトンインスタンスで管理しています。 ```swift _ = await SkyWayNoiseCanceller.shared.setup() ``` セットアップ後は `SkyWayNoiseCanceller.shared.start() / stop()` でノイズ抑制の ON/OFF を切り替えられます。 ### ノイズ抑制の ON/OFF 切り替え `Main.storyboard` にて、ノイズ抑制の ON/OFF ボタンを追加します。 コードに接続し、ノイズ抑制の ON/OFF 関数を実装します。 ```swift @IBOutlet weak var startButton: UIButton! @IBOutlet weak var stopButton: UIButton! ``` ```swift @IBAction func startButtonTapped(_ sender: UIButton) { _ = SkyWayNoiseCanceller.shared.start() } @IBAction func stopButtonTapped(_ sender: UIButton) { _ = SkyWayNoiseCanceller.shared.stop() } ``` アプリをビルドし、前述[音声の確認方法]に従って、発話しながらマウスクリックやキータイプを行うことで、ノイズ付きの音声を確認できます。 `startButton` および `stopButton` により、聞き比べながらノイズが低減されることが確認できます。 ### ノイズ抑制の強度変更 次に、ノイズ抑制の強度を変更できるように実装を変更します。 `Main.storyboard` にて、ノイズ抑制の強度を変更するためのスライダを追加します。 最小値は1、最大値は100に設定し、初期値は100にします。 コードに接続し、ノイズ抑制の強度変更関数を実装します。 ```swift @IBOutlet weak var strengthSlider: UISlider! ``` ```swift @IBAction func strengthSliderValueChanged(_ sender: UISlider) { let value = sender.value _ = SkyWayNoiseCanceller.shared.changeStrength(Int(value)) } ``` 変更が完了したら、もう一度アプリをビルドし、端末にインストールして動作を確認してみましょう。 スライダを操作することでノイズ抑制の強度を変更できます。処理強度を変更しながら出力音声を確認するとノイズ抑制の効果がわかりやすくなります。 はじめに一度だけノイズ抑制強度を変更すれば良い場合、init の引数から設定できます。 詳しい使い方は [API リファレンス](https://ios-sdk.api-reference.skyway.ntt.com/ai-noise-canceller/)をご参照ください。 ### ノイズ抑制の解除(Dispose) Room 退出時や音声の Unpublish 時など、ノイズ抑制が不要になったタイミングで `SkyWayNoiseCanceller.shared.dispose()` を呼び出してください。 ```swift @IBOutlet weak var disposeButton: UIButton! ``` ```swift @IBAction func disposeButtonTapped(_ sender: UIButton) { SkyWayNoiseCanceller.shared.dispose() } ``` ## Tutorial完成コード ```swift import UIKit import SkyWayRoom class ViewController: UIViewController { @IBOutlet weak var localView: CameraPreviewView! @IBOutlet weak var remoteView: VideoView! @IBOutlet weak var startButton: UIButton! @IBOutlet weak var stopButton: UIButton! @IBOutlet weak var strengthSlider: UISlider! @IBOutlet weak var disposeButton: UIButton! override func viewDidLoad() { super.viewDidLoad() Task { let appId = "アプリケーションIDを入力してください" let secretKey = "シークレットキーを入力してください" // SkyWayのセットアップ let contextOpt: ContextOptions = .init() contextOpt.logLevel = .trace try? await Context.setupForDev(withAppId: appId, secretKey: secretKey, options: contextOpt) AudioSettings.setPreferredSampleRate(16000.0) let roomInit: Room.InitOptions = .init() guard let room: Room = try? await .create(with: roomInit) else { print("[Tutorial] Creating room failed.") return } let memberInit: Room.MemberInitOptions = .init() memberInit.name = "Alice" // Memberに名前を付けることができます guard let member = try? await room.join(with: memberInit) else { print("[Tutorial] Join failed.") return } // AudioStreamの作成 let audioSource: MicrophoneAudioSource = .init() let audioStream = audioSource.createStream() let audioPublicationOptions: RoomPublicationOptions = .init() audioPublicationOptions.type = .SFU guard let audioPublication = try? await member.publish(audioStream, options: audioPublicationOptions) else { print("[Tutorial] Publishing failed.") return } // Audioの場合、subscribeした時から音声が流れます guard let _ = try? await member.subscribe(publicationId: audioPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing audio stream successfully.") // Cameraの設定 guard let camera = CameraVideoSource.supportedCameras().first(where: { $0.position == .front }) else { print("Supported cameras is not found."); return } // キャプチャーの開始 try! await CameraVideoSource.shared().startCapturing(with: camera, options: nil) // Previewの描画 CameraVideoSource.shared().attach(localView) // VideoStreamの作成 let localVideoStream = CameraVideoSource.shared().createStream() let videoPublicationOptions: RoomPublicationOptions = .init() videoPublicationOptions.type = .SFU guard let videoPublication = try? await member.publish(localVideoStream, options: videoPublicationOptions) else { print("[Tutorial] Publishing failed.") return } guard let videoSubscription = try? await member.subscribe(publicationId: videoPublication.id, options: nil) else { print("[Tutorial] Subscribing failed.") return } print("🎉Subscribing video stream successfully.") let remoteVideoStream = videoSubscription.stream as! RemoteVideoStream remoteVideoStream.attach(remoteView) } } @IBAction func startButtonTapped(_ sender: UIButton) { _ = SkyWayNoiseCanceller.shared.start() } @IBAction func stopButtonTapped(_ sender: UIButton) { _ = SkyWayNoiseCanceller.shared.stop() } @IBAction func strengthSliderValueChanged(_ sender: UISlider) { let value = sender.value _ = SkyWayNoiseCanceller.shared.changeStrength(Int(value)) } @IBAction func disposeButtonTapped(_ sender: UIButton) { SkyWayNoiseCanceller.shared.dispose() } } ``` ## おわりに 以上がノイズ抑制機能の基本的な使い方となります。 さらに詳しい API の説明や注意事項は [API リファレンス](https://ios-sdk.api-reference.skyway.ntt.com/ai-noise-canceller/)を参照してください。 --- ## ユーザーガイド/AI Noise Canceller/iOS 版/開発ガイド Path: user-guide_ai-noise-canceller_ios_development-guide.md # 開発ガイド ## エラーハンドリング AI Noise Canceller は、短時間のネットワーク切断時には自動的に再接続処理を行います。しかし、長時間のネットワーク切断など回復不可能なエラーが発生した場合や利用料制限に達した場合には、`fatalErrorHandler` が呼び出されます。この場合、 AI Noise Canceller は終了し、内部リソースは解放されています。再度 AI Noise Canceller を利用する場合は、 `SkyWayNoiseCanceller.setup` を呼び出す必要があります。 ```swift SkyWayNoiseCanceller.shared.fatalErrorHandler = { errorCode in // 利用量制限の場合は何もしない if errorCode == .quotaExceeded { return } // 再度AI Noise Cancellerを利用する場合 Task { let isSetup = await SkyWayNoiseCanceller.shared.setup() } } ``` ## 課金対象期間について AI Noise Canceller の課金対象期間は、ノイズ抑制機能の開始(`SkyWayNoiseCanceller.start` の呼び出し)から 終了(`SkyWayNoiseCanceller.stop`の呼び出し)までの期間です。 ![Billing](/media/posts/docs/00_13_03_02_ai-noise-canceller-development-guide-billing-start-stop.png) ## ノイズ抑制強度(strength)の調整 `strength` はノイズ抑制の強度を設定するための値です。`SkyWayNoiseCanceller.setup` 時に設定できるほか、`changeStrength` メソッドを使用して任意のタイミングでの変更も可能です。 何も指定しなかった場合は 100 が設定されます。 ```swift // セットアップ時に設定する場合 let isSetup = await SkyWayNoiseCanceller.shared.setup(options: .init(strength: 90)) // changeStrengthで変更する場合(SkyWayNoiseCanceller.setup以降に呼び出してください) let isChanged = SkyWayNoiseCanceller.shared.changeStrength(80) ``` ## モデルタイプについて モデルタイプは `small` , `medium` , `large` の 3 種類があり、セットアップ時に設定できます。 何も指定しなかった場合は `small` が設定されます。 `small` が最も処理負荷を抑えられるため、様々なデバイスで活用されるユースケースでは `small` の利用を推奨します。 モデルタイプを変更したい場合は、`SkyWayNoiseCanceller.dispose` して `SkyWayNoiseCanceller.setup` より設定してください。 ```swift // 初期化時にモデルを'medium'に設定 let isSetup = await SkyWayNoiseCanceller.shared.setup(options: .init(modelType: .medium)) ``` ## サンプリングレートについて 通話品質および処理の最適化のためにサンプリングレートは 16kHz にすることを推奨します。 サンプリングレートを変更せずとも AI Noise Canceller を利用することは可能ですが、ノイズ抑制の効果が低下する可能性があります。 音声の Publish/Subscribe の前に、サンプリングレートを設定してください。 ただし、音声デバイスによっては実際のサンプリングレートの変更ができない場合があることに注意してください。 ```swift // サンプリングレートを 16kHz に設定 AudioSettings.setPreferredSampleRate(16000.0) ``` --- ## ユーザーガイド/AI Noise Canceller/Android 版/クイックスタート Path: user-guide_ai-noise-canceller_android_quickstart.md # 🚀 クイックスタート Android SDK のクイックスタートをベースに、SkyWay Android SDK と AI Noise Canceller ライブラリを使った簡単なアプリケーション `tutorial` を作成します。 ## 動作環境 - 対応 OS バージョン: Android 5.0 (API 21) 以降 ## 通話アプリの作成 [クイックスタート(JetPack Compose)](https://skyway.ntt.com/ja/docs/user-guide/android-sdk/quickstart-compose/) の手順に従って SkyWay を使った通話アプリを作成してください。 > 重要: AI Noise Canceller を利用するには SkyWay Android SDK v3.2.0 以降 が必要です。SDK をインストールする際はバージョンを必ず確認してください。 ### 音声の確認方法 1 人で動作を確認したい場合、以下の手順で確認できます。 - Android 端末を2台(実機 2 台または実機 + エミュレータ)用意し、先ほど作成した通話アプリをインストールします。 - 両端末でアプリを起動し、同じ Room Name を入力して join ボタンを押します。 - join に成功したら、お互いのvideo/audioが自動的にpublish/subscribeされるため、音声を確認することができます。 ※ 1 人で動作を確認する際に出力音声へ影響を与えないよう、イヤホンの利用を推奨します。 ## AI Noise Canceller を組み込んだアプリへ改修 ### ライブラリのインストール 1. 環境変数の設定 ライブラリをインストールする前に、環境変数を設定する必要があります。 appId と secret の値を差し替えて、以下のコマンドを実行してください。 ```sh export SKYWAY_APP_ID="your-app-id" export SKYWAY_SECRET_KEY="your-app-secret" ``` 2. インストールスクリプトの実行 以下のコマンドを実行して、ライブラリをインストールします。 ```sh curl -fsSL https://raw.githubusercontent.com/skyway/ai-noise-canceller/refs/heads/main/tools/android/install.sh | bash ``` > なお、 Windows のネイティブ環境(PowerShell や CMD など)ではサポートされていません。 > Windows をご利用の場合は、WSL(Windows Subsystem for Linux)上で実行してください。 > > また、スクリプトを実行するには、Apache Maven が必要です。 > macOS(Homebrew使用)の場合は、`brew install maven` でインストールできます。 > その他の OS でのインストール方法は、[Apache Maven Project](https://maven.apache.org/install.html)をご確認ください。 > インストール後、 `mvn -v` でバージョンを確認できます。 > > 上記で実行するシェルスクリプトは、 端末内で [SkyWay Admin Auth Token](https://skyway.ntt.com/ja/docs/user-guide/authentication/skyway-admin-auth-token/) を生成※しライブラリ取得の認証に利用しています。 > この SkyWay Admin Auth Token は、アプリケーションの管理者(サーバー)用APIを利用する際に必要なトークンであり、本トークンが流出した場合は第三者に管理者(サーバー)用APIを悪用されてしまう恐れがあります。 > 取り扱いには十分に気をつけてください。 > > ※ SkyWay Admin Auth Token の有効期限は1時間です。 3. Mavenローカルリポジトリの参照設定 スクリプト実行後、カレントディレクトリ直下の tmp ディレクトリに最新版の AI Noise Canceller がダウンロードされ、Maven ローカルリポジトリに追加されます。 導入先のプロジェクトを開き、`settings.gradle.kts` にて Maven ローカルリポジトリを参照するよう設定を追加します。 `setting.gradle.kts`: ```java dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositories { google() mavenCentral() mavenLocal() // 追加 } } ``` 4. 依存関係の追加 `libs.version.toml` とアプリモジュールの `build.gradle.kts` に次の内容を追加してください。 `lib.version.toml`: ```java [versions] skywayNoiseCanceller = "x.x.x" # 最新版に置き換え # リリース情報: https://github.com/skyway/ai-noise-canceller/releases [libraries] # 追加 skyway-noise-canceller = { group = "com.ntt.skyway.audio", name = "ai-noise-canceller", version.ref = "skywayNoiseCanceller" } ``` `app/build.gradle.kts`: ```java dependencies { ... // 追加 implementation(libs.skyway.noise.canceller) } ``` ### ノイズ抑制のセットアップ `MainViewModel.kt` にて、`SkyWayContext` のセットアップが成功した後に、`SkyWayNoiseCanceller.setup` によりノイズ抑制処理のセットアップを行います。 ```kotlin class MainViewModel(): ViewModel() { ... fun joinAndPublish(roomName: String) { viewModelScope.launch() { val result = SkyWayContext.setupForDev(applicationContext!!, appId, secretKey, option) if (result) { Log.d("App", "Setup succeed") } // 追加 val ncReady = SkyWayNoiseCanceller.setup(applicationContext!!) if (ncReady) { Log.d("App", "SkyWayNoiseCanceller setup succeed") } ... } } // end of joinAndPublish } // end of MainViewModel ``` セットアップ後は `SkyWayNoiseCanceller.start() / stop()` でノイズ抑制の ON/OFF を切り替えられます。 ### ノイズ抑制の ON/OFF 切り替え `MainViewModel.kt` にて、ノイズ抑制の ON/OFF 関数を実装します。 ```kotlin class MainViewModel(): ViewModel() { ... //追加 fun startNoiseCancelling() { SkyWayNoiseCanceller.start() } fun stopNoiseCancelling() { SkyWayNoiseCanceller.stop() } } // end of MainViewModel ``` `MainScreen.kt` にて、ノイズ抑制の ON/OFF ボタンを追加します。 ```kotlin @Composable fun MainScreen( mainViewModel: MainViewModel, modifier: Modifier ) { ... Column(modifier = Modifier.fillMaxSize()) { ... //追加 Row( horizontalArrangement = Arrangement.Center, modifier = Modifier .fillMaxWidth() ) { Button( onClick = { mainViewModel.startNoiseCancelling() } ) { Text("Start NoiseCancelling") } Button( onClick = { mainViewModel.stopNoiseCancelling() } ) { Text("Stop NoiseCancelling") } } } // end of Column } // end of MainScreen ``` アプリをビルドし、前述[音声の確認方法]に従って、発話しながらマウスクリックやキータイプを行うことで、ノイズ付きの音声を確認することができます。 `Start NoiseCancelling` および `Stop NoiseCancelling` ボタンにより、聞き比べながらノイズが低減されることが確認できます。 ### ノイズ抑制の強度変更 次に、ノイズ抑制の強度を変更できるように実装を変更します。 `MainViewModel.kt` にて、ノイズ抑制の強度変更関数を実装します。 ```kotlin class MainViewModel(): ViewModel() { ... //追加 fun changeStrength(strength: Int) { // 1-100の範囲で強度を設定 SkyWayNoiseCanceller.changeStrength(strength) } } // end of MainViewModel ``` `MainScreen.kt` にて、ノイズ抑制の強度を変えられるように修正します。 ```kotlin @Composable fun MainScreen( mainViewModel: MainViewModel, modifier: Modifier ) { ... //追加 var ncStrength by remember { mutableStateOf(100) } Column(modifier = Modifier.fillMaxSize()) { ... //追加 Row( horizontalArrangement = Arrangement.Center, modifier = Modifier .fillMaxWidth() ) { TextField( value = ncStrength.toString(), onValueChange = { ncStrength = it.toIntOrNull() ?: 1 }, modifier = Modifier.weight(1f), label = { Text("NC Strength: 1~100") } ) Spacer(modifier = Modifier.width(8.dp)) Button(onClick = { mainViewModel.changeStrength(ncStrength) }, modifier = Modifier.weight(1f)) { Text(text = "Change Strength") } } } // end of Column } // end of MainScreen ``` 変更が完了したら、もう一度アプリをビルドし、端末にインストールして動作を確認してみましょう。 `NC Strength: 1~100` TextFieldより、強度を入力し、`Change Strength` ボタン押すとノイズ抑制の強度を変更できます。 ### ノイズ抑制の解除(Dispose) Room 退出時やAudio配信終了時など、ノイズ抑制が不要になったタイミングで `SkyWayNoiseCanceller.dispose()` を呼び出してください。 `MainViewModel.kt` にて、退室処理を追加します。 ```kotlin class MainViewModel(): ViewModel() { ... //追加 fun leave() { viewModelScope.launch { localRoomMember?.leave() // UIスレッドから直接呼び出すことは避けてください withContext(Dispatchers.IO) { SkyWayNoiseCanceller.dispose() } } } } // end of MainViewModel ``` `MainScreen.kt` にて、退室ボタンを追加します。 ```kotlin @Composable fun MainScreen( mainViewModel: MainViewModel, modifier: Modifier ) { ... Column(modifier = Modifier.fillMaxSize()) { ... //追加 Row( horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth() ) { Button( onClick = { mainViewModel.leave() } ) { Text("Leave") } } } // end of Column } // end of MainScreen ``` さらに詳しい API の説明や注意事項は [API Reference](https://android-sdk.api-reference.skyway.ntt.com/ai-noise-canceller/) を参照してください。 --- ## ユーザーガイド/AI Noise Canceller/Android 版/開発ガイド Path: user-guide_ai-noise-canceller_android_development-guide.md # 開発ガイド ## エラーハンドリング AI Noise Canceller は、短時間のネットワーク切断時には自動的に再接続処理を行います。しかし、長時間のネットワーク切断など回復不可能なエラー、または利用料制限エラーが発生した場合には、`fatalErrorHandler` が呼び出されます。この場合、 AI Noise Canceller は終了し、内部リソースは解放されています。再度 AI Noise Canceller を利用する場合は、 `SkyWayNoiseCanceller.setup` を呼び出す必要があります。 ```kotlin SkyWayNoiseCanceller.onFatalErrorHandler = { errorCode -> when (errorCode) { ErrorCode.QUOTA_EXCEEDED -> Log.d("YourAppTag", "Usage limit exceeded error. Please confirm your plan and usage time.") else -> Log.d("YourAppTag", "Error occurred: ${errorCode.code}. Please call SkyWayNoiseCanceller.setup() to re-setup.") // your code... } } ``` ## 課金対象期間について AI Noise Canceller の課金対象期間は、ノイズ抑制機能の開始(`SkyWayNoiseCanceller.start` の呼び出し)から 終了(`SkyWayNoiseCanceller.stop`の呼び出し)までの期間です。 ![Billing](/media/posts/docs/00_13_03_02_ai-noise-canceller-development-guide-billing-start-stop.png) ## ノイズ抑制強度(strength)の調整 `strength` はノイズ抑制の強度を設定するための値です。`SkyWayNoiseCanceller.setup` 時に設定できるほか、`changeStrength` メソッドを使用して任意のタイミングでの変更も可能です。 何も指定しなかった場合は 100 が設定されます。 ```kotlin // セットアップ時に設定する場合 val ncOptions = SkyWayNoiseCanceller.Options.create( strength = 90 ) val ncReady = SkyWayNoiseCanceller.setup( applicationContext, ncOptions ) // changeStrengthで変更する場合(SkyWayNoiseCanceller.setup以降呼び出してください) SkyWayNoiseCanceller.changeStrength(80) ``` ## モデルタイプについて モデルタイプは `small` , `medium` , `large` の 3 種類があり、セットアップ時に設定できます。 何も指定しなかった場合は `small` が設定されます。 `small` が最も処理負荷を抑えられるため、様々なデバイスで活用されるユースケースでは `small` の利用を推奨します。 モデルタイプを変更したい場合は、`SkyWayNoiseCanceller.dispose` して `SkyWayNoiseCanceller.setup` より設定してください。 ```kotlin // 初期化時にモデルを'medium'に設定 val ncOptions = SkyWayNoiseCanceller.Options.create( modelType = ModelType.MEDIUM ) val ncReady = SkyWayNoiseCanceller.setup( applicationContext, ncOptions ) ``` ## レイテンシーについて `tolerableLatencyMs` は、ノイズ抑制処理における許容可能な遅延時間を表します。デフォルトは 30ms に設定されています。通常はこのパラメータを調整する必要はありませんが、性能の低い端末でノイズ抑制性能が最も高い `large` モデルを使用する場合など、端末の処理負荷が高くなり、ノイズ抑制が正しく機能しないことがあります。 そのような場合は、`tolerableLatencyMs` の値を(最大 100ms まで)大きくすることで処理負荷を軽減し、より安定した動作が期待できます。 レイテンシーを変更したい場合、`SkyWayNoiseCanceller.dispose` して `SkyWayNoiseCanceller.setup` より設定してください。 ```kotlin // 初期化時にモデルを'medium'に設定 val ncOptions = SkyWayNoiseCanceller.Options.create( tolerableLatencyMs = 100 ) val ncReady = SkyWayNoiseCanceller.setup( applicationContext, ncOptions ) ``` ## サンプリングレートについて 通話品質および音声処理の最適化のため、Audio Input のサンプリングレートは 16kHz に設定することを推奨します。 SkyWay Android SDK の最新バージョン(v3.2.0以降)では、`audioInputSampleRate` のデフォルト値がすでに 16kHz に設定されています。別の値を使用したい場合は、`SkyWayContext.Options.audioInputSampleRate` にて設定を変更してください。 > audioInputSampleRate は SkyWayContext.setup() の呼び出し時のみ設定可能ですので、ご注意ください。 --- ## ユーザーガイド/配信 β版/概要 Path: user-guide_livestreaming_overview.md # 概要 本機能は、複数の参加者の音声・映像を SkyWay のサーバー上で合成し、YouTube や Twitch などの映像配信サービスを通じて視聴者に届ける機能です。 ## オープンベータ版について 本機能は現在オープンベータ版として提供しています。ご利用をご希望の方は、[SkyWayお問い合わせ](https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=14615614124185)よりご連絡ください。 ## 主な特徴 - **サーバーサイド合成**: 参加者から送信された音声・映像データは SkyWay のサーバー上で自動的に合成されます。 - **複数レイアウト対応**: グリッド表示や画面共有のピクチャーインピクチャー表示など、用途に合わせた合成レイアウトが選択可能です。 - **主要な配信サービスとの連携**: YouTube や Twitch といった主要な配信プラットフォームと連携できます。 ## 利用方法 本機能を利用するには、SkyWay LiveStreaming API (REST API) をお客様のサーバーから操作する必要があります。詳細な実装手順は「クイックスタート」セクションで解説しています。 ![システム構成図](/media/posts/docs/live.svg) ## 仕様 ### Base URL SkyWay LiveStreaming API の Base URL は、オープンベータ版についてお問い合わせいただいた際にご案内いたします。 ### 対応プロトコル、対応配信サービス RTMP による出力に対応しています。以下の配信サービスでの動作確認を行っています。 - YouTube - Twitch その他のサービスでのサポートが必要な場合はお問い合わせください。 ### 対応通信方式 本機能は SFU を利用している場合のみ利用可能です。 Room ライブラリを用いて P2P ルームを利用している場合と、SFU Bot ライブラリを使わずに Core ライブラリを利用している場合は本機能は利用できません。 ### 認証 SkyWay LiveStreaming API のリクエストには SkyWay Admin Auth Token による認証が必要です。 SkyWay Admin Auth Token の仕様については次のリンクを参照してください。 [**SkyWay Admin Auth Token**](/ja/docs/user-guide/authentication/skyway-admin-auth-token/) SkyWay LiveStreaming API の HTTP リクエストのリクエストヘッダーには `Authorization: Bearer ` が含まれている必要があります。 以下に curl コマンドでリクエストヘッダーを指定する例を示します。 ```bash curl -X POST ENDPOINT_URL \ -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" ``` ## ライフサイクル LiveStreamingSession の状態の遷移と基本的な配信の流れについて説明します。 ### 状態の遷移 LiveStreamingSession は以下の 5 種類の状態を持ちます。 - Preparing - 配信の準備中です。最大で 10 分程度の待ち時間の後、自動的に Available に遷移します。 - Available - 配信の準備が完了した状態です。startSession のリクエストが送られると、Livestreaming に遷移します。 - Livestreaming - 配信中です。deleteSession のリクエストが送られると、Deleting に遷移します。 - Deleting - 配信の削除中です。このプロセスが完了すると、LiveStreamingSession は自動的に削除されます。 - Unavailable - 配信が利用できない状態です。回復不能なエラーが発生した場合に遷移します。getSession のリクエストを送ることでエラーの内容を確認できます。deleteSession のリクエストを送ることで LiveStreamingSession の削除を行います。 ![状態遷移図](/media/posts/docs/live_life_cycle_1.png) ### 基本的な配信の流れ #### 配信の開始 ![配信開始フロー](/media/posts/docs/live_life_cycle_2.png) #### 配信の終了 ![配信終了フロー](/media/posts/docs/live_life_cycle_3.png) --- ## ユーザーガイド/配信 β版/クイックスタート Path: user-guide_livestreaming_quickstart.md # クイックスタート 本チュートリアルでは、SkyWay LiveStreaming API を利用して映像・音声を配信する方法を紹介します。 なお、SkyWay LiveStreaming API は現在オープンベータ版として提供しています。本機能のご利用をご希望の方は、[SkyWayお問い合わせ](https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=14615614124185)よりご連絡ください。SkyWay LiveStreaming API の Base URL をご案内いたします。 ## 配信サービスの準備 ### 動作確認済みの配信サービス SkyWay LiveStreaming API では、以下の配信サービスで動作確認を行っています。 配信の開始には、ストリームキーが必要です。以下を参考に取得してください。 #### YouTube - **今すぐライブ配信を開始する** [YouTubeサポート記事](https://support.google.com/youtube/answer/2907883?hl=ja&ref_topic=9257984&sjid=10895521970513023508-AP#zippy=%2C%E4%BB%8A%E3%81%99%E3%81%90%E3%83%A9%E3%82%A4%E3%83%96%E9%85%8D%E4%BF%A1%E3%82%92%E9%96%8B%E5%A7%8B%E3%81%99%E3%82%8B) #### Twitch - **ストリームキー** [Twitch Stream Keyに関するFAQ](https://help.twitch.tv/s/article/twitch-stream-key-faq?language=ja) --- その他の配信サービスで配信を開始するには、配信 URL とストリームキーが必要です。配信サービスのドキュメントをご参照ください。 ### 開発時に用いることができる配信サービス 開発中に YouTube や Twitch などのサービスを使わずに自前でテストを行いたい場合は、外部からアクセス可能なサーバー上に以下のような RTMP/HLS サーバーをデプロイして利用できます。 - [https://github.com/TareqAlqutami/rtmp-hls-server](https://github.com/TareqAlqutami/rtmp-hls-server) - [https://github.com/illuspas/Node-Media-Server](https://github.com/illuspas/Node-Media-Server) 利用方法は、それぞれのリポジトリのドキュメントをご参照ください。これらのサービスはサポートの対象外となります。 最終的な動作確認は実際に配信を行う際に利用するサービスで実施してください。 --- ## チュートリアルアプリの作成 ここからは JavaScript SDK と Node.js を用いて、映像・音声の配信機能を体験できるシンプルなアプリケーションを作成します。 最終的に、以下のような形で映像と音声をYouTubeなどに配信できます。 ![配信画面の例](/media/posts/docs/live-example.png) ### 構成 本チュートリアルで作成するアプリケーションは、管理者が利用するサーバーサイドと、ブラウザで動作するクライアントサイドで構成されます。 - **サーバーサイド** SkyWay のサーバーサイド API を制御し、トークンの管理などを担います。 - **クライアントサイド** ブラウザで音声・ビデオ通話を行うアプリケーションを動かし、実際に会議や映像配信を行います。 また、管理者はサーバーの API に対して curl コマンドでリクエストを送信して、以下の操作を行います。 1. 配信の準備 2. 配信の開始 3. 配信設定の更新 4. 配信の終了 --- ### 環境構築 1. **Node.js のインストール** バージョン20以降をインストールしてください。 2. **ディレクトリ構成** 任意の作業ディレクトリに `tutorial` ディレクトリを作成し、 `tutorial` ディレクトリの中に以下の内容の `package.json` を配置します。 ```json { "name": "tutorial", "version": "1.0.0", "main": "index.js", "type": "module", "scripts": { "server": "node server/main.js", "client": "parcel client/index.html --open" }, "dependencies": { "@skyway-sdk/room": "^2.0.0", "@skyway-sdk/token": "^1.7.0", "cors": "^2.8.5", "express": "^4.21.2", "jsrsasign": "^11.1.0" }, "devDependencies": { "@types/express": "^4.17.21", "parcel": "^2.13.3" } } ``` 3. **パッケージのインストール** `package.json` を配置したディレクトリ内で、以下のコマンドを実行してください。 ```bash npm i ``` --- ### サーバーサイド SkyWay Live Streaming API を HTTP 経由で操作するため、Node.js のサーバーアプリケーションを作成します。 #### ソースファイルの作成 1. `tutorial` ディレクトリ内に `server` ディレクトリを作成し、`server` ディレクトリの中に以下の内容の `main.js` を配置します。 _tutorial/server/main.js_ ```js import { nowInSec, SkyWayAuthToken, uuidV4 } from "@skyway-sdk/token"; import cors from "cors"; import express from "express"; import jsrsasign from "jsrsasign"; // SkyWayのアプリケーションIDとシークレットキー const appId = "ここにアプリケーションIDをペーストしてください"; const secret = "ここにシークレットキーをペーストしてください"; // LiveStreaming APIのBase URLは、オープンベータ版のご利用申請後にご案内いたします // お問い合わせ: https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=14615614124185 const livestreamingApiBaseUrl = "ここにLiveStreaming APIのBase URLを入力してください"; const channelApiUrl = "https://channel.skyway.ntt.com/v1/json-rpc"; // 配信サービスの設定 const output = { type: "RTMP", target: { service: "ここに「YOUTUBE」か「TWITCH」と入力するか、その他のサービスを利用する場合はRTMPのエンドポイントを入力してください", streamKey: "ここに配信サービスのストリームキーをペーストしてください", }, }; ``` **補足:** - `appId` と `secret` はご自身の SkyWay アプリケーションで発行されたアプリケーション ID・シークレットキーに置き換えてください。 - `service` : 動作を保証している YouTube と Twitch に関してはそれぞれのラベルである「YOUTUBE」と「TWITCH」を指定することで利用できます。その他のサービスは RTMP のエンドポイント(rtmp://で始まる)を指定してください - `streamKey` : 配信先(YouTube, Twitch など)のストリームキーを指定してください。 --- #### SkyWay Admin Auth Token の作成 SkyWay の LiveStreaming API および Channel API を操作するためには、**SkyWay Admin Auth Token** を作成する必要があります。詳細は以下のドキュメントを参照してください。 - [SkyWay Admin Auth Token](/ja/docs/user-guide/authentication/skyway-admin-auth-token/) SkyWay Admin Auth Token を作成するための `createSkyWayAdminAuthToken` 関数を `main.js` ファイルに追記します。 _tutorial/server/main.js_ ```js // Channel APIとLiveStreaming APIを操作するためのトークンを作成 const createSkyWayAdminAuthToken = () => { const token = jsrsasign.KJUR.jws.JWS.sign( "HS256", JSON.stringify({ alg: "HS256", typ: "JWT" }), JSON.stringify({ exp: nowInSec() + 60, iat: nowInSec(), jti: uuidV4(), appId, }), secret ); return token; }; ``` --- #### SkyWay Auth Token の作成 SkyWay のクライアントサイド SDK を利用するためには、**SkyWay Auth Token** が必要です。詳細は以下を参照してください。 - [SkyWay Auth Token](/ja/docs/user-guide/authentication/skyway-auth-token/) SkyWay Auth Token を作成するための `createSkywayAuthToken` 関数を `main.js` ファイルに追記します。 _tutorial/server/main.js_ ```js // クライアント用のトークン const createSkywayAuthToken = (channelId) => { const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId, rooms: [ { id: channelId, methods: [], member: { id: "*", methods: ["publish", "subscribe", "updateMetadata"], }, }, ], }, }).encode(secret); return token; }; ``` --- #### Channelの作成 サーバー起動時に、一度だけ SkyWay の Channel を作成します。 下記のコードでは、`fetch` 関数を使い、Channel API の `createChannel` メソッドを呼び出して新しい Channel を作成しています。 SkyWay Channel API の詳細な仕様は以下の記事を参照してください。 - [SkyWay Channel API](/ja/docs/user-guide/channel-api/) Channel API の `createChannel` メソッドを呼び出すコードを `main.js` に追記します。 _tutorial/server/main.js_ ```js // SkyWayのChannelを作成 const createChannelResponse = await fetch(channelApiUrl, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ jsonrpc: "2.0", id: uuidV4(), method: "createChannel", params: {}, }), }); if (!createChannelResponse.ok) { console.error(await createChannelResponse.json()); throw new Error("Failed to create channel"); } const { result: { channel: { id: channelId }, }, } = await createChannelResponse.json(); ``` --- #### サーバーフレームワークの設定 チュートリアルアプリでは、HTTP サーバーを実行するためのフレームワークとして Express を利用します。 Express の設定を行うためのコードを `main.js` に追記します。 _tutorial/server/main.js_ ```js const app = express(); app.use(cors()); app.use(express.json()); ``` --- #### メディア合成のプリセット設定 メディア合成の設定では、配信される映像と音声がどのような形式になるか定義できます。例えば参加者全員の映像を格子状に並べることや、ある特定の属性を持つ Member や Publication を選択して画面に表示すること等ができます。 このチュートリアルでは、以下 2 種類の合成レイアウトを定義します。 **1. Grid** - 映像: 複数の参加者の映像を格子状に分割して表示します。 - 音声: 音量が大きい順に上位4名の音声を出力します。 ![Gridレイアウト](/media/posts/docs/grid.png) **2. Picture In Picture (pip)** プレゼンター(`presenter`)の属性を metadata に持つメンバーの映像をメイン画面に、カメラ映像を小窓に表示します。それ以外のメンバー(`audience`)の映像は表示しません。 - 映像: プレゼンターの画面共有映像とカメラ映像を合成して表示します。 - 音声: プレゼンターの音声を出力します。 ![PiPレイアウト](/media/posts/docs/pip.png) --- メディア合成の詳細な仕様は以下の記事を参照してください [合成のルール](/ja/docs/user-guide/livestreaming/composite) また、メディア合成の結果の事前確認やレイアウト調整に合成結果プレビューツールを使うこともできます。使用方法については以下の記事を参照してください [合成結果プレビューツール](/ja/docs/user-guide/livestreaming/previewer) `grid` と `pip` のレイアウトの定義を `main.js` に追記します。 _tutorial/server/main.js_ ```js // 合成のプリセット設定 /** Gridレイアウトでは、numVideoElementsを4に固定していますが、 roomに参加しているメンバーやPublicationの数を反映させて可変にすることも可能です。 **/ const grid = () => { const canvasWidth = 1280; const canvasHeight = 720; // videoElementsの数 (Gridのセルの数)を指定する // ここを適宜変更することで動的にセル数を変化させられます const numVideoElements = 4; // 列数: 要素数の平方根の切り上げ const columns = Math.ceil(Math.sqrt(numVideoElements)); // 行数: 要素数 ÷ 列数の切り上げ const rows = Math.ceil(numVideoElements / columns); // Canvas サイズから計算する1セルあたりの幅・高さ const cellWidth = Math.round(canvasWidth / columns); const cellHeight = Math.round(canvasHeight / rows); const videoElements = [...new Array(numVideoElements).keys()].map((i) => { const row = Math.floor(i / columns); const col = i % columns; return { publisher: { id: "*", query: { sortBy: "AUDIO_LEVEL", index: i, }, }, x: col * cellWidth, y: row * cellHeight, width: cellWidth, height: cellHeight, }; }); // 音声は最大4つまで合成できるため、音量上位4つを設定 const audioElements = [...new Array(4).keys()].map((i) => ({ publisher: { id: "*", query: { sortBy: "AUDIO_LEVEL", index: i, }, }, })); const composite = { mode: "CUSTOM", videoConfig: { canvasWidth, canvasHeight, elements: videoElements, }, audioConfig: { elements: audioElements, }, }; return composite; }; /** Picture in Pictureのメイン画面と小窓には memberのmetadataに"presenter"が指定されているmemberのうち 最も音量が大きいmemberが表示される。 メイン画面にはpublicationのmetadataに"screen"が入ったpublicationが表示され、 小窓には"camera"が入ったpublicationが表示される。 **/ const pip = { mode: "CUSTOM", options: { // クライアントからmetadataによる合成対象の更新を行う際にtrueにする handleUpdateMetadata: true, }, videoConfig: { canvasWidth: 1280, canvasHeight: 720, elements: [ { publisher: { metadata: "presenter", query: { sortBy: "AUDIO_LEVEL", index: 0, }, videoPublication: { metadata: "screen", }, }, x: 0, y: 0, width: 1280, height: 720, }, { publisher: { metadata: "presenter", query: { sortBy: "AUDIO_LEVEL", index: 0, }, videoPublication: { metadata: "camera", }, }, x: 960, y: 540, width: 320, height: 180, zIndex: 1, }, ], }, audioConfig: { elements: [ { publisher: { metadata: "presenter", query: { sortBy: "AUDIO_LEVEL", index: 0, }, }, }, ], }, }; ``` --- #### ユーザーのChannelへの入室 クライアントからリクエストを受け取り、Channel への入室用トークンと ChannelID を返すエンドポイントの定義を `main.js` に追記します。 _tutorial/server/main.js_ ```js // ユーザーがChannelに入室するためのエンドポイント app.post("/user/join", async (_, res) => { // 入室できるchannelIdを制限したトークンを作成する const token = createSkywayAuthToken(channelId); res.send({ token, channelId }); }); ``` --- #### 配信の準備 管理者が配信を行う前に、「配信の準備」をする必要があります。 この処理は最大で 10 分ほどかかる場合があるため、Chunked レスポンスで進捗を返すように実装します。 PrepareLiveStreamingSession API を呼び出し、`status` が `AVAILABLE` になるまで定期的に状態を確認する処理を `main.js` に追記します。 _tutorial/server/main.js_ ```js // 管理者が配信の準備をするエンドポイント let liveStreamingSessionId = ""; app.post("/admin/prepare", async (_, res) => { // prepare は最大で10分ほどかかる可能性があるため // 逐次ステータスを "Chunked" レスポンスで返す。 // AVAILABLE と表示されたら準備完了。 const response = await fetch( `${livestreamingApiBaseUrl}/channels/${channelId}/sessions/prepare`, { method: "POST", headers: { Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, } ); if (!response.ok) { console.error(await response.json()); res.status(500).send(); return; } const { id } = await response.json(); liveStreamingSessionId = id; res.set({ "Content-Type": "text/plain", "Transfer-Encoding": "chunked", }); res.write(`sessionId:${liveStreamingSessionId}\n`); for (;;) { // LiveStreamingセッションのStatusを取得 const response = await fetch( `${livestreamingApiBaseUrl}/channels/${channelId}/sessions/${liveStreamingSessionId}`, { headers: { Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, method: "GET", } ); const { status } = await response.json(); res.write(`status:${status}\n`); if (status === "AVAILABLE") { break; } await new Promise((resolve) => setTimeout(resolve, 1000)); } res.end(); }); ``` --- #### 配信の開始 配信の準備が完了したら、管理者は配信を開始できます。 配信を開始するために、StartLiveStreamingSession API を呼び出す処理を `main.js` に追記します。 ここでは、デフォルトの合成設定を `grid` にし、リクエストの `preset` のパラメーターが `"pip"` の場合は `pip` レイアウトに切り替えています。 _tutorial/server/main.js_ ```js // 管理者が配信を開始するエンドポイント app.post("/admin/start", async (req, res) => { const { preset } = req.body; let composite = grid(); if (preset === "pip") { composite = pip; } const response = await fetch( `${livestreamingApiBaseUrl}/channels/${channelId}/sessions/${liveStreamingSessionId}/start`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ composite, output, }), } ); if (!response.ok) { console.error(await response.json()); res.status(500).send(); return; } res.send(); }); ``` --- #### 配信の設定更新 配信中に、`grid` ↔ `pip` の切り替えなど、メディア合成の設定を更新できます。 メディア合成の設定を更新するために、UpdateLiveStreamingSession API を呼び出す処理を `main.js` に追記します。 ```js // 管理者が配信のメディア合成設定を変更するエンドポイント app.post("/admin/update", async (req, res) => { const { preset } = req.body; let composite = grid(); if (preset === "pip") { composite = pip; } // canvasWidthとcanvasHeightは更新できないパラメーターであるため、updateのパラメーターから削除する delete composite.videoConfig.canvasWidth; delete composite.videoConfig.canvasHeight; const response = await fetch( `${livestreamingApiBaseUrl}/channels/${channelId}/sessions/${liveStreamingSessionId}`, { method: "PATCH", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ composite, output, }), } ); if (!response.ok) { console.error(await response.json()); res.status(500).send(); return; } res.send(); }); ``` --- #### 配信の終了 管理者が配信を終了するためのエンドポイントです。 配信を終了するために、DeleteLiveStreamingSession API を呼び出す処理を `main.js` に追記します。 _tutorial/server/main.js_ ```js // 配信を終了するエンドポイント app.post("/admin/delete", async (_, res) => { const response = await fetch( `${livestreamingApiBaseUrl}/channels/${channelId}/sessions/${liveStreamingSessionId}`, { method: "DELETE", headers: { Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, } ); if (!response.ok) { console.error(await response.json()); res.status(500).send(); return; } res.send(); }); ``` --- #### サーバーの起動 最後に、ポート `9091` で HTTP サーバーを起動するためのコードを `main.js` に追記します。 ```js app.listen(9091); console.log("Server is running on http://localhost:9091"); ``` --- ### クライアントサイド `tutorial` ディレクトリ内に `client` ディレクトリを作成し、`client` ディレクトリの中に以下の内容の `index.html` を配置します。 _tutorial/client/index.html_ ```html SkyWay Tutorial

ID:

role: audience
``` 次に、`client` ディレクトリの中に以下の内容の `main.js` を配置します。 _tutorial/client/main.js_ ```js import { SkyWayContext, SkyWayRoom, SkyWayStreamFactory, } from "@skyway-sdk/room"; (async () => { const localMediaArea = document.getElementById("local-media-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const publishCamera = document.getElementById("publish-camera"); const publishScreen = document.getElementById("publish-screen"); const becomePresenter = document.getElementById("become-presenter"); const becomeAudience = document.getElementById("become-audience"); const roleLabel = document.getElementById("role-label"); joinButton.onclick = async () => { // サーバーからChannel IDとトークンを取得 const response = await fetch(`http://localhost:9091/user/join`, { method: "POST", }); const { token, channelId } = await response.json(); // SkyWayContextを作成し、既存のChannelに参加 const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.Find(context, { id: channelId }, { type: 'sfu' }); const me = await room.join(); myId.textContent = me.id; // Presenter / Audience の切り替え becomePresenter.onclick = async () => { roleLabel.textContent = "role: presenter"; // LiveStreaming APIでは次のような構造のメタデータのlabelを参照することができます await me.updateMetadata( JSON.stringify({ SKYWAY_SYSTEM: { liveStreaming: { label: "presenter", }, }, }) ); }; becomeAudience.onclick = async () => { roleLabel.textContent = "role: audience"; await me.updateMetadata( JSON.stringify({ SKYWAY_SYSTEM: { liveStreaming: { label: "audience", }, }, }) ); }; // ストリームのPublishを行う関数 const publish = async (stream, label) => { const publication = await me.publish( stream, label != undefined ? { metadata: JSON.stringify({ SKYWAY_SYSTEM: { liveStreaming: { label, }, }, }), } : undefined ); // DOMに表示 const newMedia = document.createElement("div"); const labelElement = document.createElement("span"); newMedia.appendChild(labelElement); labelElement.textContent = ` id:${publication.id} contentType:${publication.contentType} label:${label} `; const unpublishButton = document.createElement("button"); newMedia.appendChild(unpublishButton); unpublishButton.textContent = "Unpublish"; unpublishButton.onclick = async () => { await me.unpublish(publication); newMedia.remove(); }; localMediaArea.appendChild(newMedia); }; // 音声ストリームをPublish const audio = await SkyWayStreamFactory.createMicrophoneAudioStream(); await publish(audio); // カメラ映像をPublish publishCamera.onclick = async () => { const video = await SkyWayStreamFactory.createCameraVideoStream(); await publish(video, "camera"); }; // 画面共有をPublish publishScreen.onclick = async () => { const { video } = await SkyWayStreamFactory.createDisplayStreams(); await publish(video, "screen"); }; // 他メンバーがPublishしたストリームをSubscribe const subscribeAndAttach = async (publication) => { // 自分がPublishしたストリームは除外 if (publication.publisher.id === me.id) return; const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); await Promise.all(room.publications.map(subscribeAndAttach)); }; })().catch((e) => console.error("main error", e)); ``` --- ## チュートリアルアプリの実行 ### 1. アプリケーションの起動 1. **サーバーの起動** 以下のコマンドでサーバーを起動します。`http://localhost:9091` でリクエストを待ち受けます。 ```bash npm run server ``` 2. **クライアントの起動** 別のターミナルを開いて以下のコマンドを実行すると、ブラウザが自動的に起動します(またはターミナルに表示される URL を直接開きます)。 ```bash npm run client ``` ### 2. クライアントサイドの操作 - **1. join ボタン** サーバーから発行された Token と ChannelID を使って SkyWay の Channel に入室します。 - **2. publishCamera / publishScreen ボタン** それぞれ、カメラ映像・画面共有映像を配信(Publish)します。 - **3. Become Presenter / Become Audience ボタン** 現在入室中のメンバーの `metadata` を切り替え、配信時のメディア合成設定に反映させます(`presenter` と `audience` の判定)。 --- ### 管理者による配信のコントロール 管理者は次のエンドポイントを `curl` コマンドなどで呼び出すことで、配信を制御します。 ### 1. 配信の準備 ```bash curl -X POST http://localhost:9091/admin/prepare ``` - コンソール出力でステータスが逐次表示され、`AVAILABLE` となったら配信可能です。 ### 2. 配信の開始 ```bash curl -X POST http://localhost:9091/admin/start ``` - 配信開始時のレイアウトはデフォルトで `grid` が指定されます。 - レイアウトを指定する場合、以下のように `preset` を JSON で渡します。 ```bash curl -X POST -H "Content-Type: application/json" \ -d '{"preset":"pip"}' \ http://localhost:9091/admin/start ``` #### **pip レイアウト利用時の注意** - 「presenter」役のメンバーがいない場合や映像の Publish が行われていない場合、何も表示されません。デモ時には必ず「Become Presenter」ボタンを押し、映像を Publish しているか確認してください。 ### 3. 配信設定の更新 以下のように、`preset` に `pip` または `grid` を指定してレイアウトを切り替えます。 ```bash curl -X POST -H "Content-Type: application/json" \ -d '{"preset":"pip"}' \ http://localhost:9091/admin/update ``` ### 4. 配信の終了 ```bash curl -X POST http://localhost:9091/admin/delete ``` - 配信を終了すると、LiveStreamingSession は削除されます。 - 再度配信を開始する場合は `prepare → start` のフローを改めて行ってください。 --- ## トラブルシューティング このクイックスタートでエラーが発生した場合は、エラーレスポンスを元に[開発ガイド](/ja/docs/user-guide/livestreaming/guide/)を参照してください。 --- ## ユーザーガイド/配信 β版/合成のルール Path: user-guide_livestreaming_composite.md # 合成のルール このドキュメントでは SkyWay LiveStreaming API で SkyWay の映像や音声を合成する設定のルールについて説明します。 JSON 形式の設定によって映像と音声の合成の制御が可能です。 指定可能なパラメーターの詳細は、[APIリファレンス](https://livestreaming.api-reference.skyway.ntt.com/api-reference/index.html)を参照してください。 ![合成処理の概要](/media/posts/docs/composite.drawio.svg) 合成の設定は Channel 中の Publication を選択し、どのようなレイアウトで表示するかを設定します。 LiveStreaming Service は合成の設定に従って Publication を合成した映像を配信サービスに配信します。 合成の設定は大きく3つに分かれています。 - mode - 現在は CUSTOM モードのみ利用可能です - videoConfig - 映像の合成に関する設定を行います - audioConfig - 音声の合成に関する設定を行います ## 映像合成の設定 videoConfig では映像の合成について、以下の設定が可能です。 - 必須 - canvasWidth - 出力映像の横幅のピクセル数 - canvasHeight - 出力映像の縦幅のピクセル数 - elements - 合成対象となる映像を設定します。指定方法は後述します。 - オプション - backgroundImage - 出力映像の背景画像 - png 形式の画像を base64化した文字列を入力する - 背景画像は常に要素より奥に描画されます ## 音声合成の設定 videoConfig では音声の合成について、以下の設定が可能です。 - 必須 - elements - 合成対象となる音声を設定します。指定方法は後述します。 ## 要素の設定 要素とは、合成における個々の映像または音声の要素を指します。たとえば、会議中の参加者のカメラ映像や画面共有、個別の音声がそれに該当します。 ### 映像の要素の設定 videoConfig 内の elements プロパティで、各映像の要素について以下の設定が可能です。 - 必須 - publisher - 合成対象となる Publication を選択します。選択方法は後述します。 - width - 要素の横幅のピクセル数 - height - 要素の縦幅のピクセル数 - x - 要素の X 座標 - 左側が原点 - y - 要素の Y 座標 - 上側が原点 - オプション - zIndex - 要素の重なりの順の指定 - 0以上255以下の値を指定できます - 0が最も奥です - 背景画像は要素の zIndex の値にかかわらず常にエレメントより奥に描画されます - デフォルトで0が割り当てられます 映像の要素は49個まで設定できます。 次のように x を30,y を60と設定した場合はこのような出力となります。 ```json { "mode": "CUSTOM", "videoConfig": { "canvasWidth": 720, "canvasHeight": 720, "elements": [ { "publisher": { "id": "*" }, "height": 300, "width": 300, "x": 30, "y": 60 } ] }, "audioConfig": { "elements": [] } } ``` ![座標指定例](/media/posts/docs/composite1.png) ### 音声の要素の設定 audioConfig 内の elements プロパティで、各音声の要素について以下の設定が可能です。 - 必須 - publisher - 合成対象となる Publication を選択します。選択方法は後述します。 音声の要素は4個まで設定できます。 ### 要素のPublicationの選択 各要素では、publisher プロパティを使用して合成対象となる Publication の選択をする必要があります。選択の手順は以下のような流れになります。 1. publisher の選択 この時点で選択した Publisher のすべての Publication が候補になります。 2. query による絞り込み 1で選択した候補から最終的に1つの Publisher を選択します。 3. publisher の publication を選択 2で選択した Publisher が publish している Publication を候補として Publication を選択します。この時点で複数の候補が存在する可能性があります。 4. 対象の Publication の決定 3で選択した候補から最終的に1つの Publication を選択します。 #### publisherの選択 以下の識別子から Publisher(Publication を Publish した Member)を選択できます。 (※core ライブラリを利用している場合は SFU-Bot ではなく元の Publication を Publish している Person が対象です。) - id - name - metadata いずれの識別子も選択されていない場合は channel 中のすべての Member が候補となります #### queryによる候補の絞り込み publisher の選択の結果、複数の Publisher の候補が存在する場合、query プロパティによって1つに絞り込みます。 query では以下の設定が可能です。 - sortBy - Publisher の候補をリアルタイムに、どのような順序で並べるかを指定できます - AUDIO_LEVEL - 各 Publisher の Publish している AudioPublication のうち、最も音量が大きい AudionPublication の音量が大きい順 - TIMELINE - Publisher が Room に Join した時刻順 - index - ソートされた Publisher の候補の配列から最終的な Publisher を選択するための添字 - -1を指定すると候補の配列の最後の Publisher が選択される query を指定しない場合はデフォルトで sortBy が TIMELINE、index が0に設定されます。 **例** Channel 中の AUDIO_LEVEL 上位2 publisher の publication を選択して描画する設定と結果は次のようになります。 ```json { "mode": "CUSTOM", "videoConfig": { "canvasWidth": 1920, "canvasHeight": 1080, "elements": [ { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 0 } }, "x": 0, "y": 0, "width": 960, "height": 1080 }, { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 1 } }, "x": 960, "y": 0, "width": 960, "height": 1080 } ] }, "audioConfig": { "elements": [ { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 0 } } }, { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 1 } } } ] } } ``` ![音量ソート表示例](/media/posts/docs/composite2.png) --- #### publicationの選択 audioPublication または videoPublication プロパティでは以下の識別子から Publisher の Publication を選択できます。 - id - metadata 選択できる Publication は config の種類と同じものに限られます。(audioConfig なら contentType が Audio のもの、videoConfig なら contentType が Video のもの) publication プロパティを設定しない場合は最初に Publish された Publication が選択されます。 publication プロパティを設定した上で Publication の候補が複数ある場合は、より早く Publish された Publication が選択されます。 ### 識別子の指定方法 id, name, metadata といった識別子は以下の方法で指定できます。 - id - 対象のリソースに設定されている文字列(完全一致)、`*`(ワイルドカード) - name - 対象のリソースに設定されている文字列(完全一致)、`*` を含む文字列(部分一致)、 `*`(ワイルドカード) - metadata - 対象のリソースに設定されている文字列(完全一致)、`*` を含む文字列(部分一致)、 `*`(ワイルドカード) name や metadata では、`audience-*` のような書き方をすることで部分一致で判定できます。 また name や metadata に `*` を指定した場合はその要素を持たない場合も含みます。 metadata を指定する場合は channel 中の member と publication の metadata に以下のフォーマットのデータを JSON 文字列の形式で格納してください。`SKYWAY_SYSTEM.liveStreaming.label` の内容と `metadata` に指定した識別子の内容でマッチングを行うことができます。 ```json { "SKYWAY_SYSTEM": { "liveStreaming": { "label": "" } } } ``` 識別子で指定可能な文字種は、Channel、Member の name として使用可能な文字種、およびワイルドカードのみとなります。 [Channel、Memberのnameとして使用可能な文字種](/ja/docs/user-guide/commons/quotas-and-limits/#63) ## 設定の例 ここでは以下の3種類の合成例を説明します。 - side by side(横並びの映像) - picture in picture(プレゼン用等の表示) - Grid(全映像の表示) ### side by side 以下のスクリーンショットのような合成を行うための設定について説明します。 ![Side by Sideレイアウト](/media/posts/docs/sbs.png) ```json { "mode": "CUSTOM", "videoConfig": { "canvasWidth": 1920, "canvasHeight": 1080, "elements": [ { "publisher": { "id": "*", "query": { "sortBy": "TIMELINE", "index": 0 } }, "x": 0, "y": 0, "width": 960, "height": 1080 }, { "publisher": { "id": "*", "query": { "sortBy": "TIMELINE", "index": 1 } }, "x": 960, "y": 0, "width": 960, "height": 1080 } ] }, "audioConfig": { "elements": [ { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 0 } } }, { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 1 } } } ] } } ``` この例では、話者の音量が大きい上位2名の映像を画面の左と右に表示し、それらの音声を合成して出力しています。 ### picture in picture 以下のスクリーンショットのような合成を行うための設定について説明します。 ![PiPレイアウト](/media/posts/docs/pip.png) ```json { "mode": "CUSTOM", "videoConfig": { "canvasWidth": 1280, "canvasHeight": 720, "elements": [ { "publisher": { "id": "*", "query": { "sortBy": "TIMELINE", "index": 0 } }, "x": 0, "y": 0, "width": 1280, "height": 720, "zIndex": 0 }, { "publisher": { "id": "*", "query": { "sortBy": "TIMELINE", "index": 1 } }, "x": 960, "y": 540, "width": 320, "height": 180, "zIndex": 1 } ] }, "audioConfig": { "elements": [ { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 0 } } }, { "publisher": { "id": "*", "query": { "sortBy": "AUDIO_LEVEL", "index": 1 } } } ] } } ``` この例では `screen-share` という label を metadata に持った映像を全画面に表示し、 `camera` という label を metadata に持った映像を小窓に表示しています。またそれらと同じ label を metadata に持った音声を合成して出力しています。 ### Grid 以下のスクリーンショットのような合成を行うための設定を JavaScript のプログラムで作成する例について説明します。 ![Gridレイアウト](/media/posts/docs/grid.png) ```js const canvasWidth = 1280; const canvasHeight = 720; // elementsの数を指定する const numElements = 9; // 列数: 要素数の平方根の切り上げ const columns = Math.ceil(Math.sqrt(numElements)); // 行数: 要素数 ÷ 列数の切り上げ const rows = Math.ceil(numElements / columns); // Canvas サイズから計算する1セルあたりの幅・高さ const cellWidth = Math.floor(canvasWidth / columns); const cellHeight = Math.floor(canvasHeight / rows); const videoElements = [...new Array(numElements).keys()].map((i) => { const row = Math.floor(i / columns); const col = i % columns; return { publisher: { id: "*", query: { sortBy: "TIMELINE", index: i, }, }, x: col * cellWidth, y: row * cellHeight, width: cellWidth, height: cellHeight, }; }); // 音声は最大4つまで合成できるので音量上位4つを設定する const audioElements = [...new Array(4).keys()].map((i) => ({ publisher: { id: "*", query: { sortBy: "AUDIO_LEVEL", index: i, }, }, })); const composite = { mode: "CUSTOM", videoConfig: { canvasWidth, canvasHeight, elements: videoElements, }, audioConfig: { elements: audioElements, }, }; ``` この例では指定した element の数に合わせてレスポンシブに映像を Grid 状に合成します。 --- ## ユーザーガイド/配信 β版/開発ガイド Path: user-guide_livestreaming_guide.md # 開発ガイド ## 想定されるエラー SkyWay LiveStreaming API を利用する際に発生する可能性のあるエラーについて説明します。 ### **SkyWay LiveStreaming API のエラーレスポンス** ### **400** 正しくないリクエストであることを示します。以下の原因が考えられます。 1. パラメータの問題 リクエストに含まれるパラメータに問題があります。ドキュメントを参照して正しい値をパラメータに入れるように修正してください。 2. LiveStreamingSession の状態 状態遷移が正しくありません。 詳細については[ライフサイクルの説明](/ja/docs/user-guide/livestreaming/overview/#59)を参照してください。 ### **401** 有効な SkyWay Admin Auth Token が使用されていません。[**SkyWay Admin Auth Token のドキュメント**](/ja/docs/user-guide/authentication/skyway-admin-auth-token/)を参照して正しいトークンを使用してください。 ### **403** リクエストが拒否されたことを示します。以下の原因が考えられます 1. 権限不足 外部の配信サービスの認証情報が不正な可能性があります。正しい認証情報を指定してください。 2. リソースの割り当て不足 割り当てられたリソースが上限に達している可能性があります。[**リソースの割り当て**](/ja/docs/user-guide/commons/quotas-and-limits/#7)の項目に記載されている範囲内で利用してください。 ### **404** 対象のリソースが存在しません。 ### **429** レートリミットの制限を超えた数のリクエストが送られています。[**リクエストレートの制限**](/ja/docs/user-guide/commons/quotas-and-limits/#47)の項目に記載されている範囲内で利用してください。 ### **500** LiveStreaming サーバーにおいてエラーが発生しています。リクエストの処理が完了していない可能性があるため、リクエストを再試行してください。 複数回再試行を行なってもエラーが解消しない場合は、SkyWay のサポートに問い合わせてください。 ### **503** 利用可能になるまで時間がかかることを示します。以下の原因が考えられます 1. 準備中 LiveStreamingSession の準備中です。完了に数分かかります。 2. 解放処理中 割り当てられたリソースが上限に達していますが、リソースの解放処理が進行中です。 リソースの解放が完了するまで時間がかかるので数分時間を空けるとリトライを再試行できます。 --- ## ユーザーガイド/配信 β版/合成結果プレビューツール Path: user-guide_livestreaming_previewer.md ## 1. アプリケーション概要 本アプリケーションは、**複数の映像や音声ストリームをサーバー側でひとつに合成**する際の設定や結果を、**ローカル環境でシミュレーションできるツール**です。 サーバー上で最終的に行う合成の事前確認やレイアウト調整を行うことを目的としています。(音声は出力されません) 最終的に確定した合成の設定は、LiveStreaming API に渡して映像・音声合成に利用できます。 - **リンク** - [https://livestreaming.api-reference.skyway.ntt.com/apps/mcu-previewer/index.html](https://livestreaming.api-reference.skyway.ntt.com/apps/mcu-previewer/index.html) ![プレビューツール画面](/media/posts/docs/preview1.png) 本ツールでは、以下のような操作を行います。 1. **合成の設定(Composite Setting)の編集** 2. **入力される映像・音声(Publication)のオン/オフ・パラメータ管理** 3. **最終的なレイアウト・プレビューの確認** --- ## 2. 画面構成 画面は大きく次の2つのエリアに分かれています。 1. **左側:Composite Setting(JSONエディター)** 2. **右側:Input Publication の管理とプレビュー** ### 2.1. 左側:Composite Setting - **目的** サーバー側で合成するための設定を直接編集します。ここで編集した内容がプレビューやサーバーの合成結果に反映されます。 - **操作方法** 1. **プリセット設定(presets)** - エディター上部にある「presets」ドロップダウンから、あらかじめ用意された合成の設定(例:`side by side` など)を選択できます。 - プリセットを選択すると、その設定がテキストエリアに反映されます。 - プリセットを自由に編集して、お好みの配置に調整することも可能です。 2. **背景画像の設定(backgroundImage)** - `backgroundImage` ボタンから画像ファイルを指定すると、テキストエリア内の合成設定に背景画像の設定が追加されます。 - テキストエリアへ PNG 画像の Base64 文字列を直接指定することもできます。 - 背景画像を使用しない場合は省略可能です。 3. **JSONエディターでの編集** - テキストエリア内に表示されている合成設定を直接編集できます。 - 合成ルールの詳細については、[合成のルールのドキュメント](/ja/docs/user-guide/livestreaming/composite)をご参照ください - 編集内容は常時プレビューに反映されます。 4. **format ボタン** - 画面下部の「**format**」ボタンをクリックすると、エディター内の JSON を整形できます。 5. **エラー時の挙動** - JSON の記述に誤り(シンタックスエラーなど)がある場合、プレビューは反映されません。 - エラーメッセージが表示された場合は、内容を確認して原因を特定してください。 --- ### 2.2. Input Publication 右側エリアの下部には、**映像・音声の入力(Publication)を管理**するためのリストが表示されています。 - **各 Publication の操作** - **published**: 0から49の数字が割り当てられたボタンをクリックするとボタンの色が反転し Channel に Publish されているものとして扱われ、合成対象に含まれます。 - **disabled**: チェックを入れると Publication の state パラメータが disabled になり、合成対象には含まれるが映像・音声はオフ(出力されない)状態になります。 - **detail**: パラメータ編集用のモーダルウィンドウを開きます(後述「2.4. detail 編集画面」参照)。 ### 2.3. Preview 右側エリアの上部では、**合成結果の見た目を確認するプレビュー画面**が表示されます。 - 左側の JSON エディターで編集した合成設定を元に、Input Publication の設定内容がリアルタイムに合成され、反映されます。 ### 2.4. detail 編集画面(モーダルウィンドウ) ![詳細編集モーダル](/media/posts/docs/preview2.png) 「detail」ボタンをクリックすると、対象の Publication に紐づくパラメータを編集する**モーダルウィンドウ**が表示されます。 以下のような項目を必要に応じて編集し、「Save」ボタンで確定します。 - **contentType**: メディアの種類(例: `Video`, `Audio`) - **originPublisherId**: 映像・音声を Publish した Person の ID - **originPublisherName**: 映像・音声を Publish した Person の Name - **originPublisherMetadata**: 映像・音声を Publish した Person の Metadata - **publicationId**: Publication の ID - **metadata**: Publication の Metadata --- ## 3. 基本的な操作の流れ 以下は、ツールを使って合成設定を作成・調整する際のおおまかな手順です。 ### 3.1. Composite Setting の編集 1. **プリセット選択**: presets から用途に近い合成設定を選択する(ゼロから作る場合は `plain` など)。 2. **JSON 編集**: 左側のテキストエリアで合成の設定を編集する。 3. **format ボタン**: 必要に応じて JSON を整形する。 ### 3.2. Input Publication の設定 1. 右側の「Input Publication」一覧で合成対象に含めたい映像・音声の Publication にて **"published"** ボタンをクリックする。 2. 一時的に映像・音声をオフにしたい場合は **disabled** にチェックを入れて出力をオフにする。 3. さらに詳細を設定したい場合は「detail」ボタンでモーダルを開き、各種パラメータを編集して「Save」。 ### 3.3. プレビューの確認 - 右側の「Preview」で、設定したレイアウトで合成した際の画面イメージを確認。 - Input Publication のオン/オフ変更や、detail モーダルでのパラメータ変更が想定通り反映されているかチェックする。 ### 3.4. 合成設定の確定・API への連携 - 合成の設定が固まったら、テキストエリアの JSON をコピーして **LiveStreaming API** で利用できます。 - 実際の合成結果はライブストリーミングサービスの配信画面などでご確認ください。 --- ## ユーザーガイド/SkyWay コンソール/概要 Path: user-guide_console-site_overview.md # 概要 SkyWay コンソールは、プロジェクトとそれに紐づくアプリケーションを一元管理し、SDK 利用に必要なシークレットキーの発行や利用状況の確認を行えます。 本章では、ログイン後の画面構成と操作のポイントを解説します。 ![console 概要](/media/posts/docs/console/console-overview.webp) ## 画面構成 ### ヘッダーメニュー(画面上部) - **プロジェクト切り替え**: 画面上部で操作対象のプロジェクトを選択・変更できます。 - **ユーザーメニュー**: 画面右上のアイコンからプロフィール編集やログアウトが可能です。 ### サイドバー(左メニュー) - **アプリケーション一覧**: 作成したアプリケーションを一覧表示します。 - **ご利用状況・料金**: 月次の通信量や課金額をグラフ・表で確認できます。 フリープランをご利用の場合、「ご利用状況」とのみ表示されます。 - **プロジェクト詳細**: プロジェクトの設定やメンバー管理、契約情報の確認ができます。 - **サポート**: 問い合わせやテクニカルサポート窓口へのリンクを提供します。 --- ## 新規プロジェクト作成方法 - 画面上部のプロジェクト切り替えメニューから **「プロジェクトを作成」** を選択します。 - プロジェクト名を入力し、作成をクリックします。 - プロジェクト名は後から変更可能です。 > ※ 新規に作成されるプロジェクトは、Free プラン(無料版)で開始されます。 --- ## アプリケーション一覧画面 ![予算アラート設定後の画面](/media/posts/docs/console/application-card.webp) 各アプリケーションは以下の情報を含むカードで表示されます。 | 項目 | 説明 | |----------------|------------------------| | **アプリ名** | サービスや用途に応じて付与した識別用の名称 | | **アプリケーションID** | UUID 形式の一意キー | | **シークレットキー** | SkyWay の利用に必要なシークレットキー | | **ご利用状況(当月)** | アプリケーションごとの利用料を表示 | | **詳細** | アプリケーション詳細画面へ移動 | | **Analytics** | Analytics ダッシュボードへ移動 | --- ## 新規アプリケーション作成手順 - 画面右上の **「アプリケーションを作成」** ボタンをクリック - 任意のアプリケーション名を入力し、作成をクリック - アプリケーション名は後から変更可能です。 - 作成後に表示される **アプリケーションID** と **シークレットキー** をコピーし、SDK で利用します。 > ※ アプリケーションは 1 つのプロジェクトにつき最大 100 件まで作成可能です。 --- ### 注意事項 - **シークレットキーの保管**: 第三者に漏洩しないよう、安全に管理してください。不正利用のリスクがあります。 - **権限管理**: プロジェクトメンバーへは、ロールに応じた閲覧・編集権限を付与できます。 - 詳細はこちらをご覧ください:[プロジェクト詳細](./user-guide/console-site/project-detail) - **Analytics**: 接続状況や通信状況など、詳細な分析には Analytics をご活用ください。 - 詳細はこちらをご覧ください:[Analytics 概要](./user-guide/analytics/overview) --- ## ユーザーガイド/SkyWay コンソール/ご利用状況・料金 Path: user-guide_console-site_usage.md ## ご利用状況・料金 プロジェクトの利用量および料金を確認できる画面です。 Free プランでは料金は発生しないため、利用量のみが表示されます。 Enterprise プランでは利用量に加えて、概算料金も確認できます。 ![ご利用状況・料金画面](/media/posts/docs/console/console-usage-enterprise.webp) --- ### 画面構成 左上に「概算料金 – SW20xxxxxx 」が表示されます。 SW20xxxxxx はご契約ごとに発行される契約IDです。 Enterprise プランのみ右上に当月の概算料金が表示されます。 > ※表示される金額はあくまで概算です。実際の請求額は請求書をご確認ください。 --- ### 概算料金セクション ![概算料金セクション](/media/posts/docs/console/console-price-section.webp) 概算料金セクションでは、当月の概算料金を内訳ごとに表示します。 概算料金セクションは Enterprise プランでのみ表示されます。 - 詳細な料金体系は、[料金ページ](https://skyway.ntt.com/ja/pricing/) をご参照ください。 --- ### ご利用状況 **接続回数/TURN・SFU通信量/稼働時間/SFU リソース確保時間/録音・録画・データ転送量/ノイズキャンセリング利用時間** を表形式で表示します。 ![ご利用状況](/media/posts/docs/console/console-application-usage.webp) --- ### 推移グラフ ![推移](/media/posts/docs/console/console-usage-graph.webp) 「ご利用状況」同様の過去 12 か月のトレンドを棒グラフで表示。 - 接続回数 - TURN 通信量 - SFU 通信量・リソース確保時間 - 録音・録画データ転送量 - ノイズキャンセリング利用時間 ## 予算アラート機能 Enterprise プランで利用できる「予算アラート」は、月次の概算料金が事前に設定した予算額に達した際に、プロジェクトオーナーおよび管理者にメール通知を行う機能です。 予算アラートの設定は、プロジェクトオーナーおよび管理者のみが行えます。 --- ### アラート設定の流れ - 画面右上の **「予算アラートの設定」** ボタンをクリック - モーダルが開くので、以下を入力・選択 - **予算**:アラートを発報するしきい値(金額/円) - **「設定」** をクリックして保存 - **「削除」**:既存アラートを削除 - **「キャンセル」**:変更を破棄してモーダルを閉じる ![予算アラート設定モーダル](/media/posts/docs/console/budget-alert-modal.webp) --- ### 設定後の表示 - 設定が完了すると、概算料金セクションに合計金額欄に「/ 予算額(予算)」が追記されます。 - 例:`141,642 円 / 140,000 円(予算)` ![予算アラート設定後の画面](/media/posts/docs/console/budget-alert-set.webp) --- ## ユーザーガイド/SkyWay コンソール/プロジェクト詳細 Path: user-guide_console-site_project-detail.md ## プロジェクト詳細 プロジェクト全体の設定・メンバー管理・請求情報などを一元管理できる画面です。 以下のセクションで構成されています。 ![プロジェクト詳細](/media/posts/docs/console/console-project-detail.webp) --- ### メンバー管理 - **メンバー一覧**: プロジェクトに所属するユーザーが一覧表示されます。 - 表示項目: - ユーザー名 - ロール - 操作: - **変更**:メンバーロールを編集できます。 - **ゴミ箱アイコン**:メンバーをプロジェクトから削除します。 - **メンバーを招待**: 画面右上の `メンバーを招待` ボタンをクリックすると、メールでユーザーを招待できます。 #### ロール権限一覧 | 機能カテゴリ | 機能項目 | オーナー | 管理者 | 開発者 | オペレーター | |----------------|--------------------|--------------|--------------|--------------|--------------| | **プロジェクト管理** | プロジェクトの新規作成 | ✔️ (全員付与)[1] | ✔️ (全員付与)[1] | ✔️ (全員付与)[1] | ✔️ (全員付与)[1] | | | プロジェクト編集/削除 | ✔️ | ❌[2] | ❌ | ❌ | | | プラン/請求情報の変更 | ✔️ | ❌ | ❌ | ❌ | | | メンバーのアカウント作成・編集・削除 | ✔️ | ✔️[3] | ❌ | ❌ | | | データ利用量・概算料金の確認 | ✔️ | ✔️ | ✔️ | ✔️ | | | 予算アラートの設定・受信 | ✔️ | ✔️ | ❌ | ❌ | | **アプリケーション管理** | アプリケーションの作成・編集・削除 | ✔️ | ✔️ | ❌ | ❌ | | | 利用/停止切替・シークレット再生成 | ✔️ | ✔️ | ❌ | ❌ | | | シークレットキーの確認 | ✔️ | ✔️ | ✔️ | ❌ | | | アプリ利用量の確認 | ✔️ | ✔️ | ✔️ | ✔️ | | **Analytics** | Analytics の確認 | ✔️ | ✔️ | ✔️ | ✔️ | | **アカウント管理** | メールアドレス変更 | ✔️ | ✔️ | ✔️ | ✔️ | | | パスワード変更 | ✔️ | ✔️ | ✔️ | ✔️ | | | 二段階認証用電話番号登録 | ✔️ | ✔️ | ✔️ | ✔️ | **脚注** - [1]プロジェクトを作成したアカウントに自動的にオーナー権限が付きます。 - [2]プロジェクト名の編集は可、プロジェクトの削除は不可です。 - [3]オーナー権限の付与はできません。 --- ### メンバー招待の手順 1. **メールアドレス入力**: 招待したいユーザーのメールアドレスを入力し、`+` ボタンをクリックします。 2. **ロール選択**: 招待するユーザーのロールを選択し、`確認` ボタンをクリックします。 3. **招待内容の確認・送信**: 内容を確認し、`送信` ボタンをクリックすると、対象ユーザーに招待メール(件名:【SkyWay】プロジェクトに招待されました / You've received invitation to project)が送信されます。 4. **招待承認**: 招待メール内のリンクから登録手続きを完了すると、メンバーとしてプロジェクトに参加できます。 --- ### 契約プラン - **現在のプラン**: 現在契約中のプラン名が表示されます。 - **プラン変更**: - `Enterpriseプランへ変更` ボタンから、Enterprise プランをご契約いただけます。 - `Freeプランへ変更` ボタンから、現在のプランを Free プランに変更できます。 --- ### 請求先情報 Enterprise プラン(有料版)のみ表示されます。 - **会社名・部署名・住所**: 請求書に記載される情報をまとめて表示。 - **編集**: `変更` ボタンから請求先情報を更新できます。 --- ### Analytics 利用状況 - **利用中/未利用**: Analytics の利用状況が表示されます。 - **利用開始**: `利用開始` ボタンをクリックすると、Analytics が有効化されます。 - Enterprise プランの場合は料金が発生します。 - **利用終了**: `利用終了` ボタンで Analytics を停止できます。 --- ### プロジェクト削除 - 画面最下部の **「プロジェクトを削除」** リンクから、プロジェクト全体を完全に削除できます。 - 削除操作は復元できないため、実行前に十分ご注意ください。 --- ## ユーザーガイド/SkyWay コンソール/サポートページ Path: user-guide_console-site_support.md ## サポート 「サポート」では、 以下の 2 つのサポート窓口を用意しています。 ![サポートページ](/media/posts/docs/console/console-support.webp) --- ### ご相談・お問い合わせ - **操作手順**: 1. 「フォームを開く」ボタンをクリックします。 2. 表示されたご相談・お問い合わせフォームに必要事項を記入・送信します。 --- ### テクニカルサポート > **※テクニカルサポートは Enterprise プラン(有料版)ご契約者のみご利用いただけます。** - **操作手順**: 1. 「チケットを作成する」ボタンをクリック 2. 問題の詳細や再現手順を記載のうえ、送信 --- --- ## ユーザーガイド/SkyWay コンソール/アカウント情報 Path: user-guide_console-site_account.md ## アカウント情報 ユーザー個人のアカウントに関する情報を確認・編集できる画面です。メールアドレスやパスワード、多要素認証の管理を行います。 ![アカウント情報](/media/posts/docs/console/console-account.webp) --- ### アカウント名 プロジェクト詳細画面のメンバー一覧などで使用されるアカウント名です。 `編集` ボタンから変更できます。 --- ### メールアドレス 登録済みのメールアドレスを確認できます。 SkyWay からのお知らせメールの受信設定も切り替え可能です。 `再設定` ボタンからメールアドレスの変更手続きを開始できます。 --- ### パスワード 安全のため伏せ字で表示されます。 `再設定` ボタンをクリックすると、パスワード変更用メールが送信されます。 --- ### 多要素認証(MFA) アカウントのセキュリティを強化するため、以下 2 種類の MFA をサポートしています。 アカウントの安全性向上のため、設定を強く推奨します。 | 認証方式 | 説明 | |-----------|------------------------------------------------------------------------------------| | 認証アプリ(推奨) | スマートフォンの認証アプリ(例:Google Authenticator など)でワンタイムコードを生成し、ログイン時に入力します。セキュリティが高く推奨されます。 | | SMS 認証 | 登録した携帯電話番号宛に送信されるワンタイムコードをログイン時に入力します。認証アプリが利用できない場合にご利用ください。 | - **設定解除**:不要になった認証方式を解除できます。再度有効化する場合は、もう一度設定を行ってください。 --- ### アカウント削除 画面最下部の **「アカウントを削除」** から、アカウントを完全に削除できます。 削除後はデータの復旧ができませんので、実行前にご注意ください。 --- ## ユーザーガイド/その他 共通仕様/通信要件 Path: user-guide_commons_communication-requirements.md # 通信要件 SkyWay を利用する際の通信要件は以下のとおりです。 ## コントロールプレーン ### RTC APIサーバー向け通信 | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP (https/wss) | 443 | rtc-api.skyway.ntt.com | コントロールプレーンの操作 | ## P2P ### シグナリングサーバー向け通信 | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP (https/wss) | 443 | signaling.skyway.ntt.com | P2P通信のためのシグナリング | ### クライアント端末同士のP2P通信 | 通信方向 | プロトコル | ポート番号 | クライアントアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアントA→クライアントB | UDP | 1024〜65535の間で動的に決まる | 動的に決まる | P2Pによるデータ、メディアの通信 | | クライアントB→クライアントA | UDP | 1024〜65535の間で動的に決まる | 動的に決まる | P2Pによるデータ、メディアの通信 | ## STUN/TURN STUN/TURNサーバーを利用する場合、まずクライアントはURL提供サーバーにリクエストを送り、STUN/TURNサーバーの接続先URLを取得します。 その後、取得したURLを使用して、STUNリクエストまたはTURN接続を開始します。 ### URL提供サーバー向け通信 | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP(https) | 443 | ice-params.skyway.ntt.com | STUNサーバー、TURNサーバーのURLを取得する | ### STUN/TURNサーバー向け通信 | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | UDP or TCP | 443 | *.skyway.ntt.com | 自分自身のグローバルIP、ポート番号を問い合わせる。また、P2P通信が確立できない場合に、通信を中継する。 | 通信相手の環境によっては、TURNサーバー向け通信時に一部の経路でUDP(ポート15000-65535)が使用される場合があります。 Enterpriseプランをご契約中でご希望の方には、TURNサーバーのIPアドレスリストをご提供いたします。 こちらの[お問い合わせフォーム](https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=19013322121113)からお申し込みください。 ## SFU ### SFUサーバー向けシグナリング用通信 | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP(https) | 443 | sfu.skyway.ntt.com | SFUサーバーとのシグナリング | ### SFUサーバー向けメディア用通信 | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | UDP or TCP | 33000-34000 | 動的に決まる | SFU向けのメディアの通信 | ## Analytics | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP (https/wss) | 443 | analytics-logging.skyway.ntt.com | 通信ログ収集サーバーに向けた通信ログの送信 | ## AI Noise Canceller | 通信方向 | プロトコル | ポート番号 | サーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP (https) | 443 | noise-cancelling.skyway.ntt.com | モデルダウンロードURLの取得とログの送信 | モデルダウンロードのために Google Cloud Storage へのリクエストが発生します。 --- ## ユーザーガイド/その他 共通仕様/対応コーデック Path: user-guide_commons_codecs.md # 対応コーデック ## 概要 SkyWay ではやりとりする音声や映像などのメディアのコーデックをユーザが指定できます。 音声コーデックは Opus がデファクトスタンダードとなっており、現状そのほかに選択の余地はありません。一方で、映像コーデックは VP8や H264などのさまざまなコーデックを(消費電力や映像品質などの)重視する特性やデバイスに応じて選択することで、ユーザのプロダクトに合わせた最適化を行うことができます。 ## 分類 SkyWay で利用可能なコーデックは、サポートコーデックと指定可能コーデックの2つに分類されます。 サポートコーデックはコーデックに重大な Issue が見つかった場合やブラウザ/SDK のアップデートの場合に動作検証を行い、問題があった場合に対応します。 指定可能コーデックはリリース時には動作確認を行いますが、サポートコーデックと違い定期的な動作検証を行いません。 また、P2P の場合と SFU の場合でも利用できるコーデックは異なります。 ## サポートコーデック ### P2P #### JavaScript/iOS/Android SDK共通 - 映像コーデック - H264 - VP8 - VP9 - 音声コーデック - Opus #### Linux®︎ SDK - 映像コーデック - VP8 - VP9 - 音声コーデック - Opus ### SFU #### JavaScript SDK - 映像コーデック - H264 - VP8 - 音声コーデック - Opus #### iOS/Android SDK - 映像コーデック - VP8 - 音声コーデック - Opus ## 指定可能コーデック ### P2P #### JavaScript/iOS/Android/Linux®︎ SDK共通 - 映像コーデック - AV1 - 音声コーデック - Opus/RED #### JavaScript - 映像コーデック - H265(Google Chrome 107以降/Safari 11以降/Microsoft Edge18以降などの一部ブラウザのみ。特定環境においては別途プラグインのインストールなどが必要。) ### SFU #### JavaScript SDK - 映像コーデック - VP9(サイマルキャストには非対応) ## 商標 Linux®︎は、米国およびその他の国における Linus Torvalds の登録商標です。 --- ## ユーザーガイド/その他 共通仕様/割り当てと制限 Path: user-guide_commons_quotas-and-limits.md # 割り当てと制限 SkyWay では、すべてのユーザーに公平かつ安定的にシステムをご利用いただくために、各アプリケーションが使用可能なリソース量に一定の割り当てを定め、リクエストの頻度に制限を設けております。 割り当てと制限は利用される SDK に関わらず、アプリケーション(アプリケーション ID)ごとに適用されます。 ## リソースの割り当て リソースの割り当ては、同時に利用可能なリソース(Room(Channel) と Member、Publication、Subscription)の数を定めるものです。 ユーザーはこれらの割り当て量を超えて、リソースを作成することはできません。リソースの割当を以下の表に示します。 ### Roomライブラリを使用する場合 | 割り当ての種類 | P2P Room | SFU Room | | --- | --- | --- | | Roomに同時に存在できるMemberの最大数 | 320 | 320 | | Roomに同時に存在できるPublicationの最大数 | 256 | 128 | | Roomに同時に存在できるSubscriptionの最大数 | 5120 | 5120 | | Roomに同時に存在できるRecordingSessionの最大数 | 256 | 256 | | 1つのMemberが同時にpublishできるPublicationの最大数 | 8 | 8 | | 1つのMemberが同時にsubscribeできるSubscriptionの最大数 | 128 | 128 | | 1つのPublicationに同時に紐づけられる(subscribeできる)Subscriptionの最大数 | 320 | Publication に設定した [maxSubscribers](./user-guide/sfu/#38) の値に準ずる | | 1つのRecordingSessionに同時に紐づけられるPublicationの最大数 | - | 256 | | 同時に録音・録画可能な1つのMemberのPublicationの最大数 | - | 4 | | アプリケーションごとに同時に存在できるLiveStreamingSessionの最大数 | - | 3 | ### Coreライブラリを使用する場合 | 割り当ての種類 | | | --- | --- | | Channelに同時に存在できるMemberの最大数 | 320 | | Channelに同時に存在できるPublicationの最大数 | 256 | | Channelに同時に存在できるSubscriptionの最大数 | 5120 | | Channelに同時に存在できるRecordingSessionの最大数 | 256 | | 1つのPersonが同時にpublishできるPublicationの最大数 | 8 | | 1つのPersonが同時にsubscribeできるSubscriptionの最大数 | 128 | | 1つのPublicationに同時に紐づけられる(subscribeできる)Subscriptionの最大数 | 320(SFU Bot ライブラリを利用の場合は Publication に設定した [maxSubscribers](./user-guide/sfu/#38) の値に準ずる) | | 1つのRecordingSessionに同時に紐づけられるPublicationの最大数 | 256 | | 同時に録音・録画可能な1つのPersonのPublicationの最大数 | 4 | | アプリケーションごとに同時に存在できるLiveStreamingSessionの最大数 | 3 | なお、Channel あたりの最大同時 Member、Publication、Subscription 数の制限について、 SFU Bot によって作られたリソースの数は含まれません。 また、Member あたりの最大同時 Publication 数について、SFU Bot は最大で128個まで同時に Publish することが可能です。 LiveStreamingSessionにおいて、DeleteLiveStreamingSessionのプロセスに時間がかかることがあります。この時プロセスが完了していないLiveStreamingSessionもLiveStreamingSession数のカウントの対象となります。 削除プロセス中にLiveStreamingSession数の最大値の制限によってPrepareLiveStreamingSessionのリクエストに失敗することがあります。その場合は削除の完了を待つため数分おいて再度リクエストを送信してください。 ## リクエストレートの制限 リクエストレートの制限は、SkyWay のサーバーに対して実行可能なリクエストの数を定めるものです。リクエストレートの制限は、以下の表に示す通りです。 | 制限 | 制限値 | | ------------------------------------------------- | ------ | | 1秒あたりの最大Channel作成リクエスト数 | 50 | | Channelごとの1秒あたりの最大リクエスト数 | 500 | | SkyWay Recording APIのアプリケーションごとの1秒あたりの最大リクエスト数 | 20 | | SkyWay LiveStreaming APIのアプリケーションごとのprepareSessionの1秒あたりの最大リクエスト数 | 1 | | SkyWay LiveStreaming APIのアプリケーションごとのstartSessionの1秒あたりの最大リクエスト数 | 20 | | SkyWay LiveStreaming APIのアプリケーションごとのgetSessionの1秒あたりの最大リクエスト数 | 20 | | SkyWay LiveStreaming APIのアプリケーションごとのupdateSessionの1秒あたりの最大リクエスト数 | 3 | | SkyWay LiveStreaming APIのアプリケーションごとのdeleteSessionの1秒あたりの最大リクエスト数 | 1 | Channel ごとの1秒あたりの最大リクエスト数は、Member の入室や退室、Metadata の変更の他、Channel 内の Publication や Subscription などのリソースに対するすべてのリクエストが対象となります。 この制限は、リクエストが SDK によるものか SkyWay Channel API によるものかに関わらず適用されます。 ## Channelの有効期限 Channel は作成した時点より7日間有効で、有効期限が延長されることはありません。 Channel の削除をせずに利用を続けて有効期限を迎えた場合、通話の途中で接続が切れる可能性があります。 これを防ぐために、同じ Channel を長期間使い続けるのではなく、ユーザーが居なくなったタイミングで Channel を削除するといった実装をおすすめします。 ## Channel、Memberのnameとして使用可能な文字種 Room(Channel)、Memberにはそれぞれ任意のnameを設定することができますが、使用可能な文字種・文字数に制限があります。 制限の内容は以下のとおりです。 | 制限の種類 | 制限の内容 | | ------ | -- | | 使用可能な文字種 | `a-z`, `A-Z`, `0-9`, `-`, `.`, `_`, `%`, `*` で構成された文字のみ | | 文字数 | 1~128文字(0文字は許容されない) | ただし、 `*` 単体を指定することはできません。 ## Channel、Memberのmetadataとして使用可能な文字種 Room(Channel)、Memberにはそれぞれ任意のmetadataを設定することができますが、使用可能な文字種・文字数に制限があります。 制限の内容は以下のとおりです。 | 制限の種類 | 制限の内容 | | ------ | -- | | 使用可能な文字種 | (アルファベット、数字、記号、日本語等を含む)任意の文字 | | 文字数 | 0~1024文字 | ## SkyWay Auth Tokenのサイズの制限 SkyWay Auth Tokenは、ヘッダー部、ペイロード部、署名部すべてを含めて、 7 KB 以下である必要があります。 ## RecordingSessionの有効期限 RecordingSession は作成した時点より最大7日間有効で、有効期限が延長されることはありません。 RecordingSession の削除をせずに利用を続けて有効期限を迎えた場合、録音・録画が終了し、終了時点までの録音・録画ファイルが保存されます。 なお、RecordingSession に紐づく Channel が削除された場合は、有効期限を待たずに RecordingSession も削除されます。 ## 録音・録画可能な時間の制限 録音・録画は最小1秒から最大12時間まで行うことができます。 - 開始から1秒以内に終了した録音・録画はファイルとして保存されません。 - 開始から12時間が経過した録音・録画は停止され、クラウドストレージに録音・録画ファイルが保存されます。 なお、以下の場合は、有効期限を待たずに録音・録画が停止されます。 - Publication が unpublish された場合 - Channel が削除された場合 - Channel の有効期限を迎えた場合 - RecordingSession の有効期限を迎えた場合 - 録音・録画中に回復不能なエラーが発生した場合 録音・録画の時間制限は、Publisher および RecordingSession に紐づいてカウントされます。時間制限は、録音・録画対象となる1つ目の Publication の録音・録画が開始されてからカウントが始まります。 録音・録画可能な時間のカウントは、以下のいずれかのタイミングでリセットされます。 - Publisher が leave した - 録音・録画対象となっている Publication が全て unpublish された - RecordingSession が削除された すでに録音・録画が行われている状況で、録音・録画対象となる2つ目以降の Publication が publish された場合でも、制限時間のカウントは継続されます。 したがって、 2つ目以降の Publication については、録音・録画が行われる時間の上限が12時間未満となります。 ![録音・録画可能な時間の制限の仕様](/media/posts/docs/recording-limit-1.png) 2つ目以降の Publication を時間の上限まで録音・録画したい場合は、以下のいずれかの対応が必要となります。 - 別の RecordingSession を作成し、新たに録音・録画を行う ![RecordingSessionを分けて録音・録画を行う](/media/posts/docs/recording-limit-2.png) - すでに録音・録画中の Publication を全て unpublish した後で、対象となる Publication を publish する ![録音・録画中のPublicationをunpublishしてから新たに録音・録画を行う](/media/posts/docs/recording-limit-3.png) # LiveStreamingSessionの有効期限 LiveStreamingSessionは以下の有効期限を持ちます。 - PREPARINGのステータスでは最大1時間 - StartLiveStreamingSessionのリクエストを送られてから12時間 PrepareLiveStreamingSessionのリクエストが送信されると、LiveStreamingSessionが作成され、最大1時間の待機状態に入ります。この間にStartLiveStreamingSessionのリクエストを送信することで、その時点から最大12時間の配信を開始することができます。 これらの有効期限を迎えた場合、配信は終了し、LiveStreamingSessionは削除されます。 --- ## ユーザーガイド/その他 共通仕様/RoomとP2PRoom・SFURoomの併用 Path: user-guide_commons_default-room.md # RoomとP2PRoom・SFURoomの併用 ## 概要 Room(type: default。以下 Room)は P2P 通信と SFU 通信を同一 Room 内で Publish することが可能な新しいタイプの Room です。 Room を使うことで、以下のユースケースに対応できます。 - 録音・録画(SFUを利用)と同時に P2P で通話を行う - SFU で通話をしながら P2P でデータ通信を行う - 人数に応じて P2P/SFU での通話を使い分ける Room は既存の P2PRoom および SFURoom と相互接続が可能であり、アプリ側で段階的に移行することが可能です。 ## Roomに対応しているSDK 2025年10月現在、Room に対応している SDK は以下の通りです。 - JavaScript SDK(v2.0.0以降) - iOS SDK(v3.1.0以降) - Android SDK(v3.3.0以降) ## P2PRoom/SFURoomとの違い P2PRoom/SFURoom は、作成した時点で通信方式が固定されます。 P2P と SFU を併用したい場合はそれぞれに対応する Room を作成する必要があり、SkyWay Auth Token や Member などのリソース管理が複雑になる問題がありました。 ```javascript // 通信方式を指定してP2PRoomを作成する const room = await SkyWayRoom.FindOrCreate(context, { type: "p2p" }); const me = await room.join(); // 通信方式は必ずP2Pになる await me.publish(audio); ``` 一方、Room は作成した時点ではなく、Publish 時に通信方式を指定します。 ```javascript // 作成時点では通信方式が固定されない const room = await SkyWayRoom.FindOrCreate(context); const me = await room.join(); // Publish時に通信方式指定する await me.publish(audio, {type: "p2p"}); // 同一Memberから異なる通信方式でPublishすることもできる await me.publish(video, {type: "sfu"}); ``` このように、1つの Room 内で P2P と SFU を柔軟に併用できます。 ## P2PRoom/SFURoomとの相互接続 SDK のアップデート時に、Room を利用するアプリと P2PRoom/SFURoom を利用するアプリが混在するケースがあります。 この場合、Room と P2PRoom/SFURoom を相互に接続することが可能です。 ただし、注意点があります。 Room に対応していないバージョンの SDK において SFURoom から Publish を行った場合、対向の Room からは Publication が2つあるように見えます。 そのため、イベントをハンドリングする際にフィルター処理を行う必要があります。 ### JavaScript SDK ```javascript // Publisher側(SFURoomを使用) const room = await SkyWayRoom.FindOrCreate(context, { type: "sfu", name: "room1" }); const me = await room.join(); await me.publish(audio); ``` ```javascript // Subscriber側(Roomを使用) const room = await SkyWayRoom.FindOrCreate(context, { name: "room1" }); const me = await room.join(); room.onStreamPublished.add((e) => { // P2PのPublicationの場合はスキップする if (e.publication.type == "p2p") return me.subscribe(e.publication) }); ``` ### iOS SDK ```swift // Publisher側(SFURoomを使用) let roomInit: Room.InitOptions = .init() roomInit.name = "room1" let room = try? await SFURoom.findOrCreate(with: roomInit) let me = try? await room?.join(with: nil) _ = try? await me?.publish(audio, options: nil) ``` ```swift // Subscriber側(Roomを使用) let roomInit: Room.InitOptions = .init() roomInit.name = "room1" self.room = try? await Room.findOrCreate(with: roomInit) self.room?.delegate = self self.me = try? await self.room?.join(with: nil) // MARK: - RoomDelegate func room(_ room: Room, didPublishStreamOf publication: RoomPublication) { if publication.type == .P2P { return } Task { try? await self.me?.subscribe(publicationId: publication.id, options: nil) } } ``` ### Android SDK ```kotlin // Publisher側(SFURoomを使用) val room = SFURoom.findOrCreate(name = "room1") val me = room?.join(memberInit) me.publish(audio) ``` ```kotlin // Subscriber側(Roomを使用) val room = SFURoom.findOrCreate(name = "room1") val me = room?.join(memberInit) room?.onStreamPublishedHandler = Any@{ // P2PのPublicationの場合はスキップする if (it.type == RoomPublication.Type.P2P) return@Any me.subscribe(it) } ``` ## 通信方式の切り替え Publish した後に通信方式を変更することはできません。 通信方式を切り替えたい場合は、一度 Unpublish してから再度 type を変更して Publish してください。 --- ## ユーザーガイド/用語集 Path: user-guide_terminology.md # 用語集 ## アプリケーション ユーザーが SkyWay を利用して提供するサービスです。SkyWay は1つのアプリケーションに対して1つのアプリケーション ID とシークレットキーを発行します。 ## Bot Core ライブラリにおいて特殊な目的を持って Channel に Join する、SkyWay サービス側が提供する Member です。現在は多人数通話や映像配信を実現するための SFU Bot が存在し、SkyWay はこれを個別のプラグインパッケージとして提供しています。 ## Channel Core ライブラリにおいて使用されるメディア通信を行うグループの単位です。Channel に含まれる Member は Channel 内にいる他の Member と映像/音声/データの送受信が出来ます。SFU Bot を用いることで大規模な双方向通信を行うことができます。 Channel は一意な識別子である ID と、オプショナルな値である Name を持ちます。ID は Channel 作成時に自動的に払い出される値であり、Name はユーザーが Channel を作成する際に指定できる任意の値です。アプリケーション内で重複した Name を指定することはできません。 ## Core ライブラリ 複数人で通信をするアプリケーションを作るためのライブラリです。 JavaScript SDK, Android SDK, iOS SDK それぞれに用意されています。Room ライブラリでカバーできないような、SkyWay によって提供される機能をより細かに制御し最大限に利用したいユースケースに向いています。 ## Forwarding ある Member の Publication を SFU Bot が Subscribe して、その内容を改めて SFU Bot が Publish することで、複数の Member に対して配信を行う操作です。 ## LiveStreamingSession LiveStreamingSessionは、配信機能における配信のセッションのことです。 一つのLiveStreamingSessionにつき一つの配信サービスのエンドポイントに配信できます。 ## Member Channel/Room 内で他のクライアントとの通信を管理するエージェントです。映像や音声を送信したり、受信したりすることが出来ます。Member は一意な識別子である ID と、オプショナルな値である Name を持ちます。 ID は Member 作成時に自動的に払い出される値であり、Name はクライアントが Channel/Room に Join する際に指定できる任意の値です。Channel/Room 内で重複した Name を指定することはできません。 Core ライブラリにおいて、Member は Person と Bot の2種類に分類されます。 ## メタデータファイル 録音・録画において、保存した録音・録画ファイルと対応してメタデータを提供しています。メタデータファイルからは録音・録画ファイルの詳しい情報を知ることができます。 ## Person Core ライブラリにおいてコミュニケーションを行うこと自体を目的とする Member です。通常のエンドユーザーが Member として Channel に Join する場合は Person に分類されます。 ## P2P Peer-to-Peer の略で、クライアント同士がサーバーを介さずに直接通信を行う方式です。SkyWay において P2P 方式の通信を行うと SFU 方式と比較してより低遅延で実現できますが、3 人以上での通信において端末のエンコード負荷や上り帯域幅、通信量が多くなるデメリットがあります。複数人通話においてクライアント同士が繋がりあう網目上のネットワークを形成することから、メッシュ方式とも呼ばれます。 ## Publish あるクライアントが映像、音声などの Stream を他の Member が受信可能にするために Channel/Room 内に公開する操作です。Stream を Publish すると Channel/Room 内に Publication というリソースが生成されます。また、Publish を行った Member のことを Publisher と呼びます。 ## Publication Publish の操作によって Channel/Room 内に作られるリソースです。同一 Channel/Room 内の Member はこれを選んで Subscribe することで Stream の受信ができます。どの Member がどのような形式の Stream を Publish しているかの情報が含まれています。 ## RecordingSession RecordingSessionは、どのような Publication をどのクラウドストレージに保存するのかの設定を保持するオブジェクトです。1つの Channel に対して複数作成できます。1つの RecordingSessionには、0個以上の Publication が含まれます。また、1つの RecordingSessionで設定可能なクラウドストレージは1つのみです。 ## Room Room ライブラリにおいて使用されるメディア通信を行うグループの単位です。Room に含まれる Member は Room 内にいる他の Member と映像/音声/データの送受信が出来ます。 通信方式を P2P と SFU の2種類から選択可能です。 Room は一意な識別子である ID と、オプショナルな値である Name を持ちます。ID は Room 作成時に自動的に払い出される値であり、Name はユーザーが Room を作成する際に指定できる任意の値です。アプリケーション内で重複した Name を指定することはできません。 ## Room ライブラリ 複数人で通信をするアプリケーションを作るためのライブラリです。JavaScript SDK, Android SDK, iOS SDK それぞれに用意されています。Core ライブラリと比較して、詳細な制御の部分は内包されて簡単に使用できるようになっています。 ## SFU Selective Forwarding Unit の略で、サーバーを経由して通信を行う方式です。3 人以上で通信する際には上りの通信の数を節約でき端末のエンコード負荷と上り帯域幅や通信量を削減できるため、P2P 方式よりも多人数での通話や映像配信を実現できます。 ## SFU Bot Member として Channel に Join し、各 Person から1本の上りトラフィックを受信し、他の Person に対して配信することで SFU 方式を実現する Bot です。 ## シグナリングサーバー 通信を開始する前に、IP アドレスやコーデックなど、通信に必要な情報を通信相手と交換するためのサーバーです。 ## サイマルキャスト 映像を SFU Bot に Forwarding する際、配信側のクライアントが複数のエンコード設定を指定する機能です。受信側クライアントは自身の通信品質に合わせた画質やフレームレートなど、適したエンコード設定の映像を受け取ることができます。 ## SkyWay Auth Token SkyWay Auth Token とは、利用するクライアントが正当であることを示し、クライアントに必要十分な権限を与えるための JWT(JSON Web Token)形式のトークンです。全てのクライアントは自身の権限に対応した SkyWay Auth Token を取得する必要があります。SkyWay Auth Token の発行にはアプリケーションのシークレットキーが必要となります。 ## SkyWay Channel API SkyWay Channel API は、Channel の作成と取得を行うための API です。JSON-RPC API として提供され、サーバーサイドアプリケーションからリクエストを送ることができます。 ## SkyWay LiveStreaming API LiveStreaming API は、ライブ配信の操作を行うための API です。REST API として提供され、SDK を介さずにリクエストを送ることができます。 ## SkyWay Recording API SkyWay Recording API は、録音・録画の操作を行うための API です。REST API として提供され、SDK を介さずにリクエストを送ることができます。 ## Stream 連続して送受信される一連のデータのことです。音声を送信する Audio Stream、映像を送信する Video Stream, 任意のデータを送信する Data Stream があります。 ## STUN サーバー Session Traversal Utilities for NAT の略で、NAT が存在する環境で P2P 通信を行う際に必要なグローバル IP およびポート番号を取得する操作を行うサーバーです。 ## Subscribe ある Channel/Room で Publish されている Stream の受信を開始する操作です。Stream を Subscribe すると Channel/Room 内に Subscription というリソースが生成されます。また、Subscribe を行った Member のことを Subscriber と呼びます。 ## Subscription Subscribe の操作によって Channel/Room 内に作られるリソースです。 どの Member がどの Publication を Subscribe しているかの情報が含まれています。 ## TURN サーバー Traversal Using Relays around NAT の略で、データを中継することで企業ネットワークなど P2P 通信が利用できない特定のネットワーク環境での WebRTC 利用を可能にするサーバーです。 ## ユーザー SkyWay を利用してサービスを提供する事業者です。これに対してアプリケーションを利用する人のことはエンドユーザーと呼びます。 --- ## クックブック/JavaScript SDK/画面共有 Path: cookbook_javascript-sdk_screen-share.md # 画面共有 画面共有の実装方法について説明します。 `SkyWayStreamFactory.createDisplayStreams` 関数を利用します。 > この関数はPCブラウザでのみ利用可能です。 - API リファレンス https://javascript-sdk.api-reference.skyway.ntt.com/room/classes/StreamFactory.html#createDisplayStreams ```javascript import {SkyWayStreamFactory} from '@skyway-sdk/room'; const { video } = await SkyWayStreamFactory.createDisplayStreams(); ``` また、引数のオプションで、音声の取得や、どの映像を取得するかの設定が可能になります。 ```javascript const { audio, video } = await SkyWayStreamFactory.createDisplayStreams( { audio: true, // 音声も取得する video: { displaySurface: 'monitor', // 画面全体の映像 } } ); ``` options 引数はそれぞれ次の値を渡すことができます。 - video - displaySurface : 共有開始前のポップアップにてデフォルトで選ばれている共有方式 - `"monitor"` : 画面全体 - `"window"` : 特定のウィンドウ - `"browser"` : 利用しているブラウザのタブ - audio - boolean(true/false) - `true` : `displaySurface: "browser"` の場合にそのタブが発している音声を返り値: audio で取得できます。 - 画面共有時のポップアップにて、「タブの音声も共有する」のトグルが利用可能になるのでユーザ側で操作が必要です。 - この機能は一部の PC 版ブラウザのみ利用可能です。最新の対応状態については以下のリンクを確認してください。 - [mdn web docs:ブラウザの互換性 - Audio capture support](https://developer.mozilla.org/ja/docs/Web/API/Screen_Capture_API/Using_Screen_Capture#%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%83%BC%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7) - `false` : タブが発生する音声の共有機能を利用しません。 - 画面共有時のポップアップにてトグルが出なくなります。 --- ## クックブック/JavaScript SDK/カメラ、マイクの選択 Path: cookbook_javascript-sdk_select-devices.md # カメラ、マイクの選択 ### デバイスのリストを取得する JavaScript SDKの `SkyWayStreamFactory` から提供される、以下のメソッドを利用します。 - `SkyWayStreamFactory.enumerateDevices()` - `SkyWayStreamFactory.enumerateInputVideoDevices()` - `SkyWayStreamFactory.enumerateInputAudioDevices()` - `SkyWayStreamFactory.enumerateOutputAudioDevices()` --- - APIリファレンス(Roomライブラリ) - [enumerateDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/room/classes/StreamFactory.html#enumerateDevices) - [enumerateInputVideoDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateInputVideoDevices) - [enumerateInputAudioDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateInputAudioDevices) - [enumerateOutputAudioDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateOutputAudioDevices) --- - APIリファレンス(Coreライブラリ) - [enumerateDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateDevices) - [enumerateInputVideoDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateInputVideoDevices) - [enumerateInputAudioDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateInputAudioDevices) - [enumerateOutputAudioDevices()](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/StreamFactory.html#enumerateOutputAudioDevices) 以下のコードで、映像入力、音声入力、音声出力のデバイスリストを取得できます。 ```javascript= import { SkyWayStreamFactory } from "@skyway-sdk/room"; // 全てのデバイスを取得 const devices = await SkyWayStreamFactory.enumerateDevices(); // 種類ごとにデバイスを取得 const videoInputDevices = await SkyWayStreamFactory.enumerateInputVideoDevices(); const audioInputDevices = await SkyWayStreamFactory.enumerateInputAudioDevices(); const audioOutputDevices = await SkyWayStreamFactory.enumerateOutputAudioDevices(); ``` 各メソッドを呼び出す前に `createMicrophoneAudioAndCameraStream()` 、`createMicrophoneAudioStream()` 、 `createCameraVideoStream()` のいずれかのメソッドを呼び出してデバイスの使用許可を取得すると、デバイス名を取得できます。 ユーザーがデバイスを区別できるように、デバイス名を表示してデバイスを選択する UI を実装することをお勧めします。 ### 入力デバイスを指定する 取得したデバイスの情報のうち、`deviceId` を用いて、入力デバイスを指定できます。 映像、音声のストリームを取得する際に、`deviceId` を指定することで、指定したデバイスからの映像・音声を取得できます。 ```javascript= const selectedVideoInputDeviceId = videoInputDevices[0].deviceId; const selectedAudioInputDeviceId = audioInputDevices[0].deviceId; const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ video: { deviceId: selectedVideoInputDeviceId }, audio: { deviceId: selectedAudioInputDeviceId } }); ``` ### 音声出力デバイスを指定する 音声出力デバイスを指定するには、`setSinkId()` メソッドを利用します。 ```javascript= const selectedAudioOutputDeviceId = audioOutputDevices[0].deviceId; const mediaElement = document.getElementById('media-element'); await mediaElement.setSinkId(selectedAudioOutputDeviceId); ``` - APIリファレンス - [HTMLMediaElement: setSinkId() メソッド - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/HTMLMediaElement/setSinkId) --- ## クックブック/JavaScript SDK/Canvas映像の利用 Path: cookbook_javascript-sdk_canvas-to-localstream.md # Canvas映像の利用 JavaScript SDKにおいて、Canvas要素からLocalStreamを作成する方法について説明します。 以下のコードで、Canvas要素に描画されている画像を元にLocalStreamを作成できます。 ```html ``` ```javascript= const sourceCanvas = document.getElementById('source-canvas'); const sourceCanvasStream = sourceCanvas.captureStream(30); // 30fpsでキャプチャ const sourceCanvasVideoTrack = sourceCanvasStream.getVideoTracks()[0]; const localStream = new LocalVideoStream(sourceCanvasVideoTrack); ``` `captureStream()` メソッドの引数には、キャプチャするフレームレートを指定できます。 `captureStream()` メソッドの詳細な挙動については、APIリファレンスを参照してください。 - APIリファレンス - [HTMLCanvasElement: captureStream() メソッド - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/captureStream) - [@skyway-sdk/room MediaStreamTrack から AudioStream / VideoStream を作成する](https://javascript-sdk.api-reference.skyway.ntt.com/room/index.html#md:mediastreamtrack-%E3%81%8B%E3%82%89-audiostream--videostream-%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B) --- ## クックブック/JavaScript SDK/ミュートの実装 Path: cookbook_javascript-sdk_enable-disable.md # ミュートの実装 ## 概要 SkyWay でマイクのミュート/アンミュートのような通信の一時的な停止と再開を実装する際に Publication.enable/disable を使うことができます。 Publication.disable()を実行すると Publication のメディア通信が一時的に停止され、Publication.enable()を実行するとメディア通信が再開されます。 Publication.enable/disable は Publication の subscribe/unsubscribe と違って、停止と再開が接続処理を行うことなく、即時に実行される利点があります。 Publication の停止状況は Publication.state プロパティから参照できます。Publication.state は以下の 3 つの状態を取ります。 - enabled - 配信中。state が disabled の時に enable()を実行すると state が enabled に変更される - disabled - 配信停止中。state が enabled の時に disable()を実行すると state が disabled に変更される - canceled - 配信終了。Publication が Unpublish されると state が canceled に変更される。 ## 制限 Remote の Publication を disable することは可能ですが、enable することはできません。 具体的なユースケースで例えると、話している相手を強制的にミュートさせることはできますが、ミュート中の相手を強制的にアンミュートさせることはできません。 ## サンプルコード JavaScript SDK の Room ライブラリによるサンプルコードを以下に示します。 ```ts const localVideoStream = await SkyWayStreamFactory.createCameraVideoStream(); const publication = await localMember.publish(localVideoStream); // カメラ映像の配信を一時的に停止する await publication.disable(); // カメラ映像の配信を再開する await publication.enable(); ``` --- ## クックブック/JavaScript SDK/100人規模の会議アプリを作る Path: cookbook_javascript-sdk_large-scale.md # 100人規模の会議アプリを作る 参加人数が多く、たくさんの映像を同時にディスプレイに表示するようなアプリケーションを実装する際に、いくつかの事項について注意する必要があります。 本記事では、サンプルアプリケーションを例として参照しつつ、以下の注意点について確認していきます。 - Room の種類の選択 - カメラの Stream の設定 - Publish のオプション - Subscribe のオプション - 同時に表示する映像の数 本記事では Room ライブラリを使用してアプリケーションを作成しています。 **サンプルアプリケーション** [https://github.com/skyway/js-sdk/tree/main/examples/large-room](https://github.com/skyway/js-sdk/tree/main/examples/large-room) ## Room の種類の選択 P2P による多人数通信はメディアの変換処理や通信効率の面で無駄が多く、パフォーマンスの制約が厳しく、大規模な利用には向いていません。 そのため大規模会議アプリを開発する際には必ず SFU Room を利用する必要があります。 SFU Room は次のように作成できます。 _App.tsx L21_ ```tsx const context = await SkyWayContext.Create(tokenString, contextConfig); const room = await SkyWayRoom.FindOrCreate(context, { name: roomName, type: "sfu", }); const member = await room.join(); ``` ## カメラの Stream の設定 大規模会議アプリケーションでカメラとマイクの Stream を取得する際の推奨する設定について説明します。 カメラとマイクの Stream は以下のように取得しましょう。 _App.tsx L30_ ```tsx const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ video: { height: 640, width: 360, frameRate: 15 }, }); ``` カメラの Stream については次のような設定を指定しています。 ```tsx { height: 640, width: 360, frameRate: 15 } ``` 解像度 640×360 15 fps というのは一般的な Web カメラの映像を表現する品質として十分なので、このレベルの画質を基準にカメラの Stream の設定をすることをおすすめします。 ## Publish のオプション Stream を Publish して Room 上に公開し、他のユーザーが Stream を受信できるようにします。 以下のように Stream を Publish しましょう。 _App.tsx L33_ ```tsx await member.publish(audio, { maxSubscribers: 50 }); await member.publish(video, { maxSubscribers: 50, encodings: [ { scaleResolutionDownBy: 4, id: "low", maxBitrate: 100_000 }, { scaleResolutionDownBy: 1, id: "high", maxBitrate: 400_000 }, ], }); ``` それぞれの設定項目について見ていきます。 ### maxSubscribers audio,video ともに適切な maxSubscribes の値を設定する必要があります。この値が会議の最大同時参加者の数となります。 ### サイマルキャスト サイマルキャストはクライアントがいくつかの異なる画質の映像を同時に公開できる機能です。 受信側はネットワーク環境に合わせて自動的に画質を選択するか、明示的に画質を選択して受信できます(動画配信サービスの画質選択設定と同じイメージです)。 自動的に画質を選択する場合は少し時間がかかるので、 大規模会議のような、予め高画質な映像を受信するとパフォーマンスに影響が出るとわかっている場合は、はじめから低画質設定を明示的に指定することでユーザー体験を改善できます。 サンプルアプリケーションでは Video の Stream を Publish する際に以下のようにエンコード設定を設定しています。 エンコード設定の設定の数は 2 つまでを推奨しています。3 つ以上設定したとしてもデバイスの負荷状況によっては 3 つ目以降の設定が無視されることがあるからです。 ```tsx encodings: [ { scaleResolutionDownBy: 4, id: "low", maxBitrate: 80_000, maxFramerate: 5 }, { scaleResolutionDownBy: 1, id: "high", maxBitrate: 400_000, maxFramerate: 30 }, ], ``` iOS/Android SDK においては maxFramerate を複数指定することはできません。 それぞれの設定項目について見ていきます。 - **scaleResolutionDownBy** - scaleResolutionDownBy では映像の元の解像度に対して何分の 1 の解像度に変換するかを指定できます。元の解像度が 640×360 の映像で仮に 4 を入れると 4 分の 1 の解像度である 160×90 の映像が公開されます。 - **maxBitrate** - 各エンコード設定のビットレートは maxBitrate の値(bps)に制限されます。 - **maxFramerate** - 各エンコード設定のフレームレートは maxFramerate の値に制限されます。 - iOS/Android SDK においては複数指定することはできません。 - **id** - 各エンコードの設定には識別用の ID を入れる必要があります。 次にサンプルアプリケーションが設定している値について詳細に解説します。 #### 解像度の設定(scaleResolutionDownBy) 会議の参加者が少ないうちは、映像の描画負荷は問題にはなりませんが、参加者が増えてくるとその負荷は無視できなくなってきます。 最大 50 人の会議アプリケーションでグリッド状に全参加者(自身を除いた 49 人分)の映像を表示する場合、ユーザーのデバイスの総描画解像度は以下のようになります。 ``` 7*640 = 4480 7*360 = 2520 4480x2520 ``` 一般的な PC のディスプレイの解像度は 1920×1080 であることが多く、総描画解像度がデバイスの解像度を大きく上回っており、デバイスに無駄な描画負荷がかかってしまいます。 そこで描画負荷を軽減するために多人数表示用のエンコード設定を追加する必要があります。 このサンプルアプリケーションでは多人数表示用のエンコード設定の scaleResolutionDownBy を 4 に設定しています。 これにより総描画解像度は以下のようになります。 ``` 7*640/4=1120 7*360/4=630 1120x630 ``` 動作対象とするクライアントデバイスの性能やアプリケーションの要件に合わせて scaleResolutionDownBy の値は調整してください。 常に総描画解像度がクライアントのデバイスの描画領域の解像度を上回ることのないようにすることが重要です。 #### ビットレートの設定(maxBitrate) 回線速度の低い環境で快適に大規模会議を利用できるようにするために、通信量について考慮する必要があります。 最大 50 人の会議アプリケーションで全参加者(自身を除いた 49 人分)の映像(400kbps)をユーザーのデバイスに表示する場合下りの通信量は以下のようになります。 ``` 400kbps * 49 = 19.6mbps ``` モバイル回線のような環境では 5mbps 程度の速度しかないことが想定されるので、 全参加者を表示した状態で正常にアプリケーションを利用できるようにするためにはビットレートを制限する必要があります。 このサンプルアプリケーションでは多人数表示用のエンコード設定の maxBitrate を 80kbps に設定しています。 これにより通信量は以下のようになります。 ``` 80kbps * 49 = 3.92mbps ``` 動作対象とするネットワーク環境に合わせて maxBitrate の値は調整してください。 クライアントのデバイスの通信帯域を上回る量の通信を行うと、輻輳が発生し、一時的に映像や音声が停止するなどの問題が発生するので、注意する必要があります。 モバイル端末上では同時に表示する映像の数を PC より少なくするなどの最適化を適宜行ってください。 #### フレームレートの制限(maxFramerate) 映像の滑らかさと描画負荷のバランスを取るために、フレームレートの制限も重要な設定となります。 フレームレートが高いほど映像は滑らかになりますが、それに伴い描画負荷も増えます。特に多人数の会議では、各参加者の映像を描画するための負荷が大きくなるため、適切なフレームレートの制限が必要となります。 このサンプルアプリケーションでは多人数表示用のエンコード設定の maxFrameRate を 5 に設定しています。 これにより、各参加者の映像は最大で秒間 5 フレームで表示され、描画負荷を抑えることができます。 動作対象とするクライアントデバイスの性能やアプリケーションの要件に合わせて maxFrameRate の値は調整してください。 フレームレートが高すぎると、デバイスの描画負荷が増え、パフォーマンスが低下する可能性があります。逆に、フレームレートが低すぎると映像が不自然に見える可能性があります。適切なバランスを見つけることが重要です。 ## Subscribe のオプション ビデオを Subscribe する際に `preferredEncodingId` に Publish した際に設定したエンコード設定の ID を指定することで指定した ID のエンコード設定で Subscribe できます。 サイマルキャストによる画質の自動選択にはラグがあり、大規模会議で全参加者の映像を表示する場合や、性能の低いモバイル端末でアプリケーションを利用する場合は、一時的に映像や音声が停止する可能性があります。 そういったケースでは、予め低画質設定を指定して Subscribe することで、問題を回避できます。 サンプルアプリケーションでは次のように低画質設定を選択して Subscribe するようにしています。 _App.tsx L63_ ```tsx const subscribe = async (publication: RoomPublication) => { if (publication.publisher.id !== member.id) { if (publication.contentType === "video") { await member.subscribe(publication, { preferredEncodingId: "low" }); } else { await member.subscribe(publication); } } }; ``` ### Subscription のエンコード設定の切り替え Stream を Subscribe した後、Subscription の映像のエンコード設定を切り替えたい場合は次のようにします。 _App.tsx L103_ ```tsx const switchEncodingSetting = async () => { if (subscription.preferredEncoding === "high") { subscription.changePreferredEncoding("low"); } else { subscription.changePreferredEncoding("high"); } }; ``` changePreferredEncoding で変更したいエンコード設定の ID を指定すると Subscription のエンコード設定がその設定にただちに切り替わります。 会議の参加人数に合わせて受信映像の品質設定を変更するなどの工夫を施すのも有効な手段です。 ## 同時に表示する映像の数 解像度やビットレートを抑制したとしても、大量の映像を同時にデコードするのは端末に対して大きな負荷がかかります。サービスがサポートする対象の端末の性能に合わせて同時に表示する映像の数は制限する必要があります。 オンライン研修などの数十人以上のユーザーがビデオをオンにして利用するケースで、ページネーションは端末への負荷を軽減する非常に有効な手段です。 数十人のうち同時に 4×4 人や 5×5 人を同時に表示し、それ以降のユーザーはページ変更ボタンを押した際に表示するよう実装することで端末の負荷を大幅に軽減できます。 ## まとめ この記事のサンプルアプリケーションで行った最適化事項についてまとめると以下のようになります。 - SFU を使う - カメラの解像度を用途に合わせて制限する - Publish のオプション指定 - maxSubscribers - 会議の最大参加者数を指定する - encodings - 最大参加者数に合わせて解像度を制限する (scaleResolutionDownBy) - 最大参加者数に合わせてビットレートを制限する (maxBitrate) - 設定の識別用の ID を設定する - Subscribe のオプション - 参加者数が多い場合やクライアントデバイスの性能が低い場合は、はじめから低い映像品質を指定する - ページネーションの実装を検討する クライアントデバイスの通信環境や処理性能を意識した設定を行うことが重要です。 --- ## クックブック/JavaScript SDK/ネットワーク切断のハンドリング Path: cookbook_javascript-sdk_connection-error-handling.md # ネットワーク切断時のハンドリング ## 一時的なネットワーク切断時のハンドリング 通話中に一時的なネットワーク切断が生じた際、JavaScript SDKは自動的に再接続処理を行います。 この時に生じる通信状態の変化を `Publication.onConnectionStateChanged` または `Subscription.onConnectionStateChanged` イベントから取得することができます。 ### Publicationの場合 ```js const publication = await localMember.publish(video); publication.onConnectionStateChanged.add(({state, remoteMember}) => { // 通信状態が変化した際に実施したい処理 }); ``` ※SFU をご利用の場合はremoteMemberを取得することはできません。 ### Subscriptionの場合 ```js const { stream, subscription } = await localMember.subscribe(publication.id); subscription.onConnectionStateChanged.add((state) => { // 通信状態が変化した際に実施したい処理 }); ``` 上記の例において、引数として渡される `state` が通信状態を示します。通信状態は以下のように変化します。 - connected - 接続が完了している状態 - reconnecting - 再接続処理中 - disconnected - 通信が切断され、再接続処理が行われない状態 reconnecting に遷移した後、再接続が完了した場合はconnetedの状態に戻ります。 reconnecting に遷移してから一定時間内に再接続が完了しない場合は disconnected に遷移し、再接続処理は行われない状態になります。その場合は[以下の項目の手順](/ja/docs/cookbook/javascript-sdk/connection-error-handling/#44)から再接続処理を行ってください。 `state` の持つそれ以外の状態に関しては https://javascript-sdk.api-reference.skyway.ntt.com/core/modules.html#TransportConnectionState を参照してください。 ## 長時間ネットワークが切断された場合(onFatalError)のハンドリング JavaScript SDK では短時間のネットワーク切断時に自動的に再接続処理を行っています。 しかし長時間ネットワークが切断されたなどの理由で `onFatalError` が呼ばれるとこの処理が中断されます。 再度接続を行いたい場合はアプリケーション側での実装が必要です。 - [JavaScript SDK リファレンス:onFatalError](https://javascript-sdk.api-reference.skyway.ntt.com/core/classes/SkyWayContext.html#onFatalError) `onFatalError` をうけて再接続を行う例を以下に示します。変数名はご自身のアプリケーションの定義で読み替えてください。 - `SkyWayContext.onFatalError` を受け取った場合、以下の操作を実施する - `SkyWayContext.dispose` で SkyWay の利用を停止する - `SkyWayContext.Create` を実施して SkyWay の利用を再開する - `SkyWayRoom.FindOrCreate` で以前入っていた Room を取得する( Room ライブラリを利用している場合) - `room.join` で Room に再参加する ```ts /* ... // 事前のroom.join処理 let context = await SkyWayContext.Create(tokenString); let room = await SkyWayRoom.FindOrCreate(context, { name: "YourRoomName", }); let me = await room.join(); ... */ context.onFatalError.add(async () => { context.dispose(); context = await SkyWayContext.Create(tokenString); room = await SkyWayRoom.FindOrCreate(context, { name: "YourRoomName", }); me = await room.join(); }); ``` --- ## クックブック/JavaScript SDK/ログの設定 Path: cookbook_javascript-sdk_log-config.md # ログの設定 SkyWayContext を作成する際に、SDK が出力するログのログレベルが設定できます。 ```ts const context = await SkyWayContext.Create(tokenString, { log: { level: "debug" } }); ``` 設定可能なログレベルと説明については、[JavaScript SDK リファレンス](https://javascript-sdk.api-reference.skyway.ntt.com/core/variables/logLevelTypes.html)を参照してください。 アプリケーション開発時は、不具合の調査やサポートとのやり取りを円滑に行うために、ログレベルを `debug` に設定することをおすすめします。 アプリケーションをプロダクションで運用する際は、ログレベルを `error` に設定することをおすすめします。 --- ## クックブック/JavaScript SDK/カメラ、マイクの切り替え Path: cookbook_javascript-sdk_change-device.md # カメラやマイクの切り替え replaceStream メソッド を使うことで、Stream を変更できます。 Publish後、 別のデバイスのカメラの Stream に入れ替えるサンプルコードを以下に示します。 ```js const devices = await SkyWayStreamFactory.enumerateInputVideoDevices(); const camera = await SkyWayStreamFactory.createCameraVideoStream({ deviceId: devices[0].id }); const publication = await person.publish(camera); const anotherCamera = await SkyWayStreamFactory.createCameraVideoStream({ deviceId: devices[1].id }); publication.replaceStream(anotherCamera); ``` --- ## クックブック/JavaScript SDK/リモートの Member を対象とした操作 Path: cookbook_javascript-sdk_remote-member-manage.md # リモートの Member を対象とした操作 リモートの Member を対象として、 subscribe 、Publication のミュート、metadata の更新の各操作を実行できます。 なお、リモートの Member を対象とした Publication のアンミュート、および publish の操作は実行できません。 リモートの Member を対象とした操作を行う際は、SkyWay Auth Token による適切な権限付与が行われている必要があります。 Member「alice」が Member「bob」に対して操作するケースを例に、SkyWay Auth Token による権限付与について説明します。 ## リモートの Member に Publication を subscribe させる alice が利用する SkyWay Auth Token の Member リソースに、以下の権限が付与されている必要があります。 ```javascript // SkyWay Auth Token version 1 または 2 の場合 scope: { app: { // 省略 channels: [ { // 省略 members: [ { name: "bob", // 省略 subscription: { actions: ["create"] } } ], }, ] } } ``` ```javascript // SkyWay Auth Token version 3 の場合 scope: { // 省略 rooms: [ { // 省略 member: { name: "bob", methods: ["subscribe"] } } ] } ``` SFU を利用している場合は、alice だけでなく bob が利用する SkyWay Auth Token においても SFU を利用するための権限が付与されている必要があります。 ## リモートの Member が publish している Publication をミュートする alice が利用する SkyWay Auth Token の Member リソースに、以下の権限が付与されている必要があります。 ```javascript // SkyWay Auth Token version 1 または 2 の場合 scope: { app: { // 省略 channels: [ { // 省略 members: [ { name: "bob", // 省略 publication: { actions: ["disable"] }, } ], }, ] } } ``` ```javascript // SkyWay Auth Token version 3 の場合 scope: { // 省略 rooms: [ { // 省略 member: { name: "bob", methods: [] } } ] } ``` ## リモートの Member の metadata を更新する alice が利用する SkyWay Auth Token の Member リソースに、以下の権限が付与されている必要があります。 ```javascript // SkyWay Auth Token version 1 または 2 の場合 scope: { app: { // 省略 channels: [ { // 省略 members: [ { name: "bob", actions: ["updateMetadata"], // 省略 } ], }, ] } } ``` ```javascript // SkyWay Auth Token version 3 の場合 scope: { // 省略 rooms: [ { // 省略 member: { name: "bob", methods: ["updateMetadata"] } } ] } ``` --- ## クックブック/JavaScript SDK/セキュアな運用のためのnameの指定の推奨について Path: cookbook_javascript-sdk_recommendation-for-using-name.md # セキュアな運用のためのnameの指定の推奨について Room リソースや Member リソースは、自動生成される ID とは別に、ユーザーがオプショナルな値として指定できる Name を持っています。SkyWay では、リソースを作成する際に Name の指定を推奨しています。 Room ライブラリによるサンプルコードを以下に示します。 ```javascript const room = await SkyWayRoom.Create(context, { name: "lesson-room-1", }); const me = await room.join({ name: "alice" }); ``` 各リソースの ID は、リソース作成後に払い出されます。 そのため、リソース作成時に利用する SkyWay Auth Token の `id` にはワイルドカード( `*` )を指定する必要があり、操作する対象のリソースを厳密に制限できません。 一方、各リソースの Name はリソース作成前にユーザー側で決めることができます。そのため、`name` を指定することで操作対象のリソースを厳密に制限した SkyWay Auth Token を作成できます。 以下のようにリソース作成より前に権限の認可条件を指定することで、よりセキュアにアプリケーションを運用できます。 ```javascript // SkyWay Auth Token version 1 または 2 の場合 scope: { app: { id: "sample-app-id", actions: ["read"], channels: [ { name: "lesson-room-1", actions: ["create", "delete"], members: [ { name: "alice", actions: ["create", "delete"], publication: { actions: ["create", "delete"] }, subscription: { actions: ["create", "delete"] } } ], }, ] } } ``` ```javascript // SkyWay Auth Token version 3 の場合 scope: { appId: "sample-app-id", rooms: [ { name: "lesson-room-1", methods: ["create", "close", "updateMetadata"], member: { name: "alice", methods: ["publish", "subscribe", "updateMetadata"] } } ] } ``` ## ワイルドカードの利用について SkyWay Auth Token における Room リソースや Member リソースの name プロパティには、ワイルドカードを利用することができます。 > ワイルドカードを利用する場合には version を 2 以上に設定する必要があります 例えば、student-room-1 や student-room-2 には入ることができるが、teacher-room-1 には入れない、という SkyWay Auth Token を上記の alice のために作成する場合は以下のように作成します。 ```javascript // SkyWay Auth Token version 2 の場合 { jti: "aa15ef18-08a4-4755-a9be-9f2d3351465e", iat: 1577804400, exp: 1577904400, version: 2, scope: { app: { id: "sample-app-id", actions: ["read"], channels: [ { name: "student-room-*", // ワイルドカードを利用 actions: ["create", "delete"], members: [ { name: "alice", actions: ["create", "delete"], publication: { actions: ["create", "delete"] }, subscription: { actions: ["create", "delete"] } } ], }, ] } } } ``` ```javascript // SkyWay Auth Token version 3 の場合 { jti: "aa15ef18-08a4-4755-a9be-9f2d3351465e", iat: 1577804400, exp: 1577904400, version: 3, scope: { appId: "sample-app-id", rooms: [ { name: "student-room-*", // ワイルドカードを利用 methods: ["create", "close", "updateMetadata"], member: { name: "alice", methods: ["publish", "subscribe", "updateMetadata"] } } ] } } ``` --- ## クックブック/JavaScript SDK/Next.js App Router の利用方法 Path: cookbook_javascript-sdk_nextjs-app-router.md # Next.js App Router の利用方法 Next.js App Router では、原則としてすべての React コンポーネントをサーバー(Node.js)側でプリレンダリングします。しかし、SkyWay の Room ライブラリはブラウザ上でのみ提供される `RTCPeerConnection` インターフェイスを内部で使用するため、サーバー側ビルド時に読み込むと以下のようなエラーが発生します。 ```jsx 'use client'; import { useEffect, useRef } from 'react'; import { SkyWayStreamFactory } from "@skyway-sdk/room"; export default function UserVideo() { const videoRef = useRef(null); useEffect(() => { (async () => { const media = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); videoRef.current && media.video.attach(videoRef.current); })(); }, []); return