tts.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. // We make use of this 'server' variable to provide the address of the
  2. // REST Janus API. By default, in this example we assume that Janus is
  3. // co-located with the web server hosting the HTML pages but listening
  4. // on a different port (8088, the default for HTTP in Janus), which is
  5. // why we make use of the 'window.location.hostname' base address. Since
  6. // Janus can also do HTTPS, and considering we don't really want to make
  7. // use of HTTP for Janus if your demos are served on HTTPS, we also rely
  8. // on the 'window.location.protocol' prefix to build the variable, in
  9. // particular to also change the port used to contact Janus (8088 for
  10. // HTTP and 8089 for HTTPS, if enabled).
  11. // In case you place Janus behind an Apache frontend (as we did on the
  12. // online demos at http://janus.conf.meetecho.com) you can just use a
  13. // relative path for the variable, e.g.:
  14. //
  15. // var server = "/janus";
  16. //
  17. // which will take care of this on its own.
  18. //
  19. //
  20. // If you want to use the WebSockets frontend to Janus, instead, you'll
  21. // have to pass a different kind of address, e.g.:
  22. //
  23. // var server = "ws://" + window.location.hostname + ":8188";
  24. //
  25. // Of course this assumes that support for WebSockets has been built in
  26. // when compiling the server. WebSockets support has not been tested
  27. // as much as the REST API, so handle with care!
  28. //
  29. //
  30. // If you have multiple options available, and want to let the library
  31. // autodetect the best way to contact your server (or pool of servers),
  32. // you can also pass an array of servers, e.g., to provide alternative
  33. // means of access (e.g., try WebSockets first and, if that fails, fall
  34. // back to plain HTTP) or just have failover servers:
  35. //
  36. // var server = [
  37. // "ws://" + window.location.hostname + ":8188",
  38. // "/janus"
  39. // ];
  40. //
  41. // This will tell the library to try connecting to each of the servers
  42. // in the presented order. The first working server will be used for
  43. // the whole session.
  44. //
  45. var server = null;
  46. if(window.location.protocol === 'http:')
  47. //server = "http://" + window.location.hostname + ":9020/janus";
  48. // yujianbo
  49. server = "https://" + "10.80.21.211" + ":9022/janus";
  50. else
  51. //server = "https://" + window.location.hostname + ":9022/janus";
  52. // -yujianbo
  53. server = "https://" + "10.80.21.211" + ":9022/janus";
  54. var janus = null;
  55. var tts = null;
  56. var opaqueId = "tts-"+Janus.randomString(12);
  57. var spinner = null;
  58. $(document).ready(function() {
  59. // Initialize the library (all console debuggers enabled)
  60. Janus.init({debug: "all", callback: function() {
  61. // Use a button to start the demo
  62. $('#start').on('click', function() {
  63. // Make sure the browser supports WebRTC
  64. if(!Janus.isWebrtcSupported()) {
  65. bootbox.alert("No WebRTC support... ");
  66. return;
  67. }
  68. if($('#tts_url').val().length == 0){
  69. bootbox.alert("Please input tts url... ");
  70. return;
  71. }
  72. $(this).attr('disabled', true).unbind('click');
  73. // Create session
  74. janus = new Janus(
  75. {
  76. server: window.EZUITalk.opt.rtcUrl,
  77. // No "iceServers" is provided, meaning janus.js will use a default STUN server
  78. // Here are some examples of how an iceServers field may look like to support TURN
  79. // iceServers: [{urls: "turn:yourturnserver.com:3478", username: "janususer", credential: "januspwd"}],
  80. // iceServers: [{urls: "turn:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}],
  81. // iceServers: [{urls: "turns:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}],
  82. // Should the Janus API require authentication, you can specify either the API secret or user token here too
  83. // token: "mytoken",
  84. // or
  85. // apisecret: "serversecret",
  86. success: function() {
  87. // Attach to tts plugin
  88. janus.attach(
  89. {
  90. plugin: "rtcgw.plugin.tts",
  91. opaqueId: opaqueId,
  92. success: function(pluginHandle) {
  93. $('#details').remove();
  94. tts = pluginHandle;
  95. Janus.log("Plugin attached! (" + tts.getPlugin() + ", id=" + tts.getId() + ")");
  96. // Negotiate WebRTC
  97. //var url = "tts://61.130.6.23:8664/talk://D13781761:0:1:cas.ys7.com:6500?97fbd2a75fa94b7682c994d3d1fac8ca:ut.5porslgu79e9r7ca48z32k8abgl3rp58-77bhb6i7xr-1kmumtg-jkhy7pvfr:0:3"
  98. //var url = "tts://10.86.15.209:8664/talk://D13781761:0:1:cas.ys7.com:6500?32db2578ba7c4a84be22ecc0bcd0f8db:ut.5lqpkhim5m7cdk2y5w60g7hm9vd7i3v0-3d2pwhxe2t-11wx2ge-sh4yazbll:0:3"
  99. //var url = "tts://10.86.15.209:8664/talk://D13781761:0:1:cas.ys7.com:6500"
  100. //test12.ys.com
  101. //var url = "tts://10.86.15.209:8664/talk://D08197169:0:1:cas.ys7.com:6500"
  102. //test10.ys.com
  103. //var url = "tts://10.86.29.210:8664/talk://D08197169:0:1:cas.ys7.com:6500"
  104. var url = $('#tts_url').val();
  105. var body = { "request": "start", "url": url, "codec": "opus", "dir": "sendrecv", "audio_debug": 1};
  106. //tts.send({"message": body});
  107. Janus.debug("Trying a createOffer too (audio/video sendrecv)");
  108. tts.createOffer(
  109. {
  110. // No media provided: by default, it's sendrecv for audio and video
  111. media: { audio: true, video: false, data: false }, // Audio only
  112. // If you want to test simulcasting (Chrome and Firefox only), then
  113. // pass a ?simulcast=true when opening this demo page: it will turn
  114. // the following 'simulcast' property to pass to janus.js to true
  115. simulcast: false,
  116. simulcast2: false,
  117. success: function(jsep) {
  118. Janus.debug("Got SDP!");
  119. Janus.debug(jsep);
  120. tts.send({"message": body, "jsep": jsep});
  121. },
  122. error: function(error) {
  123. Janus.error("WebRTC error:", error);
  124. // bootbox.alert("WebRTC error... " + JSON.stringify(error));
  125. }
  126. });
  127. // $('#start').removeAttr('disabled').html("Stop")
  128. // .click(function() {
  129. // $(this).attr('disabled', true);
  130. // janus.destroy();
  131. // });
  132. },
  133. error: function(error) {
  134. console.error(" -- Error attaching plugin...", error);
  135. bootbox.alert("Error attaching plugin... " + error);
  136. },
  137. consentDialog: function(on) {
  138. Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now");
  139. if(on) {
  140. // Darken screen and show hint
  141. // $.blockUI({
  142. // message: '<div><img src="up_arrow.png"/></div>',
  143. // css: {
  144. // border: 'none',
  145. // padding: '15px',
  146. // backgroundColor: 'transparent',
  147. // color: '#aaa',
  148. // top: '10px',
  149. // left: (navigator.mozGetUserMedia ? '-100px' : '300px')
  150. // } });
  151. } else {
  152. // Restore screen
  153. // $.unblockUI();
  154. }
  155. },
  156. iceState: function(state) {
  157. Janus.log("ICE state changed to " + state);
  158. },
  159. mediaState: function(medium, on) {
  160. Janus.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
  161. },
  162. webrtcState: function(on) {
  163. Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
  164. $("#audioleft").parent().unblock();
  165. },
  166. slowLink: function(uplink, lost) {
  167. Janus.warn("Janus reports problems " + (uplink ? "sending" : "receiving") +
  168. " packets on this PeerConnection (" + lost + " lost packets)");
  169. },
  170. onmessage: function(msg, jsep) {
  171. Janus.debug(" ::: Got a message :::");
  172. Janus.debug(msg);
  173. if(jsep !== undefined && jsep !== null) {
  174. Janus.debug("Handling SDP as well...");
  175. Janus.debug(jsep);
  176. tts.handleRemoteJsep({jsep: jsep});
  177. }
  178. var result = msg["result"];
  179. if(result !== null && result !== undefined) {
  180. if(result === "done") {
  181. // The plugin closed
  182. bootbox.alert("The TTS Test is over");
  183. if(spinner !== null && spinner !== undefined)
  184. spinner.stop();
  185. spinner = null;
  186. $('#myaudio').remove();
  187. //$('#waitingvideo').remove();
  188. $('#peeraudio').remove();
  189. return;
  190. }
  191. // Any loss?
  192. var status = result["status"];
  193. if(status === "slow_link") {
  194. //~ var bitrate = result["bitrate"];
  195. //~ toastr.warning("The bitrate has been cut to " + (bitrate/1000) + "kbps", "Packet loss?", {timeOut: 2000});
  196. toastr.warning("Janus apparently missed many packets we sent, maybe we should reduce the bitrate", "Packet loss?", {timeOut: 2000});
  197. }
  198. }
  199. },
  200. onlocalstream: function(stream) {
  201. Janus.debug(" ::: Got a local stream :::");
  202. Janus.debug(stream);
  203. if($('#myaudio').length === 0) {
  204. $('#audios').removeClass('hide').show();
  205. $('#audioleft').append('<audio id="myaudio" autoplay controls muted>Your browser does not support audio tag</audio>');
  206. }
  207. Janus.attachMediaStream($('#myaudio').get(0), stream);
  208. //$("#myaudio").get(0).muted = "muted";
  209. if(tts.webrtcStuff.pc.iceConnectionState !== "completed" &&
  210. tts.webrtcStuff.pc.iceConnectionState !== "connected") {
  211. // $("#audioleft").parent().block({
  212. // message: '<b>Publishing...</b>',
  213. // css: {
  214. // border: 'none',
  215. // backgroundColor: 'transparent',
  216. // color: 'white'
  217. // }
  218. // });
  219. // No remote video yet
  220. //$('#audioright').append('<video class="rounded centered" id="waitingvideo" width=320 height=240 />');
  221. if(spinner == null) {
  222. var target = document.getElementById('audioright');
  223. //spinner = new Spinner({top:100}).spin(target);
  224. } else {
  225. spinner.spin();
  226. }
  227. }
  228. var audioTracks = stream.getAudioTracks();
  229. if(audioTracks === null || audioTracks === undefined || audioTracks.length === 0) {
  230. $('#myaudio').hide();
  231. } else {
  232. $('#myaudio').removeClass('hide').show();
  233. }
  234. },
  235. onremotestream: function(stream) {
  236. Janus.debug(" ::: Got a remote stream :::");
  237. Janus.debug(stream);
  238. if($('#peeraudio').length === 0) {
  239. $('#audios').removeClass('hide').show();
  240. $('#audioright').append('<audio id="peeraudio" autoplay controls>Your browser does not support audio tag</audio>');
  241. // Show the video, hide the spinner and show the resolution when we get a playing event
  242. $("#peeraudio").bind("playing", function () {
  243. //$('#waitingvideo').remove();
  244. $('#peeraudio').removeClass('hide').show();
  245. if(spinner !== null && spinner !== undefined)
  246. spinner.stop();
  247. spinner = null;
  248. });
  249. }
  250. Janus.attachMediaStream($('#peeraudio').get(0), stream);
  251. var audioTracks = stream.getAudioTracks();
  252. if(audioTracks === null || audioTracks === undefined || audioTracks.length === 0) {
  253. $('#peeraudio').hide();
  254. } else {
  255. $('#peeraudio').removeClass('hide').show();
  256. }
  257. },
  258. ondataopen: function(data) {
  259. Janus.log("The DataChannel is available!");
  260. },
  261. ondata: function(data) {
  262. Janus.debug("We got data from the DataChannel! " + data);
  263. },
  264. oncleanup: function() {
  265. Janus.log(" ::: Got a cleanup notification :::");
  266. if(spinner !== null && spinner !== undefined)
  267. spinner.stop();
  268. spinner = null;
  269. $('#myaudio').remove();
  270. //$('#waitingvideo').remove();
  271. $("#audioleft").parent().unblock();
  272. $('#peeraudio').remove();
  273. }
  274. });
  275. },
  276. error: function(error) {
  277. Janus.error(error);
  278. bootbox.alert(error, function() {
  279. // window.location.reload();
  280. });
  281. },
  282. destroyed: function() {
  283. // window.location.reload();
  284. }
  285. });
  286. });
  287. window.stopTalk = function (){
  288. janus.destroy();
  289. }
  290. EZUITalkStopTalk = function (){
  291. janus.destroy();
  292. }
  293. // debugger;
  294. window.startTalk = startTalk;
  295. function startTalk() {
  296. // Make sure the browser supports WebRTC
  297. if(!Janus.isWebrtcSupported()) {
  298. bootbox.alert("No WebRTC support... ");
  299. return;
  300. }
  301. // if($('#tts_url').val().length == 0){
  302. // bootbox.alert("Please input tts url... ");
  303. // return;
  304. // }
  305. $(this).attr('disabled', true).unbind('click');
  306. // Create session
  307. janus = new Janus(
  308. {
  309. server: window.EZUITalk.opt.rtcUrl,
  310. // No "iceServers" is provided, meaning janus.js will use a default STUN server
  311. // Here are some examples of how an iceServers field may look like to support TURN
  312. // iceServers: [{urls: "turn:yourturnserver.com:3478", username: "janususer", credential: "januspwd"}],
  313. // iceServers: [{urls: "turn:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}],
  314. // iceServers: [{urls: "turns:yourturnserver.com:443?transport=tcp", username: "janususer", credential: "januspwd"}],
  315. // Should the Janus API require authentication, you can specify either the API secret or user token here too
  316. // token: "mytoken",
  317. // or
  318. // apisecret: "serversecret",
  319. success: function() {
  320. // Attach to tts plugin
  321. janus.attach(
  322. {
  323. plugin: "rtcgw.plugin.tts",
  324. opaqueId: opaqueId,
  325. success: function(pluginHandle) {
  326. $('#details').remove();
  327. tts = pluginHandle;
  328. Janus.log("Plugin attached! (" + tts.getPlugin() + ", id=" + tts.getId() + ")");
  329. // Negotiate WebRTC
  330. //var url = "tts://61.130.6.23:8664/talk://D13781761:0:1:cas.ys7.com:6500?97fbd2a75fa94b7682c994d3d1fac8ca:ut.5porslgu79e9r7ca48z32k8abgl3rp58-77bhb6i7xr-1kmumtg-jkhy7pvfr:0:3"
  331. //var url = "tts://10.86.15.209:8664/talk://D13781761:0:1:cas.ys7.com:6500?32db2578ba7c4a84be22ecc0bcd0f8db:ut.5lqpkhim5m7cdk2y5w60g7hm9vd7i3v0-3d2pwhxe2t-11wx2ge-sh4yazbll:0:3"
  332. //var url = "tts://10.86.15.209:8664/talk://D13781761:0:1:cas.ys7.com:6500"
  333. //test12.ys.com
  334. //var url = "tts://10.86.15.209:8664/talk://D08197169:0:1:cas.ys7.com:6500"
  335. //test10.ys.com
  336. //var url = "tts://10.86.29.210:8664/talk://D08197169:0:1:cas.ys7.com:6500"
  337. var url = window.EZUITalk.opt.talkLink;
  338. console.log("ttsUlr",url);
  339. var body = { "request": "start", "url": url, "codec": "opus", "dir": "sendrecv", "audio_debug": 1};
  340. //tts.send({"message": body});
  341. Janus.debug("Trying a createOffer too (audio/video sendrecv)");
  342. tts.createOffer(
  343. {
  344. // No media provided: by default, it's sendrecv for audio and video
  345. media: { audio: true, video: false, data: false }, // Audio only
  346. // If you want to test simulcasting (Chrome and Firefox only), then
  347. // pass a ?simulcast=true when opening this demo page: it will turn
  348. // the following 'simulcast' property to pass to janus.js to true
  349. simulcast: false,
  350. simulcast2: false,
  351. success: function(jsep) {
  352. Janus.debug("Got SDP!");
  353. Janus.debug(jsep);
  354. tts.send({"message": body, "jsep": jsep});
  355. },
  356. error: function(error) {
  357. Janus.error("WebRTC error:", error);
  358. // bootbox.alert("WebRTC error... " + JSON.stringify(error));
  359. }
  360. });
  361. // $('#start').removeAttr('disabled').html("Stop")
  362. // .click(function() {
  363. // $(this).attr('disabled', true);
  364. // janus.destroy();
  365. // });
  366. },
  367. error: function(error) {
  368. console.error(" -- Error attaching plugin...", error);
  369. bootbox.alert("Error attaching plugin... " + error);
  370. },
  371. consentDialog: function(on) {
  372. Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now");
  373. if(on) {
  374. // Darken screen and show hint
  375. // $.blockUI({
  376. // message: '<div><img src="up_arrow.png"/></div>',
  377. // css: {
  378. // border: 'none',
  379. // padding: '15px',
  380. // backgroundColor: 'transparent',
  381. // color: '#aaa',
  382. // top: '10px',
  383. // left: (navigator.mozGetUserMedia ? '-100px' : '300px')
  384. // } });
  385. } else {
  386. // Restore screen
  387. // $.unblockUI();
  388. }
  389. },
  390. iceState: function(state) {
  391. Janus.log("ICE state changed to " + state);
  392. },
  393. mediaState: function(medium, on) {
  394. Janus.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
  395. },
  396. webrtcState: function(on) {
  397. Janus.log("Janus says our WebRTC PeerConnection is " + (on ? "up" : "down") + " now");
  398. // $("#audioleft").parent().unblock();
  399. },
  400. slowLink: function(uplink, lost) {
  401. Janus.warn("Janus reports problems " + (uplink ? "sending" : "receiving") +
  402. " packets on this PeerConnection (" + lost + " lost packets)");
  403. },
  404. onmessage: function(msg, jsep) {
  405. Janus.debug(" ::: Got a message :::");
  406. Janus.debug(msg);
  407. if(jsep !== undefined && jsep !== null) {
  408. Janus.debug("Handling SDP as well...");
  409. Janus.debug(jsep);
  410. tts.handleRemoteJsep({jsep: jsep});
  411. }
  412. var result = msg["result"];
  413. if(result !== null && result !== undefined) {
  414. if(result === "done") {
  415. // The plugin closed
  416. bootbox.alert("The TTS Test is over");
  417. if(spinner !== null && spinner !== undefined)
  418. spinner.stop();
  419. spinner = null;
  420. $('#myaudio').remove();
  421. //$('#waitingvideo').remove();
  422. $('#peeraudio').remove();
  423. return;
  424. }
  425. // Any loss?
  426. var status = result["status"];
  427. if(status === "slow_link") {
  428. //~ var bitrate = result["bitrate"];
  429. //~ toastr.warning("The bitrate has been cut to " + (bitrate/1000) + "kbps", "Packet loss?", {timeOut: 2000});
  430. toastr.warning("Janus apparently missed many packets we sent, maybe we should reduce the bitrate", "Packet loss?", {timeOut: 2000});
  431. }
  432. }
  433. },
  434. onlocalstream: function(stream) {
  435. Janus.debug(" ::: Got a local stream :::");
  436. Janus.debug(stream);
  437. if($('#myaudio').length === 0) {
  438. $('#audios').removeClass('hide').show();
  439. $('#audioleft').append('<audio id="myaudio" autoplay controls muted>Your browser does not support audio tag</audio>');
  440. }
  441. Janus.attachMediaStream($('#myaudio').get(0), stream);
  442. //$("#myaudio").get(0).muted = "muted";
  443. if(tts.webrtcStuff.pc.iceConnectionState !== "completed" &&
  444. tts.webrtcStuff.pc.iceConnectionState !== "connected") {
  445. // $("#audioleft").parent().block({
  446. // message: '<b>Publishing...</b>',
  447. // css: {
  448. // border: 'none',
  449. // backgroundColor: 'transparent',
  450. // color: 'white'
  451. // }
  452. // });
  453. // No remote video yet
  454. //$('#audioright').append('<video class="rounded centered" id="waitingvideo" width=320 height=240 />');
  455. if(spinner == null) {
  456. var target = document.getElementById('audioright');
  457. //spinner = new Spinner({top:100}).spin(target);
  458. } else {
  459. spinner.spin();
  460. }
  461. }
  462. var audioTracks = stream.getAudioTracks();
  463. if(audioTracks === null || audioTracks === undefined || audioTracks.length === 0) {
  464. $('#myaudio').hide();
  465. } else {
  466. $('#myaudio').removeClass('hide').show();
  467. }
  468. },
  469. onremotestream: function(stream) {
  470. Janus.debug(" ::: Got a remote stream :::");
  471. Janus.debug(stream);
  472. if($('#peeraudio').length === 0) {
  473. $('#audios').removeClass('hide').show();
  474. $('#audioright').append('<audio id="peeraudio" autoplay controls>Your browser does not support audio tag</audio>');
  475. // Show the video, hide the spinner and show the resolution when we get a playing event
  476. $("#peeraudio").bind("playing", function () {
  477. //$('#waitingvideo').remove();
  478. $('#peeraudio').removeClass('hide').show();
  479. if(spinner !== null && spinner !== undefined)
  480. spinner.stop();
  481. spinner = null;
  482. });
  483. }
  484. Janus.attachMediaStream($('#peeraudio').get(0), stream);
  485. var audioTracks = stream.getAudioTracks();
  486. if(audioTracks === null || audioTracks === undefined || audioTracks.length === 0) {
  487. $('#peeraudio').hide();
  488. } else {
  489. $('#peeraudio').removeClass('hide').show();
  490. }
  491. },
  492. ondataopen: function(data) {
  493. Janus.log("The DataChannel is available!");
  494. },
  495. ondata: function(data) {
  496. Janus.debug("We got data from the DataChannel! " + data);
  497. },
  498. oncleanup: function() {
  499. Janus.log(" ::: Got a cleanup notification :::");
  500. if(spinner !== null && spinner !== undefined)
  501. spinner.stop();
  502. spinner = null;
  503. $('#myaudio').remove();
  504. //$('#waitingvideo').remove();
  505. $("#audioleft").parent().unblock();
  506. $('#peeraudio').remove();
  507. }
  508. });
  509. },
  510. error: function(error) {
  511. Janus.error(error);
  512. console.log("error",error)
  513. },
  514. destroyed: function() {
  515. // window.location.reload();
  516. }
  517. });
  518. }
  519. }});
  520. });
  521. function checkEnter(event) {
  522. var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
  523. if(theCode == 13) {
  524. sendData();
  525. return false;
  526. } else {
  527. return true;
  528. }
  529. }
  530. // Helper to parse query string
  531. function getQueryStringValue(name) {
  532. name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
  533. var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
  534. results = regex.exec(location.search);
  535. return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  536. }