library_godot_webrtc.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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__proxy: 'sync',
  89. godot_js_rtc_datachannel_ready_state_get__sig: 'ii',
  90. godot_js_rtc_datachannel_ready_state_get: function (p_id) {
  91. const ref = IDHandler.get(p_id);
  92. if (!ref) {
  93. return 3; // CLOSED
  94. }
  95. switch (ref.readyState) {
  96. case 'connecting':
  97. return 0;
  98. case 'open':
  99. return 1;
  100. case 'closing':
  101. return 2;
  102. case 'closed':
  103. default:
  104. return 3;
  105. }
  106. },
  107. godot_js_rtc_datachannel_send__proxy: 'sync',
  108. godot_js_rtc_datachannel_send__sig: 'iiiii',
  109. godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) {
  110. const ref = IDHandler.get(p_id);
  111. if (!ref) {
  112. return 1;
  113. }
  114. const bytes_array = new Uint8Array(p_length);
  115. for (let i = 0; i < p_length; i++) {
  116. bytes_array[i] = GodotRuntime.getHeapValue(p_buffer + i, 'i8');
  117. }
  118. if (p_raw) {
  119. ref.send(bytes_array.buffer);
  120. } else {
  121. const string = new TextDecoder('utf-8').decode(bytes_array);
  122. ref.send(string);
  123. }
  124. return 0;
  125. },
  126. godot_js_rtc_datachannel_is_ordered__proxy: 'sync',
  127. godot_js_rtc_datachannel_is_ordered__sig: 'ii',
  128. godot_js_rtc_datachannel_is_ordered: function (p_id) {
  129. return GodotRTCDataChannel.get_prop(p_id, 'ordered', true);
  130. },
  131. godot_js_rtc_datachannel_id_get__proxy: 'sync',
  132. godot_js_rtc_datachannel_id_get__sig: 'ii',
  133. godot_js_rtc_datachannel_id_get: function (p_id) {
  134. return GodotRTCDataChannel.get_prop(p_id, 'id', 65535);
  135. },
  136. godot_js_rtc_datachannel_max_packet_lifetime_get__proxy: 'sync',
  137. godot_js_rtc_datachannel_max_packet_lifetime_get__sig: 'ii',
  138. godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) {
  139. const ref = IDHandler.get(p_id);
  140. if (!ref) {
  141. return 65535;
  142. }
  143. if (ref['maxPacketLifeTime'] !== undefined) {
  144. return ref['maxPacketLifeTime'];
  145. } else if (ref['maxRetransmitTime'] !== undefined) {
  146. // Guess someone didn't appreciate the standardization process.
  147. return ref['maxRetransmitTime'];
  148. }
  149. return 65535;
  150. },
  151. godot_js_rtc_datachannel_max_retransmits_get__proxy: 'sync',
  152. godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii',
  153. godot_js_rtc_datachannel_max_retransmits_get: function (p_id) {
  154. return GodotRTCDataChannel.get_prop(p_id, 'maxRetransmits', 65535);
  155. },
  156. godot_js_rtc_datachannel_is_negotiated__proxy: 'sync',
  157. godot_js_rtc_datachannel_is_negotiated__sig: 'ii',
  158. godot_js_rtc_datachannel_is_negotiated: function (p_id) {
  159. return GodotRTCDataChannel.get_prop(p_id, 'negotiated', 65535);
  160. },
  161. godot_js_rtc_datachannel_get_buffered_amount__proxy: 'sync',
  162. godot_js_rtc_datachannel_get_buffered_amount__sig: 'ii',
  163. godot_js_rtc_datachannel_get_buffered_amount: function (p_id) {
  164. return GodotRTCDataChannel.get_prop(p_id, 'bufferedAmount', 0);
  165. },
  166. godot_js_rtc_datachannel_label_get__proxy: 'sync',
  167. godot_js_rtc_datachannel_label_get__sig: 'ii',
  168. godot_js_rtc_datachannel_label_get: function (p_id) {
  169. const ref = IDHandler.get(p_id);
  170. if (!ref || !ref.label) {
  171. return 0;
  172. }
  173. return GodotRuntime.allocString(ref.label);
  174. },
  175. godot_js_rtc_datachannel_protocol_get__sig: 'ii',
  176. godot_js_rtc_datachannel_protocol_get: function (p_id) {
  177. const ref = IDHandler.get(p_id);
  178. if (!ref || !ref.protocol) {
  179. return 0;
  180. }
  181. return GodotRuntime.allocString(ref.protocol);
  182. },
  183. godot_js_rtc_datachannel_destroy__proxy: 'sync',
  184. godot_js_rtc_datachannel_destroy__sig: 'vi',
  185. godot_js_rtc_datachannel_destroy: function (p_id) {
  186. GodotRTCDataChannel.close(p_id);
  187. IDHandler.remove(p_id);
  188. },
  189. godot_js_rtc_datachannel_connect__proxy: 'sync',
  190. godot_js_rtc_datachannel_connect__sig: 'viiiiii',
  191. godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
  192. const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
  193. const onmessage = GodotRuntime.get_func(p_on_message).bind(null, p_ref);
  194. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_ref);
  195. const onclose = GodotRuntime.get_func(p_on_close).bind(null, p_ref);
  196. GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
  197. },
  198. godot_js_rtc_datachannel_close__proxy: 'sync',
  199. godot_js_rtc_datachannel_close__sig: 'vi',
  200. godot_js_rtc_datachannel_close: function (p_id) {
  201. const ref = IDHandler.get(p_id);
  202. if (!ref) {
  203. return;
  204. }
  205. GodotRTCDataChannel.close(p_id);
  206. },
  207. };
  208. autoAddDeps(GodotRTCDataChannel, '$GodotRTCDataChannel');
  209. mergeInto(LibraryManager.library, GodotRTCDataChannel);
  210. const GodotRTCPeerConnection = {
  211. $GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotRuntime', '$GodotRTCDataChannel'],
  212. $GodotRTCPeerConnection: {
  213. // Enums
  214. ConnectionState: {
  215. 'new': 0,
  216. 'connecting': 1,
  217. 'connected': 2,
  218. 'disconnected': 3,
  219. 'failed': 4,
  220. 'closed': 5,
  221. },
  222. ConnectionStateCompat: {
  223. // Using values from IceConnectionState for browsers that do not support ConnectionState (notably Firefox).
  224. 'new': 0,
  225. 'checking': 1,
  226. 'connected': 2,
  227. 'completed': 2,
  228. 'disconnected': 3,
  229. 'failed': 4,
  230. 'closed': 5,
  231. },
  232. IceGatheringState: {
  233. 'new': 0,
  234. 'gathering': 1,
  235. 'complete': 2,
  236. },
  237. SignalingState: {
  238. 'stable': 0,
  239. 'have-local-offer': 1,
  240. 'have-remote-offer': 2,
  241. 'have-local-pranswer': 3,
  242. 'have-remote-pranswer': 4,
  243. 'closed': 5,
  244. },
  245. // Callbacks
  246. create: function (config, onConnectionChange, onSignalingChange, onIceGatheringChange, onIceCandidate, onDataChannel) {
  247. let conn = null;
  248. try {
  249. conn = new RTCPeerConnection(config);
  250. } catch (e) {
  251. GodotRuntime.error(e);
  252. return 0;
  253. }
  254. const id = IDHandler.add(conn);
  255. if ('connectionState' in conn && conn['connectionState'] !== undefined) {
  256. // Use "connectionState" if supported
  257. conn.onconnectionstatechange = function (event) {
  258. if (!IDHandler.get(id)) {
  259. return;
  260. }
  261. onConnectionChange(GodotRTCPeerConnection.ConnectionState[conn.connectionState] || 0);
  262. };
  263. } else {
  264. // Fall back to using "iceConnectionState" when "connectionState" is not supported (notably Firefox).
  265. conn.oniceconnectionstatechange = function (event) {
  266. if (!IDHandler.get(id)) {
  267. return;
  268. }
  269. onConnectionChange(GodotRTCPeerConnection.ConnectionStateCompat[conn.iceConnectionState] || 0);
  270. };
  271. }
  272. conn.onicegatheringstatechange = function (event) {
  273. if (!IDHandler.get(id)) {
  274. return;
  275. }
  276. onIceGatheringChange(GodotRTCPeerConnection.IceGatheringState[conn.iceGatheringState] || 0);
  277. };
  278. conn.onsignalingstatechange = function (event) {
  279. if (!IDHandler.get(id)) {
  280. return;
  281. }
  282. onSignalingChange(GodotRTCPeerConnection.SignalingState[conn.signalingState] || 0);
  283. };
  284. conn.onicecandidate = function (event) {
  285. if (!IDHandler.get(id)) {
  286. return;
  287. }
  288. const c = event.candidate;
  289. if (!c || !c.candidate) {
  290. return;
  291. }
  292. const candidate_str = GodotRuntime.allocString(c.candidate);
  293. const mid_str = GodotRuntime.allocString(c.sdpMid);
  294. onIceCandidate(mid_str, c.sdpMLineIndex, candidate_str);
  295. GodotRuntime.free(candidate_str);
  296. GodotRuntime.free(mid_str);
  297. };
  298. conn.ondatachannel = function (event) {
  299. if (!IDHandler.get(id)) {
  300. return;
  301. }
  302. const cid = IDHandler.add(event.channel);
  303. onDataChannel(cid);
  304. };
  305. return id;
  306. },
  307. destroy: function (p_id) {
  308. const conn = IDHandler.get(p_id);
  309. if (!conn) {
  310. return;
  311. }
  312. conn.onconnectionstatechange = null;
  313. conn.oniceconnectionstatechange = null;
  314. conn.onicegatheringstatechange = null;
  315. conn.onsignalingstatechange = null;
  316. conn.onicecandidate = null;
  317. conn.ondatachannel = null;
  318. IDHandler.remove(p_id);
  319. },
  320. onsession: function (p_id, callback, session) {
  321. if (!IDHandler.get(p_id)) {
  322. return;
  323. }
  324. const type_str = GodotRuntime.allocString(session.type);
  325. const sdp_str = GodotRuntime.allocString(session.sdp);
  326. callback(type_str, sdp_str);
  327. GodotRuntime.free(type_str);
  328. GodotRuntime.free(sdp_str);
  329. },
  330. onerror: function (p_id, callback, error) {
  331. const ref = IDHandler.get(p_id);
  332. if (!ref) {
  333. return;
  334. }
  335. GodotRuntime.error(error);
  336. callback();
  337. },
  338. },
  339. godot_js_rtc_pc_create__proxy: 'sync',
  340. godot_js_rtc_pc_create__sig: 'iiiiiiii',
  341. 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) {
  342. const wrap = function (p_func) {
  343. return GodotRuntime.get_func(p_func).bind(null, p_ref);
  344. };
  345. return GodotRTCPeerConnection.create(
  346. JSON.parse(GodotRuntime.parseString(p_config)),
  347. wrap(p_on_connection_state_change),
  348. wrap(p_on_signaling_state_change),
  349. wrap(p_on_ice_gathering_state_change),
  350. wrap(p_on_ice_candidate),
  351. wrap(p_on_datachannel)
  352. );
  353. },
  354. godot_js_rtc_pc_close__proxy: 'sync',
  355. godot_js_rtc_pc_close__sig: 'vi',
  356. godot_js_rtc_pc_close: function (p_id) {
  357. const ref = IDHandler.get(p_id);
  358. if (!ref) {
  359. return;
  360. }
  361. ref.close();
  362. },
  363. godot_js_rtc_pc_destroy__proxy: 'sync',
  364. godot_js_rtc_pc_destroy__sig: 'vi',
  365. godot_js_rtc_pc_destroy: function (p_id) {
  366. GodotRTCPeerConnection.destroy(p_id);
  367. },
  368. godot_js_rtc_pc_offer_create__proxy: 'sync',
  369. godot_js_rtc_pc_offer_create__sig: 'viiii',
  370. godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) {
  371. const ref = IDHandler.get(p_id);
  372. if (!ref) {
  373. return;
  374. }
  375. const onsession = GodotRuntime.get_func(p_on_session).bind(null, p_obj);
  376. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
  377. ref.createOffer().then(function (session) {
  378. GodotRTCPeerConnection.onsession(p_id, onsession, session);
  379. }).catch(function (error) {
  380. GodotRTCPeerConnection.onerror(p_id, onerror, error);
  381. });
  382. },
  383. godot_js_rtc_pc_local_description_set__proxy: 'sync',
  384. godot_js_rtc_pc_local_description_set__sig: 'viiiii',
  385. godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) {
  386. const ref = IDHandler.get(p_id);
  387. if (!ref) {
  388. return;
  389. }
  390. const type = GodotRuntime.parseString(p_type);
  391. const sdp = GodotRuntime.parseString(p_sdp);
  392. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
  393. ref.setLocalDescription({
  394. 'sdp': sdp,
  395. 'type': type,
  396. }).catch(function (error) {
  397. GodotRTCPeerConnection.onerror(p_id, onerror, error);
  398. });
  399. },
  400. godot_js_rtc_pc_remote_description_set__proxy: 'sync',
  401. godot_js_rtc_pc_remote_description_set__sig: 'viiiiii',
  402. godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
  403. const ref = IDHandler.get(p_id);
  404. if (!ref) {
  405. return;
  406. }
  407. const type = GodotRuntime.parseString(p_type);
  408. const sdp = GodotRuntime.parseString(p_sdp);
  409. const onerror = GodotRuntime.get_func(p_on_error).bind(null, p_obj);
  410. const onsession = GodotRuntime.get_func(p_session_created).bind(null, p_obj);
  411. ref.setRemoteDescription({
  412. 'sdp': sdp,
  413. 'type': type,
  414. }).then(function () {
  415. if (type !== 'offer') {
  416. return Promise.resolve();
  417. }
  418. return ref.createAnswer().then(function (session) {
  419. GodotRTCPeerConnection.onsession(p_id, onsession, session);
  420. });
  421. }).catch(function (error) {
  422. GodotRTCPeerConnection.onerror(p_id, onerror, error);
  423. });
  424. },
  425. godot_js_rtc_pc_ice_candidate_add__proxy: 'sync',
  426. godot_js_rtc_pc_ice_candidate_add__sig: 'viiii',
  427. godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) {
  428. const ref = IDHandler.get(p_id);
  429. if (!ref) {
  430. return;
  431. }
  432. const sdpMidName = GodotRuntime.parseString(p_mid_name);
  433. const sdpName = GodotRuntime.parseString(p_sdp);
  434. ref.addIceCandidate(new RTCIceCandidate({
  435. 'candidate': sdpName,
  436. 'sdpMid': sdpMidName,
  437. 'sdpMlineIndex': p_mline_idx,
  438. }));
  439. },
  440. godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
  441. godot_js_rtc_pc_datachannel_create__proxy: 'sync',
  442. godot_js_rtc_pc_datachannel_create__sig: 'iiii',
  443. godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) {
  444. try {
  445. const ref = IDHandler.get(p_id);
  446. if (!ref) {
  447. return 0;
  448. }
  449. const label = GodotRuntime.parseString(p_label);
  450. const config = JSON.parse(GodotRuntime.parseString(p_config));
  451. const channel = ref.createDataChannel(label, config);
  452. return IDHandler.add(channel);
  453. } catch (e) {
  454. GodotRuntime.error(e);
  455. return 0;
  456. }
  457. },
  458. };
  459. autoAddDeps(GodotRTCPeerConnection, '$GodotRTCPeerConnection');
  460. mergeInto(LibraryManager.library, GodotRTCPeerConnection);