Skip to content

Commit 1bda402

Browse files
authored
Merge pull request #430 from shinyoshiaki/feature/update-ice
support restart ice
2 parents 51e5da2 + 937cda3 commit 1bda402

File tree

38 files changed

+3299
-4284
lines changed

38 files changed

+3299
-4284
lines changed

.github/workflows/nodejs.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ jobs:
3434
npm i
3535
cd e2e
3636
npm i
37-
npx playwright install
37+
npx playwright install chromium
3838
cd ../
39+
# - name: Setup tmate session
40+
# uses: mxschmitt/action-tmate@v3
3941
- name: test
4042
env:
4143
CI: true

e2e/package-lock.json

Lines changed: 1369 additions & 1661 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/package.json

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,34 @@
1111
"ci:silent": "npm run build && run-p server:silent chrome:prod",
1212
"clean": "pkill -9 gst-launch",
1313
"debug": "cd debug && npm run dev",
14+
"dev": "vitest ./tests --browser.headless",
1415
"firefox:dev": "vitest run ./tests --browser.headless --config vitest.firefox.config.ts",
1516
"format": "biome check --write server tests",
17+
"server": "ts-node-dev --project tsconfig.server.json ./server/main.ts",
1618
"server:dev": "DEBUG=werift* ts-node-dev --project tsconfig.server.json ./server/main.ts",
1719
"server:prod": "DEBUG=werift* node lib/e2e/server/main.js",
1820
"server:silent": "node lib/e2e/server/main.js",
19-
"type": "tsc --noEmit -p tsconfig.server.json && tsc --noEmit -p tsconfig.json"
21+
"type": "tsc --noEmit -p tsconfig.server.json && tsc --noEmit -p tsconfig.json",
22+
"upgrade-interactive": "npx npm-check-updates -i"
2023
},
2124
"dependencies": {
2225
"bowser": "^2.11.0",
23-
"werift": "^0.15.10",
24-
"werift-rtp": "^0.3.1"
26+
"werift": "^0.20.1",
27+
"werift-rtp": "^0.8.2"
2528
},
2629
"devDependencies": {
27-
"@types/express": "^4.17.13",
28-
"@types/protoo-client": "^4.0.1",
29-
"@types/protoo-server": "^4.0.2",
30-
"@vitest/browser": "2.0.5",
31-
"axios": "^1.7.4",
30+
"@types/express": "^5.0.0",
31+
"@types/protoo-client": "^4.0.4",
32+
"@types/protoo-server": "^4.0.6",
33+
"@vitest/browser": "2.1.8",
34+
"axios": "^1.7.9",
3235
"babel-preset-env": "^1.7.0",
33-
"express": "^4.21.1",
34-
"jsonc-parser": "^3.1.0",
35-
"npm-run-all2": "^6.1.1",
36-
"playwright": "^1.41.2",
36+
"express": "^4.21.2",
37+
"jsonc-parser": "^3.3.1",
38+
"npm-run-all2": "^7.0.2",
39+
"playwright": "^1.49.1",
3740
"protoo-client": "^4.0.6",
3841
"protoo-server": "^4.0.6",
39-
"vite-plugin-node-polyfills": "^0.21.0"
42+
"vite-plugin-node-polyfills": "^0.22.0"
4043
}
4144
}

