|
@@ -1,442 +0,0 @@
|
|
-<!DOCTYPE html>
|
|
|
|
-<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
|
|
-<head>
|
|
|
|
- <meta charset="utf-8">
|
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
|
|
- <link href="https://fonts.googleapis.com/css?family=Nunito:200,600,700" rel="stylesheet">
|
|
|
|
- <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
|
|
|
- <link href="/css/app.css" rel="stylesheet">
|
|
|
|
- <link rel="stylesheet" href="/fontawesome-free/css/all.min.css">
|
|
|
|
- <link href="/css/meeting.css" rel="stylesheet">
|
|
|
|
- <link href="/css/style.css" rel="stylesheet">
|
|
|
|
- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
|
|
|
- <script defer src=//download.agora.io/sdk/web/AgoraRTC_N-4.1.0.js></script>
|
|
|
|
-</head>
|
|
|
|
-
|
|
|
|
-<body class="p-0 m-0">
|
|
|
|
-
|
|
|
|
- <div id="proCallComponent">
|
|
|
|
-
|
|
|
|
- @if($client)
|
|
|
|
- <div v-show="videoActive" class="text-center py-2 border-bottom font-weight-normal mcp-theme-1">
|
|
|
|
- <i class="fa fa-user-injured small mr-2"></i><a href="#" onclick="return window.top.openInLHS('/patients/view/{{$client->uid}}')">
|
|
|
|
- <span class="font-weight-bold">{{ $client->displayName() }}</span>
|
|
|
|
- </a>
|
|
|
|
- </div>
|
|
|
|
- @endif
|
|
|
|
-
|
|
|
|
- <div class="py-2 d-flex align-items-center justify-content-center border-bottom">
|
|
|
|
- <a href="#" v-if="ringer" v-on:click.prevent="toggleRinger()"
|
|
|
|
- class="font-weight-bold btn btn-sm btn-success">
|
|
|
|
- Ringer
|
|
|
|
- <i class="ml-1 fa fa-volume-up"></i>
|
|
|
|
- </a>
|
|
|
|
- <a href="#" v-if="!ringer" v-on:click.prevent="toggleRinger()"
|
|
|
|
- class="font-weight-bold btn btn-sm btn-warning">
|
|
|
|
- Ringer
|
|
|
|
- <i class="ml-1 fa fa-volume-mute"></i>
|
|
|
|
- </a>
|
|
|
|
- </div>
|
|
|
|
-
|
|
|
|
- <div class="" v-show="videoActive">
|
|
|
|
- <div class="py-3 text-center" v-if="started">
|
|
|
|
- <h6 class="text-black font-weight-bold m-0">Call in progress: @{{ timeDisplay() }}</h6>
|
|
|
|
- </div>
|
|
|
|
- <div class="py-3 text-center" v-if="noOneElseInCall">
|
|
|
|
- <h6 class="text-black font-weight-bold m-0">No other participants in the call.
|
|
|
|
- <a href="#" class="text-danger font-weight-bold" v-on:click.prevent="hangUp()">Hang up</a>
|
|
|
|
- </h6>
|
|
|
|
- </div>
|
|
|
|
- <div class="main-view mx-auto">
|
|
|
|
- <div id="self-view" class="full-view" data-name="{{ $pro->name_display }}" data-type="PRO"></div>
|
|
|
|
- <div class="thumbs">
|
|
|
|
-
|
|
|
|
- </div>
|
|
|
|
- <button class="btn btn-danger rounded-circle hang-up"
|
|
|
|
- v-if="started"
|
|
|
|
- title="Leave Call"
|
|
|
|
- v-on:click.prevent="hangUp()">
|
|
|
|
- <i class="fa fa-phone"></i>
|
|
|
|
- </button>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
-
|
|
|
|
- @if($client)
|
|
|
|
- <div class="" v-show="!videoActive && client">
|
|
|
|
- <button class="btn btn-sm btn-primary font-weight-bold mx-auto mt-4 d-block"
|
|
|
|
- v-on:click.prevent="connect()">
|
|
|
|
- Start video call with {{ $client->displayName() }}
|
|
|
|
- </button>
|
|
|
|
- </div>
|
|
|
|
- @endif
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- </div>
|
|
|
|
-
|
|
|
|
- <div class="border-top patient-queue mcp-theme-1" id="queueComponent">
|
|
|
|
- <div class="bg-secondary text-white font-weight-bold text-center py-1" v-if="items.length > 0">
|
|
|
|
- @{{ items.length }} patient@{{ items.length > 1 ? 's' : '' }} in the queue
|
|
|
|
- </div>
|
|
|
|
- <div class="bg-secondary text-white font-weight-bold text-center py-1" v-if="items.length === 0">
|
|
|
|
- No patients in the queue
|
|
|
|
- </div>
|
|
|
|
- <div v-if="items && items.length" class="d-flex align-items-center my-1">
|
|
|
|
- <div v-for="item in items">
|
|
|
|
- <div class="queue-item border border-primary rounded mx-1" :title="item.name">
|
|
|
|
- <div class="patient-avatar mb-1 text-dark">@{{ item.initials }}</div>
|
|
|
|
- <div class="font-weight-bold small text-nowrap text-ellipsis">@{{ item.name }}</div>
|
|
|
|
- </div>
|
|
|
|
- <button class="btn btn-sm btn-primary mt-1 text-white font-weight-bold py-0 mx-auto d-block"
|
|
|
|
- v-on:click.prevent="claim(item.clientUid)">Claim</button>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
-
|
|
|
|
- <script>
|
|
|
|
- (function () {
|
|
|
|
- new Vue({
|
|
|
|
- el: '#proCallComponent',
|
|
|
|
- delimiters: ['@{{', '}}'],
|
|
|
|
- data: {
|
|
|
|
-
|
|
|
|
- appId: '{{ env('AGORA_APPID') }}',
|
|
|
|
- channel: '{{ $client ? $client->uid : '' }}',
|
|
|
|
- uid: '{{ $session->id + 1000000 }}',
|
|
|
|
-
|
|
|
|
- time: 0,
|
|
|
|
- startTime: 0,
|
|
|
|
- started: false,
|
|
|
|
- client: false,
|
|
|
|
- pro: false,
|
|
|
|
-
|
|
|
|
- selfName: '',
|
|
|
|
- selfToken: '',
|
|
|
|
-
|
|
|
|
- clientUid: '',
|
|
|
|
-
|
|
|
|
- otSessionId: '',
|
|
|
|
-
|
|
|
|
- publisher: false,
|
|
|
|
-
|
|
|
|
- otSession: false,
|
|
|
|
-
|
|
|
|
- selfUserType: 'PRO',
|
|
|
|
- noOneElseInCall: true,
|
|
|
|
- patientInQueue: false,
|
|
|
|
-
|
|
|
|
- videoActive: false,
|
|
|
|
-
|
|
|
|
- heartbeatTimer: false,
|
|
|
|
-
|
|
|
|
- ringer: {{ $pro->is_ring_on ? 'true' : 'false' }},
|
|
|
|
- },
|
|
|
|
- methods: {
|
|
|
|
- toggleRinger: function () {
|
|
|
|
- let self = this, endPoint = this.ringer ? 'turnOffRing' : 'turnOnRing';
|
|
|
|
- $.post('/api/pro/' + endPoint, function (_data) {
|
|
|
|
- if (_data && _data.success) {
|
|
|
|
- self.ringer = !self.ringer;
|
|
|
|
- } else {
|
|
|
|
- if (_data.message) {
|
|
|
|
- toastr.error(_data.message);
|
|
|
|
- } else {
|
|
|
|
- toastr.error('Unable to change ringer status');
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }, 'json');
|
|
|
|
- },
|
|
|
|
- connect: function () {
|
|
|
|
- var self = this;
|
|
|
|
- self.selfName = '{{ $pro->name_display }}';
|
|
|
|
- $.get('/api/agora/getClientToken', {
|
|
|
|
- clientUid: self.clientUid,
|
|
|
|
- }, function (_data) {
|
|
|
|
- console.log(_data);
|
|
|
|
- self.selfToken = _data.data;
|
|
|
|
- self.initAgora();
|
|
|
|
- });
|
|
|
|
- },
|
|
|
|
- timeDisplay: function () {
|
|
|
|
- var seconds = this.time / 1000,
|
|
|
|
- minutes = parseInt(seconds / 60, 10);
|
|
|
|
- seconds = parseInt(seconds % 60, 10);
|
|
|
|
- return minutes + " min, " + seconds + " sec";
|
|
|
|
- },
|
|
|
|
- hangUp: function () {
|
|
|
|
- var self = this;
|
|
|
|
- if (this.otSession) {
|
|
|
|
- try {
|
|
|
|
- this.otSession.unpublish(this.publisher);
|
|
|
|
- this.otSession.disconnect();
|
|
|
|
- } catch (e) {
|
|
|
|
- console.log('Was already disconnected.');
|
|
|
|
- }
|
|
|
|
- this.otSession = false;
|
|
|
|
- this.otSessionId = '';
|
|
|
|
- this.started = false;
|
|
|
|
- this.startTime = false;
|
|
|
|
- this.videoActive = false;
|
|
|
|
- if(self.publisher){
|
|
|
|
- self.publisher.destroy();
|
|
|
|
- }
|
|
|
|
- window.top.hideRHS();
|
|
|
|
- // this.client = false;
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- initAgora: function () {
|
|
|
|
-
|
|
|
|
- /* fake video feed (temp) */
|
|
|
|
- const randomColour = () => {
|
|
|
|
- return Math.round(Math.random() * 255);
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const canvas = document.createElement('canvas');
|
|
|
|
- canvas.width = 640;
|
|
|
|
- canvas.height = 480;
|
|
|
|
- const ctx = canvas.getContext('2d');
|
|
|
|
- var pos = 100;
|
|
|
|
- window.setInterval(function () {
|
|
|
|
- ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
- ctx.font = "20px Georgia";
|
|
|
|
- ctx.fillStyle = `rgb(220, 220, 220)`;
|
|
|
|
- ctx.fillText("Video feed from {{ $pro->name_display }}", 20, pos);
|
|
|
|
- pos += 5;
|
|
|
|
- if (pos > canvas.height) pos = 100;
|
|
|
|
- }, 1000);
|
|
|
|
-
|
|
|
|
- let self = this;
|
|
|
|
-
|
|
|
|
- async function _initAgora(){
|
|
|
|
-
|
|
|
|
- const client = AgoraRTC.createClient({mode:'rtc', codec:'h264'})
|
|
|
|
- let camera, mic
|
|
|
|
- try { mic = await AgoraRTC.createMicrophoneAudioTrack() } catch {
|
|
|
|
- console.log('ALIX: error in getting mic');
|
|
|
|
- }
|
|
|
|
- try { camera = await AgoraRTC.createCameraVideoTrack() } catch {
|
|
|
|
- console.log('ALIX: error in getting camera');
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // testing
|
|
|
|
- try { camera = await AgoraRTC.createScreenVideoTrack() } catch { }
|
|
|
|
-
|
|
|
|
- if (!mic && !camera){
|
|
|
|
- alert('Do you have camera/mic? Unable to hear or see you.')
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Add myself to the page.
|
|
|
|
- // createUserEl(agora.uid)
|
|
|
|
- // addUserVideo(agora.uid, camera)
|
|
|
|
- if(camera) {
|
|
|
|
- camera.play($('#self-view')[0]);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // events
|
|
|
|
- client.on('user-joined', user => {
|
|
|
|
-
|
|
|
|
- // add a div for remove view
|
|
|
|
- $('[data-stream="' + user.uid + '"]').remove();
|
|
|
|
- var remoteViewID = 'remote-view-' + user.uid;
|
|
|
|
- var remoteElem = $('<div id="' + remoteViewID + '" class="remote-view thumb-view" data-stream="' + user.uid + '"></div>');
|
|
|
|
- remoteElem.appendTo('.thumbs');
|
|
|
|
-
|
|
|
|
- // TODO
|
|
|
|
- // if (connectionData.type === 'CLIENT') {
|
|
|
|
- // self.client = true;
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- if (!self.startTime) {
|
|
|
|
- self.startTime = new Date().getTime();
|
|
|
|
- window.setInterval(function () {
|
|
|
|
- self.time = new Date().getTime() - self.startTime;
|
|
|
|
- }, 1000);
|
|
|
|
- self.started = true;
|
|
|
|
- }
|
|
|
|
- self.activateParty(user.uid);
|
|
|
|
- self.noOneElseInCall = false;
|
|
|
|
- })
|
|
|
|
- client.on('user-left', user => {
|
|
|
|
-
|
|
|
|
- if ($('.full-view[data-stream="' + user.uid + '"]').length) {
|
|
|
|
- var allThumbs = $('.thumbs [data-stream]:not([data-stream=""]):not(.disconnected-view):visible');
|
|
|
|
- if (allThumbs.length) {
|
|
|
|
- $('.thumbs [data-stream]:not([data-stream=""])').each(function () {
|
|
|
|
- if ($(this).attr('data-stream') !== user.uid) {
|
|
|
|
- self.activateParty($(this).attr('data-stream'));
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- } else {
|
|
|
|
- self.noOneElseInCall = true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- $('[data-stream="' + user.uid + '"]').remove();
|
|
|
|
-
|
|
|
|
- // TODO:
|
|
|
|
- // var connectionData = JSON.parse(data);
|
|
|
|
- // if (connectionData.type === 'CLIENT') {
|
|
|
|
- // self.client = false;
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // if no other parties in call, hang up
|
|
|
|
- if (!$('[data-stream]:not([data-stream="' + {{ $session->id }} + '"])').length) {
|
|
|
|
- console.warn('No other parties in the call!');
|
|
|
|
- self.startTime = 0;
|
|
|
|
- self.started = false;
|
|
|
|
- self.noOneElseInCall = true;
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- client.on('user-published', async function(user, mediaType){
|
|
|
|
- await client.subscribe(user, mediaType)
|
|
|
|
- mediaType === 'audio'
|
|
|
|
- ? user.audioTrack.play()
|
|
|
|
- : user.videoTrack.play($('[data-stream="' + user.uid + '"]')[0]);
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- await client.join(self.appId, self.channel, self.selfToken, self.uid)
|
|
|
|
- await client.publish([mic, camera].filter(Boolean))
|
|
|
|
-
|
|
|
|
- // assume connected by this point, notify backend & show self video
|
|
|
|
- if (mic || camera) {
|
|
|
|
- self.joinMeetingAsPro(self.selfUserType);
|
|
|
|
- $('#self-view').attr('data-type', 'PRO').show();
|
|
|
|
- self.activateParty('self');
|
|
|
|
- self.videoActive = true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- _initAgora();
|
|
|
|
-
|
|
|
|
- // TODO:
|
|
|
|
- // self disconnected
|
|
|
|
- /*self.otSession.on('sessionDisconnected', function sessionDisconnected(event) {
|
|
|
|
- console.log('You were disconnected from the session.', event.reason);
|
|
|
|
-
|
|
|
|
- // turn pro video off
|
|
|
|
- $.post('/api/clientVideoVisit/turnProVideoOff', {}, function (_data) {
|
|
|
|
- console.log(_data);
|
|
|
|
-
|
|
|
|
- // stop heart beat
|
|
|
|
- if (self.heartbeatTimer) {
|
|
|
|
- window.clearInterval(self.heartbeatTimer);
|
|
|
|
- self.heartbeatTimer = false;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
- */
|
|
|
|
- },
|
|
|
|
- joinMeetingAsPro: function (_type) {
|
|
|
|
- var self = this;
|
|
|
|
- $.ajax({
|
|
|
|
- type: 'post',
|
|
|
|
- url: '/api/clientVideoVisit/joinVideoVisitAsPro',
|
|
|
|
- headers: {
|
|
|
|
- 'sessionKey': '{{ request()->cookie('sessionKey') }}'
|
|
|
|
- },
|
|
|
|
- data: {uid: self.clientUid},
|
|
|
|
- dataType: 'json'
|
|
|
|
- })
|
|
|
|
- .done(function (_data) {
|
|
|
|
- console.log(_data);
|
|
|
|
-
|
|
|
|
- // navigate to this patient on LHS
|
|
|
|
- window.top.openInLHS('/patients/view/' + self.clientUid, true, false);
|
|
|
|
-
|
|
|
|
- })
|
|
|
|
- .fail(function (_data) {
|
|
|
|
- console.warn(_data);
|
|
|
|
- alert(_data.message);
|
|
|
|
- });
|
|
|
|
- },
|
|
|
|
- activateParty: function (_stream = 'self') {
|
|
|
|
- var current = $('.full-view');
|
|
|
|
- if (current.attr('data-stream') === _stream) return;
|
|
|
|
- current.removeClass('full-view').addClass('thumb-view');
|
|
|
|
- if (current.attr('data-type') === 'CLIENT') {
|
|
|
|
- current.prependTo('.thumbs');
|
|
|
|
- } else {
|
|
|
|
- current.appendTo('.thumbs');
|
|
|
|
- }
|
|
|
|
- if (_stream === 'self') {
|
|
|
|
- $('#self-view')
|
|
|
|
- .removeClass('thumb-view')
|
|
|
|
- .removeClass('disconnected-view')
|
|
|
|
- .addClass('full-view')
|
|
|
|
- .prependTo('.main-view');
|
|
|
|
- } else {
|
|
|
|
- $('div[data-stream="' + _stream + '"]')
|
|
|
|
- .removeClass('thumb-view')
|
|
|
|
- .removeClass('disconnected-view')
|
|
|
|
- .addClass('full-view')
|
|
|
|
- .prependTo('.main-view');
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- mounted: function () {
|
|
|
|
-
|
|
|
|
- var self = this;
|
|
|
|
-
|
|
|
|
- $(document).on('click', '.thumbs>div[data-stream]', function () {
|
|
|
|
- self.activateParty($(this).attr('data-stream'));
|
|
|
|
- return false;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- window.onbeforeunload = function () {
|
|
|
|
- if (self.started) {
|
|
|
|
- return "A call is in progress";
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- @if(isset($client))
|
|
|
|
- self.client = true;
|
|
|
|
- self.clientUid = '{{ $client->uid }}';
|
|
|
|
- self.videoActive = false;
|
|
|
|
- @endif
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- new Vue({
|
|
|
|
- el: '#queueComponent',
|
|
|
|
- data: {
|
|
|
|
- items: []
|
|
|
|
- },
|
|
|
|
- mounted: function() {
|
|
|
|
- let self = this;
|
|
|
|
- this.refresh();
|
|
|
|
- window.setInterval(function() {
|
|
|
|
- self.refresh();
|
|
|
|
- }, 15000); // once in 15 seconds
|
|
|
|
- },
|
|
|
|
- methods: {
|
|
|
|
- refresh: function() {
|
|
|
|
- let self = this;
|
|
|
|
- $.get('/patients-in-queue', function(_data) {
|
|
|
|
- self.items = _data;
|
|
|
|
- }, 'json');
|
|
|
|
- },
|
|
|
|
- claim: function(_uid) {
|
|
|
|
- $.post('/api/mcpRequest/claim', {clientUid: _uid}, function(_data) {
|
|
|
|
- if(_data && _data.success) {
|
|
|
|
- // open patient in LHS
|
|
|
|
- window.top.openInLHS('/patients/view/' + _uid);
|
|
|
|
- // open patient video in RHS
|
|
|
|
- window.top.openInRHS('/pro/meet/' + _uid);
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- if (_data.message) {
|
|
|
|
- window.top.toastr.error(_data.message);
|
|
|
|
- } else {
|
|
|
|
- window.top.toastr.error('Unable to claim the patient');
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }, 'json');
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- })();
|
|
|
|
- </script>
|
|
|
|
-
|
|
|
|
-</body>
|
|
|
|
-</html>
|
|
|