Преглед на файлове

Switch from agora to OT

Vijayakrishnan преди 4 години
родител
ревизия
36a03700e9
променени са 3 файла, в които са добавени 707 реда и са изтрити 122 реда
  1. 386 0
      resources/views/app/video/call-agora.blade.php
  2. 0 0
      resources/views/app/video/call-ot.blade.php
  3. 321 122
      resources/views/app/video/call.blade.php

+ 386 - 0
resources/views/app/video/call-agora.blade.php

@@ -0,0 +1,386 @@
+<!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-stream="self" data-name="You" 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: {
+
+                    agoraClient: null,
+                    appId: '{{ config('app.agora_appid') }}',
+                    channel: '{{ $client ? $client->uid : '' }}',
+                    uid: '{{ $session->id + 1000000 }}',
+
+                    time: 0,
+                    startTime: 0,
+                    started: false,
+                    client: false,
+                    pro: false,
+
+                    selfName: '',
+                    selfToken: '',
+
+                    clientUid: '',
+
+                    selfUserType: 'PRO',
+                    noOneElseInCall: true,
+                    patientInQueue: false,
+
+                    videoActive: false,
+
+                    heartbeatTimer: false,
+
+                    ringer: {{ $pro->is_ring_on ? 'true' : 'false' }},
+                },
+                methods: {
+                    resolveParticipantNames: function() {
+                        $('[data-stream]:not([data-name])').each(function() {
+                            let elem = this;
+                            $.post('/pro/meet/get-participant-info', {
+                                _token: '{{ csrf_token() }}',
+                                uid: $(elem).attr('data-stream')
+                            }, function(_data) {
+                                $(elem).attr('data-type', _data.type);
+                                $(elem).attr('data-name', _data.name);
+                            }, 'json');
+                        });
+                    },
+                    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;
+                        async function _leave() {
+                            if(self.agoraClient) {
+                                await self.agoraClient.leave();
+                                window.top.hideRHS();
+                                window.location.reload();
+                            }
+                        }
+                        _leave();
+                    },
+                    initAgora: function () {
+
+                        let self = this;
+
+                        async function _initAgora(){
+
+                            self.agoraClient = 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
+                            @if(config('app.agora_mode') === 'screen')
+                                try { camera = await AgoraRTC.createScreenVideoTrack() } catch { }
+                            @endif
+
+                            if (!mic && !camera){
+                                alert('Do you have camera/mic? Unable to hear or see you.')
+                                return
+                            }
+
+                            // Add myself to the page.
+                            if(camera) {
+                                camera.play($('#self-view')[0]);
+                            }
+
+                            // events
+                            self.agoraClient.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');
+
+                                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;
+                                self.resolveParticipantNames();
+                            })
+                            self.agoraClient.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();
+
+                                // 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;
+                                }
+                            })
+                            self.agoraClient.on('user-published', async function(user, mediaType){
+                                await self.agoraClient.subscribe(user, mediaType)
+                                mediaType === 'audio'
+                                    ? user.audioTrack.play()
+                                    : user.videoTrack.play($('[data-stream="' + user.uid + '"]')[0]);
+                            })
+
+                            await self.agoraClient.join(self.appId, self.channel, self.selfToken, self.uid)
+                            await self.agoraClient.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();
+
+                    },
+                    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;
+                    });
+
+                    @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>

+ 0 - 0
resources/views/app/video/call-backup.blade.php → resources/views/app/video/call-ot.blade.php


+ 321 - 122
resources/views/app/video/call.blade.php

@@ -11,7 +11,7 @@
     <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>
+    <script src="https://static.opentok.com/v2/js/opentok.js"></script>
 </head>
 
 <body class="p-0 m-0">
@@ -26,6 +26,20 @@
         </div>
         @endif
 
+        {{--
+        <div v-show="!videoActive" class="align-items-center justify-content-center py-3 border-bottom" style="display: flex;">
+            <button class="btn btn-sm btn-primary px-4 font-weight-bold stag-primary-bg stag-primary-border"
+                    v-on:click.prevent="nextPatient()"
+                    :disabled="client || checkingForNextPatient || started || !(patientInQueue && !started)">Next Patient</button>
+            <span v-if="patientInQueue && !started" class="text-success text-sm ml-3 small">
+                <i class="fa fa-circle"></i>
+            </span>
+            <span v-if="!patientInQueue && !started" class="text-secondary text-sm ml-3 small cancelled-item">
+                <i class="fa fa-circle"></i>
+            </span>
+        </div>
+        --}}
+
         <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">
@@ -39,6 +53,8 @@
             </a>
         </div>
 
