<!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 src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"
            integrity="sha512-5yJ548VSnLflcRxWNqVWYeQZnby8D8fJTmYRLyvs445j1XmzR8cnWi85lcHx3CUEeAX+GrK3TqTfzOO6LKDpdw=="
            crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"
            integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g=="
            crossorigin="anonymous"></script>
    <script defer src=//download.agora.io/sdk/web/AgoraRTC_N-4.1.0.js></script>
    <link rel="stylesheet" href="{{ asset('/css/toastr.min.css') }}">
    <script src="{{ asset('js/toastr.min.js') }}" type="application/javascript"></script>
    <title>Leadership Health: Meet</title>
</head>

<body class="p-0 m-0">

    <div id="proCallComponent" v-cloak>

        <div class="d-flex px-2 border-bottom mb-2 {{ $client ? '' : 'justify-content-center' }}">
            @if($client)
            <div class="py-2 font-weight-normal mcp-theme-1 d-inline-flex align-items-center flex-grow-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>
                <span class="text-secondary ml-3">{{ $pro->displayName() }}</span>
                {{--<span class="text-secondary ml-3">{{ date('H:i:s') }}</span>--}}
            </div>
            @endif

            <div class="py-2 d-flex align-items-center">
                <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>

        @if($client)
        <div v-if="appMode === 'name'" class="d-flex">
            <button v-on:click.prevent="enterClientRoomAsPro()"
                    class="btn btn-primary px-4 font-weight-bold mx-auto my-3">Start Video</button>
        </div>
        <div v-show="appMode === 'video'">
            <div class="main-view mx-auto">
                <div id="self-view" class="full-view"
                     :data-self="mainViewParticipant.self"
                     :data-uid="mainViewParticipant.uid"
                     :data-name="mainViewParticipant.name"
                     :data-type="mainViewParticipant.type">
                    <div class="user-type-indicator">
                        <i v-if="mainViewParticipant.type === 'CLIENT_GUEST'" class="fa fa-user text-white"></i>
                        <i v-if="mainViewParticipant.type === 'PRO'" class="fa fa-stethoscope text-white"></i>
                    </div>
                    <div class="media-status-indicator">
                        <i v-show="!getMediaByMediaServiceId(mainViewParticipant.uid).isCameraOn"
                           class="fa fa-video-slash muted ml-1"
                           :class="!getMediaByMediaServiceId(mainViewParticipant.uid).isCameraAcquired ? 'text-danger' : 'text-white'"></i>
                        <i v-show="!getMediaByMediaServiceId(mainViewParticipant.uid).isMicrophoneOn"
                           class="fa fa-microphone-slash muted ml-1"
                           :class="!getMediaByMediaServiceId(mainViewParticipant.uid).isMicrophoneAcquired ? 'text-danger' : 'text-white'"></i>
                    </div>
                </div>
                <div class="thumbs">
                    <div v-if="mainViewParticipant.uid !== myMediaServiceIdentifier"
                         :id="'remote-view-' + myMediaServiceIdentifier"
                         :data-self="true"
                         :data-uid="myMediaServiceIdentifier"
                         :data-name="'You (' + myName + ')'"
                         :data-type="'PRO'"   {{-- TODO: change in FE4 --}}
                         :data-audio="myMedia && myMedia.isMicrophoneOn ? 'on' : 'off'"
                         v-on:click.prevent="showInCenterView(true, myMediaServiceIdentifier, 'You (' + myName + ')', 'PRO')"
                         class="remote-view thumb-view c-pointer">
                        <div class="user-type-indicator">
                            <i class="fa fa-stethoscope text-white"></i>
                        </div>
                        <div class="media-status-indicator">
                            <i v-show="!myMedia || !myMedia.isCameraOn"
                               class="fa fa-video-slash muted ml-1"
                               :class="!myMedia || !myMedia.isCameraAcquired ? 'text-danger' : 'text-white'"></i>
                            <i v-show="!myMedia || !myMedia.isMicrophoneOn"
                               class="fa fa-microphone-slash muted ml-1"
                               :class="!myMedia || !myMedia.isMicrophoneAcquired ? 'text-danger' : 'text-white'"></i>
                        </div>
                    </div>
                    <div v-for="participant in otherParticipants"
                         v-if="mainViewParticipant.uid !== (+participant.mediaServiceIdentifier)"
                         :id="'remote-view-' + participant.mediaServiceIdentifier"
                         :data-self="false"
                         :data-uid="participant.mediaServiceIdentifier"
                         :data-name="participant.displayName"
                         :data-type="participant.participantType"
                         :data-audio="participant.media && participant.media.isMicrophoneOn ? 'on' : 'off'"
                         v-on:click.prevent="showInCenterView(false, participant.mediaServiceIdentifier, participant.displayName, participant.participantType)"
                         class="remote-view thumb-view c-pointer">
                        <div class="user-type-indicator">
                            <i v-if="participant.participantType === 'CLIENT_GUEST'" class="fa fa-user text-white"></i>
                            <i v-if="participant.participantType === 'PRO'" class="fa fa-stethoscope text-white"></i>
                        </div>
                        <div class="media-status-indicator">
                            <i v-show="!participant.media || !participant.media.isCameraOn"
                               class="fa fa-video-slash muted ml-1"
                               :class="!participant.media || !participant.media.isCameraAcquired ? 'text-danger' : 'text-white'"></i>
                            <i v-show="!participant.media || !participant.media.isMicrophoneOn"
                               class="fa fa-microphone-slash muted ml-1"
                               :class="!participant.media || !participant.media.isMicrophoneAcquired ? 'text-danger' : 'text-white'"></i>
                        </div>
                    </div>
                </div>
                <div class="call-actions d-flex align-items-center">
                    <button class="btn btn-danger rounded-circle"
                            title="Leave Call"
                            v-on:click.prevent="leaveClientRoom()">
                        <i class="fa fa-phone"></i>
                    </button>
                    <button v-if="myMedia.isCameraOn" class="btn btn-default bg-light border rounded-circle"
                            title="Stop Camera"
                            v-on:click.prevent="myCameraIsOff()">
                        <i class="fa fa-video"></i>
                    </button>
                    <button v-if="!myMedia.isCameraOn" class="btn btn-secondary rounded-circle"
                            title="Start Camera"
                            v-on:click.prevent="myCameraIsOn()">
                        <i class="fa fa-video-slash"></i>
                    </button>
                    <button v-if="myMedia.isMicrophoneOn" class="btn btn-default bg-light border rounded-circle"
                            title="Stop Microphone"
                            v-on:click.prevent="myMicrophoneIsOff()">
                        <i class="fa fa-microphone"></i>
                    </button>
                    <button v-if="!myMedia.isMicrophoneOn" class="btn btn-secondary rounded-circle"
                            title="Start Microphone"
                            v-on:click.prevent="myMicrophoneIsOn()">
                        <i class="fa fa-microphone-slash"></i>
                    </button>
                </div>
            </div>
        </div>
        @endif

    </div>
    <script>
        (function () {
            window.proCallComponent = new Vue({
                el: '#proCallComponent',
                delimiters: ['@{{', '}}'],
                data: {

                    // main model - declare up-front to make reactive - override with server data on mount
                    amIInAMeeting: false,
                    meetingType: '', // PRO/CLIENT,
                    inMeetingForClientUid: '',
                    inMeetingForClient: {
                        clientMediaServiceRoomIdentifier: '',
                        uid: '',
                        displayName: '',
                        dob: '',
                    },
                    myMediaServiceToken: '',
                    myMediaServiceIdentifier: '',
                    myMedia: {
                        isCameraAcquired: false,
                        isCameraOn: false,
                        isMicrophoneAcquired: false,
                        isMicrophoneOn: false,
                    },
                    otherParticipants: [
                        {
                            participantType: '', // PRO/CLIENT_GUEST,
                            uid: '',
                            mediaServiceIdentifier: '',
                            displayName: '',
                            media: {
                                isCameraAcquired: false,
                                isCameraOn: false,
                                isMicrophoneAcquired: false,
                                isMicrophoneOn: false,
                            },
                            awayMessage: '',
                            deviceType: '',
                            isMeetingAccessGranted: '',
                            isSocketConnected: '',
                        }
                    ],
                    awayMessage: '',
                    myName: '{{ $performer->pro->displayName() }}',

                    // agora
                    mediaServiceClient: null, // set on agora init
                    appId: '{{ config('app.agora_appid') }}',
                    channel: '', // set on mount
                    myMicrophone: null,
                    myCamera: null,
                    appMode: 'name', // Agora needs a user-gesture to init correctly

                    // sockets
                    backendWsURL: '{{ config('app.backend_ws_url') }}',
                    socketClient: null,

                    // other
                    ringer: {{ $pro->is_ring_on ? 'true' : 'false' }},

                    // agora <-> WS sync
                    unrenderedParticipants: [],     // exists in otherParticipants, but not yet in DOM
                    unresolvedParticipants: [],     // does not exist in otherParticipants, but came in via Agora
                    unrenderedParticipantsTimer: false,
                    unresolvedParticipantsTimer: false,

                    // main-view participant
                    mainViewParticipant: {
                        self: true,
                        uid: '',
                        type: 'PRO',
                        name: 'You ({{ $performer->pro->displayName() }})',
                    },
                },
                methods: {

                    // start: main flow
                    enterClientRoomAsPro: function() {
                        @if($client)
                            this.socketClient.send("/app/leaveClientRoom", {},
                                JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                            );
                            window.setTimeout(() => {
                                $.post('/api/meeting/enterClientRoomAsPro', {clientUid: '{{ $client->uid }}'}, (_data) => {
                                    if(!this.hasError(_data)) {
                                        this.appMode = 'video';
                                        this.getMeetingInfo(true);
                                    }
                                });
                            }, 250);
                        @endif
                    },
                    getMeetingInfo: function(_firstRun = false) {
                        $.post('/api/meeting/getMyMeeting', (_data) => {
                            if(_data && _data.success) {
                                let state = _data.data;
                                console.log(state);

                                // overwrite model data
                                this.amIInAMeeting = state.amIInAMeeting;
                                this.inMeetingForClientUid = state.inMeetingForClientUid;
                                this.inMeetingForClient.clientMediaServiceRoomIdentifier =
                                    state.inMeetingForClient.clientMediaServiceRoomIdentifier;
                                this.inMeetingForClient.uid = state.inMeetingForClient.uid;
                                this.inMeetingForClient.displayName = state.inMeetingForClient.displayName;
                                this.inMeetingForClient.dob = state.inMeetingForClient.dob;
                                this.meetingType = state.meetingType;
                                // NOTE: this now comes from its own end-point (see below)
                                // this.myMediaServiceToken = state.myMediaServiceToken;
                                this.myMediaServiceIdentifier = +state.myMediaServiceIdentifier;
                                this.otherParticipants = state.otherParticipants;

                                if(_firstRun) {
                                    this.mainViewParticipant.uid = +state.myMediaServiceIdentifier;
                                    $.post('/api/meeting/refreshMyMediaServiceToken', (_data) => {  // get new agora token
                                        if(!this.hasError(_data)) {
                                            this.myMediaServiceToken = _data.data;
                                            this.channel = this.inMeetingForClient.clientMediaServiceRoomIdentifier;
                                            this.initMediaService();
                                        }
                                    }, 'json');
                                }

                                console.log(this.$data);
                            }
                        }, 'json');
                    },
                    registerSocket: function(_done) {
                        let socket = new SockJS(this.backendWsURL);
                        this.socketClient = Stomp.over(socket);
                        this.socketClient.connect({}, (frame) => {
                            console.log('Connected: ' + frame);
                            this.initSocketListeners();                     // init listeners
                            this.socketClient.send("/app/register", {},     // register self
                                JSON.stringify({
                                    sessionKey: '{{$performer->session_key}}'
                                })
                            );
                            window.setInterval(() => {
                                this.socketClient.send("/app/heartbeat", {},
                                    JSON.stringify({sessionKey: '{{ request()->cookie('sessionKey') }}'})
                                );
                            }, 5000);
                        });
                    },
                    initSocketListeners: function() {

                        function _isSelf(_eventData) {
                            return _eventData.performer === '{{ $session->uid  }}';
                        }

                        function _setParticipantProperty(_message, _propertyName, _valueKeyName) {
                            if(_message && _message.body) {
                                let eventData = JSON.parse(_message.body);
                                if(!_isSelf(eventData) && eventData.data) {
                                    for (let i = 0; i < this.otherParticipants.length; i++) {
                                        if(this.otherParticipants[i].uid === eventData.performer) {
                                            this.otherParticipants[i][_propertyName] = eventData.data[_valueKeyName];
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        function _setParticipantMediaProperty(_message, _propertyName, _value) {
                            if(_message && _message.body) {
                                let eventData = JSON.parse(_message.body);
                                if(!_isSelf(eventData) && eventData.data) {
                                    for (let i = 0; i < this.otherParticipants.length; i++) {
                                        if(this.otherParticipants[i].uid === eventData.performer) {
                                            this.otherParticipants[i].media[_propertyName] = _value;
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        this.socketClient.subscribe("/user/topic/newParticipant", (message) => {
                            console.log("newParticipant received:", message.body);
                            if(message && message.body) {
                                let eventData = JSON.parse(message.body);
                                if(!_isSelf(eventData) && eventData.data) {
                                    let existing = this.otherParticipants.filter(_participant => {
                                        return _participant.uid === eventData.performer;
                                    });
                                    if(!existing.length) this.otherParticipants.push(eventData.data);
                                    Vue.nextTick(() => {
                                        this.refreshVideos();
                                    });
                                }
                            }
                        });

                        this.socketClient.subscribe("/user/topic/myMicrophoneIsAcquired", (message) => {
                            console.log("myMicrophoneIsAcquired received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isMicrophoneAcquired', true);
                        });

                        this.socketClient.subscribe("/user/topic/myMicrophoneIsNotAcquired", (message) => {
                            console.log("myMicrophoneIsNotAcquired received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isMicrophoneAcquired', false);
                            _setParticipantMediaProperty.call(this, message, 'isMicrophoneOn', false);
                        });

                        this.socketClient.subscribe("/user/topic/myMicrophoneIsOn", (message) => {
                            console.log("myMicrophoneIsOn received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isMicrophoneOn', true);
                            _setParticipantMediaProperty.call(this, message, 'isMicrophoneAcquired', true);
                        });

                        this.socketClient.subscribe("/user/topic/myMicrophoneIsOff", (message) => {
                            console.log("ALIX myMicrophoneIsOff received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isMicrophoneOn', false);
                        });

                        this.socketClient.subscribe("/user/topic/myCameraIsAcquired", (message) => {
                            console.log("myCameraIsAcquired received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isCameraAcquired', true);
                        });

                        this.socketClient.subscribe("/user/topic/myCameraIsNotAcquired", (message) => {
                            console.log("myCameraIsNotAcquired received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isCameraAcquired', false);
                            _setParticipantMediaProperty.call(this, message, 'isCameraOn', false);
                        });

                        this.socketClient.subscribe("/user/topic/myCameraIsOn", (message) => {
                            console.log("myCameraIsOn received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isCameraOn', true);
                            _setParticipantMediaProperty.call(this, message, 'isCameraAcquired', true);
                        });

                        this.socketClient.subscribe("/user/topic/myCameraIsOff", (message) => {
                            console.log("myCameraIsOff received:", message.body);
                            _setParticipantMediaProperty.call(this, message, 'isCameraOn', false);
                        });

                        this.socketClient.subscribe("/user/topic/editMyName", (message) => {
                            console.log("editMyName received:", message.body);
                            _setParticipantProperty.call(this, message, 'displayName', 'myName');
                        });

                        this.socketClient.subscribe("/user/topic/setMyAwayMessage", (message) => {
                            console.log("setMyAwayMessage received:", message.body);
                        });

                        this.socketClient.subscribe("/user/topic/removeMyAwayMessage", (message) => {
                            console.log("removeMyAwayMessage received:", message.body);
                        });

                        this.socketClient.subscribe("/user/topic/leaveClientRoom", (message) => {
                            console.log("leaveClientRoom received:", message.body);
                            if(message && message.body) {
                                let eventData = JSON.parse(message.body);
                                if(!_isSelf(eventData) && eventData.data) {

                                    // if the participant who left is in center view, switch center view to self
                                    for (let i = 0; i < this.otherParticipants.length; i++) {
                                        if (this.otherParticipants[i].uid === eventData.performer) {
                                            if(this.mainViewParticipant.uid === (+this.otherParticipants[i].mediaServiceIdentifier)) {
                                                this.showInCenterView(true, this.myMediaServiceIdentifier, 'You (' + this.myName + ')', 'PRO');
                                                break;
                                            }
                                        }
                                    }

                                    this.otherParticipants = this.otherParticipants.filter(_participant => {
                                         return _participant.uid !== eventData.performer;
                                    });

                                    Vue.nextTick(() => {
                                        this.refreshVideos();
                                    });
                                }
                            }
                        });

                    },
                    initMediaService: function() {

                        this.mediaServiceClient = AgoraRTC.createClient({mode:'rtc', codec:'vp8'});

                        async function _acquireMicrophone() {
                            this.myMedia.isMicrophoneAcquired = false;
                            this.myMedia.isMicrophoneOn = false;
                            try {
                                this.myMicrophone = await AgoraRTC.createMicrophoneAudioTrack();
                                this.myMedia.isMicrophoneAcquired = true;
                            }
                            catch (e) {
                                console.log('ALIX: error in getting mic');
                            }
                        }
                        async function _acquireCamera() {
                            this.myMedia.isCameraAcquired = false;
                            this.myMedia.isCameraOn = false;
                            try {
                                @if(config('app.agora_mode') === 'screen') // testing
                                    this.myCamera = await AgoraRTC.createScreenVideoTrack();
                                @else
                                    this.myCamera = await AgoraRTC.createCameraVideoTrack({
                                        optimizationMode: "motion"
                                    });
                                @endif
                                this.myMedia.isCameraAcquired = true;
                            }
                            catch (e) {
                                console.log('ALIX: error in getting camera');
                            }
                        }
                        async function _initMediaServiceEvents() {
                            this.mediaServiceClient.on('user-joined', user => {
                                // do nothing, newParticipant logic handled via WS
                            });
                            this.mediaServiceClient.on('user-left', user => {
                                // do nothing, leaveClientRoom logic handled via WS
                            });
                            this.mediaServiceClient.on('user-published', async (user, mediaType) => {
                                console.log('ALIX user-published', user);
                                await this.mediaServiceClient.subscribe(user, mediaType)
                                this.attemptToPlayParticipantMedia(user, mediaType);
                                window.setTimeout(() => {
                                    this.refreshVideos();
                                }, 500);
                            });
                        }
                        async function _initMediaService() {

                            await _acquireMicrophone.call(this);  // get mic
                            await _acquireCamera.call(this);      // get cam (or screen for testing)

                            if (!this.myMicrophone && !this.myCamera) {
                                alert('Do you have camera/mic? Unable to hear or see you.');
                                // return; // allow to proceed without any device!
                            }

                            await _initMediaServiceEvents.call(this);

                            // Show own feed
                            if(this.myCamera && this.myMedia.isCameraAcquired) {
                                this.myCamera.play($('#self-view')[0], {fit: 'contain'});
                            }

                            // init unrenderedParticipantsTimer and unresolvedParticipantsTimer
                            this.initUnrenderedParticipantsTimer();
                            this.initUnresolvedParticipantsTimer();

                            await this.mediaServiceClient.join(         // join agora channel
                                this.appId,
                                this.channel,
                                this.myMediaServiceToken,
                                +this.myMediaServiceIdentifier
                            );

                            if(this.myMicrophone || this.myCamera) {
                                await this.mediaServiceClient.publish(      // publish audio/video
                                    [this.myMicrophone, this.myCamera].filter(Boolean)
                                );
                            }

                            // notify others about my camera status
                            if(this.myCamera && this.myMedia.isCameraAcquired) {
                                this.myCameraIsAcquired();
                                this.myCameraIsOn();
                            }
                            else {
                                this.myCameraIsNotAcquired();
                                this.myCameraIsOff();
                            }

                            // notify others about my microphone status
                            if(this.myMicrophone && this.myMedia.isMicrophoneAcquired) {
                                this.myMicrophoneIsAcquired();
                                this.myMicrophoneIsOn();
                            }
                            else {
                                this.myMicrophoneIsNotAcquired();
                                this.myMicrophoneIsOff();
                            }

                        }

                        _initMediaService.call(this);
                    },
                    attemptToPlayParticipantMedia: function(user, mediaType) {
                        //  LOGIC
                        //  attemptToPlayParticipantMedia
                        //      if user already in otherParticipants
                        //          if user's thumb already rendered
                        //              if yes, check participant's isCameraOn is true
                        //                  if yes, play participant's video in his thumb
                        //          else store "user" in unrenderedParticipants
                        //              and keep retrying after 500mx (max 2 times) - i.e. give vue a cycle or 2 to render thumb
                        //      else store "user" in unresolvedParticipants
                        //          and keep retrying after 500ms (max 10 times) till resolved - i.e. give WS time to receive the newParticipant event
                        let participant = this.otherParticipants.filter(function(_participant) {
                            return (+_participant.mediaServiceIdentifier) === user.uid;
                        });
                        if(participant && participant.length) {
                            participant = participant[0];
                            if($('[data-uid="' + participant.mediaServiceIdentifier + '"]').length) {
                                if(mediaType === 'audio' && user.hasAudio && user.audioTrack) {
                                    participant.media.isMicrophoneAcquired = true;
                                    participant.media.isMicrophoneOn = true;
                                    user.audioTrack.play();
                                }
                                else if(mediaType === 'video' && user.hasVideo && user.videoTrack) {
                                    participant.media.isCameraAcquired = true;
                                    participant.media.isCameraOn = true;
                                    user.videoTrack.play($('[data-uid="' + user.uid + '"]')[0], {fit: 'contain'});
                                }
                                this.markUserAsRendered(user);
                            }
                            else {
                                console.warn('Thumb not yet in DOM for participant!', user.uid);
                                this.markUserAsUnrendered(user, mediaType);
                            }
                            this.markUserAsResolved(user);
                        }
                        else {
                            console.warn('Participant not found in otherParticipants!', user.uid);
                            this.markUserAsUnresolved(user, mediaType);
                        }
                    },
                    // end: main flow

                    // start: agora <-> WS sync helpers
                    initUnrenderedParticipantsTimer: function() {
                        this.unrenderedParticipantsTimer = window.setInterval(() => {
                            this.unrenderedParticipants.forEach((_user) => {
                                this.attemptToPlayParticipantMedia(_user, _user.mediaType);
                            });
                        }, 500);
                    },
                    initUnresolvedParticipantsTimer: function() {
                        this.unresolvedParticipantsTimer = window.setInterval(() => {
                            this.unresolvedParticipants.forEach((_user) => {
                                this.attemptToPlayParticipantMedia(_user, _user.mediaType);
                            });
                        }, 1000);
                    },
                    markUserAsUnrendered: function(_user, _mediaType) {
                        let existing = !!this.unrenderedParticipants.filter((_item) => _item.uid === _user.uid).length;
                        if(!existing) {
                            _user.mediaType = _mediaType;
                            this.unrenderedParticipants.push(_user);
                        }
                    },
                    markUserAsRendered: function(_user) {
                        this.unrenderedParticipants = this.unrenderedParticipants.filter((_item) => _item.uid !== _user.uid);
                    },
                    markUserAsUnresolved: function(_user, _mediaType) {
                        let existing = !!this.unresolvedParticipants.filter((_item) => _item.uid === _user.uid).length;
                        if(!existing) {
                            _user.mediaType = _mediaType;
                            this.unresolvedParticipants.push(_user);
                        }
                    },
                    markUserAsResolved: function(_user) {
                        this.unresolvedParticipants = this.unresolvedParticipants.filter((_item) => _item.uid !== _user.uid);
                    },
                    // end: agora <-> WS sync helpers

                    // start: actions that notify participants via socket
                    myMicrophoneIsAcquired: function () {
                        this.socketClient.send("/app/myMicrophoneIsAcquired", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    myMicrophoneIsNotAcquired: function () {
                        this.socketClient.send("/app/myMicrophoneIsNotAcquired", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    myMicrophoneIsOn: function () {
                        if(this.myMicrophone && this.myMedia.isMicrophoneAcquired) {
                            this.myMedia.isMicrophoneOn = true;
                            this.myMicrophone.setEnabled(true);
                            this.socketClient.send("/app/myMicrophoneIsOn", {},
                                JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                            );
                        }
                    },
                    myMicrophoneIsOff: function () {
                        if(this.myMicrophone) {
                            this.myMicrophone.setEnabled(false);
                        }
                        this.myMedia.isMicrophoneOn = false;
                        this.socketClient.send("/app/myMicrophoneIsOff", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    myCameraIsAcquired: function () {
                        this.socketClient.send("/app/myCameraIsAcquired", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    myCameraIsNotAcquired: function () {
                        this.socketClient.send("/app/myCameraIsNotAcquired", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    myCameraIsOn: function () {
                        if(this.myCamera && this.myMedia.isCameraAcquired) {
                            this.myCamera.setEnabled(true);
                            this.myMedia.isCameraOn = true;
                            this.socketClient.send("/app/myCameraIsOn", {},
                                JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                            );
                        }
                    },
                    myCameraIsOff: function () {
                        if(this.myCamera) {
                            this.myCamera.setEnabled(false);
                        }
                        this.myMedia.isCameraOn = false;
                        this.socketClient.send("/app/myCameraIsOff", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    editMyName: function (_myNewName) {
                        this.socketClient.send("/app/editMyName", {},
                            JSON.stringify({
                                sessionKey: '{{$performer->session_key}}',
                                myNewName: _myNewName
                            })
                        );
                    },
                    setMyAwayMessage: function (_message) {
                        this.socketClient.send("/app/setMyAwayMessage", {},
                            JSON.stringify({
                                sessionKey: '{{$performer->session_key}}',
                                message: _message
                            })
                        );
                    },
                    removeMyAwayMessage: function () {
                        this.socketClient.send("/app/removeMyAwayMessage", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                    },
                    leaveClientRoom: function () {
                        this.socketClient.send("/app/leaveClientRoom", {},
                            JSON.stringify({sessionKey: '{{$performer->session_key}}'})
                        );
                        window.setTimeout(() => {   // a little timeout for the WS message sending op to complete
                            window.location.href = '/pro/meet';
                        }, 250);
                    },
                    // end: actions that notify participants via socket

                    // start: main view / thumb views
                    showInCenterView: function(_self, _uid, _name, _type) {
                        this.mainViewParticipant = {
                            self: _self,
                            uid: +_uid,
                            type: _type,
                            name: _name,
                        };
                        Vue.nextTick(() => {
                            this.refreshVideos();
                        });
                    },
                    refreshVideos: function() {
                        // play self (only video)
                        // no need to check camera/mic acquired/on etc. as only published tracks will appear here
                        for(let track in this.mediaServiceClient.localTracks) {
                            if(this.mediaServiceClient.localTracks.hasOwnProperty(track)) {
                                track = this.mediaServiceClient.localTracks[track];
                                if(track.trackMediaType === 'video') {
                                    let videoContainer = $('[data-uid="' + this.myMediaServiceIdentifier + '"]');
                                    if(videoContainer.length) {
                                        track.play(videoContainer[0], {fit: 'contain'});
                                    }
                                }
                            }
                        }

                        // play others
                        for(let remoteParticipant in this.mediaServiceClient.remoteUsers) {
                            if(this.mediaServiceClient.remoteUsers.hasOwnProperty(remoteParticipant)) {
                                remoteParticipant = this.mediaServiceClient.remoteUsers[remoteParticipant];
                                if(remoteParticipant.hasAudio && remoteParticipant.audioTrack) {
                                    remoteParticipant.audioTrack.play();
                                }
                                if(remoteParticipant.hasVideo && remoteParticipant.videoTrack) {
                                    let videoContainer = $('[data-uid="' + remoteParticipant.uid + '"]');
                                    if(videoContainer.length) {
                                        remoteParticipant.videoTrack.play(videoContainer[0], {fit: 'contain'});
                                    }
                                }
                            }
                        }
                    },
                    // end: main view / thumb views

                    // start: other/misc
                    getMediaByMediaServiceId: function(_msid) {
                        if((+this.myMediaServiceIdentifier) === _msid) {               // is it self?
                            return this.myMedia;
                        }
                        for (let i = 0; i < this.otherParticipants.length; i++) {   // or a remote participant
                            if((+this.otherParticipants[i].mediaServiceIdentifier) === _msid) {
                                return this.otherParticipants[i].media;
                            }
                        }
                        return {    // return falsy object if nothing found
                            isCameraAcquired: false,
                            isCameraOn: false,
                            isMicrophoneAcquired: false,
                            isMicrophoneOn: false,
                        };
                    },
                    toggleRinger: function () {
                        $.post('/api/pro/' + (this.ringer ? 'turnOffRing' : 'turnOnRing'), (_data) => {
                            if(!this.hasError(_data)) {
                                this.ringer = !this.ringer;
                            }
                        }, 'json');
                    },
                    hasError: function(_data) {     // check and report error if exists via toastr
                        let msg = 'Unknown error!';
                        if(_data) {
                            if(_data.success) return false;
                            else if(_data.message) msg = _data.message;
                        }
                        toastr.error(msg);
                        return true;
                    }
                    // end: other/misc
                },
                mounted: function () {
                    this.registerSocket();
                }
            });
        })();
    </script>

    @include('app/patient/partials/mcp-queue')

</body>
</html>
