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

Video call updates [WIP]

Vijayakrishnan Krishnan преди 5 години
родител
ревизия
5bc21a159a

+ 1 - 1
.idea/jsLibraryMappings.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="JavaScriptLibraryMappings">
-    <file url="PROJECT" libraries="{ionicons}" />
+    <file url="PROJECT" libraries="{ionicons, js-cookie}" />
   </component>
 </project>

+ 1 - 0
.idea/stagfe.iml

@@ -104,5 +104,6 @@
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="library" name="ionicons" level="application" />
     <orderEntry type="library" name="ionicons" level="application" />
+    <orderEntry type="library" name="js-cookie" level="application" />
   </component>
 </module>

+ 8 - 0
app/Http/Controllers/GuestController.php

@@ -45,6 +45,14 @@ class GuestController extends Controller
         ]);
     }
 
+    public function dashboard(Request $request) {
+        $clientUid = $_COOKIE['clientUid'];
+        $client = DB::table('client')->where('uid', $clientUid)->first();
+        return view('client-dashboard', [
+            "client" => $client
+        ]);
+    }
+
     public function getCheckinToken(Request $request, $uid) {
         $client = DB::table('client')->where('uid', $uid)->first();
         return json_encode([

+ 8 - 3
app/Http/Controllers/ProController.php

@@ -45,16 +45,21 @@ class ProController extends Controller
         ]);
     }
 