e2e/server/handler/ice/restart.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type { AcceptFn, Peer } from "protoo-server";
2+
import { RTCPeerConnection } from "../..";
3+
import { peerConfig } from "../../fixture";
4+
5+
const ice_restart_web_trigger_label = "ice_restart_web_trigger";
6+
export class ice_restart_web_trigger {
7+
pc!: RTCPeerConnection;
8+
9+
async exec(type: string, payload: any, accept: AcceptFn, peer: Peer) {
10+
switch (type) {
11+
case "init":
12+
{
13+
this.pc = new RTCPeerConnection({
14+
...(await peerConfig),
15+
icePasswordPrefix: "restartw",
16+
});
17+
this.pc.onIceCandidate.subscribe((candidate) => {
18+
peer
19+
.request(ice_restart_web_trigger_label + "ice", candidate)
20+
.catch((e) => {
21+
console.error(e);
22+
});
23+
});
24+
this.pc.iceConnectionStateChange.subscribe((state) => {
25+
console.log(state);
26+
});
27+
28+
const transceiver = this.pc.addTransceiver("video");
29+
transceiver.onTrack.subscribe((track) => {
30+
transceiver.sender.replaceTrack(track);
31+
const interval = setInterval(async () => {
32+
if (this.pc.signalingState === "closed") {
33+
clearInterval(interval);
34+
return;
35+
}
36+
await transceiver.receiver.sendRtcpPLI(track.ssrc);
37+
}, 3000);
38+
});
39+
40+
this.pc.setLocalDescription(await this.pc.createOffer());
41+
accept(this.pc.localDescription);
42+
}
43+
break;
44+
case "candidate":
45+
{
46+
this.pc.addIceCandidate(payload);
47+
accept({});
48+
}
49+
break;
50+
case "answer":
51+
{
52+
await this.pc.setRemoteDescription(payload);
53+
accept({});
54+
}
55+
break;
56+
case "offer":
57+
{
58+
await this.pc.setRemoteDescription(payload);
59+
const answer = await this.pc.createAnswer();
60+
this.pc.setLocalDescription(answer);
61+
accept(this.pc.localDescription);
62+
}
63+
break;
64+
case "fin":
65+
{
66+
this.pc.close();
67+
accept({});
68+
}
69+
break;
70+
}
71+
}
72+
}
73+
74+
const ice_restart_node_trigger_label = "ice_restart_node_trigger";
75+
export class ice_restart_node_trigger {
76+
pc!: RTCPeerConnection;
77+
78+
async exec(type: string, payload: any, accept: AcceptFn, peer: Peer) {
79+
switch (type) {
80+
case "init":
81+
{
82+
this.pc = new RTCPeerConnection(await peerConfig);
83+
this.pc.onIceCandidate.subscribe((candidate) => {
84+
peer
85+
.request(ice_restart_node_trigger_label + "ice", candidate)
86+
.catch((e) => {
87+
console.error(e);
88+
});
89+
});
90+
this.pc.iceConnectionStateChange.subscribe((state) => {
91+
console.log(state);
92+
});
93+
94+
const transceiver = this.pc.addTransceiver("video");
95+
transceiver.onTrack.subscribe((track) => {
96+
transceiver.sender.replaceTrack(track);
97+
const interval = setInterval(async () => {
98+
if (this.pc.signalingState === "closed") {
99+
clearInterval(interval);
100+
return;
101+
}
102+
await transceiver.receiver.sendRtcpPLI(track.ssrc);
103+
}, 3000);
104+
});
105+
106+
this.pc.setLocalDescription(await this.pc.createOffer());
107+
accept(this.pc.localDescription);
108+
}
109+
break;
110+
case "candidate":
111+
{
112+
this.pc.addIceCandidate(payload);
113+
accept({});
114+
}
115+
break;
116+
case "answer":
117+
{
118+
await this.pc.setRemoteDescription(payload);
119+
accept({});
120+
}
121+
break;
122+
case "restart":
123+
{
124+
await this.pc.setLocalDescription(
125+
await this.pc.createOffer({ iceRestart: true }),
126+
);
127+
accept(this.pc.localDescription);
128+
}
129+
break;
130+
case "fin":
131+
{
132+
this.pc.close();
133+
accept({});
134+
}
135+
break;
136+
}
137+
}
138+
}