+        <div v-if="!started && noNextPatient" class="bg-light rounded text-center py-1 font-weight-bold text-sm my-3 mx-3">@{{ noNextPatient }}</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>
@@ -49,7 +65,7 @@
                 </h6>
             </div>
             <div class="main-view mx-auto">
-                <div id="self-view" class="full-view" data-stream="self" data-name="You" data-type="PRO"></div>
+                <div id="self-view" class="full-view" data-name="{{ $pro->name_display }}" data-type="PRO"></div>
                 <div class="thumbs">
 
                 </div>
@@ -62,15 +78,6 @@
             </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">
@@ -98,12 +105,6 @@
                 el: '#proCallComponent',
                 delimiters: ['@{{', '}}'],
                 data: {
-
-                    agoraClient: null,
-                    appId: '{{ config('app.agora_appid') }}',
-                    channel: '{{ $client ? $client->uid : '' }}',
-                    uid: '{{ $session->id + 1000000 }}',
-
                     time: 0,
                     startTime: 0,
                     started: false,
@@ -115,7 +116,17 @@
 
                     clientUid: '',
 
+                    otSessionId: '',
+
+                    publisher: false,
+
+                    checkingForNextPatient: false,
+                    noNextPatient: false,
+
+                    otSession: false,
+
                     selfUserType: 'PRO',
+                    selfStreamId: '',
                     noOneElseInCall: true,
                     patientInQueue: false,
 
@@ -126,18 +137,6 @@
                     ringer: {{ $pro->is_ring_on ? 'true' : 'false' }},
                 },
                 methods: {
-                    resolveParticipantNames: function() {
-                        $('[data-stream]:not([data-name])').each(function() {
-                            let elem = this;
-                            $.post('/pro/meet/get-participant-info', {
-                                _token: '{{ csrf_token() }}',
-                                uid: $(elem).attr('data-stream')
-                            }, function(_data) {
-                                $(elem).attr('data-type', _data.type);
-                                $(elem).attr('data-name', _data.name);
-                            }, 'json');
-                        });
-                    },
                     toggleRinger: function () {
                         let self = this, endPoint = this.ringer ? 'turnOffRing' : 'turnOnRing';
                         $.post('/api/pro/' + endPoint, function (_data) {
@@ -152,15 +151,50 @@
                             }
                         }, 'json');
                     },
