meet.blade.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. @extends('layouts.meeting')
  2. @section('content')
  3. <div id="meetComponent">
  4. {{--<h5 class="bg-dark font-weight-bold text-white m-0 py-3 px-4 d-flex">
  5. <span>Meeting</span>
  6. <span class="ml-auto" v-if="!started">
  7. Connecting...
  8. </span>
  9. <span class="ml-auto" v-if="started">
  10. <i class="fa fa-clock mr-1 text-light"></i>
  11. @{{ timeDisplay() }}
  12. </span>
  13. </h5>--}}
  14. @if(!$guest)
  15. <div v-if="!started" class="d-flex align-items-center justify-content-center py-3 border-bottom">
  16. <span class="mr-3">
  17. {{ $pro->name_display }} | {{ $pro->is_video_visit_assistant ? 'ASSISTANT' : 'MCP' }}
  18. </span>
  19. <button class="btn btn-sm btn-primary px-4 font-weight-bold"
  20. v-on:click.prevent="nextPatient()"
  21. :disabled="client || checkingForNextPatient">Next Patient</button>
  22. </div>
  23. <div v-if="!started && noNextPatient" class="bg-light rounded text-center py-1 font-weight-bold text-sm">@{{ noNextPatient }}</div>
  24. @endif
  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. @if($guest)
  30. <div class="py-3 text-center" v-if="!pro || !started">
  31. <h6 class="text-black font-weight-bold m-0">Please wait. Your doctor will be with you shortly...</h6>
  32. </div>
  33. @endif
  34. <div class="main-view mx-auto" <?= !$guest ? 'v-show="!!client"' : '' ?>>
  35. <div id="self-view" class="full-view"></div>
  36. <div class="thumbs">
  37. <div id="remote-view-1" class="remote-view disconnected-view"
  38. data-stream="" data-from=""></div>
  39. <div id="remote-view-2" class="remote-view disconnected-view"
  40. data-stream="" data-from=""></div>
  41. </div>
  42. <button class="btn btn-danger rounded-circle hang-up"
  43. v-if="started"
  44. title="Leave Call"
  45. v-on:click.prevent="hangUp()">
  46. <i class="fa fa-phone"></i>
  47. </button>
  48. <button class="btn btn-success rounded-circle call-mcp"
  49. v-if="selfUserType === 'ASSISTANT'"
  50. title="Call MCP Pro"
  51. v-on:click.prevent="callMCPPro()">
  52. <i class="fa fa-user-md"></i>
  53. </button>
  54. </div>
  55. </div>
  56. </div>
  57. <script>
  58. new Vue({
  59. el: '#meetComponent',
  60. delimiters: ['@{{', '}}'],
  61. data: {
  62. time: 0,
  63. startTime: 0,
  64. started: false,
  65. client: false,
  66. pro: false,
  67. selfName: '',
  68. selfToken: '',
  69. @if($guest)
  70. clientUid: '',
  71. checkInToken: '',
  72. @endif
  73. otSessionId: '',
  74. @if(!$guest)
  75. checkingForNextPatient: false,
  76. noNextPatient: false,
  77. @endif
  78. otSession: false,
  79. selfUserType: false,
  80. },
  81. methods: {
  82. nextPatient: function() {
  83. var self = this;
  84. this.checkingForNextPatient = true;
  85. $.post('/api/client/getNextClientForVideoVisit', {}, function(_data) {
  86. self.checkingForNextPatient = false;
  87. if(!_data.success) {
  88. self.noNextPatient = _data.message;
  89. window.setTimeout(function() {
  90. self.noNextPatient = false;
  91. }, 2000);
  92. }
  93. else {
  94. // get ot session key from client record
  95. self.client = true;
  96. self.clientUid = _data.data;
  97. self.getOpenTokSessionId(function() {
  98. self.selfName = 'Pro'; // TODO: replace with name of authed pro
  99. $.post('/api/openTok/getClientToken', {
  100. opentokSessionId: self.otSessionId,
  101. name: self.selfName
  102. }, function (_data) {
  103. self.selfToken = _data.data;
  104. self.initOpenTok();
  105. });
  106. });
  107. }
  108. }, 'json');
  109. },
  110. getInitials: function(_name) {
  111. var parts = _name.split(/\s+/g);
  112. parts = parts.map(_part => _part[0]);
  113. return parts.join('');
  114. },
  115. timeDisplay: function() {
  116. var seconds = this.time / 1000,
  117. minutes = parseInt(seconds / 60, 10);
  118. seconds = parseInt(seconds % 60, 10);
  119. return minutes + " min, " + seconds + " sec";
  120. },
  121. hangUp: function() {
  122. if(this.otSession) {
  123. this.otSession.disconnect();
  124. this.otSession = false;
  125. this.otSessionId = '';
  126. this.started = false;
  127. this.startTime = false;
  128. @if(!$guest)
  129. this.client = false;
  130. @else
  131. window.location = '/join';
  132. @endif
  133. }
  134. },
  135. callMCPPro: function() {
  136. // put client in mcp queue
  137. $.ajax({
  138. type: 'post',
  139. url: '/api/clientVideoVisit/PutVideoVisitInMcpQueue',
  140. headers: {
  141. 'sessionKey': localStorage.sessionKey
  142. },
  143. data: {uid: this.clientUid},
  144. dataType: 'json'
  145. })
  146. .done(function (_data) {
  147. console.log(_data);
  148. if(_data.success) {
  149. new Noty({
  150. theme: 'mint',
  151. type: 'success',
  152. text: 'Client added to MCP call queue',
  153. progressBar: false,
  154. timeout: 1500,
  155. }).show();
  156. }
  157. else {
  158. new Noty({
  159. theme: 'mint',
  160. type: 'alert',
  161. text: _data.message,
  162. progressBar: false,
  163. timeout: 3000,
  164. }).show();
  165. }
  166. })
  167. .fail(function (_data) {
  168. console.log(_data);
  169. // alert(_data.message);
  170. });
  171. },
  172. // OT methods
  173. initOpenTok: function() {
  174. /* fake video feed (temp) */
  175. const randomColour = () => {
  176. return Math.round(Math.random() * 255);
  177. };
  178. const canvas = document.createElement('canvas');
  179. canvas.width = 640;
  180. canvas.height = 480;
  181. const ctx = canvas.getContext('2d');
  182. var pos = 100;
  183. window.setInterval(function() {
  184. ctx.clearRect(0, 0, canvas.width, canvas.height);
  185. ctx.font = "20px Georgia";
  186. ctx.fillStyle = `rgb(220, 220, 220)`;
  187. var userType = '<?= $guest? "Client" : "Pro" ?>';
  188. ctx.fillText("Video feed from the " + userType, 20, pos);
  189. pos += 5;
  190. if(pos > canvas.height) pos = 100;
  191. }, 1000);
  192. var self = this;
  193. var apiKey = '<?= env('TOKBOX_API_KEY', '46678902') ?>';
  194. var sessionId = this.otSessionId;
  195. var token = this.selfToken;
  196. // destroy if existing
  197. self.hangUp();
  198. self.otSession = OT.initSession(apiKey, sessionId);
  199. // peer connected
  200. self.otSession.on('streamCreated', function streamCreated(event) {
  201. var subscriberOptions = {
  202. insertMode: 'append',
  203. width: '100%',
  204. height: '100%'
  205. };
  206. var remoteViewElem = 'remote-view-1';
  207. if($('#remote-view-1').attr('data-stream')) {
  208. remoteViewElem = 'remote-view-2';
  209. }
  210. self.otSession.subscribe(event.stream, remoteViewElem, subscriberOptions, self.handleOpenTokError);
  211. $('#' + remoteViewElem).attr('data-stream', event.stream.id);
  212. $('#' + remoteViewElem).attr('data-from', event.stream.connection.data);
  213. @if($guest)
  214. self.pro = true;
  215. @else
  216. self.joinMeetingAsPro();
  217. self.client = true;
  218. @endif
  219. $('.full-view:not(.disconnected-view)')
  220. .removeClass('full-view')
  221. .addClass('thumb-view')
  222. .prependTo('.thumbs');
  223. $('#' + remoteViewElem)
  224. .removeClass('thumb-view')
  225. .removeClass('disconnected-view')
  226. .addClass('full-view')
  227. .prependTo('.main-view');
  228. if(!self.startTime) {
  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. }
  235. });
  236. // peer disconnected
  237. self.otSession.on("streamDestroyed", function(event) {
  238. @if($guest)
  239. var remoteViewElem = $('[data-stream="' + event.stream.id + '"]');
  240. if(remoteViewElem.length) {
  241. remoteViewElem
  242. .removeClass('full-view')
  243. .removeClass('thumb-view')
  244. .addClass('disconnected-view')
  245. .prependTo('.thumbs');
  246. remoteViewElem.attr('data-stream', '');
  247. remoteViewElem.attr('data-from', '');
  248. }
  249. $('#self-view')
  250. .removeClass('thumb-view')
  251. .removeClass('disconnected-view')
  252. .addClass('full-view')
  253. .prependTo('.main-view');
  254. self.pro = false;
  255. @else
  256. $('#self-view')
  257. .removeClass('thumb-view')
  258. .removeClass('disconnected-view')
  259. .removeClass('full-view')
  260. .prependTo('.thumbs');
  261. self.client = false;
  262. @endif
  263. self.started = false;
  264. self.startTime = false;
  265. // if no other parties in call, hang up
  266. if(!$(':not([data-stream=""])').length) {
  267. self.hangUp();
  268. }
  269. });
  270. // self connected
  271. self.otSession.on("sessionConnected", function(event) {
  272. console.log(event);
  273. // call join as assistant/pro here
  274. self.selfUserType = event.target.connection.data.split('|')[1];
  275. });
  276. // self disconnected
  277. self.otSession.on('sessionDisconnected', function sessionDisconnected(event) {
  278. console.log('You were disconnected from the session.', event.reason);
  279. });
  280. // initialize the publisher
  281. var publisherOptions = {
  282. videoSource: canvas.captureStream(1).getVideoTracks()[0],
  283. insertMode: 'append',
  284. width: '100%',
  285. height: '100%',
  286. };
  287. var publisher = OT.initPublisher('self-view', publisherOptions, self.handleOpenTokError);
  288. // Connect to the session
  289. self.otSession.connect(token, function callback(error) {
  290. if (error) {
  291. self.handleOpenTokError(error);
  292. } else {
  293. // If the connection is successful, publish the publisher to the session
  294. self.otSession.publish(publisher, self.handleOpenTokError);
  295. }
  296. });
  297. },
  298. handleOpenTokError: function(e) {
  299. },
  300. getClientCheckinToken: function(_done) {
  301. var self = this;
  302. $.get('/get-client-checkin-token/' + this.clientUid, function(_data) {
  303. console.log(_data);
  304. self.checkInToken = _data.data;
  305. _done();
  306. }, 'json');
  307. },
  308. getOpenTokSessionId: function(_done) {
  309. var self = this;
  310. @if($guest)
  311. $.ajax({
  312. type: 'post',
  313. url: '/api/clientVideoVisit/startVideoVisitAsClient',
  314. headers: {
  315. 'sessionKey': localStorage.sessionKey
  316. },
  317. data: {checkInToken: this.checkInToken},
  318. dataType: 'json'
  319. })
  320. .done(function (_data) {
  321. console.log(_data);
  322. if(_data.success) {
  323. self.otSessionId = _data.data;
  324. _done();
  325. }
  326. else {
  327. alert(_data.message);
  328. }
  329. })
  330. .fail(function (_data) {
  331. console.log(_data);
  332. alert(_data.message);
  333. });
  334. @else
  335. $.get('/pro/get-opentok-session-key/' + self.clientUid, function(_data) {
  336. self.otSessionId = _data.data;
  337. _done();
  338. }, 'json');
  339. @endif
  340. },
  341. joinMeetingAsPro: function() {
  342. var self = this;
  343. $.ajax({
  344. type: 'post',
  345. url: '/api/clientVideoVisit/joinVideoVisitAsMcpPro',
  346. headers: {
  347. 'sessionKey': localStorage.sessionKey
  348. },
  349. data: {uid: self.clientUid},
  350. dataType: 'json'
  351. })
  352. .done(function (_data) {
  353. console.log(_data);
  354. })
  355. .fail(function (_data) {
  356. console.warn(_data);
  357. alert(_data.message);
  358. });
  359. }
  360. },
  361. mounted: function() {
  362. var self = this;
  363. @if($guest)
  364. this.clientUid = localStorage.clientUid;
  365. this.getClientCheckinToken(function() { // get client check-in token
  366. self.getOpenTokSessionId(function() { // get opentok session id
  367. var name = [];
  368. if (localStorage.clientFirstName) name.push(localStorage.clientFirstName);
  369. if (localStorage.clientLastName) name.push(localStorage.clientLastName);
  370. this.selfName = name.join(' ');
  371. $.ajax({
  372. type: 'post',
  373. url: '/api/openTok/getClientToken',
  374. headers: {
  375. 'sessionKey': localStorage.sessionKey
  376. },
  377. data: {
  378. opentokSessionId: self.otSessionId,
  379. name: name.join(' ')
  380. },
  381. dataType: 'json'
  382. })
  383. .done(function (_data) {
  384. console.log(_data);
  385. self.selfToken = _data.data;
  386. self.initOpenTok();
  387. })
  388. .fail(function (_data) {
  389. console.warn(_data);
  390. alert(_data.message);
  391. });
  392. });
  393. });
  394. @endif
  395. $(document).on('click', '.thumbs>div', function() {
  396. $('.full-view')
  397. .removeClass('full-view')
  398. .removeClass('disconnected-view')
  399. .addClass('thumb-view')
  400. .prependTo('.thumbs');
  401. $(this)
  402. .removeClass('thumb-view')
  403. .removeClass('disconnected-view')
  404. .addClass('full-view')
  405. .prependTo('.main-view');
  406. return false;
  407. });
  408. }
  409. });
  410. </script>
  411. @endsection