Отказ подключения WEBRTC между приложениями next.js и qtpythonPython

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Отказ подключения WEBRTC между приложениями next.js и qtpython

Сообщение Anonymous »

Я разрабатываю два приложения, приложение Next.js и qtpython. Цель состоит в том, что приложение Next.js будет генерировать предложение WEBRTC, опубликовать его в документ Firebase и начнет опросить ответ. Приложение Qtpython будет опросить этот документ для предложения, после чего оно будет генерировать ответ соответствующим образом и опубликовать этот ответ в тот же документ Firebase. Приложение Next.js получит этот ответ и инициирует соединение WEBRTC. Кандидаты на льду собираются с обеих сторон, используя серверы STUN и Turn от Twilio, которые принимаются с использованием функции Firebase. < /P>
Части, которые работают: < /p>
  • Ответ и предлагают создание < /li>
    . />
The parts that fail:
  • Sometimes/some of the TURN and STUN servers are failing and returning Error: 701
  • After the answer is added to the Remote Description, the ICE Connection State disconnects, and the PeerConnection state fails
код:
функция webrtc на стороне hearl.js: < /p>
const startStream = () => {
let peerConnection: RTCPeerConnection;
let sdpOffer: RTCSessionDescription | null = null;
let backoffDelay = 2000;

const waitForIceGathering = () =>
new Promise((resolve) => {
if (peerConnection.iceGatheringState === "complete") return resolve();
const check = () => {
if (peerConnection.iceGatheringState === "complete") {
peerConnection.removeEventListener("icegatheringstatechange", check);
resolve();
}
};
peerConnection.addEventListener("icegatheringstatechange", check);
});

const init = async () => {
const response = await fetch("", { method: "POST" });
if (!response.ok) {
console.error("Failed to fetch ICE servers");
setErrorMessage("Failed to fetch ICE servers");
return;
}
let iceServers = await response.json();
// iceServers[0] = {"urls": ["stun:stun.l.google.com:19302"]};

console.log("ICE servers:", iceServers);

const config: RTCConfiguration = {
iceServers: iceServers,
};

peerConnection = new RTCPeerConnection(config);
peerConnectionRef.current = peerConnection;

if (!media) {
console.error("No media stream available");
setErrorMessage("No media stream available");
return;
}

media.getTracks().forEach((track) => {
const sender = peerConnection.addTrack(track, media);
const transceiver = peerConnection.getTransceivers().find(t => t.sender === sender);
if (transceiver) {
transceiver.direction = "sendonly";
}
});

peerConnection.getTransceivers().forEach((t, i) => {
console.log(`[Transceiver ${i}] kind: ${t.sender.track?.kind}, direction: ${t.direction}`);
});
console.log("Senders:", peerConnection.getSenders());

};

const createOffer = async () => {
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log("ICE candidate:", event.candidate);
}
};

peerConnection.oniceconnectionstatechange = () => {
console.log("ICE Connection State:", peerConnection.iceConnectionState);
};

peerConnection.onicecandidateerror = (error) => {
console.error("ICE Candidate error:", error);
};

if (!media || media.getTracks().length === 0) {
console.error("No media tracks to offer. Did startMedia() complete?");
return;
}

const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
await waitForIceGathering();

sdpOffer = peerConnection.localDescription;
console.log("SDP offer created:", sdpOffer);
};

const submitOffer = async () => {
const response = await fetch("", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code: sessionCode,
offer: sdpOffer,
metadata: {
mic: isMicOn === "on",
webcam: isVidOn === "on",
resolution,
fps,
platform: "mobile",
facingMode: isFrontCamera ? "user" : "environment",
exposureLevel: exposure,
timestamp: Date.now(),
},
}),
});

console.log("Offer submitted:", sdpOffer);
console.log("Response:", response);

if (!response.ok) {
throw new Error("Failed to submit offer");
} else {
console.log("✅ Offer submitted successfully");
}

