# 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 方式よりも多人数での通信を可能にします。また、サイマルキャスト機能により、クライアントごとの通信環境に応じて自動的に品質を選択することで、快適な通信を実現できます。 詳しくは[こちら](./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 }, stt: { 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` が指定されたものとみなされます。 ### STT リソース 文字起こし(Speach-To-Text)の利用有無を設定するためのオブジェクト | プロパティ | 形式 | 必須 | 説明 | | ---------- | ------- | ---- | --------------------------------------------------------------------- | | enabled | boolean | ✔️ | 文字起こしの結果を受信できる | なお、STT リソースの指定が省略された場合は、 `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` が指定されたものとみなされます。 > SFU リソースの `enabled` を `false` に設定した場合、Room (type: default) もしくは SFURoom を利用しようとすると、権限不足によりエラーが発生します。 > 対処として、P2PRoom を利用するか、SFU リソースの権限を与えてください。 > Room の種類に関しては、[Room と P2PRoom・SFURoom の併用](/ja/docs/user-guide/commons/default-room/)のページを参照してください。 ### 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 の削除 > SFU Bot リソースの actions を設定しない場合、Room (type: default) もしくは SFURoom を利用しようとすると、権限不足によりエラーが発生します。 > 対処として、P2PRoom を利用するか、SFU Bot リソースの権限を与えてください。 > Room の種類に関しては、[Room と P2PRoom・SFURoom の併用](/ja/docs/user-guide/commons/default-room/)のページを参照してください。 ### 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 now = Math.floor(Date.now() / 1000); const payload = { iat: now, jti: crypto.randomUUID(), exp: now + 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 の全機能を利用できるライブラリです。 **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 というリソースが作成されます。 Publish 時には、通信方式を P2P と SFU から選択します。 - P2P は少人数向け - ユーザーが快適に通話できる人数は 4 人までです - SFU は多人数向け - P2P 通信に比べて多人数通話や映像配信に適しています - 詳しくは[こちら](./user-guide/sfu/)を参照してください ### 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 { SkyWayContext, SkyWayRoom, SkyWayStreamFactory } = 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 { SkyWayContext, SkyWayRoom, SkyWayStreamFactory } from "@skyway-sdk/room"; ``` #### アプリケーションの起動方法 tutorial ディレクトリで以下のコマンドを実行するとローカルサーバーが起動します。 ```sh npm run dev ``` ローカルサーバーのアドレスをブラウザで開くとアプリケーションが起動します。 ### アプリケーション ID とシークレットキーの取得 ※ SkyWay への登録がまだの方は、[SkyWay Console のサインアップページ](https://console.skyway.ntt.com/signup)から会員登録してください。 SkyWay コンソールへログインし、以下の 3 つを行います。 1. 「アプリケーションを作成」ボタンを押します。 ![Peer](/media/posts/docs/tutorial_1.png) 2. アプリケーション名を入力して作成ボタンを押します。 3. アプリケーション一覧からアプリケーション ID とシークレットキーをコピーします。 ### 開発環境向け API で初期化する このチュートリアルでは、開発環境向け API の `SkyWayContext.CreateForDevelopment(appId, secret)` を利用します。 そのため、`SkyWay Auth Token` の作成手順は省略します。 本ドキュメント内のコード上では、SkyWay コンソールで取得するシークレットキーを `secret` と表記します。 > 注意 > > この方法ではシークレットキー(`secret`)をクライアントコードに含めます。開発用途でのみ使用してください。 > 本番環境では、サーバーで発行した `SkyWay Auth Token` を `SkyWayContext.Create(token)` に渡してください。 > > 認証・認可について詳しくは、[認証・認可](/ja/docs/user-guide/authentication/)をご覧ください。 main.js に以下を入力し、取得したアプリケーション ID とシークレットキーを設定してください。 _main.js_ ```js const appId = "ここにアプリケーションIDをペーストしてください"; const secret = "ここにシークレットキーをペーストしてください"; ``` ### カメラ映像、マイク音声の取得 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 ボタンを押した際に実行されるイベントハンドラを作成し、この中に以降の処理を記載していきます。 先ほど設定した `appId` と `secret` を使って、`context` を作ります。 `context` とは、グローバルな情報を管理するオブジェクトです。認証・認可や、ログの設定などの情報を管理します。 また、このとき、`roomNameInput` が空の場合には、以降の処理が実行できないため、空かどうかのチェックを入れています。 _main.js_ ```js joinButton.onclick = async () => { if (roomNameInput.value === "") return; const context = await SkyWayContext.CreateForDevelopment(appId, secret); }; ``` 次に `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 のボタンが表示されます。そのボタンを押すと、相手の映像と音声が表示されるはずです。 このチュートリアルの実装はシークレットキー(`secret`)をクライアントに含むため、公開環境にはデプロイしないでください。 複数端末で検証する場合は、ローカルネットワークなどの開発環境で実施してください。 ライブラリの取得部分を除いたコード全体はこちらです。 _main.js_ ```js const appId = "ここにアプリケーションIDをペーストしてください"; const secret = "ここにシークレットキーをペーストしてください"; (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.CreateForDevelopment(appId, secret); 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 - Safari > macOSのSafari、もしくはiOSの各ブラウザでは、解像度の大きな映像に対して使用するとフレームレートが低下する可能性があります。該当の環境では、負荷軽減のため適切な解像度でのご利用をおすすめします。 ## インストール 以下のコマンドでインストールを行います。 ``` npm install skyway-video-processors ``` ## 使い方 以下の使い方の詳細は[Sample](https://github.com/skyway/skyway-video-processors/tree/main/examples/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/examples/simple) - [バーチャル背景を利用してSkyWayで通話するアプリケーション](https://github.com/skyway/skyway-video-processors/tree/main/examples/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, }); ``` ## Google Chrome M143 において サイマルキャスト利用時に映像を複数回 Publish できない Google Chrome M143 において、SFU を用いてサイマルキャストを有効にした映像を Publish/Unpublish 後に再度 Publish しようとすると失敗する事象が確認されています。 JavaScript SDK v2.2.1 に本事象の対応が含まれています。 2.2.0以下のバージョンをご利用のお客様につきましては、最新の SDK をご利用いただくようお願いいたします。 なお、Google Chrome 143.0.7499.147 以上のバージョンをご利用の場合は、JavaScript SDKのバージョンによらず本事象は発生しません。 --- ## ユーザーガイド/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 に参加したユーザー同士で通話を行います。 ### Member Room に参加しているユーザーのことを Member と呼びます。 ### Stream Room 上で送受信できるメディアのことを Stream といいます。以下の 3 種類の Stream を利用できます。 - AudioStream - ユーザーのマイク音声など - VideoStream - ユーザーのカメラ映像など - DataStream - 任意のメッセージ - SFUを用いた通信では利用できません ### Publish Member が Stream を Room に公開することを Publish といいます。 Stream を Publish すると Room 上に Stream に対応する Publication というリソースが作成されます。 Publish 時には、通信方式を P2P と SFU から選択します。 - P2P は少人数向け - ユーザーが快適に通話できる人数は 4 人までです - SFU は多人数向け - P2P 通信に比べて多人数通話や映像配信に適しています - 詳しくは[こちら](./user-guide/sfu/)を参照してください ### 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 に参加したユーザー同士で通話を行います。 ### Member Room に参加しているユーザーのことを Member と呼びます。 ### Stream Room 上で送受信できるメディアのことを Stream といいます。以下の 3 種類の Stream を利用できます。 - AudioStream - ユーザーのマイク音声など - VideoStream - ユーザーのカメラ映像など - DataStream - 任意のメッセージ - SFU を用いた通信では利用できません ### Publish Member が Stream を Room に公開することを Publish といいます。 Stream を Publish すると Room 上に Stream に対応する Publication というリソースが作成されます。 Publish 時には、通信方式を P2P と SFU から選択します。 - P2P は少人数向け - ユーザーが快適に通話できる人数は 4 人までです - SFU は多人数向け - P2P 通信に比べて多人数通話や映像配信に適しています - 詳しくは[こちら](./user-guide/sfu/)を参照してください ### 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/rooms/${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/rooms/${room.name}/start`, { method: 'POST', headers: { Authorization: context.authTokenString, }, }); }; stopRecordingButton.onclick = async () => { await fetch(`http://localhost:9090/rooms/${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/#94)の項目を参照してください。 --- ## ユーザーガイド/録音・録画/開発ガイド 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/#365) ```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/#65)の項目を参照して正しいクレデンシャルを使用してください。 #### 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~1rooms~1%7BroomId%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://docs.wasabi.com/docs/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://docs.wasabi.com/docs/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 ``` ## 安全な実装方法 録音・録画機能を安全に利用するための注意事項について説明します。 録音・録画機能を利用する際に扱う識別子として以下があります。 - Room ID - Publication ID - Publisher ID - Publisher Name これらの 識別子 をクライアントサイドからサーバーサイドアプリケーションへ送信して録音・録画を開始する場合、送信者が 識別子 を差し替えることで意図しない Publication が録音・録画される可能性があります。 これらの 識別子 をクライアントサイドから取得する代わりに次の手法を採用することを検討してください。なお、ここで使用する SkyWay Room API の詳細な仕様は次の記事を参照してください。 [SkyWay Room API](/ja/docs/user-guide/rtc-api-server/room-api) ### サーバサイドで createRoom を行う createRoom をクライアントサイドではなくサーバーサイドアプリケーションで SkyWay Room API を用いて実行することで常に正しい Room ID を得る事ができます。 次のようにして SkyWay Room API で createRoom を行うことができます。 ```bash curl -X POST https://room.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": "createRoom", "params": { "name": "YOUR_ROOM_NAME" }}' ``` ### Room Name から Room ID を取得する サーバーサイドアプリケーションで Room Name を払い出している場合に有効な手法です。 Room Name はサーバーサイドアプリケーションで払い出すことができるため、 SkyWay Auth Token でその Room Name を指定していれば、Room Name は信用できる情報として扱うことができます。 また、SkyWay Room API の findRoom で name として Room Name を指定することで、Room ID を取得できます。 次のようにして SkyWay Room API で Room Name から Room ID を取得できます。 ```bash curl -X POST https://room.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": "findRoom", "params": { "name": "YOUR_ROOM_NAME" }}'\ | jq -r '.result.room.id' ``` ### サーバサイドアプリケーションで Publication のリストを取得する 録音・録画対象の Publication の ID を指定する場合はクライアントサイドからサーバーサイドアプリケーションに Publication の ID を送信するのではなく、SkyWay Room API を用いて信用できる Publication のリストを取得することをおすすめします。 次のようにして SkyWay Room API で Publication のリストを取得できます。 ```bash curl -X POST https://room.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": "findRoom", "params": { "name": "YOUR_ROOM_NAME" }}' \ | jq -r '.result.room.publications' ``` ## RecordingSession の管理 RecordingSession が作成されると、以下のいずれかの状況になるまでは録音・録画の処理が継続され、指定されたクラウドストレージに対して録音・録画ファイルの送信が行われます。 - RecordingSession が削除される - RecordingSession は、以下のいずれかのタイミングで削除されます - DeleteRecordingSession API を呼び出す - RecordingSession 作成から 7 日間が経過する - RecordingSession と紐づく Room(Room)が削除される - 録音・録画対象となっている Publication が unpublish される - Publication は以下のいずれかのタイミングで unpublish されます - 明示的に SDK の unpublish の処理を実行する - 当該の Publication を publish している Member が Room(Room) から退出する - 当該の Publication が存在する Room(Room) が削除される - 録音・録画開始から 12 時間経過する これらのうち、サーバーサイドアプリケーションから録音・録画処理を停止させる方法は、DeleteRecordingSession API を利用し、RecordingSession の削除を行うことです。 したがって、DeleteRecordingSession API を利用するための、以下の情報は適切に管理・記録する必要があります。 - Room 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; startedAt: string; codecs: string[]; duration: number; sessionId?: string; publisher: { name?: string | undefined; id: string; }; publications: { id: string; publisherId: string; publisher: { name?: string | undefined; id: string; }; contentType: "AUDIO" | "VIDEO"; }[]; } ``` メタデータのフィールドの詳細について説明します。 | キー名 | フィールドの詳細 | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | name | クラウドストレージにおけるファイル名 | | path | クラウドストレージにおけるパス | | status | 録音・録画ファイルの状態 | | errors | status が FAILED の場合の原因となったエラー | | type | ファイルに含まれるメディアの種類 | | mimeType | ファイルの種別 | | startedAt | ファイルの最初の部分が作られた日時(ISO 8601 形式)。このタイムスタンプを元にして、映像・音声の合成の際に再生タイミングを調整できる。 | | codecs | ファイルに含まれるメディアのコーデックのリスト | | duration | ファイルの再生時間(ミリ秒) | | sessionId | このファイルの session の ID。クラウドストレージ上の json ファイルにのみ含まれる。 | | publisher | このファイルの元になっている 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 時にサイマルキャストの設定をしている場合、Recording API の createSession 時に、録画ファイルの画質を指定することができます。 詳細についてはAPIリファレンスの [options](https://recording.api-reference.skyway.ntt.com/#/paths/~1v1~1rooms~1%7BroomId%7D~1sessions/post) の videoSimulcastQuality フィールドをご参照ください。 ## 録音・録画ファイルの再生について 録音・録画機能で生成されるWebMファイルの再生について、SkyWayとして動作確認している環境はPC版Google Chromeです。 再生できない場合などの問題が発生した場合は、まずPC版Google Chromeで再生できるかをご確認ください。 なお、Amazon S3、またはWasabiに保存したWebMファイルは、修復処理を行わないとシークできない場合があります。 詳細は[録音・録画ファイルの修正方法](/ja/docs/user-guide/recording/recording-development-guide/#291)をご参照ください。 --- ## ユーザーガイド/録音・録画/既知の問題 Path: user-guide_recording_issues.md # 既知の問題 ## 録音・録画が一時的に中断される Recordingサーバーのエラーにより、進行中の録音・録画が一時的に中断される場合があります。その際の主な事象と対応方法は以下の通りです。 ### **主な事象** - **録音・録画の中断** Recordingサーバーのエラーにより、最大で約10秒間、録音・録画が中断される可能性があります。 - **自動再開とファイル分割** 中断後、録音・録画は自動的に再開されますが、再開後のデータは別のファイルとして保存されます。 --- ### **中断されたファイルの確認方法** - [`GetRecordingSession API`](https://recording.api-reference.skyway.ntt.com/#/paths/~1v1~1rooms~1%7BroomId%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/#192)をご参照ください。 --- ### **中断されたファイルの取り扱い** - 中断されたファイルのメタデータの`duration`フィールドは **0** となります。 - 正確な再生時間を知るには、録音・録画ファイル自体を直接確認する必要があります。 - 再生時間を確認する際に、録音・録画ファイルの修正が必要になるので開発ガイドの[**録音・録画ファイルの修正方法**](/ja/docs/user-guide/recording/recording-development-guide/#291)をご参照ください。 ## サイマルキャスト利用時の録画ファイルの映像の乱れ サイマルキャストを利用していて、 `videoSimulcastQuality` の値をhighに設定している場合、録画ファイルの映像が乱れることがあります。 映像の乱れを解消するには以下のいずれかの方法をお試しください。 - `videoSimulcastQuality` の値を未指定、または low に設定する - クライアント側の使用しているネットワークがモバイルデータ通信などの上り通信帯域幅が不十分な環境である場合、より通信帯域幅が広いネットワークに接続する --- ## ユーザーガイド/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 / Safari の
「安定版の最新 2 メジャーバージョン」 | | iOS SDK | iOS 14, iPadOS 14 以降 | | Android SDK | Android 5.0 Lollipop(API Level 21)以降 | なお、スマートフォン上では JavaScript 版の方が iOS / Android 版よりも CPU 使用率が大きくなる可能性があります。 ## サンプリングレートの仕様 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 / Safari ## 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, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨 } }); ``` 追加後は下記のような実装になっているはずです。 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, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨 } }); 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, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨 // } // }); // を以下に置き換える const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({ audio: { echoCancellation: false, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨 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, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨 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 # 開発ガイド ## TypeScript で AI Noise Canceller を利用する際の注意点 TypeScript を用いた開発に AI Noise Canceller を利用する場合、連携しようとする JavaScript SDK のバージョンによって型の互換性に伴うエラーや警告が表示される場合がございます。 対処が必要な場合は、AI Noise Canceller もしくは JavaScript SDK のどちらかを、他方に適合するバージョンへ更新してご利用ください。 なお、AI Noise Canceller と連携可能な JavaScript SDK のバージョンは、[リリースノート](https://github.com/skyway/ai-noise-canceller/releases)からご確認いただけます。 ## 未対応ブラウザ利用時のハンドリング AI Noise Canceller は Chrome / Edge / Safari で動作します。 それ以外のブラウザはサポートしておらず、動作保証はありません。そのため、動作するかどうかは、`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({ noiseSuppression: 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, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨 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` が呼び出され課金集計処理が止まります。 ## マイクの切り替え 現在 Stream の変更機能は提供しておりません。 マイクの切り替えに伴う Stream の変更が必要な場合は、新たにインスタンスを生成してご利用ください。 ```typescript // インスタンス化と初期化を行う関数 const setupNoiseCanceller = async (context: SkyWayContext, audio: LocalAudioStream): Promise< [noiseCanceller: SkyWayNoiseCanceller, processedAudio: LocalAudioStream] > => { const noiseCanceller = new SkyWayNoiseCanceller(context); return new Promise((resolve) => { noiseCanceller.onReady(async () => { const processedAudio = await noiseCanceller.connect(audio); resolve([noiseCanceller, processedAudio]); }); noiseCanceller.onFatalError(...); noiseCanceller.init(); }); }; const devices = await SkyWayStreamFactory.enumerateInputAudioDevices(); // 元のマイクの音声を Publish const audio = await SkyWayStreamFactory.createMicrophoneAudioStream({ deviceId: devices[0].id, noiseSuppression: false, }); let [noiseCanceller, processedAudio] = await setupNoiseCanceller(context, audio); const publication = await person.publish(processedAudio); // 別のマイクへの切り替え const anotherAudio = await SkyWayStreamFactory.createMicrophoneAudioStream({ deviceId: devices[1].id, noiseSuppression: false, }); const [anotherNoiseCanceller, anotherProcessedAudio] = await setupNoiseCanceller(context, anotherAudio); // 新たに SkyWayNoiseCanceller を初期化する publication.replaceStream(anotherProcessedAudio); // 元の SkyWayNoiseCanceller を破棄する noiseCanceller.dispose(); noiseCanceller = anotherNoiseCanceller; ``` ## ノイズ抑制強度(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/)のページをご参照ください。 ## ログの設定 SkyWayNoiseCanceller のインスタンスを生成する際に、出力されるログのログレベルを設定できます。 ```typescript noiseCanceller = new SkyWayNoiseCanceller(context, { logLevel: 'debug', }); ``` 設定可能なログレベルは、[APIリファレンス](https://javascript-sdk.api-reference.skyway.ntt.com/ai-noise-canceller/types/SkyWayNCOptions.html#loglevel)を参照してください。 なお、ログレベルを指定しなかった場合は`error`に設定されます。 アプリケーション開発時は、不具合の調査やテクニカルサポートとのやり取りを円滑に行うために、ログレベルを `debug` に設定することをおすすめします。 アプリケーションをプロダクションで運用する際は、ログレベルを `error` に設定することをおすすめします。 ## 開発用リポジトリでの利用 以下のようなユースケースにより開発用のリポジトリで本ライブラリを利用したい場合、インストール用スクリプトを活用したワークフローをご使用ください。 - 公開リポジトリで開発しているソフトウェアで利用したい場合 - 常に最新版のライブラリを用いてテストを実行したい場合 --- ## ユーザーガイド/AI Noise Canceller/JavaScript 版/既知の問題 Path: user-guide_ai-noise-canceller_javascript_issues.md # 既知の問題 ## Safari で利用する場合において init 実行中にタブを切り替えると初期化が完了しない 本ライブラリは SkyWayNoiseCanceller.init 実行時に [AudioContext](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) を生成し、 AudioContext.resume することで音声を扱う準備をします。 しかし、現在 iOS / macOS それぞれの Safari において、別のタブがアクティブになったことでアプリケーションがバックグラウンド状態にあるときに resume が完了しないという動作が確認されております。 そのため、本ライブラリの初期化中に別のタブがアクティブになると、処理が完了しない状態となります。 この問題を回避するには、 アプリケーションがフォアグラウンド状態にあるときに init が実行されるようにしてご利用ください。 なお、上記の問題により初期化が完了していない状態の期間は、課金は発生いたしません。 アプリケーションのタブが再びフォアグラウンドに移行した場合、そのタイミングで初期化が完了し、onReady が発火します。 --- ## ユーザーガイド/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() の呼び出し時のみ設定可能ですので、ご注意ください。 --- ## ユーザーガイド/Webhook/概要 Path: user-guide_webhook_overview.md # 概要 本機能は、SkyWay で発生したイベントをユーザーのサーバーに通知する機能です。 Webhook 機能を利用すると、ユーザーのサーバーで HTTP 経由でイベント通知を受け取ることができます。 Room のイベント(Room の作成、Member の参加、Stream の Publish、Publication の Subscribe など)、録音・録画のイベント(録音・録画の開始、終了など)についてのイベント通知を提供しています。 ## ユースケース 次のようなユースケースでご利用いただくことを想定しています。 **Room内のアクティビティをリアルタイムに監視する** - Room の作成・参加・退出、Publish・Subscribe といったイベントを Webhook で受信することで、各 Room への入室・通話開始等の状況をサーバー側から検知できます。 **録音・録画の結果をリアルタイムに受け取る** 従来では GetRecordingSession API をポーリングすることでしかリアルタイムの状況を取得できませんでした。Webhook を用いることで、よりリアルタイムかつレート制限に触れず、効率的に録音・録画の結果を取得できます。 **イベント駆動で実行したい処理の自動化** - Room のイベントを用いて、特定のユーザーの入室に合わせて、管理システムへ通知し、参加者への自動メッセージ送信、録音・録画の開始、課金処理の開始などのシステマティックな対応が可能になります。 **分析目的でのイベントログの記録** - Webhook で受信したイベントを蓄積することで、利用状況の分析やトラブルシューティングに活用できます。 ## 利用の流れ 利用するには、Webhook のリクエストを受信するためのサーバーの実装と SkyWay コンソールでの設定が必要になります。 詳細については[クイックスタート](/ja/docs/user-guide/webhook/quickstart)及び[仕様](/ja/docs/user-guide/webhook/specification)をご確認ください。 ## 通信要件 Webhook 機能の通信要件については、[通信要件](/ja/docs/user-guide/commons/communication-requirements/#79)をご確認ください。 --- ## ユーザーガイド/Webhook/クイックスタート Path: user-guide_webhook_quickstart.md # クイックスタート 本チュートリアルでは、Webhook を使用してリアルタイムで Room に関してのイベント通知を受信するための基本的な手順を説明します。 ## 前提条件 - 本チュートリアルではイベント通知を受信するサーバーとして Node.js (推奨: v20以上) を利用します。他の言語を利用する際は、適宜読み替えてください。 - [JavaScript SDKのクイックスタート](https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/quickstart/) が実施できる程度の SkyWay に関する知識が必要です。 - HTTPS エンドポイントをインターネットに公開できる環境を必要とします。 ## 環境構築 Node.js のバージョン 20 以降をインストールし、任意の作業ディレクトリに tutorial ディレクトリを作成します。 tutorial ディレクトリ直下に以下の内容の `package.json` ファイルを作成します。 ```json { "name": "skyway-webhook-demo", "version": "1.0.0", "type": "module", "scripts": { "start": "node server.js" }, "dependencies": { "express": "^4.18.0" } } ``` その後、tutorial ディレクトリでターミナルを開き `npm i` コマンドを実行してください。 ### 署名用共通鍵の準備 Webhook 機能を利用開始するためには署名用共通鍵を用いた署名検証が必要です。署名検証の流れは以下の通りです。 ![Webhook設定画面](/media/posts/docs/webhook-sequence.webp) Webhook の署名検証には、暗号学的に安全な乱数署名用共通鍵が必要です。ローカルで安全な鍵を生成してください。 ここでは Node.js の crypto を使用します。 ターミナルで、以下のコマンドを実行してください。 ```bash # 32バイト(256ビット)のランダム文字列を生成 node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` **重要**: - 32バイト(64文字)を推奨します - 推測しやすい文字列(辞書の単語、連続する数字等)は使用しないでください ## リクエスト検証用の機能 SkyWay コンソールでの設定時に、サーバーが正しく動作するかを確認するための検証リクエストが送信されます。まず、この検証に対応する基本的なサーバーを作成します。 `server.js` ファイルを作成し、以下の内容を記述します。 ```jsx // server.js import express from "express"; import { createHmac, timingSafeEqual } from "node:crypto"; const app = express(); app.use(express.json()); // 先ほど生成した署名用共通鍵をここに設定 const SIGNING_SECRET = ""; // 署名検証関数(検証リクエスト用) function verifySignature(signingSecret, body, timestamp, receivedHex) { // 1) タイムスタンプ検証(リプレイ防止) const tsNum = Number(timestamp); if (!Number.isFinite(tsNum)) return false; const ageMs = Math.abs(Date.now() - tsNum * 1000); // クイックスタートでは60秒の許容範囲を設定 if (ageMs > 60 * 1000) { return false; } // 2) HMAC を計算 const payload = `${body}${String(timestamp)}`; const expectedHex = createHmac("sha256", signingSecret).update(payload).digest("hex"); // 3) 比較(暗号学的に安全な比較) const expBuf = Buffer.from(expectedHex, "hex"); const recBuf = Buffer.from(receivedHex, "hex"); if (expBuf.length !== recBuf.length) return false; return timingSafeEqual(expBuf, recBuf); } // Webhookエンドポイント app.post("/webhook", (req, res) => { const body = JSON.stringify(req.body); const timestamp = req.headers["x-skyway-request-timestamp"]; const signature = req.headers["x-skyway-signature"]; console.log("Received request:", body); // 検証リクエスト処理 if (req.body.type === "WEBHOOK_URL_VERIFICATION") { const challenge = req.body.data?.challenge; // 必要なパラメータの存在チェック if (!challenge || !timestamp || !signature) { console.log("❌ Bad Request: Missing required parameters"); return res.status(400).send("Bad Request: Missing required parameters"); } // 署名検証を実行 if (verifySignature(SIGNING_SECRET, body, timestamp, signature)) { console.log("✅ Verification successful"); res.status(200).send(challenge); return; } else { console.log("❌ Verification failed: Invalid signature"); return res.status(400).send("Bad Request: Invalid signature"); } } // その他のWebhookリクエスト // 実装は後述 }); app.listen(3000, () => { console.log("Webhook server running on port 3000"); }); ``` このコードでは、 `type: WEBHOOK_URL_VERIFICATION` のリクエストが来た際に、署名を検証しています。3 秒以内に `status: 200` のレスポンスを返すことで正式なエンドポイントとして登録されます。 サーバーのソースコードは最終的に次のようになります。 ```js // server.js import express from "express"; import { createHmac, timingSafeEqual } from "node:crypto"; const app = express(); app.use(express.json()); // 先ほど生成した署名用共通鍵をここに設定 const SIGNING_SECRET = ""; // 署名検証関数(検証リクエスト用) function verifySignature(signingSecret, body, timestamp, receivedHex) { // 1) タイムスタンプ検証(リプレイ防止) const tsNum = Number(timestamp); if (!Number.isFinite(tsNum)) return false; const ageMs = Math.abs(Date.now() - tsNum * 1000); // クイックスタートでは60秒の許容範囲を設定 if (ageMs > 60 * 1000) { return false; } // 2) HMAC を計算 const payload = `${body}${String(timestamp)}`; const expectedHex = createHmac("sha256", signingSecret).update(payload).digest("hex"); // 3) 比較(暗号学的に安全な比較) const expBuf = Buffer.from(expectedHex, "hex"); const recBuf = Buffer.from(receivedHex, "hex"); if (expBuf.length !== recBuf.length) return false; return timingSafeEqual(expBuf, recBuf); } // Webhookエンドポイント app.post("/webhook", (req, res) => { const body = JSON.stringify(req.body); const timestamp = req.headers["x-skyway-request-timestamp"]; const signature = req.headers["x-skyway-signature"]; console.log("Received request:", body); // 検証リクエスト処理 if (req.body.type === "WEBHOOK_URL_VERIFICATION") { const challenge = req.body.data?.challenge; // 必要なパラメータの存在チェック if (!challenge || !timestamp || !signature) { console.log("❌ Bad Request: Missing required parameters"); return res.status(400).send("Bad Request: Missing required parameters"); } // 署名検証を実行 if (verifySignature(SIGNING_SECRET, body, timestamp, signature)) { console.log("✅ Verification successful"); res.status(200).send(challenge); return; } else { console.log("❌ Verification failed: Invalid signature"); return res.status(400).send("Bad Request: Invalid signature"); } } // 必要なパラメータの存在チェック if (!timestamp || !signature) { console.log("❌ Missing signature headers"); return res.status(400).send("Bad Request: Missing signature headers"); } if (!verifySignature(SIGNING_SECRET, body, timestamp, signature)) { console.log("❌ Signature verification failed"); return res.status(400).send("Bad Request: Invalid signature"); } res.status(200).send("OK"); // 任意のイベント処理 console.log("✅ Event received:", req.body); }); app.listen(3000, () => { console.log("Webhook server running on port 3000"); }); ``` ## サーバーの起動と外部公開 次のコマンドで作成したサーバーを起動できます。 ```bash npm start ``` サーバーがポート3000で起動したら、HTTPS エンドポイントとして外部からアクセス可能にする必要があります。サーバーを外部公開し、エンドポイントの URL を確認してください。 ## SkyWay コンソールでの設定 ### Webhook設定手順 1. SkyWay コンソールにログイン 2. 対象アプリケーションの Webhook のセクションを選択 3. エンドポイント URL に外部公開したエンドポイントの URL + `/webhook` を入力 4. 対象としたい通知イベント(今回は Room のイベントと録音・録画のイベント)にチェックを入れる 5. 先ほど生成した署名用共通鍵を入力 6. **設定**ボタンを押す ![Webhook設定画面](/media/posts/docs/webhook-screen-1.webp) **コンソール上での設定完了時に検証リクエストが送信されます。** - 成功した場合、コンソール上の画面が次のように変わります ![Webhook設定完了](/media/posts/docs/webhook-screen-2.webp) - サーバーログで「✅ Verification successful」が表示されることを確認してください。 - 失敗した場合は、署名用共通鍵やエンドポイントの設定を確認して再設定してください。 ## Webhookリクエストを受け取る機能 検証が成功したら、実際の Webhook イベントを受信し、署名検証を行う機能を追加します。 ### イベント処理の追加 先ほど作成した `server.js` の「`// その他のWebhookリクエスト`」の部分を以下のコードに置き換えてください。 ```jsx // その他のWebhookリクエスト // 必要なパラメータの存在チェック if (!timestamp || !signature) { console.log("❌ Missing signature headers"); return res.status(400).send("Bad Request: Missing signature headers"); } if (!verifySignature(SIGNING_SECRET, body, timestamp, signature)) { console.log("❌ Signature verification failed"); return res.status(400).send("Bad Request: Invalid signature"); } res.status(200).send("OK"); // 任意のイベント処理 console.log("✅ Event received:", req.body); ``` ## 動作確認 サーバーを再起動してください。 登録した `appId` で、JavaScript SDK の [クイックスタート](https://skyway.ntt.com/ja/docs/user-guide/javascript-sdk/quickstart/) に従って Room を作成し、Room への参加(Join)、Stream の Publish、Publication の Subscribe を行うと、次の Webhook イベントを受信します。 - `type` が `ROOM_CREATED` のイベント - `type` が `ROOM_MEMBER_JOINED` のイベント - `type` が `ROOM_STREAM_PUBLISHED` のイベント - `type` が `ROOM_PUBLICATION_SUBSCRIBED` のイベント さらに、Room からの退出(Leave)、Stream の Unpublish、Publication の Unsubscribe、および Room をクローズすると、次の Webhook イベントを受信します。 - `type` が `ROOM_MEMBER_LEFT` のイベント - `type` が `ROOM_STREAM_UNPUBLISHED` のイベント - `type` が `ROOM_PUBLICATION_UNSUBSCRIBED` のイベント - `type` が `ROOM_CLOSED` のイベント --- ## ユーザーガイド/Webhook/仕様 Path: user-guide_webhook_specification.md ## 検証リクエスト 検証リクエストは Webhook 機能にイベント送信先となるユーザーのサーバーのエンドポイントを設定するにあたって、Webhook 機能とユーザーのサーバーの双方でお互いが正当であることを確かめるための仕組みです。 ユーザーのサーバーは Webhook 機能から送信される検証リクエストに対して適切なレスポンスを返す必要があります。 検証リクエストは SkyWay コンソールで Webhook の設定を行うと送信されます。 そのため、SkyWay コンソールの操作前にユーザーのサーバーを Webhook 機能からアクセスできるように公開する必要があります。 検証リクエストのフォーマットは次の通りです。 | **項目** | **値** | | ---------------------------- | ------------------------------------------------- | | HTTP メソッド | `POST` | | **項目** | **値** | | ---------------------------- | ------------------------------------------------- | | ヘッダー | | | `Content-Type` | `application/json` | | `X-SkyWay-Request-Timestamp` | `文字列(秒)` | | `X-SkyWay-Signature` | `文字列(16進数)` | | `User-Agent` | `SkyWay-Webhook/1.0.0 (+https://skyway.ntt.com/)` | | **項目** | **値** | | ---------------------------- | ------------------------------------------------- | | ボディ | | | `type` | `WEBHOOK_URL_VERIFICATION` | | `appId` | `文字列(uuid)` | | `timestamp` | `文字列(UTC/ISO 8601 形式)` | | `data` | `{ "challenge": }` | | `schemaVersion` | `1` | **必須** ユーザーのサーバーは次のフォーマットのレスポンスを 3 秒以内に行う必要があります。 | **項目** | **値** | 補足 | | ---------------- | ------ | ------------------------------------------------------------------------------- | | ステータスコード | 2xx | 200 OK を推奨 | | ボディ | 文字列 | プレーンテキストで `challenge` と 完全一致 する文字列(UTF-8 エンコーディング) | | ヘッダー | 任意 | 無視される | ユーザーのサーバーから適切なレスポンスが返されない場合(タイムアウト、エラーステータス、challenge の不一致など)、SkyWay Console での Webhook 設定が失敗します。この場合、サーバー側の実装を修正した後、再度 SkyWay Console から設定を行ってください。 **推奨** 署名を生成し、両者が一致するかどうかを確認することで検証リクエストの送信元が本物の SkyWay の Webhook 機能であることを確かめることができます。これを署名の検証と呼びます。 検証リクエストの送信元が本物の SkyWay の Webhook 機能であることを確かめるために、`X-SkyWay-Signature` ヘッダーの値と、リクエストのボディを使って生成した署名が一致することを確認してください。 これにより、検証リクエストの送信元が本物の SkyWay の Webhook 機能であることを確認できます。 詳細な検証方法については[署名の検証](/ja/docs/user-guide/webhook/development-guide/#2) をご参照ください。 ## Webhook リクエスト Webhook 機能からユーザーのサーバーにイベントを送信するリクエストのフォーマットは次の通りです。 | **項目** | **値** | | ---------------------------- | ------------------------------------------------- | | HTTP メソッド | `POST` | | ヘッダー | | | `Content-Type` | `application/json` | | `X-SkyWay-Request-Timestamp` | `文字列(秒)` | | `X-SkyWay-Signature` | `文字列(16進数)` | | `User-Agent` | `SkyWay-Webhook/1.0.0 (+https://skyway.ntt.com/)` | | ボディ | | | `type` | `文字列` | | `appId` | `文字列(uuid)` | | `timestamp` | `文字列` | | `data` | `文字列(JSONオブジェクト)` | | `schemaVersion` | `1` | **必須** ユーザーのサーバは次のフォーマットのレスポンスを 3 秒以内に行う必要があります。 制限時間内にレスポンスが行われない場合、Webhook 機能はリクエストをリトライします。 | **項目** | **値** | 補足 | | ---------------- | ------ | -------------------------------------------------------- | | ステータスコード | 2xx | 200 OK を推奨 | | ボディ | 任意 | 無視される。サイズが 1KiB を越えるとレスポンスに失敗する | | ヘッダー | 任意 | 無視される | **推奨** 検証リクエストと同様に送信元が本物の SkyWay の Webhook 機能であることを確かめるために、`X-SkyWay-Signature` ヘッダーの値と、リクエストのボディを使って生成した署名が一致することを確認してください。 また、リプレイ攻撃を防止するためにタイムスタンプを検証できます。 ## 通知イベント ### Room のイベント Room のイベントの type 名には「ROOM_」という接頭辞が付きます。 #### Room の作成 Room が作成された時に通知されるイベントです。 ```ts { type: "ROOM_CREATED"; appId: string; data: { room: { id: string; name?: string; metadata?: string; }; }; timestamp: string; } ``` #### Room のクローズ Room がクローズされた時に通知されるイベントです。 type は `ROOM_CLOSED` で、type 以外 `ROOM_CREATED` と同じ形式です。 #### Member の入室 Room に入室した時に通知されるイベントです。 ```ts { type: "ROOM_MEMBER_JOINED"; appId: string; data: { room: { id: string; name?: string; metadata?: string; }; member: { id: string; name?: string; metadata?: string; }; }; timestamp: string; } ``` #### Member の退室 Room から退室した時に通知されるイベントです。 ```ts { type: "ROOM_MEMBER_LEFT"; appId: string; data: { room: { id: string; name?: string; metadata?: string; members: { id: string; name?: string; }[]; }; member: { id: string; name?: string; metadata?: string; }; }; timestamp: string; } ``` #### Stream の Publish Publication が Publish された時に通知されるイベントです。 ```ts { type: "ROOM_STREAM_PUBLISHED"; appId: string; data: { room: { id: string; name?: string; metadata?: string; }; publication: { id: string; publisher: { id: string; name?: string; }; contentType: "AUDIO" | "VIDEO" | "DATA"; type: "P2P" | "SFU"; isEnabled: boolean; metadata?: string; }; }; timestamp: string; } ``` #### Stream の Unpublish Publication が Unpublish された時に通知されるイベントです。 type は `ROOM_STREAM_UNPUBLISHED` で、type 以外 `ROOM_STREAM_PUBLISHED` と同じ形式です。 #### Publication の Subscribe Publication が Person に Subscribe された時に通知されるイベントです(Bot は通知対象外)。 ```ts { type: "ROOM_PUBLICATION_SUBSCRIBED"; appId: string; data: { room: { id: string; name?: string; metadata?: string; }; subscription: { id: string; publicationId: string; subscriber: { id: string; name?: string; }; }; }; timestamp: string; } ``` #### Publication の Unsubscribe Publication が Person から Unsubscribe された時に通知されるイベントです(Bot は通知対象外)。 type は `ROOM_PUBLICATION_UNSUBSCRIBED` で、type 以外 `ROOM_PUBLICATION_SUBSCRIBED` と同じ形式です。 ### 録音・録画のイベント 録音・録画のイベントの type 名には「RECORDING_」という接頭辞が付きます。 **録音・録画の開始** 録音・録画ファイルの作成を開始した時に次のような形式で通知されるイベントです。 [録音・録画可能な時間の制限](/ja/docs/user-guide/commons/quotas-and-limits/#106)の、録音・録画の最小時間を満たしたファイルのみがイベント通知の対象となります。 録音・録画を開始したファイルの状態の変化を追跡するために `data.file.path` を識別子として利用できます。 ```jsx { type: "RECORDING_FILE_STARTED"; appId: string; data: { session: { id: string; roomId: string; }; file: { name: string; path: string; status: "RECORDING" | "SUCCEEDED" | "FAILED"; errors: { detail: string; occurredAt: string; }[]; type: "AUDIO" | "VIDEO" | "AUDIO_AND_VIDEO"; createdAt: string; codecs: string[]; duration: number; publisher: { name?: string; id: string; }; publications: { id: string; publisher: { name?: string; id: string; }; contentType: "AUDIO" | "VIDEO"; }[]; }; }; timestamp: string; } ``` **録音・録画の完了** 録音・録画が正常に完了した時に通知されるイベントです。 type は `RECORDING_FILE_SUCCEEDED` で、type 以外 `RECORDING_FILE_STARTED` と同じ形式です。 **録音・録画の強制終了** 回復不能なエラーによって録音・録画が強制的に終了された時に通知されるイベントです。 type は `RECORDING_FILE_FAILED` で、type 以外 `RECORDING_FILE_STARTED` と同じ形式です。 録音・録画が強制終了された際の対処法については [エラーとなった場合の録音・録画ファイルの取得](/ja/docs/user-guide/recording/recording-development-guide/#192) を参考にしてください。 **録音・録画のエラーが発生** すべてのエラーの発生時に通知されるイベントです。 type は `RECORDING_FILE_ERROR_OCCURRED` で、type 以外 `RECORDING_FILE_STARTED` と同じ形式です。 ## Webhook の詳細仕様 ### Webhook 機能のイベント通知のリトライについて ユーザーのサーバーが一時的にイベントの通知ができない状況に対応するために Webhook 機能はイベント通知のリトライを行います。リトライの仕様は次の通りです。 - リトライ対象のステータスコード - 429 - 500~599 - 最大試行時間: 3 日 (259200 秒) 最大試行時間の間にバックオフリトライします。 ### 注意事項 **通知順序** 概ねイベントの発生時刻の通り通知されますが、完全な順序保証はされていません。 **重複通知** ごく稀に内容の重複したイベントが通知されることがあります。 通知が重複することで致命的な事態が生じないように必要に応じて[対策](/ja/docs/user-guide/webhook/development-guide/#43)を講じる必要があります。 **イベント通知の欠損** Webhook 機能はほとんどのケースにおいて Webhook リクエストを送信しますが、ごく稀に Webhook リクエストが欠損する可能性があります。 全てのイベント情報が、Webhook リクエストとして必ず送信される保証は無い点にご留意ください。 --- ## ユーザーガイド/Webhook/開発ガイド Path: user-guide_webhook_development-guide.md ## 署名の検証 署名を生成し、両者が一致するかどうかを確認することで検証リクエストの送信元が本物のSkyWayのWebhook機能であることを確かめることができます。 署名は、文字列化したボディと `X-SkyWay-Request-Timestamp` ヘッダーの値を連結したものに対して、署名用共通鍵を用いてHMAC-SHA256を計算することで生成できます。 またリプレイ攻撃を防止するために必要に応じてタイムスタンプを検証することも可能です。 **例 (Node.js)** ```jsx import { createHmac, timingSafeEqual } from "node:crypto"; export function verifySignature( signingSecret: string, body: string, timestamp: string signature: string, opts: { toleranceSeconds?: number } ): boolean { // 1) タイムスタンプ検証(リプレイ防止) const tsNum = Number(timestamp); if (!Number.isFinite(tsNum)) return false; const ageMs = Math.abs(Date.now() - tsNum * 1000); if (opts.toleranceSeconds != null && ageMs > opts.toleranceSeconds * 1000) { return false; } // 2) HMAC を計算 const payload = `${body}${String(timestamp)}`; const expectedHex = createHmac("sha256", signingSecret).update(payload).digest("hex"); // 3) 比較 const expBuf = Buffer.from(expectedHex, "hex"); const recBuf = Buffer.from(receivedHex, "hex"); if (expBuf.length !== recBuf.length) return false; return timingSafeEqual(expBuf, recBuf); } ``` ## 重複排除について 同じイベントが重複して通知される可能性があるため、イベント通知の重複が許容できない場合は重複排除の仕組みをお客様側で実装する必要があります。 Webhook機能では、Webhookリクエストを一意に識別するための識別子として、次の3つの値の組み合わせを使用しています。 - appId - type - timestamp 完全な重複の排除を行いたい場合はイベントの識別子を3日間保存し、イベントの受信時に内容の比較を行って重複判断をしてください。 --- ## ユーザーガイド/文字起こし β版/概要 Path: user-guide_stt_overview.md # 概要 本機能は、SkyWay を使った音声通話を文字起こしする機能です。専用のライブラリを利用してリアルタイムに文字起こし結果を取得できます。 ## **オープンベータ版について** 本機能は現在オープンベータ版として提供しています。ご利用をご希望の方は、[**申し込みフォーム**](https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=53594913656601)よりご連絡ください。 ## 主な特徴 - **リアルタイム**:無音判定により音声が細かく区切られ、低遅延で文字起こし結果を取得できます - **発言者の識別**:文字起こし結果には発言者の情報が紐付けられるため、誰の発言かを識別できます - **翻訳機能**:翻訳モードを ON にすると、英語と日本語の両方の文字起こし結果を取得できます - **アーカイブ機能**:文字起こし終了時に、セッション内の文字起こし結果を JSONL ファイルとしてクラウドストレージに保存できます 例えば以下のようなシーンでご活用いただけます。 - **会議の議事録**:発言者を識別して議事録を記録・表示 - **遠隔接客**:外国人との接客で互いの発言を翻訳 ## 対応SDK - JavaScript SDK 他 SDK は順次対応予定です。 なお、他の SDK を利用して文字起こし中の room で発言した内容は文字起こしの対象になります。 ## 利用方法 本機能のシステム構成は以下の通りです。 - お客様サーバーをご用意いただき、文字起こし開始などの処理で REST API を操作する必要があります。 - クライアント(フロントエンド)アプリにて JavaScript SDK および STT-Client ライブラリ(後述)のインストールが必要です。 - アーカイブ機能を利用する場合は、文字起こし開始時に `Create Session API` のリクエストへ `archive.storageConfig` を指定します。 - 文字起こし終了時に、対象セッションの文字起こし結果が JSONL 形式でまとめられ、指定したクラウドストレージにアップロードされます。 アーカイブ機能で指定できるクラウドストレージは以下です。 - Google Cloud Storage - Amazon S3 - Wasabi 設定方法の詳細は、[アーカイブ機能](/ja/docs/user-guide/stt/archive)を参照してください。 ![概要図](/media/posts/docs/stt/overview.png) 文字起こしを行う際には、エンドユーザーへの説明および同意取得が必要です。詳しくは規約をご確認ください。 ## STT(Speach-To-Text)-Clientライブラリ [GitHub skyway-stt-client-js](https://github.com/skyway/skyway-stt-client-js) ## デモ [デモアプリ](https://skyway-stt-advanced-example.netlify.app) 任意の Room 名と Member 名を入力して Room に入室し、Start live transcription/translation ボタンをクリックすることで音声通話の文字起こしを体験できます。 ![STTデモアプリ](/media/posts/docs/stt/stt-demo-app.png) --- ## ユーザーガイド/文字起こし β版/クイックスタート Path: user-guide_stt_quickstart.md # クイックスタート 本記事ではサンプルコードをもとに、文字起こし機能が付いた会議アプリの実装方法について説明します。 文字起こし結果のアーカイブ機能を利用する場合は、[アーカイブ機能](/ja/docs/user-guide/stt/archive)を参照してください。 サンプルコードは skyway-stt-client-js のリポジトリに同梱しています。 - [クライアント側コード](https://github.com/skyway/skyway-stt-client-js/tree/main/examples/tutorial) - [サーバー側コード](https://github.com/skyway/skyway-stt-client-js/tree/main/examples/server) サンプルコードの完成形はこちらからご確認いただけます。 [skyway-stt-example](https://skyway-stt-example.netlify.app/) ## クイックスタートの環境 以下の環境で実施してください。 - Node: v20 以降 ## 利用方法 ではさっそく動かしてみましょう。 まずは[GitHub skyway-stt-client-js](https://github.com/skyway/skyway-stt-client-js)を Git clone します。 `examples/server` フォルダに移動して、`.env.example` を `.env` に rename します。 ```sh cd examples/server && mv .env.example .env ``` `.env.example` 内の APP_ID と SECRET に値を入力します。 ```dotenv APP_ID=ここにアプリケーションIDをペーストしてください SECRET=ここにシークレットキーをペーストしてください ``` 依存パッケージをインストールします。 ```sh npm install ``` サーバー側アプリケーションを起動します。 ```sh npm run start ``` その後、別ターミナルでクライアント側アプリケーションを起動します。 ```sh cd examples/tutorial && npm run dev ``` クライアント側アプリケーションを起動すると、以下のような画面が立ち上がります。 ![stt-example](/media/posts/docs/stt/stt-example.png) 任意の room 名と member 名を入力して、Join ボタンを押すと、room が作成されて入室できます。 その後、Mode を選択し Start ボタンを押します。 - transcription モード: 入力音声の言語を自動で判定し、その言語で文字起こしします - translation モード: 入力音声の言語を日本語または英語で文字起こしを行い、もう一方の言語での翻訳も併記します タブを2つ開き、同じ room 名を入力して Join ボタンを押せば、両タブで会議に参加し、文字起こし結果の表示を確認できます。 ## コード解説 ### 全体像 サンプルコードはクライアントとサーバの2つのアプリケーションに分かれています。 それぞれの概要を説明します。 - クライアント - JavaScript SDK と STT-Client を利用しています。 - Room 作成や文字起こし開始についてバックエンドサーバにリクエストし、STT-Client 経由で文字起こし結果を受け取ります。 - サーバ - 3つの endpoint を持っています - POST `/rooms/:roomName/create` - リクエストされた roomName の Room を作成し、SkyWayAuthToken を返却する - POST `/rooms/:roomName/start` - リクエストされた roomName の Room について、文字起こしを開始する - DELETE `/rooms/:roomName/end` - リクエストされた roomName の Room について、文字起こしを終了する - (以降のコード解説は割愛します) - `sttApiBaseUrl` には利用開始案内メールに記載してある接続先情報をご利用ください ### クライアントのコード解説 **チャンネル作成** ```js const response = await fetch( `${SERVER_HOST}/rooms/${roomName}/create`, { method: "POST", }, ); const { token } = await response.json(); const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.Find( context, { name: roomName, }, ); ``` チャンネルを作成するサーバ API へリクエストします。レスポンスに SkyWayAuthToken が入っているので、これをもとに context を作成して room を find します。なお、**文字起こしする音声をpublishするときのtypeは必ず"sfu"を選択する必要があります。** **接続とリッスン** ```js const me = await room.join({ name: memberName }); const sttClient = new SkyWaySTTClient(context, me); sttClient.onSTTResultReceived.add(({ result }) => { const member = room.members.find((m) => m.id === result.memberId); const mode = sttMode.value; const messageElement = createSTTMessage(result, member, mode); sttResults.appendChild(messageElement); sttResults.scrollTop = sttResults.scrollHeight; }); ``` SkyWayContext と、join の返却値である Member を引数にとり、new SkyWaySTTClient でインスタンスを生成します。この時、内部的に STT サーバへ接続します。 onSTTResultReceived で、コールバック関数をセットすると、引数として文字起こし結果を得ることができます。サンプルコードではこれを HTML で表示するようにしています。 **文字起こし開始** ```js startSttButton.onclick = async () => { const result = await fetch( `${SERVER_HOST}/rooms/${roomName}/start`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ sttMode: sttMode.value, }), }, ); if (result.status === 200) { sttStatus.textContent = "ON"; console.log("STT started"); } else { console.error("Failed to Start STT"); } }; ``` start エンドポイントにたいしてリクエストを行い、文字起こしを開始します。 ### サーバのコード解説 **トークン生成** ```js // クライアント用のトークン const createSkywayAuthToken = (roomId) => { const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, version: 3, scope: { appId, rooms: [ { id: roomId, methods: ["create", "close", "updateMetadata"], member: { id: "*", // subscribeを許可する methods: ["publish", "subscribe", "updateMetadata"], }, // sttをenabledまたは省略(デフォルトでenabled)する stt: { enabled: true, }, // sfuをenabledまたは省略(デフォルトでenabled)する sfu: { enabled: true, }, }, ], }, }).encode(secret); return token; }; ``` コメントに記載の通り、文字起こし結果を取得するためには以下の設定にする必要があります。 - subscribe method を追加 - `stt.enabled` を true, または `stt` を省略 - `sfu.enabled` を true, または `sfu` を省略 **createエンドポイント** ```js app.post("/rooms/:roomName/create", async (req, res) => { const { roomName } = req.params; console.log("create", { roomName }); // リクエストされたroomNameのRoom(Channel)を作成する const response = await fetch(channelApiUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ jsonrpc: "2.0", id: uuidV4(), method: "findOrCreateChannel", params: { name: roomName, }, }), }); const { result: { channel: { id: roomId }, }, } = await response.json(); roomNameIdMap[roomName] = roomId; // 入室できるroomIdを制限したトークンを作成する const token = createSkywayAuthToken(roomId); // 今後の文字起こしの開始、終了操作を認証するためにtokenとroomIdの紐付けを行う tokenHashRoomIdMap[sha256(token)] = roomId; res.send({ token }); }); ``` - room の作成には `Channel API` を利用します。 - 参考: [SkyWay Channel API](/ja/docs/user-guide/rtc-api-server/channel-api) - SkyWayAuthToken を作成し、レスポンスとしてクライアントへ返却します。 > [SkyWay Channel API](/ja/docs/user-guide/rtc-api-server/channel-api) は将来的に廃止予定であるため、同等の機能を有する [SkyWay Room API](/ja/docs/user-guide/rtc-api-server/room-api) への移行を推奨しております。 > 移行方法は『[Channel APIからRoom APIへの移行](/ja/docs/cookbook/rtc-api-server/migrate-channel-api-to-room-api)』をご参照ください。 **startエンドポイント** ```js app.post("/rooms/:roomName/start", async (req, res) => { const { roomName } = req.params; const { authorization } = req.headers; const roomId = roomNameIdMap[roomName]; console.log("start", { roomName, roomId, body: req.body }); if (roomNameRecordingMap[roomName]) { res.status(200).send({ message: "STT already started" }); return; } // roomIdとtokenの紐付けを確認する const tokenStr = (authorization ?? "").replace(/^Bearer\s*/, ""); if (roomId !== tokenHashRoomIdMap[sha256(tokenStr)]) { res.status(403).send({ message: "Forbidden" }); return; } // 文字起こしを開始する const { sttMode } = req.body; const response = await fetch(`${sttApiBaseUrl}/rooms/${roomId}/sessions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ mode: sttMode.toUpperCase(), }), }); const json = await response.json(); if (!response.ok) { console.error(json.error); res.status(500).send({ message: json.error.message }); return; } roomNameRecordingMap[roomName] = json.id; res.status(200).send({ id: json.id }); }); ``` STT サーバの sessions エンドポイントへリクエストを行い、対象 Room の文字起こしを開始します。mode にクライアントで選択された `TRANSCRIPTION`、`TRANSLATION` のいずれかを設定します。 --- ## ユーザーガイド/文字起こし β版/アーカイブ機能 Path: user-guide_stt_archive.md # アーカイブ機能 本記事では、文字起こし結果をクラウドストレージへアーカイブする方法を説明します。 ## 概要 文字起こしセッション開始時にクラウドストレージの情報を指定すると、文字起こしセッション終了時にセッション内の文字起こし結果が JSONL ファイルとしてアップロードされます。 サンプルコードをもとに、文字起こし機能が付いた会議アプリの実装方法について説明します。基本的な利用方法は[クイックスタート](/ja/docs/user-guide/stt/quickstart)と同様です。 - [サーバー側コード](https://github.com/skyway/skyway-stt-client-js/tree/main/examples/server) ## 事前準備 利用するクラウドストレージを準備してください。準備方法は、[録音・録画クイックスタートのクラウドストレージの準備](/ja/docs/user-guide/recording/recording-quickstart/#9)を参照してください。 ## ストレージ設定例 ### Google Cloud Storage ```js const gcsConfig = { service: "GOOGLE_CLOUD_STORAGE", credential: JSON.stringify({ // サービスアカウントの鍵のJSONファイルの内容をコピーペーストする type: "", project_id: "", private_key_id: "", private_key: "", client_email: "", client_id: "", auth_uri: "", token_uri: "", auth_provider_x509_cert_url: "", client_x509_cert_url: "", }), bucket: "", }; ``` ### Amazon S3 ```js const s3Config = { service: "AMAZON_S3", bucket: "", accessKeyId: "", secretAccessKey: "", region: "", }; ``` ### Wasabi ```js const wasabiConfig = { service: "WASABI", bucket: "", accessKeyId: "", secretAccessKey: "", endpoint: "", }; ``` ## ストレージの指定 `POST /rooms/:roomName/start` で STT セッションを開始する際に、`archive.storageConfig` を指定します。 ```js app.post("/rooms/:roomName/start", async (req, res) => { const { roomName } = req.params; const { authorization } = req.headers; const roomId = roomNameIdMap[roomName]; const tokenStr = (authorization ?? "").replace(/^Bearer\s*/, ""); if (roomId !== tokenHashRoomIdMap[sha256(tokenStr)]) { res.status(403).send({ message: "Forbidden" }); return; } const { sttMode } = req.body; const response = await fetch(`${sttApiBaseUrl}/rooms/${roomId}/sessions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ mode: sttMode.toUpperCase(), archive: { // Amazon S3を使う場合は s3Config // Wasabiを使う場合は wasabiConfig storageConfig: gcsConfig, }, }), }); const json = await response.json(); if (!response.ok) { res.status(500).send({ message: json.error.message }); return; } res.status(200).send({ id: json.id }); }); ``` それ以外の設定、起動方法は[クイックスタート](/ja/docs/user-guide/stt/quickstart)と同様です。 セッション終了後、そのセッションで作成された全ての文字起こし結果がアップロードされます。セッション終了からアップロード完了まで、約5分程度かかります。 ## アップロードされるファイルの形式 transcription モード、translationモードにおいてそれぞれ、以下の形式のJSONが改行区切りになっているJSONLファイルとして保存されます。 ### transcription モード ```json { "timestamp": string, // ISO8601 形式, "memberId": "member-id", "memberName": "member-name", "text": "文字起こし結果" } ``` ### translation モード ```json { "timestamp": string, // ISO8601 形式, "memberId": "member-id", "memberName": "member-name", "texts": [ { "language": "ja", "text": "文字起こし結果" }, { "language": "en", "text": "translated text" } ] } ``` --- ## ユーザーガイド/Room API / Channel API/Channel API (非推奨) Path: user-guide_rtc-api-server_channel-api.md # SkyWay Channel API ## 概要 SkyWay Channel API は、SkyWay の Channel の作成と取得を行うことができるサーバーサイド向けの API です。 > 類似するサーバーサイド向け API である [SkyWay Room API](/ja/docs/user-guide/rtc-api-server/room-api) のリリースに伴い、SkyWay Channel API は非推奨とさせていただきました。今後は SkyWay Room API をご利用ください。 > > SkyWay Channel API から SkyWay Room API への移行については[移行ドキュメント](/ja/docs/cookbook/rtc-api-server/migrate-channel-api-to-room-api/)をご参照ください。 > クライアントサイドでの Channel の操作は各 SDK をご利用ください。 ## 使用方法 SkyWay Channel API は [JSON-RPC 2.0](https://www.jsonrpc.org/specification) に準拠しています。また、リクエストはすべて HTTP `POST` メソッドを使用します。 リクエストボディのスキーマは以下のようになっており、method プロパティに使用したい SkyWay Channel API のメソッドを指定し、params プロパティに必要なパラメータを指定します。 提供されている SkyWay Channel API のメソッドなどの詳細については [SkyWay Channel API リファレンス](/ja/docs/api-reference/rtc-api-server/channel-api) を参照してください。 |プロパティ|形式|必須|説明| |---|---|---|---| |jsonrpc|string|️✔|`2.0` を指定する| |id|string \| integer |✔| 任意の値を指定する。レスポンスにはリクエストで指定した id の値が入る | |method|string|✔|SkyWay Channel API のメソッドを指定する| |params|object|✔|SkyWay Channel API のメソッドを使用するのに必要なパラメータを指定する| 例えば SkyWay Channel API の createChannel メソッドで name `sample-channel` の Channel を作成するときのリクエストボディは以下のようになります。 ```json { "jsonrpc": "2.0", "id": 0, "method": "createChannel", "params": { "name": "sample-channel", } } ``` ### API エンドポイント 以下のエンドポイントから SkyWay Channel API を利用できます。 ``` https://channel.skyway.ntt.com/v1/json-rpc ``` ### 認証 SkyWay Channel API の利用には SkyWay Admin Auth Token による認証が必要です。 SkyWay Admin Auth Token の詳細については[SkyWay Admin Auth Token のドキュメント](/ja/docs/user-guide/authentication/skyway-admin-auth-token/)を参照してください。 ### サンプルコード 次のコードでは、curl コマンドで SkyWay Channel API の createChannel メソッドを使って name `sample-channel` の Channel を作成しています。`YOUR_SKYWAY_ADMIN_AUTH_TOKEN` は生成した SkyWay Admin Auth Token で置き換えてください。 ```sh curl -X POST https://channel.skyway.ntt.com/v1/json-rpc \ -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc": "2.0", "id": 0, "method": "createChannel", "params": {"name": "sample-channel"}}' ``` レスポンスとして作成した Channel の id や name が返されます。 ```sh {"jsonrpc":"2.0","result":{"channel":{"id":"7e19cb20-fc79-4d2f-bf29-d498ff76299a","name":"sample-channel"}},"id":0} ``` 次のコードでは、SkyWay Channel API の findChannel メソッドを使って name `sample-channel` の Channel を取得しています。 ```sh curl -X POST https://channel.skyway.ntt.com/v1/json-rpc \ -H "Authorization: Bearer YOUR_SKYWAY_ADMIN_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc": "2.0", "id": 0, "method": "findChannel", "params": {"name": "sample-channel"}}' ``` レスポンスとして Channel の詳細な情報が返されます。 ```sh {"jsonrpc":"2.0","result":{"channel":{"id":"7e19cb20-fc79-4d2f-bf29-d498ff76299a","name":"sample-channel-05","members":[],"publications":[],"subscriptions":[]}},"id":0} ``` ## リクエストレートの制限 SkyWay Channel API へのリクエストはリクエストレートの制限の対象になります。リクエストレートの制限の詳細については[リクエストレートの制限のドキュメント](/ja/docs/user-guide/commons/quotas-and-limits)を参照してください。 --- ## ユーザーガイド/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 へのリクエストが発生します。 ## Webhook | 通信方向 | プロトコル | ポート番号 | SkyWayのサーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | SkyWayのサーバー→ユーザーのサーバー | TCP (https) | 443 | 固定 | Webhookリクエストの送信 | Enterpriseプランをご契約中、もしくはご検討中のお客様で、ご希望の方には、WebhookサーバーのIPアドレスリストをご提供いたします。 こちらの[お問い合わせフォーム](https://support.skyway.ntt.com/hc/ja/requests/new?ticket_form_id=19013322121113)からお申し込みください。 ## 文字起こし | 通信方向 | プロトコル | ポート番号 | SkyWayのサーバーアドレス | 用途 | | --- | --- | --- | --- | --- | | クライアント→サーバー | TCP (https/wss) | 443 | stt-dispatcher.skyway.ntt.com | 文字起こし結果の受信 | --- ## ユーザーガイド/その他 共通仕様/対応コーデック Path: user-guide_commons_codecs.md # 対応コーデック ## 概要 SkyWay ではやりとりする音声や映像などのメディアのコーデックをユーザが指定できます。 音声コーデックは Opus がデファクトスタンダードとなっており、現状そのほかに選択の余地はありません。一方で、映像コーデックは VP8や H264などのさまざまなコーデックを(消費電力や映像品質などの)重視する特性やデバイスに応じて選択することで、ユーザのプロダクトに合わせた最適化を行うことができます。 ## 分類 SkyWay で利用可能なコーデックは、サポートコーデックと指定可能コーデックの2つに分類されます。 サポートコーデックはコーデックに重大な Issue が見つかった場合やブラウザ/SDK のアップデートの場合に動作検証を行い、問題があった場合に対応します。 指定可能コーデックはリリース時には動作確認を行いますが、サポートコーデックと違い定期的な動作検証を行いません。 また、P2P の場合と SFU の場合でも利用できるコーデックは異なります。 Unity SDKに関しては[こちら](./user-guide/unity-sdk/codecs)をご覧ください。 ## サポートコーデック ### P2P #### JavaScript/iOS/Android SDK共通 - 映像コーデック - H264 - VP8 - VP9 - 音声コーデック - Opus #### Linux®︎ SDK - 映像コーデック - VP8 - VP9 - H264([RTP映像入力](/ja/docs/cookbook/linux-sdk/rtp-video-input/)でのみ使用可能) - 音声コーデック - Opus ### SFU #### JavaScript SDK - 映像コーデック - H264 - VP8 - 音声コーデック - Opus #### iOS/Android/Linux®︎ 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ライブラリを使用する場合 | 割り当ての種類 | | | --- | --- | | Roomに同時に存在できるMemberの最大数 | 320 | | Roomに同時に存在できるPublicationの最大数 | 256 | | Roomに同時に存在できるSubscriptionの最大数 | 10240 | | Roomに同時に存在できるRecordingSessionの最大数 | 256 | | 1つのMemberが同時にpublishできるPublicationの最大数 | 8 | | 1つのMemberが同時にsubscribeできるSubscriptionの最大数 | 128 | | 1つのP2P PublishによるPublicationに同時に紐づけられる(subscribeできる)Subscriptionの最大数 | 320 | | 1つのSFU PublishによるPublicationに同時に紐づけられる(subscribeできる)Subscriptionの最大数 | 設定した [maxSubscribers](./user-guide/sfu/#36) の値に準ずる(デフォルトで10、最大で99) | | 1つのRecordingSessionに同時に紐づけられるPublicationの最大数 | 256* | | 同時に録音・録画可能な1つのMemberのPublicationの最大数 | 4* | *P2P Room および P2P Publish ではご利用いただけません。Room や Publish の種類に関しては、[Room と P2PRoom・SFURoom の併用](/ja/docs/user-guide/commons/default-room/)のページを参照してください。 ### Coreライブラリを使用する場合 | 割り当ての種類 | | | --- | --- | | Channelに同時に存在できるMemberの最大数 | 320 | | Channelに同時に存在できるPublicationの最大数 | 256 | | Channelに同時に存在できるSubscriptionの最大数 | 10240 | | Channelに同時に存在できるRecordingSessionの最大数 | 256 | | Channelに同時に存在できるSFU Botの最大数 | 5 | | 1つのPersonが同時にpublishできるPublicationの最大数 | 8 | | 1つのPersonが同時にsubscribeできるSubscriptionの最大数 | 128 | | 1つのPublicationに同時に紐づけられる(subscribeできる)Subscriptionの最大数 | 320 | | 1つのSFU経由でPublishされたPublicationに同時に紐づけられる(subscribeできる)Subscriptionの最大数 | 設定した [maxSubscribers](./user-guide/sfu/#36) の値に準ずる(デフォルトで10、最大で99) | | 1つのRecordingSessionに同時に紐づけられるPublicationの最大数 | 256 | | 同時に録音・録画可能な1つのPersonのPublicationの最大数 | 4 | なお、Channel あたりの最大同時 Member、Publication、Subscription 数の制限について、 SFU Bot によって作られたリソースの数は含まれません。 また、Member あたりの最大同時 Publication 数について、SFU Bot は最大で128個まで同時に Publish することが可能です。 ## リクエストレートの制限 リクエストレートの制限は、SkyWay のサーバーに対して実行可能なリクエストの数を定めるものです。リクエストレートの制限は、以下の表に示す通りです。 | 制限 | 制限値 | | ------------------------------------------------- | ------ | | 1秒あたりの最大Channel作成リクエスト数 | 50 | | Channelごとの1秒あたりの最大リクエスト数 | 500 | | SkyWay Recording APIのアプリケーションごとの1秒あたりの最大リクエスト数 | 20 | 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) ## 文字起こしに関する制限事項 文字起こしに関して以下の制限事項が存在します。 - Room、およびSTTSessionは最長で7日間利用可能ですが、1つのPublicationにつき文字起こし可能な時間は最大12時間です。 - 12時間経過した時点から当該Publicationの文字起こしは停止します。そのため12時間以上文字起こしをする場合は経過する前にPublicationを作成し直す必要があります。 - 1つのRoomに同時に作成可能なSTTSessionは1つまでです。 --- ## ユーザーガイド/その他 共通仕様/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 を柔軟に併用できます。 > Room は、内部的に SFURoom の機能を利用します。 > そのため、Room を利用する場合は SkyWay Auth Token にて SFU リソースの認可が必要です。 > > SkyWay Auth Token の詳細については、[SkyWay Auth Token(各種SDK用)](/ja/docs/user-guide/authentication/skyway-auth-token/)のページをご参照ください。 > > Room の作成時に SFU リソースが認可されていない場合、以下のような挙動となります。 > > - 以下のバージョンのSDKを利用する場合、Room の作成に失敗します > > | SDKの種類 | バージョン | > |---|---| > | JS SDK | v2.2.0 ~ v2.2.1 | > | iOS SDK | v3.1.0 ~ v3.1.1 | > | Android SDK | v3.3.0 ~ v3.3.3 | > > - 以下のバージョンのSDKを利用する場合、Room の作成は可能ですが、SFU 方式の Publish に失敗します(P2P 方式の Publish は問題なくご利用いただけます) > > | SDKの種類 | バージョン | > |---|---| > | JS SDK | v2.3.0以降 | > | iOS SDK | v3.2.0以降 | > | Android SDK | v3.4.0以降 | ## 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 に対して配信を行う操作です。 ## 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 Room API SkyWay Room API は、Room に対する各種操作を行うための API です。JSON-RPC API として提供され、サーバーサイドアプリケーションからリクエストを送ることができます。 ## 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 を利用してサービスを提供する事業者です。これに対してアプリケーションを利用する人のことはエンドユーザーと呼びます。 ## Webhook機能 SkyWay の Webhook によるイベント配信サービスそのものを指す単語です。 ## Webhookリクエスト Webhook 機能からユーザーに送信するリクエストのことを指します。ユーザーが正規のものであると確認するための検証リクエストとイベント通知のリクエストの2種類があります。 ## 署名用共通鍵 Webhook リクエストの署名を検証するために使用する共通鍵です。SkyWay コンソールで Webhook の設定を行う際に設定します。 --- ## クックブック/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