meet.blade.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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-2">
  16. <button class="btn btn-sm btn-primary px-4 font-weight-bold"
  17. v-on:click.prevent="nextPatient()"
  18. :disabled="client || checkingForNextPatient">Next Patient</button>
  19. </div>
  20. <div v-if="!started && noNextPatient" class="bg-light rounded text-center py-1 font-weight-bold text-sm">@{{ noNextPatient }}</div>
  21. @endif
  22. <div class="">
  23. <div class="py-3 text-center" v-if="started">
  24. <h6 class="text-black font-weight-bold m-0">Call in progress: @{{ timeDisplay() }}</h6>
  25. </div>
  26. @if($guest)
  27. <div class="py-3 text-center" v-if="!pro || !started">
  28. <h6 class="text-black font-weight-bold m-0">Please wait. Your doctor will be with you shortly...</h6>
  29. </div>
  30. @endif
  31. <div class="main-view mx-auto" <?= !$guest ? 'v-show="!!client"' : '' ?>>
  32. <div id="self-view" class="<?= $guest ? 'full-view' : 'disconnected-view' ?>"></div>
  33. <div class="thumbs">
  34. <div id="remote-view-1" class="remote-view disconnected-view"
  35. data-stream="" data-from=""></div>
  36. <div id="remote-view-2" class="remote-view disconnected-view"
  37. data-stream="" data-from=""></div>
  38. </div>
  39. <button class="btn btn-danger rounded-circle hang-up"
  40. v-if="started"
  41. v-on:click.prevent="hangUp()">
  42. <i class="fa fa-phone"></i>
  43. </button>
  44. </div>
  45. </div>
  46. </div>
  47. <script>
  48. new Vue({
  49. el: '#meetComponent',
  50. delimiters: ['@{{', '}}'],
  51. data: {
  52. time: 0,
  53. startTime: 0,
  54. started: false,
  55. client: false,
  56. pro: false,
  57. selfName: '',
  58. selfToken: '',
  59. @if($guest)
  60. clientUid: '',
  61. checkInToken: '',
  62. @endif
  63. otSessionId: '',
  64. @if(!$guest)
  65. checkingForNextPatient: false,
  66. noNextPatient: false,
  67. @endif
  68. otSession: false,
  69. },
  70. methods: {
  71. nextPatient: function() {
  72. var self = this;
  73. this.checkingForNextPatient = true;
  74. $.post('/api/client/getNextClientForVideoVisit', {}, function(_data) {
  75. self.checkingForNextPatient = false;
  76. if(!_data.success) {
  77. self.noNextPatient = _data.message;
  78. window.setTimeout(function() {
  79. self.noNextPatient = false;
  80. }, 2000);
  81. }
  82. else {
  83. // get ot session key from client record
  84. self.client = true;
  85. self.clientUid = _data.data;
  86. self.getOpenTokSessionId(function() {
  87. self.selfName = 'Pro'; // TODO: replace with name of authed pro
  88. $.post('/api/openTok/getClientToken', {
  89. opentokSessionId: self.otSessionId,
  90. name: self.selfName
  91. }, function (_data) {
  92. self.selfToken = _data.data;
  93. self.initOpenTok();
  94. });
  95. });
  96. }
  97. }, 'json');
  98. },
  99. getInitials: function(_name) {
  100. var parts = _name.split(/\s+/g);
  101. parts = parts.map(_part => _part[0]);
  102. return parts.join('');
  103. },
  104. timeDisplay: function() {
  105. var seconds = this.time / 1000,
  106. minutes = parseInt(seconds / 60, 10);
  107. seconds = parseInt(seconds % 60, 10);
  108. return minutes + " min, " + seconds + " sec";
  109. },
  110. hangUp: function() {
  111. if(this.otSession) {
  112. this.otSession.disconnect();
  113. this.otSession = false;
  114. this.otSessionId = '';
  115. this.started = false;
  116. this.startTime = false;
  117. @if(!$guest)
  118. this.client = false;
  119. @else
  120. window.location = '/join';
  121. @endif
  122. }
  123. },
  124. // OT methods
  125. initOpenTok: function() {
  126. /* fake video feed (temp) */
  127. const randomColour = () => {
  128. return Math.round(Math.random() * 255);
  129. };
  130. const canvas = document.createElement('canvas');
  131. canvas.width = 640;
  132. canvas.height = 480;
  133. const ctx = canvas.getContext('2d');
  134. var pos = 100;
  135. window.setInterval(function() {
  136. ctx.clearRect(0, 0, canvas.width, canvas.height);
  137. ctx.font = "20px Georgia";
  138. ctx.fillStyle = `rgb(220, 220, 220)`;
  139. var userType = '<?= $guest? "Client" : "Pro" ?>';
  140. ctx.fillText("Video feed from the " + userType, 20, pos);
  141. pos += 5;
  142. if(pos > canvas.height) pos = 100;
  143. }, 1000);
  144. var self = this;
  145. var apiKey = '<?= env('TOKBOX_API_KEY', '46678902') ?>';
  146. var sessionId = this.otSessionId;
  147. var token = this.selfToken;
  148. // destroy if existing
  149. self.hangUp();
  150. self.otSession = OT.initSession(apiKey, sessionId);
  151. self.otSession.on('streamCreated', function streamCreated(event) {
  152. var subscriberOptions = {
  153. insertMode: 'append',
  154. width: '100%',
  155. height: '100%'
  156. };
  157. var remoteViewElem = 'remote-view-1';
  158. if($('#remote-view-1').attr('data-stream')) {
  159. remoteViewElem = 'remote-view-2';
  160. }
  161. self.otSession.subscribe(event.stream, remoteViewElem, subscriberOptions, self.handleOpenTokError);
  162. $('#' + remoteViewElem).attr('data-stream', event.stream.id);
  163. $('#' + remoteViewElem).attr('data-from', event.stream.connection.data);
  164. @if($guest)
  165. self.pro = true;
  166. @else
  167. self.joinMeetingAsPro();
  168. self.client = true;
  169. @endif
  170. $('#self-view')
  171. .removeClass('full-view')
  172. .removeClass('disconnected-view')
  173. .addClass('thumb-view')
  174. .prependTo('.thumbs');
  175. $('#' + remoteViewElem)
  176. .removeClass('thumb-view')
  177. .removeClass('disconnected-view')
  178. .addClass('full-view')
  179. .prependTo('.main-view');
  180. if(!self.startTime) {
  181. self.startTime = new Date().getTime();
  182. window.setInterval(function() {
  183. self.time = new Date().getTime() - self.startTime;
  184. }, 1000);
  185. self.started = true;
  186. }
  187. });
  188. self.otSession.on("streamDestroyed", function(event) {
  189. @if($guest)
  190. var remoteViewElem = $('[data-stream="' + event.stream.id + '"]');
  191. if(remoteViewElem.length) {
  192. remoteViewElem
  193. .removeClass('full-view')
  194. .removeClass('thumb-view')
  195. .addClass('disconnected-view')
  196. .prependTo('.thumbs');
  197. remoteViewElem.attr('data-stream', '');
  198. remoteViewElem.attr('data-from', '');
  199. }
  200. $('#self-view')
  201. .removeClass('thumb-view')
  202. .removeClass('disconnected-view')
  203. .addClass('full-view')
  204. .prependTo('.main-view');
  205. self.pro = false;
  206. @else
  207. $('#self-view')
  208. .removeClass('thumb-view')
  209. .removeClass('disconnected-view')
  210. .removeClass('full-view')
  211. .prependTo('.thumbs');
  212. self.client = false;
  213. @endif
  214. self.started = false;
  215. self.startTime = false;
  216. // if no other parties in call, hang up
  217. if(!$(':not([data-stream=""])').length) {
  218. self.hangUp();
  219. }
  220. });
  221. self.otSession.on('sessionDisconnected', function sessionDisconnected(event) {
  222. console.log('You were disconnected from the session.', event.reason);
  223. });
  224. // initialize the publisher
  225. var publisherOptions = {
  226. videoSource: canvas.captureStream(1).getVideoTracks()[0],
  227. insertMode: 'append',
  228. width: '100%',
  229. height: '100%',
  230. };
  231. var publisher = OT.initPublisher('self-view', publisherOptions, self.handleOpenTokError);
  232. // Connect to the session
  233. self.otSession.connect(token, function callback(error) {
  234. if (error) {
  235. self.handleOpenTokError(error);
  236. } else {
  237. // If the connection is successful, publish the publisher to the session
  238. self.otSession.publish(publisher, self.handleOpenTokError);
  239. }
  240. });
  241. },
  242. handleOpenTokError: function(e) {
  243. },
  244. getClientCheckinToken: function(_done) {
  245. var self = this;
  246. $.get('/get-client-checkin-token/' + this.clientUid, function(_data) {
  247. console.log(_data);
  248. self.checkInToken = _data.data;
  249. _done();
  250. }, 'json');
  251. },
  252. getOpenTokSessionId: function(_done) {
  253. var self = this;
  254. @if($guest)
  255. $.ajax({
  256. type: 'post',
  257. url: '/api/clientVideoVisit/startVideoVisitAsClient',
  258. headers: {
  259. 'sessionKey': localStorage.sessionKey
  260. },
  261. data: {checkInToken: this.checkInToken},
  262. dataType: 'json'
  263. })
  264. .done(function (_data) {
  265. console.log(_data);
  266. if(_data.success) {
  267. self.otSessionId = _data.data;
  268. _done();
  269. }
  270. else {
  271. alert(_data.message);
  272. }
  273. })
  274. .fail(function (_data) {
  275. console.log(_data);
  276. alert(_data.message);
  277. });
  278. @else
  279. $.get('/pro/get-opentok-session-key/' + self.clientUid, function(_data) {
  280. self.otSessionId = _data.data;
  281. _done();
  282. }, 'json');
  283. @endif
  284. },
  285. joinMeetingAsPro: function() {
  286. var self = this;
  287. $.ajax({
  288. type: 'post',
  289. url: '/api/clientVideoVisit/joinVideoVisitAsMcpPro',
  290. headers: {
  291. 'sessionKey': localStorage.sessionKey
  292. },
  293. data: {uid: self.clientUid},
  294. dataType: 'json'
  295. })
  296. .done(function (_data) {
  297. console.log(_data);
  298. })
  299. .fail(function (_data) {
  300. console.warn(_data);
  301. alert(_data.message);
  302. });
  303. }
  304. },
  305. mounted: function() {
  306. var self = this;
  307. @if($guest)
  308. this.clientUid = localStorage.clientUid;
  309. this.getClientCheckinToken(function() { // get client check-in token
  310. self.getOpenTokSessionId(function() { // get opentok session id
  311. var name = [];
  312. if (localStorage.clientFirstName) name.push(localStorage.clientFirstName);
  313. if (localStorage.clientLastName) name.push(localStorage.clientLastName);
  314. this.selfName = name.join(' ');
  315. $.post('/api/openTok/getClientToken', {
  316. opentokSessionId: self.otSessionId,
  317. name: name.join(' ')
  318. }, function (_data) {
  319. self.selfToken = _data.data;
  320. self.initOpenTok();
  321. });
  322. });
  323. });
  324. @endif
  325. }
  326. });
  327. </script>
  328. @endsection