client-dashboard.blade.php 20 KB

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