e2e/server/handler/ice/trickle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class ice_trickle_offer {
5555
this.pc.onIceCandidate.subscribe((candidate) => {
5656
peer.request("ice_trickle_offer", candidate).catch(() => {});
5757
});
58-
this.pc.setRemoteDescription(payload);
58+
await this.pc.setRemoteDescription(payload);
5959
this.pc.setLocalDescription(await this.pc.createAnswer());
6060

6161
accept(this.pc.localDescription);

e2e/server/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import {
2727
datachannel_answer,
2828
datachannel_offer,
2929
} from "./handler/datachannel/datachannel";
30+
import {
31+
ice_restart_node_trigger,
32+
ice_restart_web_trigger,
33+
} from "./handler/ice/restart";
3034
import { ice_trickle_answer, ice_trickle_offer } from "./handler/ice/trickle";
3135
import {
3236
mediachannel_addTrack_answer,
@@ -116,6 +120,8 @@ server.on("connectionrequest", async (_, accept) => {
116120
mediachannel_addtrack_removefirst_addtrack:
117121
new mediachannel_addtrack_removefirst_addtrack(),
118122
mediachannel_offer_replace_second: new mediachannel_offer_replace_second(),
123+
ice_restart_web_trigger: new ice_restart_web_trigger(),
124+
ice_restart_node_trigger: new ice_restart_node_trigger(),
119125
};
120126

121127
const transport = accept();

e2e/tests/bundle/max-bundle.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ describe("bundle_max_bundle", () => {
2222
peer.on("notification", (e) => {
2323
if (e.method === "candidate") {
2424
if (pc.signalingState === "closed") return;
25-
console.log(e.data);
2625
pc.addIceCandidate(e.data!);
2726
}
2827
});

e2e/tests/fixture.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ export const peer = new Peer(transport);
1111

1212
export async function waitVideoPlay(track: MediaStreamTrack) {
1313
const video = document.createElement("video");
14-
const media = new MediaStream();
15-
media.addTrack(track);
14+
const media = new MediaStream([track]);
1615
video.srcObject = media;
1716
video.autoplay = true;
1817
video.muted = true;

e2e/tests/ice/restart.test.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { peer, sleep, waitVideoPlay } from "../fixture";
2+
3+
const ice_restart_web_trigger_label = "ice_restart_web_trigger";
4+
const ice_restart_node_trigger_label = "ice_restart_node_trigger";
5+
6+
describe("ice/restart", () => {
7+
it(ice_restart_web_trigger_label, async () => {
8+
if (!peer.connected) await new Promise<void>((r) => peer.on("open", r));
9+
await sleep(100);
10+
11+
const pc = new RTCPeerConnection({
12+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
13+
});
14+
// pc.oniceconnectionstatechange = () => {
15+
// console.log("ice connection state change", pc.iceConnectionState);
16+
// };
17+
pc.onicecandidate = ({ candidate }) => {
18+
if (candidate) {
19+
peer
20+
.request(ice_restart_web_trigger_label, {
21+
type: "candidate",
22+
payload: candidate,
23+
})
24+
.catch(() => {});
25+
}
26+
};
27+
28+
const [track] = (
29+
await navigator.mediaDevices.getUserMedia({ video: true })
30+
).getTracks();
31+
pc.addTrack(track);
32+
33+
peer.on("request", async (request, accept) => {
34+
if (request.method !== ice_restart_web_trigger_label + "ice") {
35+
return;
36+
}
37+
const candidate = request.data;
38+
pc.addIceCandidate(candidate);
39+
accept();
40+
});
41+
42+
const offer = await peer.request(ice_restart_web_trigger_label, {
43+
type: "init",
44+
});
45+
await pc.setRemoteDescription(offer);
46+
await pc.setLocalDescription(await pc.createAnswer());
47+
48+
peer
49+
.request(ice_restart_web_trigger_label, {
50+
type: "answer",
51+
payload: pc.localDescription,
52+
})
53+
.catch(() => {});
54+
55+
const remote = pc.getTransceivers().map((t) => t.receiver.track)[0];
56+
await waitVideoPlay(remote);
57+
58+
{
59+
const offer = await pc.createOffer({ iceRestart: true });
60+
await pc.setLocalDescription(offer);
61+
peer
62+
.request(ice_restart_web_trigger_label, {
63+
type: "offer",
64+
payload: pc.localDescription,
65+
})
66+
.catch(() => {});
67+
}
68+
69+
await waitVideoPlay(remote);
70+
71+
await peer.request(ice_restart_web_trigger_label, {
72+
type: "fin",
73+
});
74+
}, 20_000);
75+
76+
it(ice_restart_node_trigger_label, async () => {
77+
if (!peer.connected) await new Promise<void>((r) => peer.on("open", r));
78+
await sleep(100);
79+
80+
const pc = new RTCPeerConnection({
81+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
82+
});
83+
// pc.oniceconnectionstatechange = () => {
84+
// console.log("ice connection state change", pc.iceConnectionState);
85+
// };
86+
pc.onicecandidate = ({ candidate }) => {
87+
if (candidate) {
88+
peer
89+
.request(ice_restart_node_trigger_label, {
90+
type: "candidate",
91+
payload: candidate,
92+
})
93+
.catch(() => {});
94+
}
95+
};
96+
97+
const [track] = (
98+
await navigator.mediaDevices.getUserMedia({ video: true })
99+
).getTracks();
100+
pc.addTrack(track);
101+
102+
peer.on("request", async (request, accept) => {
103+
if (request.method !== ice_restart_node_trigger_label + "ice") {
104+
return;
105+
}
106+
const candidate = request.data;
107+
pc.addIceCandidate(candidate);
108+
accept();
109+
});
110+
111+
const offer = await peer.request(ice_restart_node_trigger_label, {
112+
type: "init",
113+
});
114+
await pc.setRemoteDescription(offer);
115+
await pc.setLocalDescription(await pc.createAnswer());
116+
117+
peer
118+
.request(ice_restart_node_trigger_label, {
119+
type: "answer",
120+
payload: pc.localDescription,
121+
})
122+
.catch(() => {});
123+
124+
const remote = pc.getTransceivers().map((t) => t.receiver.track)[0];
125+
await waitVideoPlay(remote);
126+
127+
{
128+
const offer = await peer.request(ice_restart_node_trigger_label, {
129+
type: "restart",
130+
});
131+
await pc.setRemoteDescription(offer);
132+
await pc.setLocalDescription(await pc.createAnswer());
133+
}
134+
135+
peer
136+
.request(ice_restart_node_trigger_label, {
137+
type: "answer",
138+
payload: pc.localDescription,
139+
})
140+
.catch(() => {});
141+
142+
await waitVideoPlay(remote);
143+
144+
await peer.request(ice_restart_node_trigger_label, {
145+
type: "fin",
146+
});
147+
}, 20_000);
148+
});

0 commit comments

Comments
 (0)