Creating a keep alive connection
This commit is contained in:
@@ -34,6 +34,7 @@ export class SmallWebRTCTransport {
|
|||||||
private reconnectionAttempts = 0;
|
private reconnectionAttempts = 0;
|
||||||
private maxReconnectionAttempts = 3;
|
private maxReconnectionAttempts = 3;
|
||||||
private isReconnecting = false;
|
private isReconnecting = false;
|
||||||
|
private keepAliveInterval: number | null = null;
|
||||||
|
|
||||||
constructor(callbacks: SmallWebRTCTransportCallbacks) {
|
constructor(callbacks: SmallWebRTCTransportCallbacks) {
|
||||||
this._callbacks = callbacks
|
this._callbacks = callbacks
|
||||||
@@ -287,12 +288,22 @@ export class SmallWebRTCTransport {
|
|||||||
|
|
||||||
dc.addEventListener('close', () => {
|
dc.addEventListener('close', () => {
|
||||||
this.log("datachannel closed")
|
this.log("datachannel closed")
|
||||||
|
if (this.keepAliveInterval) {
|
||||||
|
clearInterval(this.keepAliveInterval)
|
||||||
|
this.keepAliveInterval = null
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dc.addEventListener('open', () => {
|
dc.addEventListener('open', () => {
|
||||||
this.log("datachannel opened")
|
this.log("datachannel opened")
|
||||||
// Sending message that the client is ready, just for testing
|
// Sending message that the client is ready, just for testing
|
||||||
dc.send(JSON.stringify({id: 'clientReady', label: 'rtvi-ai', type:'client-ready'}))
|
dc.send(JSON.stringify({id: 'clientReady', label: 'rtvi-ai', type:'client-ready'}))
|
||||||
|
// @ts-ignore
|
||||||
|
this.keepAliveInterval = setInterval(() => {
|
||||||
|
const message = 'ping: ' + new Date().getTime()
|
||||||
|
dc.send(message);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dc.addEventListener('message', (evt: MessageEvent) => {
|
dc.addEventListener('message', (evt: MessageEvent) => {
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class SmallWebRTCClient:
|
|||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
if self._webrtcConnection.is_connected():
|
if self._webrtcConnection.is_connected():
|
||||||
logger.warning("Timeout: No video frame received within the specified time.")
|
logger.warning("Timeout: No video frame received within the specified time.")
|
||||||
# self._webrtcConnection.ask_to_renegotiate()
|
self._webrtcConnection.ask_to_renegotiate()
|
||||||
frame = None
|
frame = None
|
||||||
|
|
||||||
if frame is None or not isinstance(frame, VideoFrame):
|
if frame is None or not isinstance(frame, VideoFrame):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
import time
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ class SmallWebRTCConnection(EventEmitter):
|
|||||||
self._tracks = set()
|
self._tracks = set()
|
||||||
self._data_channel = None
|
self._data_channel = None
|
||||||
self._renegotiation_in_progress = False
|
self._renegotiation_in_progress = False
|
||||||
|
self._last_received_time = None
|
||||||
|
|
||||||
def _setup_listeners(self):
|
def _setup_listeners(self):
|
||||||
@self.pc.on("datachannel")
|
@self.pc.on("datachannel")
|
||||||
@@ -35,17 +37,29 @@ class SmallWebRTCConnection(EventEmitter):
|
|||||||
@channel.on("message")
|
@channel.on("message")
|
||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
try:
|
try:
|
||||||
json_message = json.loads(message)
|
# aiortc does not provide any way so we can be aware when we are disconnected,
|
||||||
await self.emit("appMessage", json_message)
|
# so we are using this keep alive message as a way to implement that
|
||||||
|
if isinstance(message, str) and message.startswith("ping"):
|
||||||
|
self._last_received_time = time.time()
|
||||||
|
else:
|
||||||
|
json_message = json.loads(message)
|
||||||
|
await self.emit("appMessage", json_message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error parsing JSON message {message}, {e}")
|
logger.exception(f"Error parsing JSON message {message}, {e}")
|
||||||
|
|
||||||
|
# Despite the fact that aiortc provides this listener, they don't have a status for "disconnected"
|
||||||
|
# So, in case we loose connection, this event will not be triggered
|
||||||
@self.pc.on("connectionstatechange")
|
@self.pc.on("connectionstatechange")
|
||||||
async def on_connectionstatechange():
|
async def on_connectionstatechange():
|
||||||
logger.info(f"Connection state is {self.pc.connectionState}")
|
await self._handle_new_connection_state()
|
||||||
await self.emit(self.pc.connectionState, self)
|
|
||||||
if self.pc.connectionState == "failed":
|
# Despite the fact that aiortc provides this listener, they don't have a status for "disconnected"
|
||||||
await self.close()
|
# So, in case we loose connection, this event will not be triggered
|
||||||
|
@self.pc.on("iceconnectionstatechange")
|
||||||
|
async def on_iceconnectionstatechange():
|
||||||
|
logger.info(
|
||||||
|
f"Ice connection state is {self.pc.iceConnectionState}, connection is {self.pc.connectionState}"
|
||||||
|
)
|
||||||
|
|
||||||
@self.pc.on("track")
|
@self.pc.on("track")
|
||||||
async def on_track(track):
|
async def on_track(track):
|
||||||
@@ -80,7 +94,7 @@ class SmallWebRTCConnection(EventEmitter):
|
|||||||
await self._create_answer(sdp, type)
|
await self._create_answer(sdp, type)
|
||||||
await self.pc.setLocalDescription(self.answer)
|
await self.pc.setLocalDescription(self.answer)
|
||||||
|
|
||||||
# TODO maybe we should refactor to receive a message from the client side when the renegotiation is completed.
|
# Maybe we should refactor to receive a message from the client side when the renegotiation is completed.
|
||||||
# or look at the peer connection listeners
|
# or look at the peer connection listeners
|
||||||
# but this is good enough for now for testing.
|
# but this is good enough for now for testing.
|
||||||
async def delayed_task():
|
async def delayed_task():
|
||||||
@@ -133,8 +147,24 @@ class SmallWebRTCConnection(EventEmitter):
|
|||||||
"pc_id": self.pc_id,
|
"pc_id": self.pc_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def _handle_new_connection_state(self):
|
||||||
|
state = self.pc.connectionState
|
||||||
|
logger.info(f"Connection state changed to: {state}")
|
||||||
|
await self.emit(state, self)
|
||||||
|
if state == "failed":
|
||||||
|
logger.warning("Connection failed, closing peer connection.")
|
||||||
|
await self.close()
|
||||||
|
|
||||||
|
# Despite the fact that aiortc provides this listener, they don't have a status for "disconnected"
|
||||||
|
# So, there is no advantage in looking at self.pc.connectionState
|
||||||
|
# That is why we are trying to keep our own state
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
return self.pc.connectionState == "connected"
|
if self._last_received_time is None:
|
||||||
|
# if we have never received a message, it is probably because the client has not created a data channel
|
||||||
|
# so we are going to trust aiortc in this case
|
||||||
|
return self.pc.connectionState == "connected"
|
||||||
|
# Checks if the last received ping was within the last 3 seconds.
|
||||||
|
return (time.time() - self._last_received_time) < 3
|
||||||
|
|
||||||
def audio_input_track(self):
|
def audio_input_track(self):
|
||||||
# Transceivers always appear in creation-order for both peers
|
# Transceivers always appear in creation-order for both peers
|
||||||
|
|||||||
Reference in New Issue
Block a user