client-dashboard.blade.php 20 KB

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