11import logging
22from abc import ABC , abstractmethod
3+ from typing import Optional
34
45from aiortc import (
56 RTCPeerConnection ,
1415
1516
1617class JanusPlugin (ABC ):
17- """Base class to inherit when implementing a plugin """
18+ """Base class for implementing Janus plugins. """
1819
1920 name : str = "janus.plugin.base.dummy"
20- """Plugin name
21+ """Plugin name that must match the plugin name in Janus server."""
2122
22- Must override to match plugin name in Janus server.
23- """
24-
25- __id : str
26- """Plugin handle ID. Given by Janus."""
23+ __id : Optional [int ]
24+ """Plugin handle ID assigned by Janus."""
2725
2826 __session : JanusSession
29- """Session instance this plugin is created from ."""
27+ """Session instance this plugin is attached to ."""
3028
3129 _pc : RTCPeerConnection
32- """A WebRTC PeerConnection. A plugin handle is expected to have
33- only 1 PC.
34- """
30+ """WebRTC PeerConnection for this plugin handle."""
3531
3632 def __init__ (self ) -> None :
3733 self .__id = None
3834 self ._pc = RTCPeerConnection ()
3935
4036 @property
41- def id (self ) -> int :
37+ def id (self ) -> Optional [int ]:
38+ """Get the plugin handle ID.
39+
40+ Returns:
41+ The plugin handle ID assigned by Janus, or None if not attached.
42+ """
4243 return self .__id
4344
44- async def attach (self , session : JanusSession ):
45+ async def attach (self , session : JanusSession ) -> None :
46+ """Attach this plugin to a Janus session.
47+
48+ Args:
49+ session: The JanusSession to attach this plugin to.
50+
51+ Raises:
52+ Exception: If plugin is already attached to a session.
53+ """
4554 if self .__id :
4655 raise Exception (f"Plugin already attached to session ({ self .__session } )" )
4756
4857 self .__session = session
4958 self .__id = await session .attach_plugin (self )
5059
51- async def destroy (self ):
52- """Destroy plugin handle"""
53-
60+ async def destroy (self ) -> None :
61+ """Destroy the plugin handle and clean up resources."""
5462 message_transaction = await self .send ({"janus" : "detach" })
5563 await message_transaction .get ()
5664 await message_transaction .done ()
@@ -67,31 +75,66 @@ async def send(
6775 self ,
6876 message : dict ,
6977 ) -> MessageTransaction :
70- """Send raw message to plugin
78+ """Send a message to this plugin handle.
79+
80+ Automatically attaches the plugin handle ID to the message.
7181
72- Will auto attach plugin ID to the message.
82+ Args:
83+ message: JSON serializable dictionary to send to the plugin.
7384
74- :param message: JSON serializable dictionary to send
75- :return: Synchronous reply from server
85+ Returns:
86+ MessageTransaction for tracking the response.
87+
88+ Raises:
89+ Exception: If plugin is not attached to a session.
7690 """
91+ if self .__id is None :
92+ raise Exception ("Plugin not attached to session" )
7793
7894 self .__sanitize_message (message = message )
7995
8096 return await self .__session .send (message , handle_id = self .__id )
8197
8298 @abstractmethod
83- async def on_receive (self , response : dict ):
84- """Handle asynchronous events from Janus"""
99+ async def on_receive (self , response : dict ) -> None :
100+ """Handle asynchronous events from Janus.
101+
102+ This method must be implemented by subclasses to handle
103+ plugin-specific messages and events.
104+
105+ Args:
106+ response: The response message from Janus.
107+ """
85108 pass
86109
87110 async def create_jsep (self , pc : RTCPeerConnection , trickle : bool = False ) -> dict :
111+ """Create a JSEP object from a peer connection's local description.
112+
113+ Args:
114+ pc: The RTCPeerConnection to extract the description from.
115+ trickle: Whether to enable trickle ICE.
116+
117+ Returns:
118+ A JSEP dictionary containing SDP, type, and trickle settings.
119+ """
88120 return {
89121 "sdp" : pc .localDescription .sdp ,
90122 "trickle" : trickle ,
91123 "type" : pc .localDescription .type ,
92124 }
93125
94- async def on_receive_jsep (self , jsep : dict ):
126+ async def on_receive_jsep (self , jsep : dict ) -> None :
127+ """Handle incoming JSEP (WebRTC signaling) messages.
128+
129+ Sets the remote description on the peer connection from the
130+ received JSEP offer or answer.
131+
132+ Args:
133+ jsep: The JSEP message containing SDP and type.
134+
135+ Raises:
136+ Exception: If the peer connection is in closed state.
137+ """
95138 if self ._pc :
96139 if self ._pc .signalingState == "closed" :
97140 raise Exception ("Received JSEP when PeerConnection is closed" )
@@ -100,13 +143,15 @@ async def on_receive_jsep(self, jsep: dict):
100143 RTCSessionDescription (sdp = jsep ["sdp" ], type = jsep ["type" ])
101144 )
102145
103- async def trickle (self , sdpMLineIndex , candidate ):
104- """Send WebRTC candidates to Janus
146+ async def trickle (
147+ self , sdpMLineIndex : int , candidate : Optional [str ] = None
148+ ) -> None :
149+ """Send WebRTC ICE candidates to Janus using trickle ICE.
105150
106- :param sdpMLineIndex: (I don't know what is this)
107- :param candidate: Candidate payload. (I got it from WebRTC instance callback)
151+ Args:
152+ sdpMLineIndex: The SDP media line index for the candidate.
153+ candidate: The ICE candidate string, or None to signal end of candidates.
108154 """
109-
110155 candidate_payload = dict ()
111156 if candidate :
112157 candidate_payload = {
@@ -119,6 +164,5 @@ async def trickle(self, sdpMLineIndex, candidate):
119164 # TODO: test it
120165 candidate_payload = None
121166
122- # await self.send({"janus": "trickle", "candidate": candidate_payload})
123167 await self .send ({"janus" : "trickle" , "candidate" : candidate_payload })
124168 # TODO: Implement sending an array of candidates
0 commit comments