peerConnection.onconnectionstatechange = () => {
console.log("PeerConnection state:", peerConnection.connectionState);
};

};

const addAnswer = async (answer: string) => {
const parsed = JSON.parse(answer);
if (!peerConnection.currentRemoteDescription) {
await peerConnection.setRemoteDescription(parsed);
console.log("✅ Remote SDP answer set");
setConnectionStatus("connected");
setIsStreamOn(true);
}
};

const pollForAnswer = async () => {
const response = await fetch("", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: sessionCode }),
});

if (response.status === 204) {
return false;
}

if (response.ok) {
const data = await response.json();
console.log("Polling response:", data);
if (data.answer) {
await addAnswer(JSON.stringify(data.answer));
setInterval(async () => {
const stats = await peerConnection.getStats();
stats.forEach(report => {
if (report.type === "candidate-pair" && report.state === "succeeded") {
console.log("✅ ICE Connected:", report);
}
if (report.type === "outbound-rtp" && report.kind === "video") {
console.log("📤 Video Sent:", {
packetsSent: report.packetsSent,
bytesSent: report.bytesSent,
});
}
});
}, 3000);
return true;
}
}
return false;
};

const pollTimer = async () => {
while (true) {
const gotAnswer = await pollForAnswer();
if (gotAnswer) break;

await new Promise((r) => setTimeout(r, backoffDelay));
backoffDelay = Math.min(backoffDelay * 2, 30000);
}
};

(async () => {
try {
await init();
await createOffer();
await submitOffer();
await pollTimer();
} catch (err) {
console.error("WebRTC sendonly setup error:", err);
}
})();
};
< /code>
Класс Webrtc на стороне qtpython: < /p>
class WebRTCWorker(QObject):
video_frame_received = pyqtSignal(object)
connection_state_changed = pyqtSignal(str)

def __init__(self, code: str, widget_win_id: int, offer):
super().__init__()
self.code = code
self.offer = offer
self.pc = None
self.running = False
# self.gst_pipeline = GStreamerPipeline(widget_win_id)

def start(self):
self.running = True
threading.Thread(target = self._run_async_thread, daemon = True).start()

def stop(self):
self.running = False
if self.pc:
asyncio.run_coroutine_threadsafe(self.pc.close(), asyncio.get_event_loop())
# self.gst_pipeline.stop()

def _run_async_thread(self):
asyncio.run(self._run())

async def _run(self):
ice_servers = self.fetch_ice_servers()
print("[TURN] Using ICE servers:", ice_servers)
config = RTCConfiguration(iceServers = ice_servers)
self.pc = RTCPeerConnection(configuration = config)

@self.pc.on("connectionstatechange")
async def on_connectionstatechange():
state = self.pc.connectionState
print(f"[WebRTC] State: {state}")
self.connection_state_changed.emit(state)

@self.pc.on("track")
def on_track(track):
print(f"[WebRTC] Track received: {track.kind}")
if track.kind == "video":
# asyncio.ensure_future(self.consume_video(track))
asyncio.ensure_future(self.handle_track(track))

@self.pc.on("datachannel")
def on_datachannel(channel):
print(f"Data channel established: {channel.label}")

@self.pc.on("iceconnectionstatechange")
async def on_iceconnchange():
print("[WebRTC] ICE connection state:", self.pc.iceConnectionState)

if not self.offer:
self.connection_state_changed.emit("failed")
return

self.pc.addTransceiver("video", direction="recvonly")
self.pc.addTransceiver("audio", direction="recvonly")

await self.pc.setRemoteDescription(RTCSessionDescription(**self.offer))
answer = await self.pc.createAnswer()
print("[WebRTC] Created answer:", answer)
await self.pc.setLocalDescription(answer)
print("[WebRTC] Local SDP answer:\n", self.pc.localDescription.sdp)
self.send_answer(self.pc.localDescription)

