|
@@ -0,0 +1,706 @@
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
+<html lang="en">
|
|
|
|
+<head>
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
+ <title>Leadership Health</title>
|
|
|
|
+
|
|
|
|
+ <link rel="stylesheet" href="/css/bootstrap.min.css">
|
|
|
|
+ <link rel="stylesheet" href="/fontawesome-free/css/all.min.css">
|
|
|
|
+ <link rel="stylesheet" href="/css/toastr.min.css">
|
|
|
|
+ <script src="/js/jquery-3.5.1.min.js"></script>
|
|
|
|
+ <script src="/js/toastr.min.js"></script>
|
|
|
|
+ <script defer src=/js/AgoraRTC_N-4.1.0.js></script>
|
|
|
|
+ <script src="/js/sockjs.min.js"></script>
|
|
|
|
+ <script src="/js/stomp.min.js"></script>
|
|
|
|
+
|
|
|
|
+ <link href="/css/call-minimal.css" rel="stylesheet">
|
|
|
|
+</head>
|
|
|
|
+
|
|
|
|
+<body class="p-0 m-0">
|
|
|
|
+
|
|
|
|
+<div class="d-flex px-3 border-bottom">
|
|
|
|
+ <div class="py-2 font-weight-normal mcp-theme-1 d-inline-flex align-items-center">
|
|
|
|
+ <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>
|
|
|
|
+</div>
|
|
|
|
+
|
|
|
|
+<div class="videos-container">
|
|
|
|
+
|
|
|
|
+ {{-- check video button --}}
|
|
|
|
+ <button id="btn-start-video" disabled
|
|
|
|
+ class="btn btn-primary px-4 font-weight-bold d-none mx-auto my-3">
|
|
|
|
+ Check Video
|
|
|
|
+ </button>
|
|
|
|
+
|
|
|
|
+ <div class="instruction-container">
|
|
|
|
+ <div class="mic-access-chrome-desktop border bg-light rounded m-3 px-3 pt-3 pb-2 d-none">
|
|
|
|
+ <p><b>We were unable to access your microphone!</b></p>
|
|
|
|
+ <p>To allow access, please tap the blocked media icon in your browser's address bar (indicated in the figure below).</p>
|
|
|
|
+ <img src="/img/mic-access-chrome-desktop.png" class="mw-100 mx-auto mb-3 d-block">
|
|
|
|
+ <p class="mb-0">Once you have allowed access, please click <b>Start Video</b> again to retry.</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="cam-access-chrome-desktop border bg-light rounded m-3 px-3 pt-3 pb-2 d-none">
|
|
|
|
+ <p><b>We were unable to access your camera!</b></p>
|
|
|
|
+ <p>To allow access, please tap the blocked media icon in your browser's address bar (indicated in the figure below).</p>
|
|
|
|
+ <img src="/img/mic-access-chrome-desktop.png" class="mw-100 mx-auto mb-3 d-block">
|
|
|
|
+ <p class="mb-0">Once you have allowed access, please click <b>Start Video</b> again to retry.</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div id="video-container" class="container d-none">
|
|
|
|
+ <div class="main-view mx-auto">
|
|
|
|
+ <div id="self-view" class="video-view" data-user-id="">
|
|
|
|
+ <i class="fa fa-volume-mute text-white muted-icon"></i>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div id="call-actions" class="d-none">
|
|
|
|
+ <button class="btn btn-danger" id="btn-hang-up" title="End">
|
|
|
|
+ <i class="fa fa-phone"></i>
|
|
|
|
+ </button>
|
|
|
|
+<!-- <button class="ml-2 btn btn-default bg-light border" id="btn-stop-camera" title="Stop Camera">
|
|
|
|
+ <i class="fa fa-video"></i>
|
|
|
|
+ </button>
|
|
|
|
+ <button class="ml-2 btn btn-secondary d-none" id="btn-start-camera" title="Start Camera">
|
|
|
|
+ <i class="fa fa-video-slash"></i>
|
|
|
|
+ </button>
|
|
|
|
+ <button class="ml-2 btn btn-default bg-light border" id="btn-mute-audio" title="Mute">
|
|
|
|
+ <i class="fa fa-microphone"></i>
|
|
|
|
+ </button>
|
|
|
|
+ <button class="ml-2 btn btn-secondary d-none" id="btn-unmute-audio" title="Unmute">
|
|
|
|
+ <i class="fa fa-microphone-slash"></i>
|
|
|
|
+ </button>-->
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+</div>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+ (function() {
|
|
|
|
+
|
|
|
|
+ window.StagVideo = {
|
|
|
|
+
|
|
|
|
+ // model
|
|
|
|
+
|
|
|
|
+ // data returned by getMyMeeting
|
|
|
|
+ meetingData: {
|
|
|
|
+ amIInAMeeting: false,
|
|
|
|
+ awayMessage: '',
|
|
|
|
+ inMeetingForClient: {
|
|
|
|
+ clientMediaServiceRoomIdentifier: '',
|
|
|
|
+ displayName: '',
|
|
|
|
+ dob: '',
|
|
|
|
+ uid: '',
|
|
|
|
+ },
|
|
|
|
+ inMeetingForClientUid: null,
|
|
|
|
+ inMeetingForPro: null,
|
|
|
|
+ inMeetingForProUid: null,
|
|
|
|
+ meetingType: "CLIENT",
|
|
|
|
+ myMedia: {
|
|
|
|
+ isCameraAcquired: false,
|
|
|
|
+ isCameraOn: false,
|
|
|
|
+ isMicrophoneAcquired: false,
|
|
|
|
+ isMicrophoneOn: false
|
|
|
|
+ },
|
|
|
|
+ myMediaServiceIdentifier: '',
|
|
|
|
+ myMediaServiceToken: null,
|
|
|
|
+ otherParticipants: []
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // data local to StagVideo
|
|
|
|
+ myName: '{{ $performer->pro->displayName() }}',
|
|
|
|
+ clientUid: '{{ $client->uid }}',
|
|
|
|
+
|
|
|
|
+ // agora
|
|
|
|
+ mediaServiceClient: null, // instantiated on agora init
|
|
|
|
+ appId: '{{ config('app.agora_appid') }}',
|
|
|
|
+
|
|
|
|
+ // handle to own tracks (needed later for muting and unmuting)
|
|
|
|
+ myAudio: null,
|
|
|
|
+ myVideo: null,
|
|
|
|
+
|
|
|
|
+ // sockets
|
|
|
|
+ backendWsURL: '{{ config('app.backend_ws_url') }}',
|
|
|
|
+ socketClient: null,
|
|
|
|
+
|
|
|
|
+ // cache elements to avoid running selectors everytime
|
|
|
|
+ // Notation: $ at the beginning for jQuery objects
|
|
|
|
+ $btnStartVideo: null,
|
|
|
|
+ $videoContainer: null,
|
|
|
|
+ $selfView: null,
|
|
|
|
+ $callActions: null,
|
|
|
|
+ $btnHangUp: null,
|
|
|
|
+ $btnStopCamera: null,
|
|
|
|
+ $btnStartCamera: null,
|
|
|
|
+ $btnMuteAudio: null,
|
|
|
|
+ $btnUnmuteAudio: null,
|
|
|
|
+
|
|
|
|
+ // methods
|
|
|
|
+ init: function() {
|
|
|
|
+
|
|
|
|
+ // to distinguish between reloads
|
|
|
|
+ this.log('page refreshed ----------------------------------');
|
|
|
|
+
|
|
|
|
+ // cache elements to avoid running selectors everytime
|
|
|
|
+ this.$btnStartVideo = $('#btn-start-video');
|
|
|
|
+ this.$videoContainer = $('#video-container');
|
|
|
|
+ this.$selfView = $('#self-view');
|
|
|
|
+ this.$callActions = $('#call-actions');
|
|
|
|
+ this.$btnHangUp = $('#btn-hang-up');
|
|
|
|
+
|
|
|
|
+ this.$btnStopCamera = $('#btn-stop-camera');
|
|
|
|
+ this.$btnStartCamera = $('#btn-start-camera');
|
|
|
|
+ this.$btnMuteAudio = $('#btn-mute-audio');
|
|
|
|
+ this.$btnUnmuteAudio = $('#btn-unmute-audio');
|
|
|
|
+
|
|
|
|
+ this.registerSocket(() => {
|
|
|
|
+ this.initMedia();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // event handlers
|
|
|
|
+ $(document)
|
|
|
|
+ .off('click.start-video', '#btn-start-video')
|
|
|
|
+ .on('click.start-video', '#btn-start-video', () => {
|
|
|
|
+ this.initMedia();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ $(document)
|
|
|
|
+ .off('click.hang-up', '#btn-hang-up')
|
|
|
|
+ .on('click.hang-up', '#btn-hang-up', () => {
|
|
|
|
+ this.leaveRoomAsPro();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ $(document)
|
|
|
|
+ .off('click.stop-camera', '#btn-stop-camera')
|
|
|
|
+ .on('click.stop-camera', '#btn-stop-camera', () => {
|
|
|
|
+ this.stopCamera();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ $(document)
|
|
|
|
+ .off('click.start-camera', '#btn-start-camera')
|
|
|
|
+ .on('click.start-camera', '#btn-start-camera', () => {
|
|
|
|
+ this.startCamera();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ $(document)
|
|
|
|
+ .off('click.mute-audio', '#btn-mute-audio')
|
|
|
|
+ .on('click.mute-audio', '#btn-mute-audio', () => {
|
|
|
|
+ this.muteAudio();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ $(document)
|
|
|
|
+ .off('click.unmute-audio', '#btn-unmute-audio')
|
|
|
|
+ .on('click.unmute-audio', '#btn-unmute-audio', () => {
|
|
|
|
+ this.unmuteAudio();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ this.enterRoomAsPro();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // register socket
|
|
|
|
+ registerSocket: function (_done) {
|
|
|
|
+ let socket = new SockJS(this.backendWsURL);
|
|
|
|
+ this.socketClient = Stomp.over(socket);
|
|
|
|
+ this.socketClient.connect({}, (frame) => {
|
|
|
|
+ this.socketClient.send("/app/register", {},
|
|
|
|
+ JSON.stringify({
|
|
|
|
+ sessionKey: '{{$performer->session_key}}'
|
|
|
|
+ })
|
|
|
|
+ );
|
|
|
|
+ window.setInterval(() => {
|
|
|
|
+ this.socketClient.send("/app/heartbeat", {},
|
|
|
|
+ JSON.stringify({sessionKey: '{{ request()->cookie('sessionKey') }}'})
|
|
|
|
+ );
|
|
|
|
+ }, 5000);
|
|
|
|
+
|
|
|
|
+ this.initSocketEvents();
|
|
|
|
+
|
|
|
|
+ _done.call(this);
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // on pro side, handle all events
|
|
|
|
+ initSocketEvents: function() {
|
|
|
|
+
|
|
|
|
+ this.socketClient.subscribe("/user/topic/myMicrophoneIsOn", (message) => {
|
|
|
|
+ this.log("WSE myMicrophoneIsOn received: " + message.body);
|
|
|
|
+ this.handleParticipantMicrophoneMutedChangeWSEvent(message, true);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.socketClient.subscribe("/user/topic/myMicrophoneIsOff", (message) => {
|
|
|
|
+ this.log("WSE myMicrophoneIsOff received:" + message.body);
|
|
|
|
+ this.handleParticipantMicrophoneMutedChangeWSEvent(message, false);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.log("Initialized WS event handlers")
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ handleParticipantMicrophoneMutedChangeWSEvent: function(_event, _value) {
|
|
|
|
+ if(_event && _event.body) {
|
|
|
|
+ let eventData = JSON.parse(_event.body);
|
|
|
|
+ if(eventData.performer !== '{{ $session->uid }}' && eventData.data) {
|
|
|
|
+ for (let i = 0; i < this.meetingData.otherParticipants.length; i++) {
|
|
|
|
+ if(this.meetingData.otherParticipants[i].uid === eventData.performer) {
|
|
|
|
+ this.meetingData.otherParticipants[i].media.isMicrophoneOn = _value;
|
|
|
|
+ if(!_value) {
|
|
|
|
+ $('.video-view[data-user-id="' + this.meetingData.otherParticipants[i].mediaServiceIdentifier + '"]')
|
|
|
|
+ .attr('participant-muted', 1);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ $('.video-view[data-user-id="' + this.meetingData.otherParticipants[i].mediaServiceIdentifier + '"]')
|
|
|
|
+ .removeAttr('participant-muted');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // join meeting
|
|
|
|
+ enterRoomAsPro: function () {
|
|
|
|
+ this.ajax('/api/meeting/enterClientRoomAsPro', {clientUid: this.clientUid}, (_data) => {
|
|
|
|
+ this.getMeetingInfo(true, () => {
|
|
|
|
+ this.$btnStartVideo.prop('disabled', false);
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // leave meeting
|
|
|
|
+ leaveRoomAsPro: function() {
|
|
|
|
+
|
|
|
|
+ // notify java
|
|
|
|
+ this.socketClient.send("/app/leaveClientRoom",
|
|
|
|
+ {},
|
|
|
|
+ JSON.stringify({sessionKey: '{{$performer->session_key}}'})
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // refresh to non-client page
|
|
|
|
+ window.location.href = '/pro/meet/';
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // get meeting info and populate model
|
|
|
|
+ getMeetingInfo: function (_firstRun = false, _done = null) {
|
|
|
|
+ this.ajax('/api/meeting/getMyMeeting', {}, (_data) => {
|
|
|
|
+
|
|
|
|
+ // fill model
|
|
|
|
+ this.meetingData = _data.data;
|
|
|
|
+
|
|
|
|
+ // set own myMediaServiceIdentifier to #selfView
|
|
|
|
+ if(_firstRun) this.$selfView.attr('data-user-id', this.meetingData.myMediaServiceIdentifier);
|
|
|
|
+
|
|
|
|
+ if(_done) _done.call(this);
|
|
|
|
+ }, 'json');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // init media
|
|
|
|
+ initMedia: function() {
|
|
|
|
+
|
|
|
|
+ // create client
|
|
|
|
+ this.mediaServiceClient = AgoraRTC.createClient({mode: 'rtc', codec: 'vp8'});
|
|
|
|
+
|
|
|
|
+ // no need to acquire devices in check-video page
|
|
|
|
+ /*this.attemptToAcquireMicrophone(() => {
|
|
|
|
+ this.attemptToAcquireCamera(() => {
|
|
|
|
+ this.log('Hurrah! Mic and camera both acquired :)', 'log');
|
|
|
|
+
|
|
|
|
+ // go to video UI
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
|
|
+ this.$videoContainer.removeClass('d-none').addClass('d-block');
|
|
|
|
+
|
|
|
|
+ // show call actions
|
|
|
|
+ this.$callActions.removeClass('d-none').addClass('d-flex');
|
|
|
|
+
|
|
|
|
+ // show own video
|
|
|
|
+ this.$selfView.height(this.$selfView.width() * 0.75); // 4x3 mode
|
|
|
|
+ this.myVideo.play(this.$selfView[0], {fit: 'contain'});
|
|
|
|
+
|
|
|
|
+ // show own name-bar
|
|
|
|
+ this.$selfView.find('.name-bar').remove();
|
|
|
|
+ this.$selfView.append($('<div class="name-bar"></div>').text(this.myName));
|
|
|
|
+
|
|
|
|
+ // start listening to events
|
|
|
|
+ this.initMediaEvents();
|
|
|
|
+
|
|
|
|
+ // start publishing
|
|
|
|
+ this.initMediaPublishing();
|
|
|
|
+
|
|
|
|
+ })
|
|
|
|
+ });*/
|
|
|
|
+
|
|
|
|
+ // go to video UI
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
|
|
+ this.$videoContainer.removeClass('d-none').addClass('d-block');
|
|
|
|
+
|
|
|
|
+ // show call actions
|
|
|
|
+ this.$callActions.removeClass('d-none').addClass('d-flex');
|
|
|
|
+
|
|
|
|
+ // own feed not to be displayed in check-video
|
|
|
|
+ /*// show own video
|
|
|
|
+ this.$selfView.height(this.$selfView.width() * 0.75); // 4x3 mode
|
|
|
|
+ this.myVideo.play(this.$selfView[0], {fit: 'contain'});
|
|
|
|
+
|
|
|
|
+ // show own name-bar
|
|
|
|
+ this.$selfView.find('.name-bar').remove();
|
|
|
|
+ this.$selfView.append($('<div class="name-bar"></div>').text(this.myName));*/
|
|
|
|
+
|
|
|
|
+ // start listening to events
|
|
|
|
+ this.initMediaEvents();
|
|
|
|
+
|
|
|
|
+ // start publishing
|
|
|
|
+ this.initMediaPublishing();
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ attemptToAcquireMicrophone: function(_done) {
|
|
|
|
+ AgoraRTC.onMicrophoneChanged = (_info) => {
|
|
|
|
+ this.log("microphone changed! - " + JSON.stringify(_info.state), 'log');
|
|
|
|
+ if(_info.state === 'ACTIVE') {
|
|
|
|
+
|
|
|
|
+ if(!this.myAudio || !this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
+ window.location.reload();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // reactive acquisition (because user later granted access)
|
|
|
|
+ this.acquireMicrophone().then(() => {
|
|
|
|
+ this.log('microphone acquisition attempt completed', 'log');
|
|
|
|
+
|
|
|
|
+ // if all good, allow to proceed
|
|
|
|
+ if(this.myAudio && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
+ _done.call(this);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // proactive acquisition (already allowed OR user clicked "Allow" when prompted
|
|
|
|
+ this.acquireMicrophone().then(() => {
|
|
|
|
+ this.log('microphone acquisition attempt completed', 'log');
|
|
|
|
+
|
|
|
|
+ // if all good, allow to proceed
|
|
|
|
+ if(this.myAudio && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
+ _done.call(this);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ acquireMicrophone: async function() {
|
|
|
|
+
|
|
|
|
+ // if already acquired, ignore
|
|
|
|
+ if(this.myAudio && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
+ // Skipping microphone acquisition..
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.meetingData.myMedia.isMicrophoneAcquired = false;
|
|
|
|
+ this.meetingData.myMedia.isMicrophoneOn = false;
|
|
|
|
+ try {
|
|
|
|
+ this.myAudio = await AgoraRTC.createMicrophoneAudioTrack({
|
|
|
|
+ AEC: true,
|
|
|
|
+ AGC: true,
|
|
|
|
+ ANS: true,
|
|
|
|
+ encoderConfig: 'speech_standard'
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.meetingData.myMedia.isMicrophoneAcquired = true;
|
|
|
|
+ this.log('acquired microphone :)');
|
|
|
|
+ // TODO: notify others via WS
|
|
|
|
+
|
|
|
|
+ this.myAudio.setEnabled(true);
|
|
|
|
+ this.myAudio.setVolume(100); // default volume, max allowed is 1000
|
|
|
|
+ this.meetingData.myMedia.isMicrophoneOn = true;
|
|
|
|
+ this.log('microphone is ON and not on mute :)');
|
|
|
|
+ // TODO: notify others via WS
|
|
|
|
+
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.log('could not acquire microphone', 'error');
|
|
|
|
+
|
|
|
|
+ this.meetingData.myMedia.isMicrophoneAcquired = false;
|
|
|
|
+ this.meetingData.myMedia.isMicrophoneOn = false;
|
|
|
|
+ // TODO: notify others via WS
|
|
|
|
+
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
|
|
+ // TODO: Use device/browser specific instruction & image
|
|
|
|
+ $('.mic-access-chrome-desktop').removeClass('d-none').addClass('d-block');
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ attemptToAcquireCamera: function(_done) {
|
|
|
|
+ AgoraRTC.onCameraChanged = (_info) => {
|
|
|
|
+ this.log("camera changed! - " + JSON.stringify(_info));
|
|
|
|
+ if(_info.state === 'ACTIVE') {
|
|
|
|
+
|
|
|
|
+ if(!this.myVideo || !this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
+ window.location.reload();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // reactive acquisition (because user later granted access)
|
|
|
|
+ this.acquireCamera().then(() => {
|
|
|
|
+ this.log('camera acquisition attempt completed');
|
|
|
|
+
|
|
|
|
+ // if all good, allow to proceed
|
|
|
|
+ if(this.myVideo && this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
+ _done.call(this);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // proactive acquisition (already allowed OR user clicked "Allow" when prompted
|
|
|
|
+ this.acquireCamera().then(() => {
|
|
|
|
+ this.log('camera acquisition attempt completed');
|
|
|
|
+
|
|
|
|
+ // if all good, allow to proceed
|
|
|
|
+ if(this.myVideo && this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
+ _done.call(this);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ acquireCamera: async function() {
|
|
|
|
+
|
|
|
|
+ // if already acquired, ignore
|
|
|
|
+ if(this.myVideo && this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
+ // Skipping camera acquisition
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.meetingData.myMedia.isCameraAcquired = false;
|
|
|
|
+ this.meetingData.myMedia.isCameraOn = false;
|
|
|
|
+ try {
|
|
|
|
+ this.myVideo = await AgoraRTC.createCameraVideoTrack({
|
|
|
|
+ optimizationMode: "motion"
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.meetingData.myMedia.isCameraAcquired = true;
|
|
|
|
+ this.log('acquired camera :)');
|
|
|
|
+ // TODO: notify others via WS
|
|
|
|
+
|
|
|
|
+ this.myVideo.setEnabled(true);
|
|
|
|
+ this.meetingData.myMedia.isCameraOn = true;
|
|
|
|
+ this.log('camera is ON :)');
|
|
|
|
+ // TODO: notify others via WS
|
|
|
|
+
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.log('could not acquire camera', 'error');
|
|
|
|
+
|
|
|
|
+ this.meetingData.myMedia.isCameraAcquired = false;
|
|
|
|
+ this.meetingData.myMedia.isCameraOn = false;
|
|
|
|
+ // TODO: notify others via WS
|
|
|
|
+
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
|
|
+ // TODO: Use device/browser specific instruction & image
|
|
|
|
+ $('.cam-access-chrome-desktop').removeClass('d-none').addClass('d-block');
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // join agora & start publishing
|
|
|
|
+ getMyMediaServiceToken: function(_done) {
|
|
|
|
+ $.post('/api/meeting/refreshMyMediaServiceToken', (_data) => { // get new agora token
|
|
|
|
+ if (!this.hasError(_data)) {
|
|
|
|
+ this.meetingData.myMediaServiceToken = _data.data;
|
|
|
|
+ _done.call(this);
|
|
|
|
+ }
|
|
|
|
+ }, 'json');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // init media events
|
|
|
|
+ initMediaEvents: function() {
|
|
|
|
+ this.mediaServiceClient.on('user-left', user => {
|
|
|
|
+ // remove user's video div
|
|
|
|
+ $('.video-view[data-user-id="' + user.uid + '"]').remove();
|
|
|
|
+
|
|
|
|
+ // refresh state using getMyMeeting
|
|
|
|
+ this.getMeetingInfo();
|
|
|
|
+
|
|
|
|
+ // TODO log
|
|
|
|
+ });
|
|
|
|
+ this.mediaServiceClient.on('user-published', async (user, mediaType) => {
|
|
|
|
+ this.log('user-published - ' + user.uid);
|
|
|
|
+
|
|
|
|
+ // subscribe to the stream
|
|
|
|
+ try {
|
|
|
|
+ await this.mediaServiceClient.subscribe(user, mediaType);
|
|
|
|
+
|
|
|
|
+ // subscription success, proceed to playing the stream
|
|
|
|
+ if(mediaType === 'video') {
|
|
|
|
+ let element = $('.video-view[data-user-id="' + user.uid + '"]');
|
|
|
|
+ if(!element.length) {
|
|
|
|
+ element = $('<div class="video-view" data-user-id="' + user.uid + '"></div>');
|
|
|
|
+ element.appendTo('.main-view');
|
|
|
|
+ }
|
|
|
|
+ element.height(element.width() * 0.75); // 4x3 mode
|
|
|
|
+ user.videoTrack.play(element[0], {fit: 'contain'});
|
|
|
|
+
|
|
|
|
+ // refreshState & show user's name over video
|
|
|
|
+ this.getMeetingInfo(false, () => {
|
|
|
|
+ let participant = null;
|
|
|
|
+ for (let i = 0; i < this.meetingData.otherParticipants.length; i++) {
|
|
|
|
+ if (this.meetingData.otherParticipants[i].mediaServiceIdentifier === ('' + user.uid)) {
|
|
|
|
+ participant = this.meetingData.otherParticipants[i];
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (participant) {
|
|
|
|
+ element.find('.name-bar').remove();
|
|
|
|
+ element.append($('<div class="name-bar"></div>').text(participant.displayName));
|
|
|
|
+ element.append('<i class="fa fa-volume-mute text-white muted-icon"></i>');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ else if(mediaType === 'audio') {
|
|
|
|
+ user.audioTrack.play();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.log('got ' + mediaType + ' stream from user: ' + user.uid);
|
|
|
|
+ }
|
|
|
|
+ catch (e) {
|
|
|
|
+ this.log('could not subscribe to ' + mediaType + ' from ' + user.uid + ' - ' + e.toString(), 'error');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ initMediaPublishing: function() {
|
|
|
|
+
|
|
|
|
+ // get a new token
|
|
|
|
+ this.getMyMediaServiceToken(async () => {
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+
|
|
|
|
+ // join
|
|
|
|
+ await this.mediaServiceClient.join(
|
|
|
|
+ this.appId,
|
|
|
|
+ this.meetingData.inMeetingForClient.clientMediaServiceRoomIdentifier,
|
|
|
|
+ this.meetingData.myMediaServiceToken,
|
|
|
|
+ +this.meetingData.myMediaServiceIdentifier
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ this.log('Joined agora channel :)');
|
|
|
|
+ }
|
|
|
|
+ catch (e) {
|
|
|
|
+ this.log('could not join the room! - ' + e.toString(), 'error');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // no publishing in check-video page
|
|
|
|
+ /*try {
|
|
|
|
+ // publish
|
|
|
|
+ await this.mediaServiceClient.publish([this.myAudio, this.myVideo]);
|
|
|
|
+
|
|
|
|
+ this.log('Started publishing own audio and video to channel :)');
|
|
|
|
+ }
|
|
|
|
+ catch (e) {
|
|
|
|
+ this.log('could not publish media to media service! - ' + e.toString(), 'error');
|
|
|
|
+ }*/
|
|
|
|
+
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // mute/unmute cam/mic
|
|
|
|
+ stopCamera: async function () {
|
|
|
|
+ try {
|
|
|
|
+ await this.myVideo.setEnabled(false);
|
|
|
|
+ this.socketClient.send("/app/myCameraIsOff", {},
|
|
|
|
+ JSON.stringify({sessionKey: '{{$performer->session_key}}'})
|
|
|
|
+ );
|
|
|
|
+ this.$btnStopCamera.addClass('d-none');
|
|
|
|
+ this.$btnStartCamera.removeClass('d-none');
|
|
|
|
+ // TODO log
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.log('could not stop camera! - ' + e.toString(), 'error');
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ startCamera: async function () {
|
|
|
|
+ try {
|
|
|
|
+ await this.myVideo.setEnabled(true);
|
|
|
|
+ this.socketClient.send("/app/myCameraIsOn", {},
|
|
|
|
+ JSON.stringify({sessionKey: '{{$performer->session_key}}'})
|
|
|
|
+ );
|
|
|
|
+ this.$btnStartCamera.addClass('d-none');
|
|
|
|
+ this.$btnStopCamera.removeClass('d-none');
|
|
|
|
+ // TODO log
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.log('could not start camera! - ' + e.toString(), 'error');
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ muteAudio: async function () {
|
|
|
|
+ try {
|
|
|
|
+ await this.myAudio.setEnabled(false);
|
|
|
|
+ this.socketClient.send("/app/myMicrophoneIsOff", {},
|
|
|
|
+ JSON.stringify({sessionKey: '{{$performer->session_key}}'})
|
|
|
|
+ );
|
|
|
|
+ this.$btnMuteAudio.addClass('d-none');
|
|
|
|
+ this.$btnUnmuteAudio.removeClass('d-none');
|
|
|
|
+ this.$selfView.attr('participant-muted', 1);
|
|
|
|
+ // TODO log
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.log('could not mute audio! - ' + e.toString(), 'error');
|
|
|
|
+ // TODO log
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ unmuteAudio: async function () {
|
|
|
|
+ try {
|
|
|
|
+ await this.myAudio.setEnabled(true);
|
|
|
|
+ this.socketClient.send("/app/myMicrophoneIsOn", {},
|
|
|
|
+ JSON.stringify({sessionKey: '{{$performer->session_key}}'})
|
|
|
|
+ );
|
|
|
|
+ this.$btnUnmuteAudio.addClass('d-none');
|
|
|
|
+ this.$btnMuteAudio.removeClass('d-none');
|
|
|
|
+ this.$selfView.removeAttr('participant-muted');
|
|
|
|
+ // TODO log
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.log('could not unmute audio! ' + e.toString(), 'error');
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // utils - start ============================================================== //
|
|
|
|
+
|
|
|
|
+ // generic logger
|
|
|
|
+ log: function(_message, _type = 'log') {
|
|
|
|
+ console[_type](
|
|
|
|
+ 'StagVideo => ' + '[' + (new Date()).toLocaleTimeString() + '] ' + this.myName,
|
|
|
|
+ _message);
|
|
|
|
+ // post to userEventLog
|
|
|
|
+ $.post('/api/userEventLog/create', {
|
|
|
|
+ eventName: 'video-event',
|
|
|
|
+ dataJson: JSON.stringify({type: _type, message: _message})
|
|
|
|
+ }, function () {}, 'json');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // ajax request with error checking and reporting
|
|
|
|
+ ajax: function (_url, _input, _callback) {
|
|
|
|
+ $.post(_url, _input, (_data) => {
|
|
|
|
+ if (!this.hasError(_data)) {
|
|
|
|
+ _callback.call(this, _data);
|
|
|
|
+ }
|
|
|
|
+ }, 'json');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // check and report error
|
|
|
|
+ hasError: function (_data) {
|
|
|
|
+ let msg = 'Unknown error!';
|
|
|
|
+ if (_data) {
|
|
|
|
+ if (_data.success) return false;
|
|
|
|
+ else if (_data.message) msg = _data.message;
|
|
|
|
+ }
|
|
|
|
+ toastr.error(msg);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // utils - end ============================================================== //
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ window.StagVideo.init();
|
|
|
|
+
|
|
|
|
+ }).call(window);
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+</body>
|
|
|
|
+</html>
|