library_godot_webrtc.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. /**************************************************************************/
  2. /* library_godot_webrtc.js */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. const GodotRTCDataChannel = {
  31. // Our socket implementation that forwards events to C++.
  32. $GodotRTCDataChannel__deps: ['$IDHandler', '$GodotRuntime'],
  33. $GodotRTCDataChannel: {
  34. connect: function (p_id, p_on_open, p_on_message, p_on_error, p_on_close) {
  35. const ref = IDHandler.get(p_id);
  36. if (!ref) {
  37. return;
  38. }
  39. ref.binaryType = 'arraybuffer';
  40. ref.onopen = function (event) {
  41. p_on_open();
  42. };
  43. ref.onclose = function (event) {
  44. p_on_close();
  45. };
  46. ref.onerror = function (event) {
  47. p_on_error();
  48. };
  49. ref.onmessage = function (event) {
  50. let buffer;
  51. let is_string = 0;
  52. if (event.data instanceof ArrayBuffer) {
  53. buffer = new Uint8Array(event.data);
  54. } else if (event.data instanceof Blob) {
  55. GodotRuntime.error('Blob type not supported');
  56. return;
  57. } else if (typeof event.data === 'string') {
  58. is_string = 1;
  59. const enc = new TextEncoder('utf-8');
  60. buffer = new Uint8Array(enc.encode(event.data));
  61. } else {
  62. GodotRuntime.error('Unknown message type');
  63. return;
  64. }
  65. const len = buffer.length * buffer.BYTES_PER_ELEMENT;
  66. const out = GodotRuntime.malloc(len);
  67. HEAPU8.set(buffer, out);
  68. p_on_message(out, len, is_string);
  69. GodotRuntime.free(out);
  70. };
  71. },
  72. close: function (p_id) {
  73. const ref = IDHandler.get(p_id);
  74. if (!ref) {
  75. return;
  76. }
  77. ref.onopen = null;
  78. ref.onmessage = null;
  79. ref.onerror = null;
  80. ref.onclose = null;
  81. ref.close();
  82. },
  83. get_prop: function (p_id, p_prop, p_def) {
  84. const ref = IDHandler.get(p_id);
  85. return (ref && ref[p_prop] !== undefined) ? ref[p_prop] : p_def;
  86. },
  87. },
  88. godot_js_rtc_datachannel_ready_state_get__sig: 'ii',
  89. godot_js_rtc_datachannel_ready_state_get: function (p_id) {
  90. const ref = IDHandler.get(p_id);
  91. if (!ref) {
  92. return 3; // CLOSED
  93. }
  94. switch (ref.readyState) {
  95. case 'connecting':
  96. return 0;
  97. case 'open':
  98. return 1;
  99. case 'closing':
  100. return 2;
  101. case 'closed':
  102. default:
  103. return 3;
  104. }
  105. },
  106. godot_js_rtc_datachannel_send__sig: 'iiiii',
  107. godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) {
  108. const ref = IDHandler.get(p_id);
  109. if (!ref) {
  110. return 1;
  111. }
  112. const bytes_array = new Uint8Array(p_length);
  113. for (let i = 0; i < p_length; i++) {
  114. bytes_array[i] = GodotRuntime.getHeapValue(p_buffer + i, 'i8');
  115. }
  116. if (p_raw) {
  117. ref.send(bytes_array.buffer);
  118. } else {
  119. const string = new TextDecoder('utf-8').decode(bytes_array);
  120. ref.send(string);
  121. }
  122. return 0;
  123. },
  124. godot_js_rtc_datachannel_is_ordered__sig: 'ii',
  125. godot_js_rtc_datachannel_is_ordered: function (p_id) {
  126. return GodotRTCDataChannel.get_prop(p_id, 'ordered', true);
  127. },
  128. godot_js_rtc_datachannel_id_get__sig: 'ii',
  129. godot_js_rtc_datachannel_id_get: function (p_id) {
  130. return GodotRTCDataChannel.get_prop(p_id, 'id', 65535);
  131. },
  132. godot_js_rtc_datachannel_max_packet_lifetime_get__sig: 'ii',
  133. godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) {
  134. const ref = IDHandler.get(p_id);
  135. if (!ref) {
  136. return 65535;
  137. }
  138. if (ref['maxPacketLifeTime'] !== undefined) {
  139. return ref['maxPacketLifeTime'];
  140. } else if (ref['maxRetransmitTime'] !== undefined) {
  141. // Guess someone didn't appreciate the standardization process.
  142. return ref['maxRetransmitTime'];
  143. }
  144. return 65535;
  145. },
  146. godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii',
  147. godot_js_rtc_datachannel_max_retransmits_get: function (p_id) {
  148. return GodotRTCDataChannel.get_prop(p_id, 'maxRetransmits', 65535);
  149. },
  150. godot_js_rtc_datachannel_is_negotiated__sig: 'ii',
  151. godot_js_rtc_datachannel_is_negotiated: function (p_id) {
  152. return GodotRTCDataChannel.get_prop(p_id, 'negotiated', 65535);
  153. },
  154. godot_js_rtc_datachannel_get_buffered_amount__sig: 'ii',
  155. godot_js_rtc_datachannel_get_buffered_amount: function (p_id) {
  156. return GodotRTCDataChannel.get_prop(p_id, 'bufferedAmount', 0);
  157. },
  158. godot_js_rtc_datachannel_label_get__sig: 'ii',
  159. godot_js_rtc_datachannel_label_get: function (p_id) {
  160. const ref = IDHandler.get(p_id);
  161. if (!ref || !ref.label) {
  162. return 0;
  163. }
  164. return GodotRuntime.allocString(ref.label);
  165. },
  166. godot_js_rtc_datachannel_protocol_get__sig: 'ii',
  167. godot_js_rtc_datachannel_protocol_get: function (p_id) {
  168. const ref = IDHandler.get(p_id);
  169. if (!ref || !ref.protocol) {
  170. return 0;
  171. }
  172. return GodotRuntime.allocString(ref.protocol);
  173. },
  174. godot_js_rtc_datachannel_destroy__sig: 'vi',
  175. godot_js_rtc_datachannel_destroy: function (p_id) {
  176. GodotRTCDataChannel.close(p_id);
  177. IDHandler.remove(p_id);
  178. },
  179. godot_js_rtc_datachannel_connect__sig: 'viiiiii',
  180. godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
  181. const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
  182. const onmessage = GodotRuntime.get_func(p_on_message).bind(null, p_ref);
  183. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_ref);
  184. const onclose = GodotRuntime.get_func(p_on_close).bind(null, p_ref);
  185. GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
  186. },
  187. godot_js_rtc_datachannel_close__sig: 'vi',
  188. godot_js_rtc_datachannel_close: function (p_id) {
  189. const ref = IDHandler.get(p_id);
  190. if (!ref) {
  191. return;
  192. }
  193. GodotRTCDataChannel.close(p_id);
  194. },
  195. };
  196. autoAddDeps(GodotRTCDataChannel, '$GodotRTCDataChannel');
  197. mergeInto(LibraryManager.library, GodotRTCDataChannel);
  198. const GodotRTCPeerConnection = {
  199. $GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotRuntime', '$GodotRTCDataChannel'],
  200. $GodotRTCPeerConnection: {
  201. // Enums
  202. ConnectionState: {
  203. 'new': 0,
  204. 'connecting': 1,
  205. 'connected': 2,
  206. 'disconnected': 3,
  207. 'failed': 4,
  208. 'closed': 5,
  209. },
  210. ConnectionStateCompat: {
  211. // Using values from IceConnectionState for browsers that do not support ConnectionState (notably Firefox).
  212. 'new': 0,
  213. 'checking': 1,
  214. 'connected': 2,
  215. 'completed': 2,
  216. 'disconnected': 3,
  217. 'failed': 4,
  218. 'closed': 5,
  219. },
  220. IceGatheringState: {
  221. 'new': 0,
  222. 'gathering': 1,
  223. 'complete': 2,
  224. },
  225. SignalingState: {
  226. 'stable': 0,
  227. 'have-local-offer': 1,
  228. 'have-remote-offer': 2,
  229. 'have-local-pranswer': 3,
  230. 'have-remote-pranswer': 4,
  231. 'closed': 5,
  232. },
  233. // Callbacks
  234. create: function (config, onConnectionChange, onSignalingChange, onIceGatheringChange, onIceCandidate, onDataChannel) {
  235. let conn = null;
  236. try {
  237. conn = new RTCPeerConnection(config);
  238. } catch (e) {
  239. GodotRuntime.error(e);
  240. return 0;
  241. }
  242. const id = IDHandler.add(conn);
  243. if ('connectionState' in conn && conn['connectionState'] !== undefined) {
  244. // Use "connectionState" if supported
  245. conn.onconnectionstatechange = function (event) {
  246. if (!IDHandler.get(id)) {
  247. return;
  248. }
  249. onConnectionChange(GodotRTCPeerConnection.ConnectionState[conn.connectionState] || 0);
  250. };
  251. } else {
  252. // Fall back to using "iceConnectionState" when "connectionState" is not supported (notably Firefox).
  253. conn.oniceconnectionstatechange = function (event) {
  254. if (!IDHandler.get(id)) {
  255. return;
  256. }
  257. onConnectionChange(GodotRTCPeerConnection.ConnectionStateCompat[conn.iceConnectionState] || 0);
  258. };
  259. }
  260. conn.onicegatheringstatechange = function (event) {
  261. if (!IDHandler.get(id)) {
  262. return;
  263. }
  264. onIceGatheringChange(GodotRTCPeerConnection.IceGatheringState[conn.iceGatheringState] || 0);
  265. };
  266. conn.onsignalingstatechange = function (event) {
  267. if (!IDHandler.get(id)) {
  268. return;
  269. }
  270. onSignalingChange(GodotRTCPeerConnection.SignalingState[conn.signalingState] || 0);
  271. };
  272. conn.onicecandidate = function (event) {
  273. if (!IDHandler.get(id)) {
  274. return;
  275. }
  276. const c = event.candidate;
  277. if (!c || !c.candidate) {
  278. return;
  279. }
  280. const candidate_str = GodotRuntime.allocString(c.candidate);
  281. const mid_str = GodotRuntime.allocString(c.sdpMid);
  282. onIceCandidate(mid_str, c.sdpMLineIndex, candidate_str);
  283. GodotRuntime.free(candidate_str);
  284. GodotRuntime.free(mid_str);
  285. };
  286. conn.ondatachannel = function (event) {
  287. if (!IDHandler.get(id)) {
  288. return;
  289. }
  290. const cid = IDHandler.add(event.channel);
  291. onDataChannel(cid);
  292. };
  293. return id;
  294. },
  295. destroy: function (p_id) {
  296. const conn = IDHandler.get(p_id);
  297. if (!conn) {
  298. return;
  299. }
  300. conn.onconnectionstatechange = null;
  301. conn.oniceconnectionstatechange = null;
  302. conn.onicegatheringstatechange = null;
  303. conn.onsignalingstatechange = null;
  304. conn.onicecandidate = null;
  305. conn.ondatachannel = null;
  306. IDHandler.remove(p_id);
  307. },
  308. onsession: function (p_id, callback, session) {
  309. if (!IDHandler.get(p_id)) {
  310. return;
  311. }
  312. const type_str = GodotRuntime.allocString(session.type);
  313. const sdp_str = GodotRuntime.allocString(session.sdp);
  314. callback(type_str, sdp_str);
  315. GodotRuntime.free(type_str);
  316. GodotRuntime.free(sdp_str);
  317. },
  318. onerror: function (p_id, callback, error) {
  319. const ref = IDHandler.get(p_id);
  320. if (!ref) {
  321. return;
  322. }
  323. GodotRuntime.error(error);
  324. callback();
  325. },
  326. },
  327. godot_js_rtc_pc_create__sig: 'iiiiiiii',
  328. godot_js_rtc_pc_create: function (p_config, p_ref, p_on_connection_state_change, p_on_ice_gathering_state_change, p_on_signaling_state_change, p_on_ice_candidate, p_on_datachannel) {
  329. const wrap = function (p_func) {
  330. return GodotRuntime.get_func(p_func).bind(null, p_ref);
  331. };
  332. return GodotRTCPeerConnection.create(
  333. JSON.parse(GodotRuntime.parseString(p_config)),
  334. wrap(p_on_connection_state_change),
  335. wrap(p_on_signaling_state_change),
  336. wrap(p_on_ice_gathering_state_change),
  337. wrap(p_on_ice_candidate),
  338. wrap(p_on_datachannel)
  339. );
  340. },
  341. godot_js_rtc_pc_close__sig: 'vi',
  342. godot_js_rtc_pc_close: function (p_id) {
  343. const ref = IDHandler.get(p_id);
  344. if (!ref) {
  345. return;
  346. }
  347. ref.close();
  348. },
  349. godot_js_rtc_pc_destroy__sig: 'vi',
  350. godot_js_rtc_pc_destroy: function (p_id) {
  351. GodotRTCPeerConnection.destroy(p_id);
  352. },
  353. godot_js_rtc_pc_offer_create__sig: 'viiii',
  354. godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) {
  355. const ref = IDHandler.get(p_id);
  356. if (!ref) {
  357. return;
  358. }
  359. const onsession = GodotRuntime.get_func(p_on_session).bind(null, p_obj);
  360. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
  361. ref.createOffer().then(function (session) {
  362. GodotRTCPeerConnection.onsession(p_id, onsession, session);
  363. }).catch(function (error) {
  364. GodotRTCPeerConnection.onerror(p_id, onerror, error);
  365. });
  366. },
  367. godot_js_rtc_pc_local_description_set__sig: 'viiiii',
  368. godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) {
  369. const ref = IDHandler.get(p_id);
  370. if (!ref) {
  371. return;
  372. }
  373. const type = GodotRuntime.parseString(p_type);
  374. const sdp = GodotRuntime.parseString(p_sdp);
  375. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
  376. ref.setLocalDescription({
  377. 'sdp': sdp,
  378. 'type': type,
  379. }).catch(function (error) {
  380. GodotRTCPeerConnection.onerror(p_id, onerror, error);
  381. });
  382. },
  383. godot_js_rtc_pc_remote_description_set__sig: 'viiiiii',
  384. godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
  385. const ref = IDHandler.get(p_id);
  386. if (!ref) {
  387. return;
  388. }
  389. const type = GodotRuntime.parseString(p_type);
  390. const sdp = GodotRuntime.parseString(p_sdp);
  391. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
  392. const onsession = GodotRuntime.get_func(p_session_created).bind(null, p_obj);
  393. ref.setRemoteDescription({
  394. 'sdp': sdp,
  395. 'type': type,
  396. }).then(function () {
  397. if (type !== 'offer') {
  398. return Promise.resolve();
  399. }
  400. return ref.createAnswer().then(function (session) {
  401. GodotRTCPeerConnection.onsession(p_id, onsession, session);
  402. });
  403. }).catch(function (error) {
  404. GodotRTCPeerConnection.onerror(p_id, onerror, error);
  405. });
  406. },
  407. godot_js_rtc_pc_ice_candidate_add__sig: 'viiii',
  408. godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) {
  409. const ref = IDHandler.get(p_id);
  410. if (!ref) {
  411. return;
  412. }
  413. const sdpMidName = GodotRuntime.parseString(p_mid_name);
  414. const sdpName = GodotRuntime.parseString(p_sdp);
  415. ref.addIceCandidate(new RTCIceCandidate({
  416. 'candidate': sdpName,
  417. 'sdpMid': sdpMidName,
  418. 'sdpMlineIndex': p_mline_idx,
  419. }));
  420. },
  421. godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
  422. godot_js_rtc_pc_datachannel_create__sig: 'iiii',
  423. godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) {
  424. try {
  425. const ref = IDHandler.get(p_id);
  426. if (!ref) {
  427. return 0;
  428. }
  429. const label = GodotRuntime.parseString(p_label);
  430. const config = JSON.parse(GodotRuntime.parseString(p_config));
  431. const channel = ref.createDataChannel(label, config);
  432. return IDHandler.add(channel);
  433. } catch (e) {
  434. GodotRuntime.error(e);
  435. return 0;
  436. }
  437. },
  438. };
  439. autoAddDeps(GodotRTCPeerConnection, '$GodotRTCPeerConnection');
  440. mergeInto(LibraryManager.library, GodotRTCPeerConnection);