diff --git a/examples/ice/restart/answer.html b/examples/ice/restart/answer.html
index 22d12f9c..71c25b98 100644
--- a/examples/ice/restart/answer.html
+++ b/examples/ice/restart/answer.html
@@ -51,28 +51,38 @@
.getVideoTracks()
.forEach((track) => pc.addTrack(track, localStream));
- socket = new WebSocket("ws://localhost:8888");
- socket.onmessage = async (ev) => {
- const msg = JSON.parse(ev.data);
- console.log("message", msg);
- if (msg.candidate) {
- await pc.addIceCandidate(msg);
- } else {
- if (msg.type === "offer") {
- await pc.setRemoteDescription(msg);
- const answer = await pc.createAnswer();
- await pc.setLocalDescription(answer);
- const sdp = JSON.stringify(pc.localDescription);
- socket.send(sdp);
- } else {
- await pc.setRemoteDescription(msg);
- }
- }
- };
+ await connect();
+ socket.send(JSON.stringify({ type: "connect" }));
})();
}, []);
+ const connect = async () => {
+ if (socket) {
+ socket.close();
+ }
+ socket = new WebSocket("wss://e410-240d-f-b88-6800-6aa2-4c0c-4726-9284.ngrok-free.app");
+ socket.onmessage = async (ev) => {
+ const msg = JSON.parse(ev.data);
+ console.log("message", msg);
+ if (msg.candidate) {
+ await pc.addIceCandidate(msg);
+ } else {
+ if (msg.type === "offer") {
+ await pc.setRemoteDescription(msg);
+ const answer = await pc.createAnswer();
+ await pc.setLocalDescription(answer);
+ const sdp = JSON.stringify(pc.localDescription);
+ socket.send(sdp);
+ } else {
+ await pc.setRemoteDescription(msg);
+ }
+ }
+ };
+ await new Promise((resolve) => (socket.onopen = resolve));
+ }
+
const restart = async () => {
+ await connect();
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
const sdp = JSON.stringify(pc.localDescription);
@@ -80,6 +90,7 @@
}
const requestRestart = async () => {
+ await connect();
socket.send(JSON.stringify({ type: "restart" }));
}
diff --git a/examples/ice/restart/offer.ts b/examples/ice/restart/offer.ts
index 147fe2a6..4f49268b 100644
--- a/examples/ice/restart/offer.ts
+++ b/examples/ice/restart/offer.ts
@@ -4,12 +4,8 @@ import { RTCPeerConnection } from "../../../packages/webrtc/src";
const server = new Server({ port: 8888 });
console.log("start");
-server.on("connection", async (socket) => {
- console.log("connection");
+(async () => {
const pc = new RTCPeerConnection();
- pc.onIceCandidate.subscribe((candidate) => {
- socket.send(JSON.stringify(candidate));
- });
pc.iceConnectionStateChange.subscribe((state) => {
console.log(state);
});
@@ -22,30 +18,37 @@ server.on("connection", async (socket) => {
}, 3000);
});
- pc.setLocalDescription(await pc.createOffer());
- const sdp = JSON.stringify(pc.localDescription);
- socket.send(sdp);
+ server.on("connection", async (socket) => {
+ pc.onIceCandidate.subscribe((candidate) => {
+ socket.send(JSON.stringify(candidate));
+ });
- socket.on("message", async (data: any) => {
- const msg = JSON.parse(data);
- console.log(msg);
- if (msg.candidate) {
- await pc.addIceCandidate(msg);
- } else {
- if (msg.type === "offer") {
- await pc.setRemoteDescription(msg);
- const answer = await pc.createAnswer();
- await pc.setLocalDescription(answer);
- const sdp = JSON.stringify(pc.localDescription);
- socket.send(sdp);
- } else if (msg.type === "answer") {
- await pc.setRemoteDescription(msg);
- } else if (msg.type === "restart") {
- const offer = await pc.createOffer({ iceRestart: true });
- await pc.setLocalDescription(offer);
- const sdp = JSON.stringify(pc.localDescription);
- socket.send(sdp);
+ socket.on("message", async (data: any) => {
+ const msg = JSON.parse(data);
+ if (msg.candidate) {
+ await pc.addIceCandidate(msg);
+ } else {
+ if (msg.type === "connect") {
+ pc.setLocalDescription(await pc.createOffer());
+ const sdp = JSON.stringify(pc.localDescription);
+ socket.send(sdp);
+ } else if (msg.type === "offer") {
+ console.log("restarted by client");
+ await pc.setRemoteDescription(msg);
+ const answer = await pc.createAnswer();
+ await pc.setLocalDescription(answer);
+ const sdp = JSON.stringify(pc.localDescription);
+ socket.send(sdp);
+ } else if (msg.type === "answer") {
+ await pc.setRemoteDescription(msg);
+ } else if (msg.type === "restart") {
+ console.log("restarted by server");
+ const offer = await pc.createOffer({ iceRestart: true });
+ await pc.setLocalDescription(offer);
+ const sdp = JSON.stringify(pc.localDescription);
+ socket.send(sdp);
+ }
}
- }
+ });
});
-});
+})();
diff --git a/package.json b/package.json
index 3f3f3394..895eb935 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"doc": "npm run doc --workspaces --if-present && rm -rf doc && cd packages/webrtc && mv doc ../..",
"e2e": "cd e2e && npm run ci:silent",
"e2e:verbose": "cd e2e && npm run ci",
- "example": "tsx examples/ice/restart/offer.ts --watch",
+ "example": "tsx watch examples/ice/restart/offer.ts",
"format": "npm run format --workspaces --if-present && run-s format:examples format:e2e",
"format:e2e": "cd e2e && cp ../biome.json ./ && npm run format",
"format:examples": "cd examples && cp ../biome.json ./ && npm run format",
diff --git a/packages/ice/src/ice.ts b/packages/ice/src/ice.ts
index 8a6bcd36..8cdaa189 100644
--- a/packages/ice/src/ice.ts
+++ b/packages/ice/src/ice.ts
@@ -600,11 +600,17 @@ export class Connection implements IceConnection {
// 4.1.1.4 ? 生存確認 life check
private queryConsent = () => {
- this.queryConsentHandle = cancelable(async (r, _, onCancel) => {
+ if (this.queryConsentHandle) {
+ this.queryConsentHandle.resolve();
+ }
+
+ this.queryConsentHandle = cancelable(async (_, __, onCancel) => {
let failures = 0;
+ let canceled = false;
const cancelEvent = new AbortController();
onCancel.once(() => {
+ canceled = true;
failures += CONSENT_FAILURES;
cancelEvent.abort();
this.queryConsentHandle = undefined;
@@ -617,7 +623,7 @@ export class Connection implements IceConnection {
// """
try {
- while (!this.remoteIsLite && this.state !== "closed") {
+ while (this.state !== "closed" && !canceled) {
// # randomize between 0.8 and 1.2 times CONSENT_INTERVAL
await timers.setTimeout(
CONSENT_INTERVAL * (0.8 + 0.4 * Math.random()) * 1000,
@@ -625,10 +631,11 @@ export class Connection implements IceConnection {
{ signal: cancelEvent.signal },
);
- const pair = this.nominated;
- if (!pair) {
+ const nominated = this.nominated;
+ if (!nominated || canceled) {
break;
}
+
const request = this.buildRequest({
nominate: false,
localUsername,
@@ -636,9 +643,9 @@ export class Connection implements IceConnection {
iceControlling,
});
try {
- const [msg, addr] = await pair.protocol.request(
+ await nominated.protocol.request(
request,
- pair.remoteAddr,
+ nominated.remoteAddr,
Buffer.from(this.remotePassword, "utf8"),
0,
);
@@ -647,16 +654,18 @@ export class Connection implements IceConnection {
this.setState("connected");
}
} catch (error) {
- log("no stun response");
- failures++;
- this.setState("disconnected");
+ if (nominated.id === this.nominated?.id) {
+ log("no stun response");
+ failures++;
+ this.setState("disconnected");
+ break;
+ }
}
if (failures >= CONSENT_FAILURES) {
log("Consent to send expired");
this.queryConsentHandle = undefined;
- // 切断検知
- r(await this.close());
- return;
+ this.setState("closed");
+ break;
}
}
} catch (error) {}
@@ -692,7 +701,8 @@ export class Connection implements IceConnection {
this.protocols = [];
this.localCandidates = [];
- this.lookup?.close();
+ this.lookup?.close?.();
+ this.lookup = undefined;
}
private setState(state: IceState) {
diff --git a/packages/ice/src/iceBase.ts b/packages/ice/src/iceBase.ts
index b8eaf86b..5a4e09a1 100644
--- a/packages/ice/src/iceBase.ts
+++ b/packages/ice/src/iceBase.ts
@@ -9,6 +9,7 @@ import type { Cancelable } from "./helper";
import { classes, methods } from "./stun/const";
import { Message } from "./stun/message";
import type { Address, Protocol } from "./types/model";
+import { randomUUID } from "crypto";
const log = debug("werift-ice : packages/ice/src/ice.ts : log");
@@ -58,6 +59,7 @@ export interface IceConnection {
}
export class CandidatePair {
+ readonly id = randomUUID();
handle?: Cancelable;
nominated = false;
remoteNominated = false;
diff --git a/packages/webrtc/src/transport/ice.ts b/packages/webrtc/src/transport/ice.ts
index b39c302c..da5a0620 100644
--- a/packages/webrtc/src/transport/ice.ts
+++ b/packages/webrtc/src/transport/ice.ts
@@ -54,16 +54,7 @@ export class RTCIceTransport {
if (state !== this.state) {
this.state = state;
- if (this.onStateChange.ended) {
- return;
- }
-
- if (state === "closed") {
- this.onStateChange.execute(state);
- this.onStateChange.complete();
- } else {
- this.onStateChange.execute(state);
- }
+ this.onStateChange.execute(state);
}
}
@@ -146,6 +137,9 @@ export class RTCIceTransport {
this.setState("closed");
await this.connection.close();
}
+ this.onStateChange.complete();
+ this.onIceCandidate.complete();
+ this.onNegotiationNeeded.complete();
}
}