-    public function meet(Request $request) {
+    public function meet(Request $request, $uid = false) {
         $session = DB::table('app_session')->where('session_key', $request->cookie('sessionKey'))->first();
         $pro = false;
         if($session && $session->pro_id) {
             $pro = DB::table('pro')->where('id', $session->pro_id)->first();
         }
-        return view('meet', [
+        $client = null;
+        if(!empty($uid)) {
+            $client = DB::table('client')->where('uid', $uid)->first();
+        }
+        return view('pro-call', [
             'guest' => false,
             'session' => $session,
-            'pro' => $pro
+            'pro' => $pro,
+            'client' => $client
         ]);
     }
 

+ 11 - 4
public/css/meeting.css

@@ -91,6 +91,7 @@ h1 {
     max-height: 100%;
     margin: 0 1rem;
     position: relative;
+    background: #000;
 }
 
 .main-view .full-view {
@@ -101,9 +102,9 @@ h1 {
     margin: 0 auto;
 }
 
-.main-view .full-view[data-from]::after {
+.main-view .full-view[data-name]::after {
     position: absolute;
-    content: attr(data-from);
+    content: attr(data-name);
     left: 0;
     width: 100%;
     top: 0;
@@ -143,9 +144,9 @@ h1 {
 .main-view .thumbs .thumb-view>* {
     pointer-events: none;
 }
-.main-view .thumbs .thumb-view[data-from]::after {
+.main-view .thumbs .thumb-view[data-name]::after {
     position: absolute;
-    content: attr(data-from);
+    content: attr(data-name);
     left: 0;
     width: 100%;
     bottom: 0;
@@ -161,6 +162,12 @@ h1 {
     opacity: 0;
     height: 0;
 }
+.main-view .not-publishing {
+    background: grey;
+}
+.main-view .not-publishing * {
+    opacity: 0;
+}
 
 .tp-bar {
     width: 120px;

+ 3 - 3
resources/views/checkin.blade.php

@@ -34,9 +34,9 @@
             .done(function (_data) {
                 console.log(_data);
                 if(_data.success) {
-                    localStorage.sessionKey = _data.data.sessionKey;
-                    localStorage.clientUid = _data.data.clientUid;
-                    window.location = '/client/dashboard';
+                    Cookies.set('sessionKey', _data.data.sessionKey, {expires: 365});
+                    Cookies.set('clientUid', _data.data.clientUid, {expires: 365});
+                    // window.location = '/client/dashboard';
                 }
                 else {
                     new Noty({

+ 407 - 0
resources/views/client-dashboard.blade.php

@@ -0,0 +1,407 @@
+<?php /** @var $client */ ?>
+
+@extends('layouts.meeting')
+@section('content')
+
+    <div id="clientCallComponent">
+
+        <div class="d-flex align-items-center py-3 border-bottom px-4">
+            <span class="mr-auto">
+                Hello {{ $client->name_first }}
+            </span>
+            <a href="#">Log Out</a>
+        </div>
+
+        <div class="">
+            <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="!pro || !started">
+                <h6 class="text-black font-weight-bold m-0">A pro may pop in here at the next chance. Please wait..</h6>
+            </div>
+            <div class="main-view mx-auto">
+                <div class="thumbs">
+                    <div id="self-view" class="thumb-view disconnected-view"
+                         data-name="{{ implode(" ", [$client->name_first, $client->name_last]) }}"
+                         data-type="CLIENT"></div>
+                </div>
+                <?php /* <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>
+
+        <div class="d-flex justify-content-center mt-3">
+            <button v-if="readyToPublish && !publishing"
+                    class="btn btn-sm btn-primary font-weight-bold"
+                    v-on:click.prevent="cameraOn()">Start My Camera</button>
+            <button v-if="readyToPublish && publishing"
+                    class="btn btn-sm btn-danger font-weight-bold"
+                    v-on:click.prevent="cameraOff()">Stop My Camera</button>
+        </div>
+
+    </div>
+
+    <script>
+
+        window.clientCallComponent = new Vue({
+            el: '#clientCallComponent',
+            delimiters: ['@{{', '}}'],
+            data: {
+
+                mdPass: true, // set this to true if client has all medicare reqs met
+
+                time: 0,
+                startTime: 0,
+                started: false,
+                client: false,
+                pro: false,
+
+                selfName: '',
+                selfToken: '',
+
+                clientUid: '{{ $client->uid }}',
+                checkInToken: '',
+
+                otSessionId: '{{ $client->opentok_session_id }}',
+
+                otSession: false,
+                publisher: false,
+                readyToPublish: false,
+                publishing: false,
+
+                selfUserType: 'CLIENT'
+            },
+            methods: {
+                timeDisplay: function() {
+                    var seconds = this.time / 1000,
+                        minutes = parseInt(seconds / 60, 10);
+                    seconds = parseInt(seconds % 60, 10);
+                    return minutes + " min, " + seconds + " sec";
+                },
+                hangUp: function() {
+                    if(this.otSession) {
+                        try {
+                            this.otSession.disconnect();
+                        }
+                        catch (e) {
+                            console.log('Was already disconnected.');
+                        }
+                        this.otSession = false;
+                        this.otSessionId = '';
+                        this.started = false;
+                        this.startTime = false;
+                        // window.location = '/join';
+                    }
+                },
+                initOpenTok: function() {
+
+                    /* fake video feed (temp) */
+                    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 {{ $client->name_first }}", 20, pos);
+                        pos += 5;
+                        if(pos > canvas.height) pos = 100;
+                    }, 1000);
+
+                    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('session->streamCreated', arguments);
+                        var subscriberOptions = {
+                            insertMode: 'append',
+                            width: '100%',
+                            height: '100%'
+                        };
+
+                        var connectionData = JSON.parse(event.stream.connection.data);
+
+                        // 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.otSession.subscribe(event.stream, remoteViewID, subscriberOptions, self.handleOpenTokError_Subscribe);
+
+                        if (connectionData.type === 'PRO') {
+                            self.pro = 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(event.stream.id);
+                    });
+
+                    // 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);
+                    // });
+
+                    function onPeerDisconnection(event, data) {
+
+                        console.log('onPeerDisconnection');
+
+                        if(event.stream && $('.full-view[data-stream="' + event.stream.id + '"]').length) {
+                            var allThumbs = $('.thumbs [data-stream]:not([data-stream=""]):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.hangUp();
+                            }
+                        }
+
+                        if(event.stream) {
+                            var remoteViewElem = $('[data-stream="' + event.stream.id + '"]');
+                            if(remoteViewElem.length) {
+                                remoteViewElem.attr('data-stream', '');
+                                remoteViewElem.attr('data-connection-data', '');
+                                remoteViewElem.attr('data-type', '');
+                                remoteViewElem.attr('data-name', '');
+                            }
+                            remoteViewElem.addClass('disconnected-view')
+                        }
+
+                        // if no other parties in call, hang up
+                        if(!$('[data-stream]:not([data-stream=""])').length) {
+                            // self.hangUp();
+                            console.warn('No other parties in the call!')
+                        }
+                    }
+
+                    // self disconnected
+                    self.otSession.on('sessionDisconnected', function sessionDisconnected(event) {
+                        console.log('You were disconnected from the session.', event.reason);
+                    });
+
+                    // initialize the publisher
+                    var publisherOptions = {
+                        videoSource: canvas.captureStream(1).getVideoTracks()[0], // TODO: Comment this line to use webcam
+                        insertMode: 'append',
+                        width: '100%',
+                        height: '100%',
+                    };
+                    self.publisher = OT.initPublisher('self-view', publisherOptions, self.handleOpenTokError_InitPublisher);
+
+                    self.publisher.on('streamCreated', function(event) {
+                        console.log('publisher->streamCreated');
+                        var selfView = $('#self-view');
+                        selfView.attr('data-stream', event.stream.id);
+                        selfView.attr('data-type', 'CLIENT');
+                        self.activateParty('self');
+                        $('#self-view').show();
+                    });
+
+                    self.publisher.on('streamDestroyed', function(event) {
+                        event.preventDefault();
+                        console.log('publisher->streamDestroyed');
+                        $('#self-view').hide();
+                        var allThumbs = $('.thumbs [data-stream]:not([data-stream=""]):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;
+                                }
+                            });
+                        }
+                    });
+
+                    // Connect to the session
+                    self.otSession.connect(token, function callback(error) {
+                        if (error) {
+                            self.handleOpenTokError_SessionConnect(error);
+                        } else {
+                            console.info('Connected to OT session :)');
+                            self.readyToPublish = true;
+                        }
+                    });
+                },
+                cameraOn: function() {
+                    if(this.readyToPublish) {
+                        this.otSession.publish(this.publisher, this.handleOpenTokError_Publish);
+                        this.publishing = true;
+                    }
+                },
+                cameraOff: function() {
+                    this.otSession.unpublish(this.publisher);
+                    this.publishing = false;
+                },
+                handleOpenTokError_SessionConnect: function(e) {
+                    console.log('handleOpenTokError_SessionConnect');
+                    console.log(e);
+                },
+                handleOpenTokError_Publish: function(e) {
+                    console.log('handleOpenTokError_Publish');
+                    console.log(e);
+                },
+                handleOpenTokError_Subscribe: function(e) {
+                    console.log('handleOpenTokError_Subscribe');
+                    console.log(e);
+                },
+                handleOpenTokError_InitPublisher: function(e) {
+                    console.log('handleOpenTokError_InitPublisher');
+                    console.log(e);
+                },
+
+                getClientCheckinToken: function(_done) {
+                    var self = this;
+                    $.get('/get-client-checkin-token/' + this.clientUid, function(_data) {
+                        console.log(_data);
+                        self.checkInToken = _data.data;
+                        _done();
+                    }, 'json');
+                },
+
+                getOpenTokSessionId: function(_done) {
+                    var self = this;
+
+                    $.ajax({
+                        type: 'post',
+                        url: '/api/clientVideoVisit/startVideoVisitAsClient',
+                        headers: {
+                            'sessionKey': '{{ request()->cookie('sessionKey') }}'
+                        },
+                        data: {checkInToken: this.checkInToken},
+                        dataType: 'json'
+                    })
+                    .done(function (_data) {
+                        console.log(_data);
+                        if(_data.success) {
+                            self.otSessionId = _data.data;
+                            _done();
+                        }
+                        else {
+                            alert(_data.message);
+                        }
+                    })
+                    .fail(function (_data) {
+                        console.log(_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;
+
+                this.getClientCheckinToken(function() { // get client check-in token
+
+                    self.getOpenTokSessionId(function() { // get opentok session id
+                        var name = [];
+                        @if (!empty($client->name_first)) name.push("{{ $client->name_first }}"); @endif
+                        @if (!empty($client->name_last)) name.push("{{ $client->name_last }}"); @endif
+                        self.selfName = name.join(' ');
+                        $.ajax({
+                            type: 'post',
+                            url: '/api/openTok/getClientToken',
+                            headers: {
+                                'sessionKey': '{{ request()->cookie('sessionKey') }}'
+                            },
+                            data: {
+                                opentokSessionId: self.otSessionId,
+                                data: JSON.stringify({
+                                    uid: '{{ $client->uid  }}',
+                                    name: self.selfName,
+                                    type: 'CLIENT'
+                                })
+                            },
+                            dataType: 'json'
+                        })
+                        .done(function (_data) {
+                            console.log(_data);
+                            self.selfToken = _data.data;
+                            self.initOpenTok();
+                        })
+                        .fail(function (_data) {
+                            console.warn(_data);
+                            alert(_data.message);
+                        });
+
+                    });
+                });
+
+                $(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";
+                    }
+                };
+
+            }
+        });
+    </script>
+
+@endsection

+ 2 - 4
resources/views/join.blade.php

@@ -43,10 +43,8 @@
             .done(function (_data) {
                 console.log(_data);
                 if(_data.success) {
-                    localStorage.clientFirstName = $('[name="nameFirst"]').val();
-                    localStorage.clientLastName = $('[name="nameLast"]').val();
-                    localStorage.clientUid = _data.data.clientUid;
-                    localStorage.sessionKey = _data.data.sessionKey;
+                    Cookies.set('sessionKey', _data.data.sessionKey, {expires: 365});
+                    Cookies.set('clientUid', _data.data.clientUid, {expires: 365});
                     window.location = '/client/dashboard';
                 }
                 else {

+ 3 - 0
resources/views/layouts/join.blade.php

@@ -22,6 +22,9 @@
     <link href="/noty/noty.css" rel="stylesheet">
     <link href="/noty/themes/mint.css" rel="stylesheet">
     <script src="/noty/noty.js" type="text/javascript"></script>
+
+    <script src="https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.js"></script>
+
 </head>
 <body class="hold-transition login-page">
 

+ 1 - 1
resources/views/meet.blade.php

@@ -229,7 +229,7 @@
                         ctx.font = "20px Georgia";
                         ctx.fillStyle = `rgb(220, 220, 220)`;
                         var userType = '<?= $guest? "Client" : "Pro" ?>';
-                        ctx.fillText("Video feed from the " + userType, 20, pos);
+                        ctx.fillText("Video feed from " + userType, 20, pos);
                         pos += 5;
                         if(pos > canvas.height) pos = 100;
                     }, 1000);

+ 419 - 0
resources/views/pro-call.blade.php

@@ -0,0 +1,419 @@
+@extends('layouts.meeting')
+@section('content')
+
+    <div id="proCallComponent">
+
+        <div class="d-flex align-items-center justify-content-center py-3 border-bottom">
+            <span class="mr-3">
+                {{ $pro->name_display }} | PRO
+            </span>
+            <button class="btn btn-sm btn-primary px-4 font-weight-bold"
+                    v-on:click.prevent="nextPatient()"
+                    :disabled="client || checkingForNextPatient || started">Next Patient</button>
+            <span v-if="patientInQueue && !started" class="patient-in-q-alert text-warning text-sm ml-2 small">
+                <i class="fa fa-circle"></i>
+            </span>
+            <span v-if="!patientInQueue && !started" class="text-success text-sm ml-2 small">
+                <i class="fa fa-circle"></i>
+            </span>
+        </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>
+            </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>
+
+    </div>
+
+    <script>
+
+        new Vue({
+            el: '#proCallComponent',
+            delimiters: ['@{{', '}}'],
+            data: {
+                time: 0,
+                startTime: 0,
+                started: false,
+                client: false,
+                pro: false,
+
+                selfName: '',
+                selfToken: '',
+
+                clientUid: '',
+
+                otSessionId: '',
+
+                checkingForNextPatient: false,
+                noNextPatient: false,
+
+                otSession: false,
+
+                selfUserType: 'PRO',
+                patientInQueue: false,
+
+                videoActive: false
+            },
+            methods: {
+                pollForNextPatient: function() {
+                    if(!this.started) {
+                        this.nextPatient(true);
+                    }
+                },
+                nextPatient: function(_pollOnly = false) {
+                    var self = this;
+                    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() {
+                    var seconds = this.time / 1000,
+                        minutes = parseInt(seconds / 60, 10);
+                    seconds = parseInt(seconds % 60, 10);
+                    return minutes + " min, " + seconds + " sec";
+                },
+                hangUp: function() {
+                    if(this.otSession) {
+                        try {
+                            this.otSession.disconnect();
+                        }
+                        catch (e) {
+                            console.log('Was already disconnected.');
+                        }
+                        this.otSession = false;
+                        this.otSessionId = '';
+                        this.started = false;
+                        this.startTime = false;
+                        this.videoActive = false;
+                        // this.client = false;
+                    }
+                },
+                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);
+
+                    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);
+
+                        // 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.otSession.subscribe(event.stream, remoteViewID, subscriberOptions, self.handleOpenTokError);
+
+                        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(event.stream.id);
+                    });
+
+                    // 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=""]):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.hangUp();
+                            }
+                        }
+
+                        if(event.stream) {
+                            var remoteViewElem = $('[data-stream="' + event.stream.id + '"]');
+                            if(remoteViewElem.length) {
+                                remoteViewElem.attr('data-stream', '');
+                                remoteViewElem.attr('data-connection-data', '');
+                                remoteViewElem.attr('data-type', '');
+                                remoteViewElem.attr('data-name', '');
+                            }
+                            remoteViewElem.addClass('disconnected-view')
+                        }
+
+                        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=""])').length) {
+                            self.hangUp();
+                        }
+                    }
+
+                    // 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);
+                    });
+
+                    // 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);
+                    });
+
+                    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();
+                    });
+
+                    publisher.on('streamDestroyed', function(event) {
+                        event.preventDefault();
+                        console.log('publisher->streamDestroyed');
+                        $('#self-view').hide();
+                        var allThumbs = $('.thumbs [data-stream]:not([data-stream=""]):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();
+                        }
+                    });
+
+
+                    // 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) {
+
+                },
+
+                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({
+                        type: 'post',
+                        url: '/api/clientVideoVisit/joinVideoVisitAsPro',
+                        headers: {
+                            'sessionKey': '{{ request()->cookie('sessionKey') }}'
+                        },
+                        data: {uid: self.clientUid},
+                        dataType: 'json'
+                    })
+                        .done(function (_data) {
+                            console.log(_data);
+                        })
+                        .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;
+                });
+
+                // 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 = true;
+                    self.startOpenTokSession();
+                @endif
+            }
+        });
+    </script>
+
+@endsection

+ 1 - 1
routes/web.php

@@ -50,7 +50,7 @@ Route::middleware('ensureValidProSession')->group(function(){
 //    Route::get("/pros/create", 'ProController@create')->name('pro-create');
 //    Route::get("/pros/show/{uid}", 'ProController@show')->name('pro-show');
 
-    Route::get('/pro/meet', 'ProController@meet');
+    Route::get('/pro/meet/{uid?}', 'ProController@meet');
     Route::get('/pro/get-opentok-session-key/{uid}', 'ProController@getOpentokSessionKey');
 
     Route::get('/pro/logout', 'AppSessionController@processProLogOut')->name('pro-logout');