-                    connect: function () {
+                    pollForNextPatient: function () {
+                        if (!this.started) {
+                            this.nextPatient(true);
+                        }
+                    },
+                    nextPatient: function (_pollOnly = false) {
                         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();
+                        if (!_pollOnly) this.checkingForNextPatient = true;
+                        $.post('/api/client/getNextClientForVideoVisit', {}, function (_data) {
+                            if (_pollOnly) {
+                                self.patientInQueue = _data.success;
+                            } else {
+                                self.checkingForNextPatient = false;
+                                if (!_data.success) {
+                                    self.noNextPatient = _data.message;
+                                    window.setTimeout(function () {
+                                        self.noNextPatient = false;
+                                    }, 2000);
+                                } else {
+                                    // get ot session key from client record
+                                    self.client = true;
+                                    self.clientUid = _data.data;
+                                    self.videoActive = true;
+                                    self.startOpenTokSession();
+                                }
+                            }
+                        }, 'json');
+                    },
+                    startOpenTokSession: function () {
+                        var self = this;
+                        self.getOpenTokSessionId(function () {
+                            self.selfName = '{{ $pro->name_display  }}';
+                            $.post('/api/openTok/getClientToken', {
+                                opentokSessionId: self.otSessionId,
+                                data: JSON.stringify({
+                                    uid: '{{ $pro->uid  }}',
+                                    name: self.selfName,
+                                    type: 'PRO'
+                                })
+                            }, function (_data) {
+                                console.log(_data);
+                                self.selfToken = _data.data;
+                                self.initOpenTok();
+                            });
                         });
                     },
                     timeDisplay: function () {
@@ -171,113 +205,264 @@
                     },
                     hangUp: function () {
                         var self = this;
-                        async function _leave() {
-                            if(self.agoraClient) {
-                                await self.agoraClient.leave();
-                                window.top.hideRHS();
-                                window.location.reload();
+                        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;
                         }
-                        _leave();
                     },
-                    initAgora: function () {
+                    initOpenTok: 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;
+                        var self = this;
+
+                        var apiKey = '<?= env('TOKBOX_API_KEY', '46678902') ?>';
+                        var sessionId = this.otSessionId;
+                        var token = this.selfToken;
+
+                        // destroy if existing
+                        // self.hangUp();
+
+                        self.otSession = OT.initSession(apiKey, sessionId);
+
+                        // peer connected
+                        self.otSession.on('streamCreated', function streamCreated(event) {
+                            console.log('streamCreated', arguments);
+                            var subscriberOptions = {
+                                insertMode: 'append',
+                                width: '100%',
+                                height: '100%'
+                            };
+
+                            var connectionData = JSON.parse(event.stream.connection.data);
 
-                        async function _initAgora(){
+                            // add a div for remove view
+                            var remoteViewID = 'remote-view-' + event.stream.id;
+                            var remoteElem = $('<div id="' + remoteViewID + '" class="remote-view thumb-view" ' +
+                                'data-stream="' + event.stream.id + '" ' +
+                                'data-connection-data="' + event.stream.connection.data + '" ' +
+                                'data-name="' + connectionData.name + '" ' +
+                                'data-type="' + connectionData.type + '"></div>');
+                            remoteElem.appendTo('.thumbs');
 
-                            self.agoraClient = AgoraRTC.createClient({mode:'rtc', codec:'h264'})
-                            let camera, mic
-                            try { mic = await AgoraRTC.createMicrophoneAudioTrack() } catch {
-                                console.log('ALIX: error in getting mic');
+                            self.otSession.subscribe(event.stream, remoteViewID, subscriberOptions, self.handleOpenTokError);
+
+                            if (connectionData.type === 'CLIENT') {
+                                self.client = true;
                             }
-                            try { camera = await AgoraRTC.createCameraVideoTrack() } catch {
-                                console.log('ALIX: error in getting camera');
+
+                            if (!self.startTime) {
+                                self.startTime = new Date().getTime();
+                                window.setInterval(function () {
+                                    self.time = new Date().getTime() - self.startTime;
+                                }, 1000);
+                                self.started = true;
                             }
 
-                            // testing
-                            @if(config('app.agora_mode') === 'screen')
-                                try { camera = await AgoraRTC.createScreenVideoTrack() } catch { }
-                            @endif
+                            self.activateParty(event.stream.id);
+
+                            self.noOneElseInCall = false;
+                        });
 
-                            if (!mic && !camera){
-                                alert('Do you have camera/mic? Unable to hear or see you.')
-                                return
+                        // peer disconnected
+                        self.otSession.on("streamDestroyed", function (event) {
+                            onPeerDisconnection(event, event.stream.connection.data);
+                        });
+                        // self.otSession.on("connectionDestroyed", function(event) {
+                        //     debugger;
+                        //     console.log('connectionDestroyed from ' + event.connection.data);
+                        //     onPeerDisconnection(event, event.connection.data);
+                        // });
+
+                        self.otSession.on("connectionCreated", function (event) {
+                            console.log('connectionCreated');
+                            console.log(event);
+                        });
+
+                        function onPeerDisconnection(event, data) {
+
+                            if (event.stream && $('.full-view[data-stream="' + event.stream.id + '"]').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') !== event.stream.id) {
+                                            self.activateParty($(this).attr('data-stream'));
+                                            return false;
+                                        }
+                                    });
+                                } else {
+                                    self.noOneElseInCall = true;
+                                }
                             }
 
-                            // Add myself to the page.
-                            if(camera) {
-                                camera.play($('#self-view')[0]);
+                            if (event.stream) {
+                                var remoteViewElem = $('[data-stream="' + event.stream.id + '"]');
+                                remoteViewElem.remove();
+                                // if(remoteViewElem.length) {
+                                //     remoteViewElem.attr('data-stream', '');
+                                //     remoteViewElem.attr('data-connection-data', '');
+                                //     remoteViewElem.attr('data-type', '');
+                                //     remoteViewElem.attr('data-name', '');
+                                // }
+                                // remoteViewElem.addClass('disconnected-view')
                             }
 
-                            // events
-                            self.agoraClient.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');
-
-                                if (!self.startTime) {
-                                    self.startTime = new Date().getTime();
-                                    window.setInterval(function () {
-                                        self.time = new Date().getTime() - self.startTime;
-                                    }, 1000);
-                                    self.started = true;
+                            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="' + self.selfStreamId + '"])').length) {
+                                console.warn('No other parties in the call!');
+                                self.startTime = 0;
+                                self.started = false;
+                                self.noOneElseInCall = true;
+                            }
+                        }
+
+                        // self connected
+                        self.otSession.on("sessionConnected", function (event) {
+                            self.joinMeetingAsPro(self.selfUserType);
+                        });
+
+                        // 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;
                                 }
-                                self.activateParty(user.uid);
-                                self.noOneElseInCall = false;
-                                self.resolveParticipantNames();
-                            })
-                            self.agoraClient.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;
+                            });
+                        });
+
+                        // initialize the publisher
+                        var publisherOptions = {
+                            // videoSource: canvas.captureStream(1).getVideoTracks()[0], // TODO: Comment this line to use webcam
+                            insertMode: 'append',
+                            width: '100%',
+                            height: '100%',
+                        };
+                        var publisher = OT.initPublisher('self-view', publisherOptions, self.handleOpenTokError);
+
+                        publisher.on('streamCreated', function (event) {
+                            var selfView = $('#self-view');
+                            selfView.attr('data-stream', event.stream.id);
+                            selfView.attr('data-connection-data', event.stream.connection.data);
+                            self.selfStreamId = event.stream.id;
+                        });
+
+                        publisher.on('streamCreated', function (event) {
+                            console.log('publisher->streamCreated');
+                            var selfView = $('#self-view');
+                            selfView.attr('data-stream', event.stream.id);
+                            selfView.attr('data-connection-data', event.stream.connection.data);
+                            selfView.attr('data-type', 'PRO');
+                            self.activateParty('self');
+                            $('#self-view').show();
+
+                            // turn pro video on
+                            $.post('/api/clientVideoVisit/turnProVideoOn', {}, function (_data) {
+                                console.log(_data);
+
+                                // start heart beat
+                                self.heartbeatTimer = window.setInterval(function () {
+                                    $.post('/api/clientVideoVisit/registerProMeetingHeartbeat', {}, function (_data) {
+                                        console.log(_data);
+                                    });
+                                }, 5000);
+                            });
+                        });
+
+                        publisher.on('streamDestroyed', function (event) {
+                            event.preventDefault();
+                            console.log('publisher->streamDestroyed');
+                            $('#self-view').hide();
+                            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') !== $('#self-view').attr('data-stream')) {
+                                        self.activateParty($(this).attr('data-stream'));
+                                        return false;
                                     }
-                                }
+                                });
+                            } else {
+                                self.hangUp();
+                            }
 
