client-dashboard.blade.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <?php
  2. /** @var $client */ ?>
  3. @extends('layouts.meeting')
  4. @section('content')
  5. <div id="clientCallComponent">
  6. <div class="d-flex align-items-center py-3 border-bottom px-4">
  7. <span class="mr-auto">
  8. Hello {{ $client->name_last }}, {{ $client->name_first }}
  9. </span>
  10. <a href="#" class="client-logout">Log Out</a>
  11. </div>
  12. <div class="">
  13. <div class="py-3 text-center" v-if="started">
  14. <h6 class="text-black font-weight-bold m-0">Call in progress: @{{ timeDisplay() }}</h6>
  15. </div>
  16. <div class="py-3 text-center" v-if="noOneElseInCall">
  17. <!-- <h6 class="text-black font-weight-bold m-0">A pro may pop in here at the next chance. Please wait..</h6> -->
  18. <h1>Free COVID-19 Safety Kit & Coaching Session.</h1>
  19. <h2>A coach will be with you shortly. This call will last approximately 10 minutes.</h2>
  20. <h3>Upon the conclusion of this call, the coach will verify your mailing address to mail you your kit. Please note that PO Boxes are not deliverable by FedEx.</h3>
  21. </div>
  22. <div class="main-view mx-auto">
  23. <div class="thumbs">
  24. <div id="self-view" class="thumb-view disconnected-view" data-name="{{ implode(" ", [$client->name_first, $client->name_last]) }}" data-type="CLIENT"></div>
  25. </div>
  26. <?php /* <button class="btn btn-danger rounded-circle hang-up"
  27. v-if="started"
  28. title="Leave Call"
  29. v-on:click.prevent="hangUp()">
  30. <i class="fa fa-phone"></i>
  31. </button> */ ?>
  32. </div>
  33. </div>
  34. <div class="d-flex justify-content-center mt-3">
  35. <button v-if="readyToPublish && !publishing" class="btn btn-sm btn-primary font-weight-bold" v-on:click.prevent="cameraOn()">Start My Camera</button>
  36. <button v-if="readyToPublish && publishing" class="btn btn-sm btn-danger font-weight-bold" v-on:click.prevent="cameraOff()">Stop My Camera</button>
  37. </div>
  38. </div>
  39. <script>
  40. window.clientCallComponent = new Vue({
  41. el: '#clientCallComponent',
  42. delimiters: ['@{{', '}}'],
  43. data: {
  44. mdPass: true, // set this to true if client has all medicare reqs met
  45. time: 0,
  46. startTime: 0,
  47. started: false,
  48. client: false,
  49. pro: false,
  50. selfName: '',
  51. selfToken: '',
  52. clientUid: '{{ $client->uid }}',
  53. checkInToken: '',
  54. otSessionId: '{{ $client->opentok_session_id }}',
  55. otSession: false,
  56. publisher: false,
  57. readyToPublish: false,
  58. publishing: false,
  59. selfUserType: 'CLIENT',
  60. selfStreamId: '',
  61. noOneElseInCall: true,
  62. heartbeatTimer: false,
  63. },
  64. methods: {
  65. timeDisplay: function() {
  66. var seconds = this.time / 1000,
  67. minutes = parseInt(seconds / 60, 10);
  68. seconds = parseInt(seconds % 60, 10);
  69. return minutes + " min, " + seconds + " sec";
  70. },
  71. hangUp: function() {
  72. if (this.otSession) {
  73. try {
  74. this.otSession.disconnect();
  75. } catch (e) {
  76. console.log('Was already disconnected.');
  77. }
  78. this.otSession = false;
  79. this.otSessionId = '';
  80. this.started = false;
  81. this.startTime = false;
  82. // window.location = '/join';
  83. }
  84. },
  85. initOpenTok: function() {
  86. /* fake video feed (temp) */
  87. const canvas = document.createElement('canvas');
  88. canvas.width = 640;
  89. canvas.height = 480;
  90. const ctx = canvas.getContext('2d');
  91. var pos = 100;
  92. window.setInterval(function() {
  93. ctx.clearRect(0, 0, canvas.width, canvas.height);
  94. ctx.font = "20px Georgia";
  95. ctx.fillStyle = `rgb(220, 220, 220)`;
  96. ctx.fillText("Video feed from {{ $client->name_first }}", 20, pos);
  97. pos += 5;
  98. if (pos > canvas.height) pos = 100;
  99. }, 1000);
  100. var self = this;
  101. var apiKey = '<?= env('TOKBOX_API_KEY', '46678902') ?>';
  102. var sessionId = this.otSessionId;
  103. var token = this.selfToken;
  104. // destroy if existing
  105. // self.hangUp();
  106. self.otSession = OT.initSession(apiKey, sessionId);
  107. // peer connected
  108. self.otSession.on('streamCreated', function streamCreated(event) {
  109. console.log('session->streamCreated', arguments);
  110. var subscriberOptions = {
  111. insertMode: 'append',
  112. width: '100%',
  113. height: '100%'
  114. };
  115. var connectionData = JSON.parse(event.stream.connection.data);
  116. // add a div for remove view
  117. var remoteViewID = 'remote-view-' + event.stream.id;
  118. var remoteElem = $('<div id="' + remoteViewID + '" class="remote-view thumb-view" ' +
  119. 'data-stream="' + event.stream.id + '" ' +
  120. 'data-connection-data="' + event.stream.connection.data + '" ' +
  121. 'data-name="' + connectionData.name + '" ' +
  122. 'data-type="' + connectionData.type + '"></div>');
  123. remoteElem.appendTo('.thumbs');
  124. self.otSession.subscribe(event.stream, remoteViewID, subscriberOptions, self.handleOpenTokError_Subscribe);
  125. if (connectionData.type === 'PRO') {
  126. self.pro = true;
  127. }
  128. self.activateParty(event.stream.id);
  129. self.noOneElseInCall = false;
  130. });
  131. // peer disconnected
  132. self.otSession.on("streamDestroyed", function(event) {
  133. onPeerDisconnection(event, event.stream.connection.data);
  134. });
  135. // self.otSession.on("connectionDestroyed", function(event) {
  136. // debugger;
  137. // console.log('connectionDestroyed from ' + event.connection.data);
  138. // onPeerDisconnection(event, event.connection.data);
  139. // });
  140. function onPeerDisconnection(event, data) {
  141. console.log('onPeerDisconnection');
  142. if (event.stream && $('.full-view[data-stream="' + event.stream.id + '"]').length) {
  143. var allThumbs = $('.thumbs [data-stream]:not([data-stream=""]):not(.disconnected-view):visible');
  144. if (allThumbs.length) {
  145. $('.thumbs [data-stream]:not([data-stream=""])').each(function() {
  146. if ($(this).attr('data-stream') !== event.stream.id) {
  147. self.activateParty($(this).attr('data-stream'));
  148. return false;
  149. }
  150. });
  151. }
  152. }
  153. if (event.stream) {
  154. var remoteViewElem = $('[data-stream="' + event.stream.id + '"]');
  155. remoteViewElem.remove();
  156. // if(remoteViewElem.length) {
  157. // remoteViewElem.attr('data-stream', '');
  158. // remoteViewElem.attr('data-connection-data', '');
  159. // remoteViewElem.attr('data-type', '');
  160. // remoteViewElem.attr('data-name', '');
  161. // }
  162. // remoteViewElem.addClass('disconnected-view')
  163. }
  164. // if no other parties in call, hang up
  165. if (!$('[data-stream]:not([data-stream="' + self.selfStreamId + '"])').length) {
  166. // self.hangUp();
  167. console.warn('No other parties in the call!');
  168. // new Noty({
  169. // theme: 'mint',
  170. // type: 'info',
  171. // text: 'All other participants have left the call',
  172. // progressBar: false,
  173. // timeout: 2500,
  174. // }).show();
  175. self.startTime = 0;
  176. self.started = false;
  177. self.noOneElseInCall = true;
  178. }
  179. }
  180. // self disconnected
  181. self.otSession.on('sessionDisconnected', function sessionDisconnected(event) {
  182. console.log('You were disconnected from the session.', event.reason);
  183. // turn client video off
  184. $.post('/api/clientVideoVisit/turnClientVideoOff', {}, function(_data) {
  185. console.log(_data);
  186. });
  187. // in case of accidental disconnection
  188. self.initOpenTok();
  189. });
  190. // initialize the publisher
  191. var publisherOptions = {
  192. videoSource: canvas.captureStream(1).getVideoTracks()[0], // TODO: Comment this line to use webcam
  193. insertMode: 'append',
  194. width: '100%',
  195. height: '100%',
  196. };
  197. self.publisher = OT.initPublisher('self-view', publisherOptions, self.handleOpenTokError_InitPublisher);
  198. self.publisher.on('streamCreated', function(event) {
  199. console.log('publisher->streamCreated');
  200. var selfView = $('#self-view');
  201. selfView.attr('data-stream', event.stream.id);
  202. selfView.attr('data-type', 'CLIENT');
  203. self.selfStreamId = event.stream.id;
  204. self.activateParty('self');
  205. $('#self-view').show();
  206. self.startTime = new Date().getTime();
  207. window.setInterval(function() {
  208. self.time = new Date().getTime() - self.startTime;
  209. }, 1000);
  210. self.started = true;
  211. // turn client video on
  212. $.post('/api/clientVideoVisit/turnClientVideoOn', {}, function(_data) {
  213. console.log(_data);
  214. });
  215. });
  216. self.publisher.on('streamDestroyed', function(event) {
  217. event.preventDefault();
  218. console.log('publisher->streamDestroyed');
  219. $('#self-view').hide();
  220. var allThumbs = $('.thumbs [data-stream]:not([data-stream=""]):visible');
  221. if (allThumbs.length) {
  222. $('.thumbs [data-stream]:not([data-stream=""])').each(function() {
  223. if ($(this).attr('data-stream') !== $('#self-view').attr('data-stream')) {
  224. self.activateParty($(this).attr('data-stream'));
  225. return false;
  226. }
  227. });
  228. }
  229. // turn client video off
  230. $.post('/api/clientVideoVisit/turnClientVideoOff', {}, function(_data) {
  231. console.log(_data);
  232. });
  233. });
  234. // Connect to the session
  235. self.otSession.connect(token, function callback(error) {
  236. if (error) {
  237. self.handleOpenTokError_SessionConnect(error);
  238. } else {
  239. console.info('Connected to OT session :)');
  240. self.readyToPublish = true;
  241. // auto start client video
  242. self.cameraOn();
  243. }
  244. });
  245. },
  246. cameraOn: function() {
  247. if (this.readyToPublish) {
  248. this.otSession.publish(this.publisher, this.handleOpenTokError_Publish);
  249. this.publishing = true;
  250. }
  251. },
  252. cameraOff: function() {
  253. this.otSession.unpublish(this.publisher);
  254. this.publishing = false;
  255. },
  256. handleOpenTokError_SessionConnect: function(e) {
  257. console.log('handleOpenTokError_SessionConnect');
  258. console.log(e);
  259. },
  260. handleOpenTokError_Publish: function(e) {
  261. console.log('handleOpenTokError_Publish');
  262. console.log(e);
  263. },
  264. handleOpenTokError_Subscribe: function(e) {
  265. console.log('handleOpenTokError_Subscribe');
  266. console.log(e);
  267. },
  268. handleOpenTokError_InitPublisher: function(e) {
  269. console.log('handleOpenTokError_InitPublisher');
  270. console.log(e);
  271. },
  272. getClientCheckinToken: function(_done) {
  273. var self = this;
  274. $.get('/get-client-checkin-token/' + this.clientUid, function(_data) {
  275. console.log(_data);
  276. self.checkInToken = _data.data;
  277. _done();
  278. }, 'json');
  279. },
  280. getOpenTokSessionId: function(_done) {
  281. var self = this;
  282. $.ajax({
  283. type: 'post',
  284. url: '/api/clientVideoVisit/startVideoVisitAsClient',
  285. headers: {
  286. 'sessionKey': '{{ request()->cookie('
  287. sessionKey ') }}'
  288. },
  289. data: {
  290. checkInToken: this.checkInToken
  291. },
  292. dataType: 'json'
  293. })
  294. .done(function(_data) {
  295. console.log(_data);
  296. if (_data.success) {
  297. self.otSessionId = _data.data;
  298. _done();
  299. } else {
  300. alert(_data.message);
  301. }
  302. })
  303. .fail(function(_data) {
  304. console.log(_data);
  305. alert(_data.message);
  306. });
  307. },
  308. activateParty: function(_stream = 'self') {
  309. var current = $('.full-view');
  310. if (current.attr('data-stream') === _stream) return;
  311. current.removeClass('full-view').addClass('thumb-view');
  312. if (current.attr('data-type') === 'CLIENT') {
  313. current.prependTo('.thumbs');
  314. } else {
  315. current.appendTo('.thumbs');
  316. }
  317. if (_stream === 'self') {
  318. $('#self-view')
  319. .removeClass('thumb-view')
  320. .removeClass('disconnected-view')
  321. .addClass('full-view')
  322. .prependTo('.main-view');
  323. } else {
  324. $('div[data-stream="' + _stream + '"]')
  325. .removeClass('thumb-view')
  326. .removeClass('disconnected-view')
  327. .addClass('full-view')
  328. .prependTo('.main-view');
  329. }
  330. }
  331. },
  332. mounted: function() {
  333. var self = this;
  334. this.getClientCheckinToken(function() { // get client check-in token
  335. self.getOpenTokSessionId(function() { // get opentok session id
  336. var name = [];
  337. @if(!empty($client - > name_first)) name.push("{{ $client->name_first }}");
  338. @endif
  339. @if(!empty($client - > name_last)) name.push("{{ $client->name_last }}");
  340. @endif
  341. self.selfName = name.join(' ');
  342. $.ajax({
  343. type: 'post',
  344. url: '/api/openTok/getClientToken',
  345. headers: {
  346. 'sessionKey': '{{ request()->cookie('
  347. sessionKey ') }}'
  348. },
  349. data: {
  350. opentokSessionId: self.otSessionId,
  351. data: JSON.stringify({
  352. uid: '{{ $client->uid }}',
  353. name: self.selfName,
  354. type: 'CLIENT'
  355. })
  356. },
  357. dataType: 'json'
  358. })
  359. .done(function(_data) {
  360. console.log(_data);
  361. self.selfToken = _data.data;
  362. self.initOpenTok();
  363. })
  364. .fail(function(_data) {
  365. console.warn(_data);
  366. alert(_data.message);
  367. });
  368. });
  369. });
  370. $(document).on('click', '.thumbs>div[data-stream]', function() {
  371. self.activateParty($(this).attr('data-stream'));
  372. return false;
  373. });
  374. window.onbeforeunload = function() {
  375. if (self.started) {
  376. return "A call is in progress";
  377. }
  378. };
  379. $(document).on('click', '.client-logout', function() {
  380. // turn client video off
  381. $.post('/api/clientVideoVisit/turnClientVideoOff', {}, function(_data) {
  382. console.log(_data);
  383. // log out
  384. $.get("/api/session/logOut", function(_data) {
  385. console.log(_data);
  386. window.location = '/join';
  387. })
  388. });
  389. return false;
  390. });
  391. // start heart beat
  392. self.heartbeatTimer = window.setInterval(function() {
  393. $.post('/api/clientVideoVisit/registerClientVideoHeartbeat', {}, function(_data) {
  394. console.log(_data);
  395. });
  396. }, 5000);
  397. }
  398. });
  399. </script>
  400. @endsection