Skip to content

Commit

Permalink
Merge pull request #430 from shinyoshiaki/feature/update-ice
Browse files Browse the repository at this point in the history
support restart ice
  • Loading branch information
shinyoshiaki authored Jan 5, 2025
2 parents 51e5da2 + 937cda3 commit 1bda402
Show file tree
Hide file tree
Showing 38 changed files with 3,299 additions and 4,284 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ jobs:
npm i
cd e2e
npm i
npx playwright install
npx playwright install chromium
cd ../
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3
- name: test
env:
CI: true
Expand Down
3,030 changes: 1,369 additions & 1,661 deletions e2e/package-lock.json

Large diffs are not rendered by default.

29 changes: 16 additions & 13 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,34 @@
"ci:silent": "npm run build && run-p server:silent chrome:prod",
"clean": "pkill -9 gst-launch",
"debug": "cd debug && npm run dev",
"dev": "vitest ./tests --browser.headless",
"firefox:dev": "vitest run ./tests --browser.headless --config vitest.firefox.config.ts",
"format": "biome check --write server tests",
"server": "ts-node-dev --project tsconfig.server.json ./server/main.ts",
"server:dev": "DEBUG=werift* ts-node-dev --project tsconfig.server.json ./server/main.ts",
"server:prod": "DEBUG=werift* node lib/e2e/server/main.js",
"server:silent": "node lib/e2e/server/main.js",
"type": "tsc --noEmit -p tsconfig.server.json && tsc --noEmit -p tsconfig.json"
"type": "tsc --noEmit -p tsconfig.server.json && tsc --noEmit -p tsconfig.json",
"upgrade-interactive": "npx npm-check-updates -i"
},
"dependencies": {
"bowser": "^2.11.0",
"werift": "^0.15.10",
"werift-rtp": "^0.3.1"
"werift": "^0.20.1",
"werift-rtp": "^0.8.2"
},
"devDependencies": {
"@types/express": "^4.17.13",
"@types/protoo-client": "^4.0.1",
"@types/protoo-server": "^4.0.2",
"@vitest/browser": "2.0.5",
"axios": "^1.7.4",
"@types/express": "^5.0.0",
"@types/protoo-client": "^4.0.4",
"@types/protoo-server": "^4.0.6",
"@vitest/browser": "2.1.8",
"axios": "^1.7.9",
"babel-preset-env": "^1.7.0",
"express": "^4.21.1",
"jsonc-parser": "^3.1.0",
"npm-run-all2": "^6.1.1",
"playwright": "^1.41.2",
"express": "^4.21.2",
"jsonc-parser": "^3.3.1",
"npm-run-all2": "^7.0.2",
"playwright": "^1.49.1",
"protoo-client": "^4.0.6",
"protoo-server": "^4.0.6",
"vite-plugin-node-polyfills": "^0.21.0"
"vite-plugin-node-polyfills": "^0.22.0"
}
}
138 changes: 138 additions & 0 deletions e2e/server/handler/ice/restart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import type { AcceptFn, Peer } from "protoo-server";
import { RTCPeerConnection } from "../..";
import { peerConfig } from "../../fixture";

const ice_restart_web_trigger_label = "ice_restart_web_trigger";
export class ice_restart_web_trigger {
pc!: RTCPeerConnection;

async exec(type: string, payload: any, accept: AcceptFn, peer: Peer) {
switch (type) {
case "init":
{
this.pc = new RTCPeerConnection({
...(await peerConfig),
icePasswordPrefix: "restartw",
});
this.pc.onIceCandidate.subscribe((candidate) => {
peer
.request(ice_restart_web_trigger_label + "ice", candidate)
.catch((e) => {
console.error(e);
});
});
this.pc.iceConnectionStateChange.subscribe((state) => {
console.log(state);
});

const transceiver = this.pc.addTransceiver("video");
transceiver.onTrack.subscribe((track) => {
transceiver.sender.replaceTrack(track);
const interval = setInterval(async () => {
if (this.pc.signalingState === "closed") {
clearInterval(interval);
return;
}
await transceiver.receiver.sendRtcpPLI(track.ssrc);
}, 3000);
});

this.pc.setLocalDescription(await this.pc.createOffer());
accept(this.pc.localDescription);
}
break;
case "candidate":
{
this.pc.addIceCandidate(payload);
accept({});
}
break;
case "answer":
{
await this.pc.setRemoteDescription(payload);
accept({});
}
break;
case "offer":
{
await this.pc.setRemoteDescription(payload);
const answer = await this.pc.createAnswer();
this.pc.setLocalDescription(answer);
accept(this.pc.localDescription);
}
break;
case "fin":
{
this.pc.close();
accept({});
}
break;
}
}
}