-                                $('[data-stream="' + user.uid + '"]').remove();
+                            // turn pro video off
+                            $.post('/api/clientVideoVisit/turnProVideoOff', {}, function (_data) {
+                                console.log(_data);
 
-                                // 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;
+                                // stop heart beat
+                                if (self.heartbeatTimer) {
+                                    window.clearInterval(self.heartbeatTimer);
+                                    self.heartbeatTimer = false;
                                 }
-                            })
-                            self.agoraClient.on('user-published', async function(user, mediaType){
-                                await self.agoraClient.subscribe(user, mediaType)
-                                mediaType === 'audio'
-                                    ? user.audioTrack.play()
-                                    : user.videoTrack.play($('[data-stream="' + user.uid + '"]')[0]);
-                            })
-
-                            await self.agoraClient.join(self.appId, self.channel, self.selfToken, self.uid)
-                            await self.agoraClient.publish([mic, camera].filter(Boolean))
+                            });
+                        });
+                        self.publisher = publisher;
 
-                            // 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;
+                        // Connect to the session
+                        self.otSession.connect(token, function callback(error) {
+                            if (error) {
+                                self.handleOpenTokError(error);
+                            } else {
+                                // If the connection is successful, publish the publisher to the session
+                                self.otSession.publish(publisher, self.handleOpenTokError);
                             }
-                        }
+                        });
+                    },
+                    handleOpenTokError: function (e) {
 
-                        _initAgora();
+                    },
 
+                    getOpenTokSessionId: function (_done) {
+                        var self = this;
+                        $.get('/pro/get-opentok-session-key/' + self.clientUid, function (_data) {
+                            self.otSessionId = _data.data;
+                            console.log(_data);
+                            _done();
+                        }, 'json');
                     },
+
                     joinMeetingAsPro: function (_type) {
                         var self = this;
                         $.ajax({
@@ -301,6 +486,7 @@
                                 alert(_data.message);
                             });
                     },
+
                     activateParty: function (_stream = 'self') {
                         var current = $('.full-view');
                         if (current.attr('data-stream') === _stream) return;
@@ -324,6 +510,7 @@
                                 .prependTo('.main-view');
                         }
                     }
+
                 },
                 mounted: function () {
 
@@ -334,10 +521,22 @@
                         return false;
                     });
 
+                    // poll for new patients and alert
+                    // window.setInterval(function () {
+                    //     self.pollForNextPatient();
+                    // }, 5000);
+
+                    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;
+                    self.clientUid = '{{ $client->uid }}';
+                    self.videoActive = true;
+                    self.startOpenTokSession();
                     @endif
                 }
             });