client-dashboard.blade.php 21 KB

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