Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0339.py @ 4067:3f62c2445df1
plugin XEP-0339: "Source-Specific Media Attributes in Jingle" implementation:
rel 439
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 01 Jun 2023 20:53:33 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
4066:e75827204fe0 | 4067:3f62c2445df1 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 # Libervia plugin | |
4 # Copyright (C) 2009-2023 Jérôme Poisson (goffi@goffi.org) | |
5 | |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
19 from typing import List | |
20 | |
21 from twisted.words.protocols.jabber.xmlstream import XMPPHandler | |
22 from twisted.words.xish import domish | |
23 from wokkel import disco, iwokkel | |
24 from zope.interface import implementer | |
25 | |
26 from sat.core import exceptions | |
27 from sat.core.constants import Const as C | |
28 from sat.core.core_types import SatXMPPEntity | |
29 from sat.core.i18n import _ | |
30 from sat.core.log import getLogger | |
31 from sat.tools import xml_tools | |
32 | |
33 log = getLogger(__name__) | |
34 | |
35 NS_JINGLE_RTP_SSMA = "urn:xmpp:jingle:apps:rtp:ssma:0" | |
36 | |
37 PLUGIN_INFO = { | |
38 C.PI_NAME: "Source-Specific Media Attributes in Jingle", | |
39 C.PI_IMPORT_NAME: "XEP-0339", | |
40 C.PI_TYPE: "XEP", | |
41 C.PI_MODES: C.PLUG_MODE_BOTH, | |
42 C.PI_PROTOCOLS: ["XEP-0339"], | |
43 C.PI_DEPENDENCIES: ["XEP-0092", "XEP-0167"], | |
44 C.PI_RECOMMENDATIONS: [], | |
45 C.PI_MAIN: "XEP_0339", | |
46 C.PI_HANDLER: "yes", | |
47 C.PI_DESCRIPTION: _("""Source-Specific Media Attributes in Jingle"""), | |
48 } | |
49 | |
50 | |
51 class XEP_0339: | |
52 def __init__(self, host): | |
53 log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization") | |
54 self.host = host | |
55 host.trigger.add("XEP-0167_parse_sdp_a", self._parse_sdp_a_trigger) | |
56 host.trigger.add( | |
57 "XEP-0167_generate_sdp_content", self._generate_sdp_content_trigger | |
58 ) | |
59 host.trigger.add("XEP-0167_parse_description", self._parse_description_trigger) | |
60 host.trigger.add("XEP-0167_build_description", self._build_description_trigger) | |
61 | |
62 def get_handler(self, client): | |
63 return XEP_0339_handler() | |
64 | |
65 def _parse_sdp_a_trigger( | |
66 self, | |
67 attribute: str, | |
68 parts: List[str], | |
69 call_data: dict, | |
70 metadata: dict, | |
71 media_type: str, | |
72 application_data: dict, | |
73 transport_data: dict, | |
74 ) -> None: | |
75 """Parse "ssrc" attributes""" | |
76 if attribute == "ssrc": | |
77 assert application_data is not None | |
78 ssrc_id = int(parts[0]) | |
79 | |
80 if len(parts) > 1: | |
81 name, *values = " ".join(parts[1:]).split(":", 1) | |
82 if values: | |
83 value = values[0] or None | |
84 else: | |
85 value = None | |
86 application_data.setdefault("ssrc", {}).setdefault(ssrc_id, {})[ | |
87 name | |
88 ] = value | |
89 else: | |
90 log.warning(f"no attribute in ssrc: {' '.join(parts)}") | |
91 application_data.setdefault("ssrc", {}).setdefault(ssrc_id, {}) | |
92 elif attribute == "ssrc-group": | |
93 assert application_data is not None | |
94 semantics, *ssrc_ids = parts | |
95 ssrc_ids = [int(ssrc_id) for ssrc_id in ssrc_ids] | |
96 application_data.setdefault("ssrc-group", {})[semantics] = ssrc_ids | |
97 elif attribute == "msid": | |
98 assert application_data is not None | |
99 application_data["msid"] = " ".join(parts) | |
100 | |
101 | |
102 def _generate_sdp_content_trigger( | |
103 self, | |
104 session: dict, | |
105 local: bool, | |
106 idx: int, | |
107 content_data: dict, | |
108 sdp_lines: List[str], | |
109 application_data: dict, | |
110 app_data_key: str, | |
111 media_data: dict, | |
112 media: str | |
113 ) -> None: | |
114 """Generate "msid" and "ssrc" attributes""" | |
115 if "msid" in media_data: | |
116 sdp_lines.append(f"a=msid:{media_data['msid']}") | |
117 | |
118 ssrc_data = media_data.get("ssrc", {}) | |
119 ssrc_group_data = media_data.get("ssrc-group", {}) | |
120 | |
121 for ssrc_id, attributes in ssrc_data.items(): | |
122 if not attributes: | |
123 # there are no attributes for this SSRC ID, we add a simple line with only | |
124 # the SSRC ID | |
125 sdp_lines.append(f"a=ssrc:{ssrc_id}") | |
126 else: | |
127 for attr_name, attr_value in attributes.items(): | |
128 if attr_value is not None: | |
129 sdp_lines.append(f"a=ssrc:{ssrc_id} {attr_name}:{attr_value}") | |
130 else: | |
131 sdp_lines.append(f"a=ssrc:{ssrc_id} {attr_name}") | |
132 for semantics, ssrc_ids in ssrc_group_data.items(): | |
133 ssrc_lines = " ".join(str(ssrc_id) for ssrc_id in ssrc_ids) | |
134 sdp_lines.append(f"a=ssrc-group:{semantics} {ssrc_lines}") | |
135 | |
136 def _parse_description_trigger( | |
137 self, desc_elt: domish.Element, media_data: dict | |
138 ) -> bool: | |
139 """Parse the <source> and <ssrc-group> elements""" | |
140 for source_elt in desc_elt.elements(NS_JINGLE_RTP_SSMA, "source"): | |
141 try: | |
142 ssrc_id = int(source_elt["ssrc"]) | |
143 media_data.setdefault("ssrc", {})[ssrc_id] = {} | |
144 for param_elt in source_elt.elements(NS_JINGLE_RTP_SSMA, "parameter"): | |
145 name = param_elt["name"] | |
146 value = param_elt.getAttribute("value") | |
147 media_data["ssrc"][ssrc_id][name] = value | |
148 if name == "msid" and "msid" not in media_data: | |
149 media_data["msid"] = value | |
150 except (KeyError, ValueError) as e: | |
151 log.warning(f"Error while parsing <source>: {e}\n{source_elt.toXml()}") | |
152 | |
153 for ssrc_group_elt in desc_elt.elements(NS_JINGLE_RTP_SSMA, "ssrc-group"): | |
154 try: | |
155 semantics = ssrc_group_elt["semantics"] | |
156 semantic_ids = media_data.setdefault("ssrc-group", {})[semantics] = [] | |
157 for source_elt in ssrc_group_elt.elements(NS_JINGLE_RTP_SSMA, "source"): | |
158 semantic_ids.append( | |
159 int(source_elt["ssrc"]) | |
160 ) | |
161 except (KeyError, ValueError) as e: | |
162 log.warning( | |
163 f"Error while parsing <ssrc-group>: {e}\n{ssrc_group_elt.toXml()}" | |
164 ) | |
165 | |
166 return True | |
167 | |
168 def _build_description_trigger( | |
169 self, desc_elt: domish.Element, media_data: dict, session: dict | |
170 ) -> bool: | |
171 """Build the <source> and <ssrc-group> elements if possible""" | |
172 for ssrc_id, parameters in media_data.get("ssrc", {}).items(): | |
173 if "msid" not in parameters and "msid" in media_data: | |
174 parameters["msid"] = media_data["msid"] | |
175 source_elt = desc_elt.addElement((NS_JINGLE_RTP_SSMA, "source")) | |
176 source_elt["ssrc"] = str(ssrc_id) | |
177 for name, value in parameters.items(): | |
178 param_elt = source_elt.addElement((NS_JINGLE_RTP_SSMA, "parameter")) | |
179 param_elt["name"] = name | |
180 if value is not None: | |
181 param_elt["value"] = value | |
182 | |
183 for semantics, ssrc_ids in media_data.get("ssrc-group", {}).items(): | |
184 ssrc_group_elt = desc_elt.addElement((NS_JINGLE_RTP_SSMA, "ssrc-group")) | |
185 ssrc_group_elt["semantics"] = semantics | |
186 for ssrc_id in ssrc_ids: | |
187 source_elt = ssrc_group_elt.addElement((NS_JINGLE_RTP_SSMA, "source")) | |
188 source_elt["ssrc"] = str(ssrc_id) | |
189 | |
190 return True | |
191 | |
192 | |
193 @implementer(iwokkel.IDisco) | |
194 class XEP_0339_handler(XMPPHandler): | |
195 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | |
196 return [disco.DiscoFeature(NS_JINGLE_RTP_SSMA)] | |
197 | |
198 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | |
199 return [] |