def fetch_ice_servers(self):
try:
response = requests.post("", timeout = 10)
response.raise_for_status()
data = response.json()

print(f"[WebRTC] Fetched ICE servers: {data}")

ice_servers = []
for server in data:
ice_servers.append(
RTCIceServer(
urls=server["urls"],
username=server.get("username"),
credential=server.get("credential")
)
)
# ice_servers[0] = RTCIceServer(urls=["stun:stun.l.google.com:19302"])
return ice_servers
except Exception as e:
print(f"❌ Failed to fetch TURN credentials: {e}")
return []

def send_answer(self, sdp):
try:
res = requests.post(
"",
json = {
"code": self.code,
"answer": {
"sdp": sdp.sdp,
"type": sdp.type
},
},
timeout = 10
)
if res.status_code == 200:
print("[WebRTC] Answer submitted successfully")
else:
print(f"[WebRTC] Answer submission failed: {res.status_code}")
except Exception as e:
print(f"[WebRTC] Answer error: {e}")

async def consume_video(self, track: MediaStreamTrack):
print("[WebRTC] Starting video track consumption")
self.gst_pipeline.build_pipeline()
while self.running:
try:
frame: VideoFrame = await track.recv()
img = frame.to_ndarray(format="rgb24")
self.gst_pipeline.push_frame(img.tobytes(), frame.width, frame.height)
except Exception as e:
print(f"[WebRTC] Video track ended: {e}")
break

async def handle_track(self, track: MediaStreamTrack):
print("Inside handle track")
self.track = track
frame_count = 0
while True:
try:
print("Waiting for frame...")
frame = await asyncio.wait_for(track.recv(), timeout = 5.0)
frame_count += 1
print(f"Received frame {frame_count}")

if isinstance(frame, VideoFrame):
print(f"Frame type: VideoFrame, pts: {frame.pts}, time_base: {frame.time_base}")
frame = frame.to_ndarray(format = "bgr24")
elif isinstance(frame, np.ndarray):
print(f"Frame type: numpy array")
else:
print(f"Unexpected frame type: {type(frame)}")
continue

# Add timestamp to the frame
current_time = datetime.now()
new_time = current_time - timedelta(seconds = 55)
timestamp = new_time.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
cv2.putText(frame, timestamp, (10, frame.shape[0] - 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imwrite(f"imgs/received_frame_{frame_count}.jpg", frame)
print(f"Saved frame {frame_count} to file")
cv2.imshow("Frame", frame)

# Exit on 'q' key press
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except asyncio.TimeoutError:
print("Timeout waiting for frame, continuing...")
except Exception as e:
print(f"Error in handle_track: {str(e)}")
if "Connection" in str(e):
break

print("Exiting handle_track")
await self.pc.close()
< /code>
Вещи, которые я пробовал < /p>

[*] Первоначально я не получал кандидатов в ледяные кандидаты с "type = relay", когда я использовал общественные оглушительные серверы и /или частные измельченные STUN и серверы поворота. После дальнейшего тестирования я обнаружил, что STUN STUR SERVER и несколько серверов поворота были недоступны. Итак, я перешел на Twilio, где я получаю кандидаты на ледяной, с «type = relay», что, на мой взгляд, означает, что с серверами поворота связываются, чтобы облегчить соединение
, чтобы проверить, почему я получаю ошибку 701, но я еще не выяснил, почему. генерируется, получен и установлен обеими сторонами. Тем не менее, соединение WEBRTC по -прежнему в конечном итоге терпит неудачу. < /P>
Я был бы признателен за любую помощь и советы. Пожалуйста, не стесняйтесь сообщить мне, требует ли вопроса больше информации или требуется ли какие -либо журналы (я не включил их, потому что я волновался, что они могут содержать Senstive Data о моем IP -адресе и моей сетевой настройке).

Подробнее здесь: https://stackoverflow.com/questions/796 ... plications
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Python»