🚀 クイックスタート
JavaScript SDK のクイックスタートをベースに、SkyWay JavaScript SDK と AI Noise Canceller ライブラリを使った簡単なアプリケーション tutorial を作成します。
クイックスタートの環境
以下の環境で実施してください。また最新版のブラウザ利用を推奨します。
- Node: v20 以降
- 対応ブラウザ: Chrome / Edge / Safari
SkyWay を使った通話アプリを作る
環境構築 [NPMを利用する場合] 以降の手順に従って SkyWay を使った通話アプリを作成してください。
完成した通話アプリに以下の変更を加えてください。
createMicrophoneAudioAndCameraStreamにオプションを追加する
// const { audio, video } =
// await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream();
// を以下に置き換える
const { audio, video } =
await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream({
audio: {
echoCancellation: false, // 1 人で動作確認する際に聞き取りやすくするための設定であり、実環境では true を推奨
}
});追加後は下記のような実装になっているはずです。
src/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>SkyWay Tutorial</title>
</head>
<body>
<p>ID: <span id="my-id"></span></p>
<div>
room name: <input id="room-name" type="text" />
<button id="join">join</button>
<button id="leave">leave</button>
</div>
<video id="local-video" width="400px" muted playsinline></video>
<div id="button-area"></div>
<div id="remote-media-area"></div>
<script type="module" src="main.js"></script>
</body>
</html>src/main.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 の値を差し替えて、以下のコマンドを実行してください。
export SKYWAY_APP_ID="your-app-id"
export SKYWAY_SECRET_KEY="your-app-secret"以下のコマンドを実行して、ライブラリをインストールします。
なお、 Windows のネイティブ環境(PowerShell や CMD など)ではサポートされていません。 Windows をご利用の場合は、WSL(Windows Subsystem for Linux)上で実行してください。
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 を生成※しライブラリ取得の認証に利用しています。 この SkyWay Admin Auth Token は、アプリケーションの管理者(サーバー)用APIを利用する際に必要なトークンであり、本トークンが流出した場合は第三者に管理者(サーバー)用APIを悪用されてしまう恐れがあります。 取り扱いには十分に気をつけてください。
※ SkyWay Admin Auth Token の有効期限は1時間です
--download-only の引数を付与することで、 ライブラリのみの取得も可能です。
# tmp ディレクトリに保存
curl -fsSL https://raw.githubusercontent.com/skyway/ai-noise-canceller/refs/heads/main/tools/js/install.sh | bash -s -- --download-only --dest="tmp"取得したライブラリが手元にあれば、パッケージマネージャーを利用して追加できます。
# npmを用いた場合
npm install ./tmp/skyway-ai-noise-canceller-x.x.x.tgzノイズ抑制の実装
次に src/main.js を編集し、ノイズ抑制の機能を組み込んでいきます。 src/main.js の先頭に以下を記載してライブラリを読み込ませます。
import { SkyWayNoiseCanceller } from "skyway-ai-noise-canceller";ノイズ抑制の機能を利用する際は認可を追加する必要があります。SkyWayAuthToken の scope へ以下のように noiseCancelling の項目を追加します。
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用)のページをご参照ください。
続いて、ブラウザが提供する noiseSuppression は不要なので音声取得の設定を変更して false にしておきます。
// 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 にする
}
});音声にノイズ抑制をかけて送信するためには以下のように変更します。
// 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 を実行して音声を確認してみましょう。
確認方法は 先程実装した通話アプリでの確認方法 と同様です。
発話しながらマウスクリックやキータイプを行うことで、これらのノイズが低減していることが確認※できます。次の手順で実装する ON/OFF 切り替え機能を利用するとより処理結果を把握しやすくなります。
※ ノイズ抑制によって音声の出力に数十 ms の遅延が発生します。この遅延はビデオ・音声通話による遅延と比べて小さいため、通話の実現について大きな影響はございません。
ノイズ抑制の ON/OFF 切り替え実装
続いて、ノイズ抑制の ON/OFF を切り替えられるように実装を変更してみましょう。 src/index.html を編集し、room name の入力フォームおよびボタンの下にノイズ抑制 ON/OFF ボタンを作ります。
<!DOCTYPE html>
<html lang="en">
...
<div>
room name: <input id="room-name" type="text" />
<button id="join">join</button>
<button id="leave">leave</button>
</div>
<video id="local-video" width="400px" muted playsinline></video>
<!-- 以下のdivを追加 -->
<div id="noise-cancel-area">
<!-- ノイズ抑制の on/off 状態を表示 -->
noise cancelling: <span id="noise-cancelling">false</span>
<!-- ノイズ抑制の on/off を切り替える -->
<button id="noise-cancel">noise cancel</button>
</div>
<!-- ここまで -->
<div id="button-area"></div>
...
</html>次に、src/main.js を編集し、ボタン操作の割り当てをするために Element を保持する変数を定義します。
// const leaveButton = document.getElementById("leave"); の下に追加
const noiseCancelButton = document.getElementById("noise-cancel");
const noiseCancelling = document.getElementById("noise-cancelling");また、先ほど追加したノイズ抑制の実装を以下のように変更します。
// 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 差し替え
- ノイズ抑制が無効であることを表示
...
noiseCancelButton.onclick = async () => {
if (noiseCancelling.textContent === "false") {
// 有効時の処理
} else {
// 無効時の処理
}
};有効時の処理は今回次のように記述します。
SkyWayNoiseCancellerのインスタンスを生成します。onReadyイベントハンドラーを登録し、ハンドラーの中でSkyWayNoiseCanceller.connectを利用し stream にノイズ抑制を適用します- イベントを登録した後に
SkyWayNoiseCanceller.initで初期化処理を実施します SkyWayNoiseCancellerの詳しい仕様については API リファレンスを参照ください- replaceStream 時に stream が途切れて再生が止まってしまうことがあるため、
releaseOldStream: falseオプションを付与しています
// インスタンスの作成
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 でインスタンスを破棄します。
myAudioPublication.replaceStream(audio, {
releaseOldStream: false,
});
// インスタンスの破棄
noiseCanceller.dispose();
// ノイズ抑制が無効であることを表示
noiseCancelling.textContent = "false";これでボタンによる ON/OFF の切り替えが実装できました。
ノイズ抑制の強度変更
次に、ノイズ抑制の強度を変更できるように実装を変更します。 src/index.html を編集し、強度変更に必要なスライダなどを追加します。
<!DOCTYPE html>
<html lang="en">
...
<!-- ノイズ抑制の処理UI -->
<div id="noise-cancel-area">
<!-- ノイズ抑制の on/off 状態を表示 -->
noise cancelling: <span id="noise-cancelling">false</span>
<!-- ノイズ抑制の on/off を切り替える -->
<button id="noise-cancel">noise cancel</button>
<!-- 以下のdivを追加 -->
<div id="noise-cancel-controller" style="display: none">
<label for="noise-cancel-strength-range">strength</label>
<!-- ノイズ抑制の strength を変更する -->
<input
id="noise-cancel-strength-range"
type="range"
min="1"
max="100"
value="100"
step="1"
/>
<span id="noise-cancel-strength-value">100</span>
</div>
<!-- ここまで -->
</div>
<div id="button-area"></div>
...
</html>強度を変更するために index.html に設定した Element を保持する変数を src/main.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 が表示されるように変更します。
// noiseCancelling.textContent = "true"; の下に以下を追加
noiseCancelController.style.display = "block";// noiseCancelling.textContent = "false";の下に以下を追加
noiseCancelController.style.display = "none";スライダが操作された際に SkyWayNoiseCanceller.changeStrength でノイズ抑制の強度を変更できるようにします。
// 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 の退出時にノイズ抑制の停止処理を追加します。
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 に戻す処理を登録します。
// 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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>SkyWay Tutorial</title>
</head>
<body>
<p>ID: <span id="my-id"></span></p>
<div>
room name: <input id="room-name" type="text" />
<button id="join">join</button>
<button id="leave">leave</button>
</div>
<video id="local-video" width="400px" muted playsinline></video>
<!-- ノイズ抑制の処理UI -->
<div id="noise-cancel-area">
<!-- ノイズ抑制の on/off 状態を表示 -->
noise cancelling: <span id="noise-cancelling">false</span>
<!-- ノイズ抑制の on/off を切り替える -->
<button id="noise-cancel">noise cancel</button>
<div id="noise-cancel-controller" style="display: none">
<label for="noise-cancel-strength-range">strength</label>
<!-- ノイズ抑制の strength を変更する -->
<input
id="noise-cancel-strength-range"
type="range"
min="1"
max="100"
value="100"
step="1"
/>
<span id="noise-cancel-strength-value">100</span>
</div>
</div>
<div id="button-area"></div>
<div id="remote-media-area"></div>
<script type="module" src="main.js"></script>
</body>
</html>src/main.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();
});
};
})();