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(); } }