SignalingMessageReceiver.java 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Daniel Calviño Sánchez
  5. * Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.nextcloud.talk.signaling;
  21. import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter;
  22. import com.nextcloud.talk.models.json.participants.Participant;
  23. import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
  24. import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
  25. import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
  26. import java.util.ArrayList;
  27. import java.util.List;
  28. import java.util.Map;
  29. /**
  30. * Hub to register listeners for signaling messages of different kinds.
  31. *
  32. * In general, if a listener is added while an event is being handled the new listener will not receive that event.
  33. * An exception to that is adding a WebRtcMessageListener when handling an offer in an OfferMessageListener; in that
  34. * case the "onOffer()" method of the WebRtcMessageListener will be called for that same offer.
  35. *
  36. * Similarly, if a listener is removed while an event is being handled the removed listener will still receive that
  37. * event. Again the exception is removing a WebRtcMessageListener when handling an offer in an OfferMessageListener; in
  38. * that case the "onOffer()" method of the WebRtcMessageListener will not be called for that offer.
  39. *
  40. * Adding and removing listeners, as well as notifying them is internally synchronized. This should be kept in mind
  41. * if listeners are added or removed when handling an event to prevent deadlocks (nevertheless, just adding or
  42. * removing a listener in the same thread handling the event is fine, and in most cases it will be fine too if done
  43. * in a different thread, as long as the notifier thread is not forced to wait until the listener is added or removed).
  44. *
  45. * SignalingMessageReceiver does not fetch the signaling messages itself; subclasses must fetch them and then call
  46. * the appropriate protected methods to process the messages and notify the listeners.
  47. */
  48. public abstract class SignalingMessageReceiver {
  49. /**
  50. * Listener for participant list messages.
  51. *
  52. * The messages are implicitly bound to the room currently joined in the signaling server; listeners are expected
  53. * to know the current room.
  54. */
  55. public interface ParticipantListMessageListener {
  56. /**
  57. * List of all the participants in the room.
  58. *
  59. * This message is received only when the internal signaling server is used.
  60. *
  61. * The message is received periodically, and the participants may not have been modified since the last message.
  62. *
  63. * Only the following participant properties are set:
  64. * - inCall
  65. * - lastPing
  66. * - sessionId
  67. * - userId (if the participant is not a guest)
  68. *
  69. * "participantPermissions" is provided in the message (since Talk 13), but not currently set in the
  70. * participant. "publishingPermissions" was provided instead in Talk 12, but it was not used anywhere, so it is
  71. * ignored.
  72. *
  73. * @param participants all the participants (users and guests) in the room
  74. */
  75. void onUsersInRoom(List<Participant> participants);
  76. /**
  77. * List of all the participants in the call or the room (depending on what triggered the event).
  78. *
  79. * This message is received only when the external signaling server is used.
  80. *
  81. * The message is received when any participant changed, although what changed is not provided and should be
  82. * derived from the difference with previous messages. The list of participants may include only the
  83. * participants in the call (including those that just left it and thus triggered the event) or all the
  84. * participants currently in the room (participants in the room but not currently active, that is, without a
  85. * session, are not included).
  86. *
  87. * Only the following participant properties are set:
  88. * - inCall
  89. * - lastPing
  90. * - sessionId
  91. * - type
  92. * - userId (if the participant is not a guest)
  93. *
  94. * "nextcloudSessionId" is provided in the message (when the "inCall" property of any participant changed), but
  95. * not currently set in the participant.
  96. *
  97. * "participantPermissions" is provided in the message (since Talk 13), but not currently set in the
  98. * participant. "publishingPermissions" was provided instead in Talk 12, but it was not used anywhere, so it is
  99. * ignored.
  100. *
  101. * @param participants all the participants (users and guests) in the room
  102. */
  103. void onParticipantsUpdate(List<Participant> participants);
  104. /**
  105. * Update of the properties of all the participants in the room.
  106. *
  107. * This message is received only when the external signaling server is used.
  108. *
  109. * @param inCall the new value of the inCall property
  110. */
  111. void onAllParticipantsUpdate(long inCall);
  112. }
  113. /**
  114. * Listener for local participant messages.
  115. *
  116. * The messages are implicitly bound to the local participant (or, rather, its session); listeners are expected
  117. * to know the local participant.
  118. *
  119. * The messages are related to the conversation, so the local participant may or may not be in a call when they
  120. * are received.
  121. */
  122. public interface LocalParticipantMessageListener {
  123. /**
  124. * Request for the client to switch to the given conversation.
  125. *
  126. * This message is received only when the external signaling server is used.
  127. *
  128. * @param token the token of the conversation to switch to.
  129. */
  130. void onSwitchTo(String token);
  131. }
  132. /**
  133. * Listener for call participant messages.
  134. *
  135. * The messages are bound to a specific call participant (or, rather, session), so each listener is expected to
  136. * handle messages only for a single call participant.
  137. *
  138. * Although "unshareScreen" is technically bound to a specific peer connection it is instead treated as a general
  139. * message on the call participant.
  140. */
  141. public interface CallParticipantMessageListener {
  142. void onRaiseHand(boolean state, long timestamp);
  143. void onReaction(String reaction);
  144. void onUnshareScreen();
  145. }
  146. /**
  147. * Listener for conversation messages.
  148. */
  149. public interface ConversationMessageListener {
  150. void onStartTyping(String session);
  151. void onStopTyping(String session);
  152. }
  153. /**
  154. * Listener for WebRTC offers.
  155. *
  156. * Unlike the WebRtcMessageListener, which is bound to a specific peer connection, an OfferMessageListener listens
  157. * to all offer messages, no matter which peer connection they are bound to. This can be used, for example, to
  158. * create a new peer connection when a remote offer for which there is no previous connection is received.
  159. *
  160. * When an offer is received all OfferMessageListeners are notified before any WebRtcMessageListener is notified.
  161. */
  162. public interface OfferMessageListener {
  163. void onOffer(String sessionId, String roomType, String sdp, String nick);
  164. }
  165. /**
  166. * Listener for WebRTC messages.
  167. *
  168. * The messages are bound to a specific peer connection, so each listener is expected to handle messages only for
  169. * a single peer connection.
  170. */
  171. public interface WebRtcMessageListener {
  172. void onOffer(String sdp, String nick);
  173. void onAnswer(String sdp, String nick);
  174. void onCandidate(String sdpMid, int sdpMLineIndex, String sdp);
  175. void onEndOfCandidates();
  176. }
  177. private final ParticipantListMessageNotifier participantListMessageNotifier = new ParticipantListMessageNotifier();
  178. private final LocalParticipantMessageNotifier localParticipantMessageNotifier = new LocalParticipantMessageNotifier();
  179. private final CallParticipantMessageNotifier callParticipantMessageNotifier = new CallParticipantMessageNotifier();
  180. private final ConversationMessageNotifier conversationMessageNotifier = new ConversationMessageNotifier();
  181. private final OfferMessageNotifier offerMessageNotifier = new OfferMessageNotifier();
  182. private final WebRtcMessageNotifier webRtcMessageNotifier = new WebRtcMessageNotifier();
  183. /**
  184. * Adds a listener for participant list messages.
  185. *
  186. * A listener is expected to be added only once. If the same listener is added again it will be notified just once.
  187. *
  188. * @param listener the ParticipantListMessageListener
  189. */
  190. public void addListener(ParticipantListMessageListener listener) {
  191. participantListMessageNotifier.addListener(listener);
  192. }
  193. public void removeListener(ParticipantListMessageListener listener) {
  194. participantListMessageNotifier.removeListener(listener);
  195. }
  196. /**
  197. * Adds a listener for local participant messages.
  198. *
  199. * A listener is expected to be added only once. If the same listener is added again it will be notified just once.
  200. *
  201. * @param listener the LocalParticipantMessageListener
  202. */
  203. public void addListener(LocalParticipantMessageListener listener) {
  204. localParticipantMessageNotifier.addListener(listener);
  205. }
  206. public void removeListener(LocalParticipantMessageListener listener) {
  207. localParticipantMessageNotifier.removeListener(listener);
  208. }
  209. /**
  210. * Adds a listener for call participant messages.
  211. *
  212. * A listener is expected to be added only once. If the same listener is added again it will no longer be notified
  213. * for the messages from the previous session ID.
  214. *
  215. * @param listener the CallParticipantMessageListener
  216. * @param sessionId the ID of the session that messages come from
  217. */
  218. public void addListener(CallParticipantMessageListener listener, String sessionId) {
  219. callParticipantMessageNotifier.addListener(listener, sessionId);
  220. }
  221. public void removeListener(CallParticipantMessageListener listener) {
  222. callParticipantMessageNotifier.removeListener(listener);
  223. }
  224. public void addListener(ConversationMessageListener listener) {
  225. conversationMessageNotifier.addListener(listener);
  226. }
  227. public void removeListener(ConversationMessageListener listener) {
  228. conversationMessageNotifier.removeListener(listener);
  229. }
  230. /**
  231. * Adds a listener for all offer messages.
  232. *
  233. * A listener is expected to be added only once. If the same listener is added again it will be notified just once.
  234. *
  235. * @param listener the OfferMessageListener
  236. */
  237. public void addListener(OfferMessageListener listener) {
  238. offerMessageNotifier.addListener(listener);
  239. }
  240. public void removeListener(OfferMessageListener listener) {
  241. offerMessageNotifier.removeListener(listener);
  242. }
  243. /**
  244. * Adds a listener for WebRTC messages from the given session ID and room type.
  245. *
  246. * A listener is expected to be added only once. If the same listener is added again it will no longer be notified
  247. * for the messages from the previous session ID or room type.
  248. *
  249. * @param listener the WebRtcMessageListener
  250. * @param sessionId the ID of the session that messages come from
  251. * @param roomType the room type that messages come from
  252. */
  253. public void addListener(WebRtcMessageListener listener, String sessionId, String roomType) {
  254. webRtcMessageNotifier.addListener(listener, sessionId, roomType);
  255. }
  256. public void removeListener(WebRtcMessageListener listener) {
  257. webRtcMessageNotifier.removeListener(listener);
  258. }
  259. protected void processEvent(Map<String, Object> eventMap) {
  260. if ("room".equals(eventMap.get("target")) && "switchto".equals(eventMap.get("type"))) {
  261. processSwitchToEvent(eventMap);
  262. return;
  263. }
  264. if ("participants".equals(eventMap.get("target")) && "update".equals(eventMap.get("type"))) {
  265. processUpdateEvent(eventMap);
  266. return;
  267. }
  268. }
  269. private void processSwitchToEvent(Map<String, Object> eventMap) {
  270. // Message schema:
  271. // {
  272. // "type": "event",
  273. // "event": {
  274. // "target": "room",
  275. // "type": "switchto",
  276. // "switchto": {
  277. // "roomid": #STRING#,
  278. // },
  279. // },
  280. // }
  281. Map<String, Object> switchToMap;
  282. try {
  283. switchToMap = (Map<String, Object>) eventMap.get("switchto");
  284. } catch (RuntimeException e) {
  285. // Broken message, this should not happen.
  286. return;
  287. }
  288. if (switchToMap == null) {
  289. // Broken message, this should not happen.
  290. return;
  291. }
  292. String token;
  293. try {
  294. token = switchToMap.get("roomid").toString();
  295. } catch (RuntimeException e) {
  296. // Broken message, this should not happen.
  297. return;
  298. }
  299. localParticipantMessageNotifier.notifySwitchTo(token);
  300. }
  301. private void processUpdateEvent(Map<String, Object> eventMap) {
  302. Map<String, Object> updateMap;
  303. try {
  304. updateMap = (Map<String, Object>) eventMap.get("update");
  305. } catch (RuntimeException e) {
  306. // Broken message, this should not happen.
  307. return;
  308. }
  309. if (updateMap == null) {
  310. // Broken message, this should not happen.
  311. return;
  312. }
  313. if (updateMap.get("all") != null && Boolean.parseBoolean(updateMap.get("all").toString())) {
  314. processAllParticipantsUpdate(updateMap);
  315. return;
  316. }
  317. if (updateMap.get("users") != null) {
  318. processParticipantsUpdate(updateMap);
  319. return;
  320. }
  321. }
  322. private void processAllParticipantsUpdate(Map<String, Object> updateMap) {
  323. // Message schema:
  324. // {
  325. // "type": "event",
  326. // "event": {
  327. // "target": "participants",
  328. // "type": "update",
  329. // "update": {
  330. // "roomid": #STRING#,
  331. // "incall": 0,
  332. // "all": true,
  333. // },
  334. // },
  335. // }
  336. long inCall;
  337. try {
  338. inCall = Long.parseLong(updateMap.get("inCall").toString());
  339. } catch (RuntimeException e) {
  340. // Broken message, this should not happen.
  341. return;
  342. }
  343. participantListMessageNotifier.notifyAllParticipantsUpdate(inCall);
  344. }
  345. private void processParticipantsUpdate(Map<String, Object> updateMap) {
  346. // Message schema:
  347. // {
  348. // "type": "event",
  349. // "event": {
  350. // "target": "participants",
  351. // "type": "update",
  352. // "update": {
  353. // "roomid": #INTEGER#,
  354. // "users": [
  355. // {
  356. // "inCall": #INTEGER#,
  357. // "lastPing": #INTEGER#,
  358. // "sessionId": #STRING#,
  359. // "participantType": #INTEGER#,
  360. // "userId": #STRING#, // Optional
  361. // "nextcloudSessionId": #STRING#, // Optional
  362. // "internal": #BOOLEAN#, // Optional
  363. // "participantPermissions": #INTEGER#, // Talk >= 13
  364. // },
  365. // ...
  366. // ],
  367. // },
  368. // },
  369. // }
  370. //
  371. // Note that "userId" in participants->update comes from the Nextcloud server, so it is "userId"; in other
  372. // messages, like room->join, it comes directly from the external signaling server, so it is "userid" instead.
  373. List<Map<String, Object>> users;
  374. try {
  375. users = (List<Map<String, Object>>) updateMap.get("users");
  376. } catch (RuntimeException e) {
  377. // Broken message, this should not happen.
  378. return;
  379. }
  380. if (users == null) {
  381. // Broken message, this should not happen.
  382. return;
  383. }
  384. List<Participant> participants = new ArrayList<>(users.size());
  385. for (Map<String, Object> user: users) {
  386. try {
  387. participants.add(getParticipantFromMessageMap(user));
  388. } catch (RuntimeException e) {
  389. // Broken message, this should not happen.
  390. return;
  391. }
  392. }
  393. participantListMessageNotifier.notifyParticipantsUpdate(participants);
  394. }
  395. protected void processUsersInRoom(List<Map<String, Object>> users) {
  396. // Message schema:
  397. // {
  398. // "type": "usersInRoom",
  399. // "data": [
  400. // {
  401. // "inCall": #INTEGER#,
  402. // "lastPing": #INTEGER#,
  403. // "roomId": #INTEGER#,
  404. // "sessionId": #STRING#,
  405. // "userId": #STRING#, // Always included, although it can be empty
  406. // "participantPermissions": #INTEGER#, // Talk >= 13
  407. // },
  408. // ...
  409. // ],
  410. // }
  411. List<Participant> participants = new ArrayList<>(users.size());
  412. for (Map<String, Object> user: users) {
  413. try {
  414. participants.add(getParticipantFromMessageMap(user));
  415. } catch (RuntimeException e) {
  416. // Broken message, this should not happen.
  417. return;
  418. }
  419. }
  420. participantListMessageNotifier.notifyUsersInRoom(participants);
  421. }
  422. /**
  423. * Creates and initializes a Participant from the data in the given map.
  424. *
  425. * Maps from internal and external signaling server messages can be used. Nevertheless, besides the differences
  426. * between the messages and the optional properties, it is expected that the message is correct and the given data
  427. * is parseable. Broken messages (for example, a string instead of an integer for "inCall" or a missing
  428. * "sessionId") may cause a RuntimeException to be thrown.
  429. *
  430. * @param participantMap the map with the participant data
  431. * @return the Participant
  432. */
  433. private Participant getParticipantFromMessageMap(Map<String, Object> participantMap) {
  434. Participant participant = new Participant();
  435. participant.setInCall(Long.parseLong(participantMap.get("inCall").toString()));
  436. participant.setLastPing(Long.parseLong(participantMap.get("lastPing").toString()));
  437. participant.setSessionId(participantMap.get("sessionId").toString());
  438. if (participantMap.get("userId") != null && !participantMap.get("userId").toString().isEmpty()) {
  439. participant.setUserId(participantMap.get("userId").toString());
  440. }
  441. if (participantMap.get("internal") != null && Boolean.parseBoolean(participantMap.get("internal").toString())) {
  442. participant.setInternal(Boolean.TRUE);
  443. }
  444. // Only in external signaling messages
  445. if (participantMap.get("participantType") != null) {
  446. int participantTypeInt = Integer.parseInt(participantMap.get("participantType").toString());
  447. EnumParticipantTypeConverter converter = new EnumParticipantTypeConverter();
  448. participant.setType(converter.getFromInt(participantTypeInt));
  449. }
  450. return participant;
  451. }
  452. protected void processSignalingMessage(NCSignalingMessage signalingMessage) {
  453. // Note that in the internal signaling server message "data" is the String representation of a JSON
  454. // object, although it is already decoded when used here.
  455. String type = signalingMessage.getType();
  456. String sessionId = signalingMessage.getFrom();
  457. String roomType = signalingMessage.getRoomType();
  458. if ("raiseHand".equals(type)) {
  459. // Message schema (external signaling server):
  460. // {
  461. // "type": "message",
  462. // "message": {
  463. // "sender": {
  464. // ...
  465. // },
  466. // "data": {
  467. // "to": #STRING#,
  468. // "sid": #STRING#,
  469. // "roomType": "video",
  470. // "type": "raiseHand",
  471. // "payload": {
  472. // "state": #BOOLEAN#,
  473. // "timestamp": #LONG#,
  474. // },
  475. // "from": #STRING#,
  476. // },
  477. // },
  478. // }
  479. //
  480. // Message schema (internal signaling server):
  481. // {
  482. // "type": "message",
  483. // "data": {
  484. // "to": #STRING#,
  485. // "sid": #STRING#,
  486. // "roomType": "video",
  487. // "type": "raiseHand",
  488. // "payload": {
  489. // "state": #BOOLEAN#,
  490. // "timestamp": #LONG#,
  491. // },
  492. // "from": #STRING#,
  493. // },
  494. // }
  495. NCMessagePayload payload = signalingMessage.getPayload();
  496. if (payload == null) {
  497. // Broken message, this should not happen.
  498. return;
  499. }
  500. Boolean state = payload.getState();
  501. Long timestamp = payload.getTimestamp();
  502. if (state == null || timestamp == null) {
  503. // Broken message, this should not happen.
  504. return;
  505. }
  506. callParticipantMessageNotifier.notifyRaiseHand(sessionId, state, timestamp);
  507. return;
  508. }
  509. if ("startedTyping".equals(type)) {
  510. conversationMessageNotifier.notifyStartTyping(sessionId);
  511. }
  512. if ("stoppedTyping".equals(type)) {
  513. conversationMessageNotifier.notifyStopTyping(sessionId);
  514. }
  515. if ("reaction".equals(type)) {
  516. // Message schema (external signaling server):
  517. // {
  518. // "type": "message",
  519. // "message": {
  520. // "sender": {
  521. // ...
  522. // },
  523. // "data": {
  524. // "to": #STRING#,
  525. // "roomType": "video",
  526. // "type": "reaction",
  527. // "payload": {
  528. // "reaction": #STRING#,
  529. // },
  530. // "from": #STRING#,
  531. // },
  532. // },
  533. // }
  534. //
  535. // Message schema (internal signaling server):
  536. // {
  537. // "type": "message",
  538. // "data": {
  539. // "to": #STRING#,
  540. // "roomType": "video",
  541. // "type": "reaction",
  542. // "payload": {
  543. // "reaction": #STRING#,
  544. // },
  545. // "from": #STRING#,
  546. // },
  547. // }
  548. NCMessagePayload payload = signalingMessage.getPayload();
  549. if (payload == null) {
  550. // Broken message, this should not happen.
  551. return;
  552. }
  553. String reaction = payload.getReaction();
  554. if (reaction == null) {
  555. // Broken message, this should not happen.
  556. return;
  557. }
  558. callParticipantMessageNotifier.notifyReaction(sessionId, reaction);
  559. return;
  560. }
  561. // "unshareScreen" messages are directly sent to the screen peer connection when the internal signaling
  562. // server is used, and to the room when the external signaling server is used. However, the (relevant) data
  563. // of the received message ("from" and "type") is the same in both cases.
  564. if ("unshareScreen".equals(type)) {
  565. // Message schema (external signaling server):
  566. // {
  567. // "type": "message",
  568. // "message": {
  569. // "sender": {
  570. // ...
  571. // },
  572. // "data": {
  573. // "roomType": "screen",
  574. // "type": "unshareScreen",
  575. // "from": #STRING#,
  576. // },
  577. // },
  578. // }
  579. //
  580. // Message schema (internal signaling server):
  581. // {
  582. // "type": "message",
  583. // "data": {
  584. // "to": #STRING#,
  585. // "sid": #STRING#,
  586. // "broadcaster": #STRING#,
  587. // "roomType": "screen",
  588. // "type": "unshareScreen",
  589. // "from": #STRING#,
  590. // },
  591. // }
  592. callParticipantMessageNotifier.notifyUnshareScreen(sessionId);
  593. return;
  594. }
  595. if ("offer".equals(type)) {
  596. // Message schema (external signaling server):
  597. // {
  598. // "type": "message",
  599. // "message": {
  600. // "sender": {
  601. // ...
  602. // },
  603. // "data": {
  604. // "to": #STRING#,
  605. // "from": #STRING#,
  606. // "type": "offer",
  607. // "roomType": #STRING#, // "video" or "screen"
  608. // "payload": {
  609. // "type": "offer",
  610. // "sdp": #STRING#,
  611. // },
  612. // "sid": #STRING#, // external signaling server >= 0.5.0
  613. // },
  614. // },
  615. // }
  616. //
  617. // Message schema (internal signaling server):
  618. // {
  619. // "type": "message",
  620. // "data": {
  621. // "to": #STRING#,
  622. // "sid": #STRING#,
  623. // "roomType": #STRING#, // "video" or "screen"
  624. // "type": "offer",
  625. // "payload": {
  626. // "type": "offer",
  627. // "sdp": #STRING#,
  628. // "nick": #STRING#, // Optional
  629. // },
  630. // "from": #STRING#,
  631. // },
  632. // }
  633. NCMessagePayload payload = signalingMessage.getPayload();
  634. if (payload == null) {
  635. // Broken message, this should not happen.
  636. return;
  637. }
  638. String sdp = payload.getSdp();
  639. String nick = payload.getNick();
  640. // If "processSignalingMessage" is called with two offers from two different threads it is possible,
  641. // although extremely unlikely, that the WebRtcMessageListeners for the second offer are notified before the
  642. // WebRtcMessageListeners for the first offer. This should not be a problem, though, so for simplicity
  643. // the statements are not synchronized.
  644. offerMessageNotifier.notifyOffer(sessionId, roomType, sdp, nick);
  645. webRtcMessageNotifier.notifyOffer(sessionId, roomType, sdp, nick);
  646. return;
  647. }
  648. if ("answer".equals(type)) {
  649. // Message schema: same as offers, but with type "answer".
  650. NCMessagePayload payload = signalingMessage.getPayload();
  651. if (payload == null) {
  652. // Broken message, this should not happen.
  653. return;
  654. }
  655. String sdp = payload.getSdp();
  656. String nick = payload.getNick();
  657. webRtcMessageNotifier.notifyAnswer(sessionId, roomType, sdp, nick);
  658. return;
  659. }
  660. if ("candidate".equals(type)) {
  661. // Message schema (external signaling server):
  662. // {
  663. // "type": "message",
  664. // "message": {
  665. // "sender": {
  666. // ...
  667. // },
  668. // "data": {
  669. // "to": #STRING#,
  670. // "from": #STRING#,
  671. // "type": "candidate",
  672. // "roomType": #STRING#, // "video" or "screen"
  673. // "payload": {
  674. // "candidate": {
  675. // "candidate": #STRING#,
  676. // "sdpMid": #STRING#,
  677. // "sdpMLineIndex": #INTEGER#,
  678. // },
  679. // },
  680. // "sid": #STRING#, // external signaling server >= 0.5.0
  681. // },
  682. // },
  683. // }
  684. //
  685. // Message schema (internal signaling server):
  686. // {
  687. // "type": "message",
  688. // "data": {
  689. // "to": #STRING#,
  690. // "sid": #STRING#,
  691. // "roomType": #STRING#, // "video" or "screen"
  692. // "type": "candidate",
  693. // "payload": {
  694. // "candidate": {
  695. // "candidate": #STRING#,
  696. // "sdpMid": #STRING#,
  697. // "sdpMLineIndex": #INTEGER#,
  698. // },
  699. // },
  700. // "from": #STRING#,
  701. // },
  702. // }
  703. NCMessagePayload payload = signalingMessage.getPayload();
  704. if (payload == null) {
  705. // Broken message, this should not happen.
  706. return;
  707. }
  708. NCIceCandidate ncIceCandidate = payload.getIceCandidate();
  709. if (ncIceCandidate == null) {
  710. // Broken message, this should not happen.
  711. return;
  712. }
  713. webRtcMessageNotifier.notifyCandidate(sessionId,
  714. roomType,
  715. ncIceCandidate.getSdpMid(),
  716. ncIceCandidate.getSdpMLineIndex(),
  717. ncIceCandidate.getCandidate());
  718. return;
  719. }
  720. if ("endOfCandidates".equals(type)) {
  721. webRtcMessageNotifier.notifyEndOfCandidates(sessionId, roomType);
  722. return;
  723. }
  724. }
  725. }