const ice_restart_node_trigger_label = "ice_restart_node_trigger";
export class ice_restart_node_trigger {
pc!: RTCPeerConnection;

async exec(type: string, payload: any, accept: AcceptFn, peer: Peer) {
switch (type) {
case "init":
{
this.pc = new RTCPeerConnection(await peerConfig);
this.pc.onIceCandidate.subscribe((candidate) => {
peer
.request(ice_restart_node_trigger_label + "ice", candidate)
.catch((e) => {
console.error(e);
});
});
this.pc.iceConnectionStateChange.subscribe((state) => {
console.log(state);
});

const transceiver = this.pc.addTransceiver("video");
transceiver.onTrack.subscribe((track) => {
transceiver.sender.replaceTrack(track);
const interval = setInterval(async () => {
if (this.pc.signalingState === "closed") {
clearInterval(interval);
return;
}
await transceiver.receiver.sendRtcpPLI(track.ssrc);
}, 3000);
});

this.pc.setLocalDescription(await this.pc.createOffer());
accept(this.pc.localDescription);
}
break;
case "candidate":
{
this.pc.addIceCandidate(payload);
accept({});
}
break;
case "answer":
{
await this.pc.setRemoteDescription(payload);
accept({});
}
break;
case "restart":
{
await this.pc.setLocalDescription(
await this.pc.createOffer({ iceRestart: true }),
);
accept(this.pc.localDescription);
}
break;
case "fin":
{
this.pc.close();
accept({});
}
break;
}
}
}
2 changes: 1 addition & 1 deletion e2e/server/handler/ice/trickle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ice_trickle_offer {
this.pc.onIceCandidate.subscribe((candidate) => {
peer.request("ice_trickle_offer", candidate).catch(() => {});
});
this.pc.setRemoteDescription(payload);
await this.pc.setRemoteDescription(payload);
this.pc.setLocalDescription(await this.pc.createAnswer());

accept(this.pc.localDescription);
Expand Down
6 changes: 6 additions & 0 deletions e2e/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import {
datachannel_answer,
datachannel_offer,
} from "./handler/datachannel/datachannel";
import {
ice_restart_node_trigger,
ice_restart_web_trigger,
} from "./handler/ice/restart";
import { ice_trickle_answer, ice_trickle_offer } from "./handler/ice/trickle";
import {
mediachannel_addTrack_answer,
Expand Down Expand Up @@ -116,6 +120,8 @@ server.on("connectionrequest", async (_, accept) => {
mediachannel_addtrack_removefirst_addtrack:
new mediachannel_addtrack_removefirst_addtrack(),
mediachannel_offer_replace_second: new mediachannel_offer_replace_second(),
ice_restart_web_trigger: new ice_restart_web_trigger(),
ice_restart_node_trigger: new ice_restart_node_trigger(),
};

const transport = accept();
Expand Down
1 change: 0 additions & 1 deletion e2e/tests/bundle/max-bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe("bundle_max_bundle", () => {
peer.on("notification", (e) => {
if (e.method === "candidate") {
if (pc.signalingState === "closed") return;
console.log(e.data);
pc.addIceCandidate(e.data!);
}
});
Expand Down
3 changes: 1 addition & 2 deletions e2e/tests/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ export const peer = new Peer(transport);

export async function waitVideoPlay(track: MediaStreamTrack) {
const video = document.createElement("video");
const media = new MediaStream();
media.addTrack(track);
const media = new MediaStream([track]);
video.srcObject = media;
video.autoplay = true;
video.muted = true;
Expand Down
148 changes: 148 additions & 0 deletions e2e/tests/ice/restart.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { peer, sleep, waitVideoPlay } from "../fixture";

const ice_restart_web_trigger_label = "ice_restart_web_trigger";
const ice_restart_node_trigger_label = "ice_restart_node_trigger";

describe("ice/restart", () => {
it(ice_restart_web_trigger_label, async () => {
if (!peer.connected) await new Promise<void>((r) => peer.on("open", r));
await sleep(100);

const pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
});
// pc.oniceconnectionstatechange = () => {
// console.log("ice connection state change", pc.iceConnectionState);
// };
pc.onicecandidate = ({ candidate }) => {
if (candidate) {
peer
.request(ice_restart_web_trigger_label, {
type: "candidate",
payload: candidate,
})
.catch(() => {});
}
};

const [track] = (
await navigator.mediaDevices.getUserMedia({ video: true })
).getTracks();
pc.addTrack(track);

peer.on("request", async (request, accept) => {
if (request.method !== ice_restart_web_trigger_label + "ice") {
return;
}
const candidate = request.data;
pc.addIceCandidate(candidate);
accept();
});

const offer = await peer.request(ice_restart_web_trigger_label, {
type: "init",
});
await pc.setRemoteDescription(offer);
await pc.setLocalDescription(await pc.createAnswer());

peer
.request(ice_restart_web_trigger_label, {
type: "answer",
payload: pc.localDescription,
})
.catch(() => {});

const remote = pc.getTransceivers().map((t) => t.receiver.track)[0];
await waitVideoPlay(remote);

{
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
peer
.request(ice_restart_web_trigger_label, {
type: "offer",
payload: pc.localDescription,
})
.catch(() => {});
}

await waitVideoPlay(remote);

await peer.request(ice_restart_web_trigger_label, {
type: "fin",
});
}, 20_000);

it(ice_restart_node_trigger_label, async () => {
if (!peer.connected) await new Promise<void>((r) => peer.on("open", r));
await sleep(100);

const pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
});
// pc.oniceconnectionstatechange = () => {
// console.log("ice connection state change", pc.iceConnectionState);
// };
pc.onicecandidate = ({ candidate }) => {
if (candidate) {
peer
.request(ice_restart_node_trigger_label, {
type: "candidate",
payload: candidate,
})
.catch(() => {});
}
};

const [track] = (
await navigator.mediaDevices.getUserMedia({ video: true })
).getTracks();
pc.addTrack(track);

peer.on("request", async (request, accept) => {
if (request.method !== ice_restart_node_trigger_label + "ice") {
return;
}
const candidate = request.data;
pc.addIceCandidate(candidate);
accept();
});

const offer = await peer.request(ice_restart_node_trigger_label, {
type: "init",
});
await pc.setRemoteDescription(offer);
await pc.setLocalDescription(await pc.createAnswer());

peer
.request(ice_restart_node_trigger_label, {
type: "answer",
payload: pc.localDescription,
})
.catch(() => {});

const remote = pc.getTransceivers().map((t) => t.receiver.track)[0];
await waitVideoPlay(remote);

{
const offer = await peer.request(ice_restart_node_trigger_label, {
type: "restart",
});
await pc.setRemoteDescription(offer);
await pc.setLocalDescription(await pc.createAnswer());
}

peer
.request(ice_restart_node_trigger_label, {
type: "answer",
payload: pc.localDescription,
})
.catch(() => {});

await waitVideoPlay(remote);

await peer.request(ice_restart_node_trigger_label, {
type: "fin",
});
}, 20_000);
});
Loading

0 comments on commit 1bda402

Please sign in to comment.