|
@@ -18,7 +18,7 @@
|
|
|
|
|
|
<body class="p-0 m-0">
|
|
<body class="p-0 m-0">
|
|
|
|
|
|
-<div class="d-flex px-3 border-bottom mb-3">
|
|
|
|
|
|
+<div class="d-flex px-3 border-bottom">
|
|
<div class="py-2 font-weight-normal mcp-theme-1 d-inline-flex align-items-center">
|
|
<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>
|
|
<i class="fa fa-user-injured small mr-2"></i>
|
|
<a href="#" onclick="return window.top.openInLHS('/patients/view/{{$client->uid}}')">
|
|
<a href="#" onclick="return window.top.openInLHS('/patients/view/{{$client->uid}}')">
|
|
@@ -51,9 +51,9 @@
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
- <div id="video-container">
|
|
|
|
|
|
+ <div id="video-container" class="container">
|
|
<div class="main-view mx-auto">
|
|
<div class="main-view mx-auto">
|
|
- <div id="self-view" class="video-view"></div>
|
|
|
|
|
|
+ <div id="self-view" class="video-view" data-user-id=""></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
@@ -120,18 +120,31 @@
|
|
clientUid: '{{ $client->uid }}',
|
|
clientUid: '{{ $client->uid }}',
|
|
|
|
|
|
// agora
|
|
// agora
|
|
- mediaServiceClient: null, // set on agora init
|
|
|
|
|
|
+ mediaServiceClient: null, // instantiated on agora init
|
|
appId: '{{ config('app.agora_appid') }}',
|
|
appId: '{{ config('app.agora_appid') }}',
|
|
- channel: '', // set on mount
|
|
|
|
- myMicrophone: null,
|
|
|
|
- myCamera: null,
|
|
|
|
|
|
+
|
|
|
|
+ // handle to own tracks (needed later for muting and unmuting)
|
|
|
|
+ myAudio: null,
|
|
|
|
+ myVideo: null,
|
|
|
|
|
|
// sockets
|
|
// sockets
|
|
backendWsURL: 'http://localhost:8080/ws', // '{{ config('app.backend_ws_url') }}',
|
|
backendWsURL: 'http://localhost:8080/ws', // '{{ config('app.backend_ws_url') }}',
|
|
socketClient: null,
|
|
socketClient: null,
|
|
|
|
|
|
|
|
+ // cache elements to avoid running selectors everytime
|
|
|
|
+ // Notation: $ at the beginning for jQuery objects
|
|
|
|
+ $btnStartVideo: null,
|
|
|
|
+ $videoContainer: null,
|
|
|
|
+ $selfView: null,
|
|
|
|
+
|
|
// methods
|
|
// methods
|
|
init: function() {
|
|
init: function() {
|
|
|
|
+
|
|
|
|
+ // cache elements to avoid running selectors everytime
|
|
|
|
+ this.$btnStartVideo = $('#btn-start-video');
|
|
|
|
+ this.$videoContainer = $('#video-container');
|
|
|
|
+ this.$selfView = $('#self-view');
|
|
|
|
+
|
|
this.registerSocket();
|
|
this.registerSocket();
|
|
|
|
|
|
// event handlers
|
|
// event handlers
|
|
@@ -168,16 +181,22 @@
|
|
enterRoomAsPro: function () {
|
|
enterRoomAsPro: function () {
|
|
this.ajax('/api/meeting/enterClientRoomAsPro', {clientUid: this.clientUid}, (_data) => {
|
|
this.ajax('/api/meeting/enterClientRoomAsPro', {clientUid: this.clientUid}, (_data) => {
|
|
this.getMeetingInfo(true, () => {
|
|
this.getMeetingInfo(true, () => {
|
|
- $('#btn-start-video').prop('disabled', false);
|
|
|
|
|
|
+ this.$btnStartVideo.prop('disabled', false);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
},
|
|
},
|
|
|
|
|
|
// get meeting info and populate model
|
|
// get meeting info and populate model
|
|
- getMeetingInfo: function (_firstRun, _done) {
|
|
|
|
|
|
+ getMeetingInfo: function (_firstRun = false, _done = null) {
|
|
this.ajax('/api/meeting/getMyMeeting', {}, (_data) => {
|
|
this.ajax('/api/meeting/getMyMeeting', {}, (_data) => {
|
|
- this.meetingData = _data.data; // fill model
|
|
|
|
- _done.call(this);
|
|
|
|
|
|
+
|
|
|
|
+ // 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');
|
|
}, 'json');
|
|
},
|
|
},
|
|
|
|
|
|
@@ -193,10 +212,19 @@
|
|
console.log('ALIX: Hurrah! Mic and camera both acquired :)');
|
|
console.log('ALIX: Hurrah! Mic and camera both acquired :)');
|
|
|
|
|
|
// go to video UI
|
|
// go to video UI
|
|
- $('#btn-start-video').removeClass('d-block').addClass('d-none');
|
|
|
|
- $('#video-container').removeClass('d-none').addClass('d-block');
|
|
|
|
- $('#self-view').height($('#self-view').width() * 0.75);
|
|
|
|
- this.myCamera.play($('#self-view')[0], {fit: 'contain'});
|
|
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
|
|
+ this.$videoContainer.removeClass('d-none').addClass('d-block');
|
|
|
|
+
|
|
|
|
+ // show own video
|
|
|
|
+ this.$selfView.height(this.$selfView.width() * 0.75); // 4x3 mode
|
|
|
|
+ this.myVideo.play(this.$selfView[0], {fit: 'contain'});
|
|
|
|
+
|
|
|
|
+ // start listening to events
|
|
|
|
+ this.initMediaEvents();
|
|
|
|
+
|
|
|
|
+ // start publishing
|
|
|
|
+ this.initMediaPublishing();
|
|
|
|
+
|
|
})
|
|
})
|
|
});
|
|
});
|
|
},
|
|
},
|
|
@@ -206,7 +234,7 @@
|
|
console.log("ALIX: microphone changed!", _info.state, _info.device);
|
|
console.log("ALIX: microphone changed!", _info.state, _info.device);
|
|
if(_info.state === 'ACTIVE') {
|
|
if(_info.state === 'ACTIVE') {
|
|
|
|
|
|
- if(!this.myMicrophone || !this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
|
|
+ if(!this.myAudio || !this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
window.location.reload();
|
|
window.location.reload();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -217,7 +245,7 @@
|
|
console.log('ALIX: (reactive) isMicrophoneOn = ', this.meetingData.myMedia.isMicrophoneOn);
|
|
console.log('ALIX: (reactive) isMicrophoneOn = ', this.meetingData.myMedia.isMicrophoneOn);
|
|
|
|
|
|
// if all good, allow to proceed
|
|
// if all good, allow to proceed
|
|
- if(this.myMicrophone && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
|
|
+ if(this.myAudio && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
_done.call(this);
|
|
_done.call(this);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
@@ -231,7 +259,7 @@
|
|
console.log('ALIX: (proactive) isMicrophoneOn = ', this.meetingData.myMedia.isMicrophoneOn);
|
|
console.log('ALIX: (proactive) isMicrophoneOn = ', this.meetingData.myMedia.isMicrophoneOn);
|
|
|
|
|
|
// if all good, allow to proceed
|
|
// if all good, allow to proceed
|
|
- if(this.myMicrophone && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
|
|
+ if(this.myAudio && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
_done.call(this);
|
|
_done.call(this);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
@@ -240,7 +268,7 @@
|
|
acquireMicrophone: async function() {
|
|
acquireMicrophone: async function() {
|
|
|
|
|
|
// if already acquired, ignore
|
|
// if already acquired, ignore
|
|
- if(this.myMicrophone && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
|
|
|
|
+ if(this.myAudio && this.meetingData.myMedia.isMicrophoneAcquired) {
|
|
console.log("ALIX: Skipping microphone acquisition..")
|
|
console.log("ALIX: Skipping microphone acquisition..")
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -248,15 +276,15 @@
|
|
this.meetingData.myMedia.isMicrophoneAcquired = false;
|
|
this.meetingData.myMedia.isMicrophoneAcquired = false;
|
|
this.meetingData.myMedia.isMicrophoneOn = false;
|
|
this.meetingData.myMedia.isMicrophoneOn = false;
|
|
try {
|
|
try {
|
|
- this.myMicrophone = await AgoraRTC.createMicrophoneAudioTrack();
|
|
|
|
|
|
+ this.myAudio = await AgoraRTC.createMicrophoneAudioTrack();
|
|
|
|
|
|
console.log('ALIX: acquired microphone :)');
|
|
console.log('ALIX: acquired microphone :)');
|
|
this.meetingData.myMedia.isMicrophoneAcquired = true;
|
|
this.meetingData.myMedia.isMicrophoneAcquired = true;
|
|
// TODO: log
|
|
// TODO: log
|
|
// TODO: notify others via WS
|
|
// TODO: notify others via WS
|
|
|
|
|
|
- this.myMicrophone.setEnabled(true);
|
|
|
|
- this.myMicrophone.setVolume(1000); // 1000 is the max allowed value
|
|
|
|
|
|
+ this.myAudio.setEnabled(true);
|
|
|
|
+ this.myAudio.setVolume(1000); // 1000 is the max allowed value
|
|
this.meetingData.myMedia.isMicrophoneOn = true;
|
|
this.meetingData.myMedia.isMicrophoneOn = true;
|
|
console.log('ALIX: microphone is ON and not on mute :)');
|
|
console.log('ALIX: microphone is ON and not on mute :)');
|
|
// TODO: log
|
|
// TODO: log
|
|
@@ -270,7 +298,7 @@
|
|
// TODO: log
|
|
// TODO: log
|
|
// TODO: notify others via WS
|
|
// TODO: notify others via WS
|
|
|
|
|
|
- $('#btn-start-video').removeClass('d-block').addClass('d-none');
|
|
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
// TODO: Use device/browser specific instruction & image
|
|
// TODO: Use device/browser specific instruction & image
|
|
$('.mic-access-chrome-desktop').removeClass('d-none').addClass('d-block');
|
|
$('.mic-access-chrome-desktop').removeClass('d-none').addClass('d-block');
|
|
}
|
|
}
|
|
@@ -281,7 +309,7 @@
|
|
console.log("ALIX: camera changed!", _info.state, _info.device);
|
|
console.log("ALIX: camera changed!", _info.state, _info.device);
|
|
if(_info.state === 'ACTIVE') {
|
|
if(_info.state === 'ACTIVE') {
|
|
|
|
|
|
- if(!this.myCamera || !this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
|
|
+ if(!this.myVideo || !this.meetingData.myMedia.isCameraAcquired) {
|
|
window.location.reload();
|
|
window.location.reload();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -292,7 +320,7 @@
|
|
console.log('ALIX: (reactive) isCameraOn = ', this.meetingData.myMedia.isCameraOn);
|
|
console.log('ALIX: (reactive) isCameraOn = ', this.meetingData.myMedia.isCameraOn);
|
|
|
|
|
|
// if all good, allow to proceed
|
|
// if all good, allow to proceed
|
|
- if(this.myCamera && this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
|
|
+ if(this.myVideo && this.meetingData.myMedia.isCameraAcquired) {
|
|
_done.call(this);
|
|
_done.call(this);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
@@ -306,7 +334,7 @@
|
|
console.log('ALIX: (proactive) isCameraOn = ', this.meetingData.myMedia.isCameraOn);
|
|
console.log('ALIX: (proactive) isCameraOn = ', this.meetingData.myMedia.isCameraOn);
|
|
|
|
|
|
// if all good, allow to proceed
|
|
// if all good, allow to proceed
|
|
- if(this.myCamera && this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
|
|
+ if(this.myVideo && this.meetingData.myMedia.isCameraAcquired) {
|
|
_done.call(this);
|
|
_done.call(this);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
@@ -315,7 +343,7 @@
|
|
acquireCamera: async function() {
|
|
acquireCamera: async function() {
|
|
|
|
|
|
// if already acquired, ignore
|
|
// if already acquired, ignore
|
|
- if(this.myCamera && this.meetingData.myMedia.isCameraAcquired) {
|
|
|
|
|
|
+ if(this.myVideo && this.meetingData.myMedia.isCameraAcquired) {
|
|
console.log("ALIX: Skipping camera acquisition..")
|
|
console.log("ALIX: Skipping camera acquisition..")
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -323,7 +351,7 @@
|
|
this.meetingData.myMedia.isCameraAcquired = false;
|
|
this.meetingData.myMedia.isCameraAcquired = false;
|
|
this.meetingData.myMedia.isCameraOn = false;
|
|
this.meetingData.myMedia.isCameraOn = false;
|
|
try {
|
|
try {
|
|
- this.myCamera = await AgoraRTC.createCameraVideoTrack({
|
|
|
|
|
|
+ this.myVideo = await AgoraRTC.createCameraVideoTrack({
|
|
optimizationMode: "motion"
|
|
optimizationMode: "motion"
|
|
});
|
|
});
|
|
|
|
|
|
@@ -332,7 +360,7 @@
|
|
// TODO: log
|
|
// TODO: log
|
|
// TODO: notify others via WS
|
|
// TODO: notify others via WS
|
|
|
|
|
|
- this.myCamera.setEnabled(true);
|
|
|
|
|
|
+ this.myVideo.setEnabled(true);
|
|
this.meetingData.myMedia.isCameraOn = true;
|
|
this.meetingData.myMedia.isCameraOn = true;
|
|
console.log('ALIX: camera is ON :)');
|
|
console.log('ALIX: camera is ON :)');
|
|
// TODO: log
|
|
// TODO: log
|
|
@@ -346,22 +374,95 @@
|
|
// TODO: log
|
|
// TODO: log
|
|
// TODO: notify others via WS
|
|
// TODO: notify others via WS
|
|
|
|
|
|
- $('#btn-start-video').removeClass('d-block').addClass('d-none');
|
|
|
|
|
|
+ this.$btnStartVideo.removeClass('d-block').addClass('d-none');
|
|
// TODO: Use device/browser specific instruction & image
|
|
// TODO: Use device/browser specific instruction & image
|
|
$('.cam-access-chrome-desktop').removeClass('d-none').addClass('d-block');
|
|
$('.cam-access-chrome-desktop').removeClass('d-none').addClass('d-block');
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
|
|
- // acquire own camera & mic and start publishing
|
|
|
|
- initMediaPublishing: function() {
|
|
|
|
-
|
|
|
|
|
|
+ // 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 subscriptions
|
|
|
|
- initMediaSubscriptions: function() {
|
|
|
|
|
|
+ // 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) => {
|
|
|
|
+ console.log('ALIX user-published', user);
|
|
|
|
+
|
|
|
|
+ // 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'});
|
|
|
|
+ }
|
|
|
|
+ else if(mediaType === 'audio') {
|
|
|
|
+ user.audioTrack.play();
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ // TODO log
|
|
|
|
+ }
|
|
|
|
+ catch (e) {
|
|
|
|
+ console.error('ALIX: could not subscribe to ' + mediaType + ' from ', user, e);
|
|
|
|
+ // TODO log
|
|
|
|
+ }
|
|
|
|
+ });
|
|
},
|
|
},
|
|
|
|
|
|
|
|
+ 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
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ catch (e) {
|
|
|
|
+ console.error('ALIX: could not join the room!', e);
|
|
|
|
+ // TODO log
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // publish
|
|
|
|
+ await this.mediaServiceClient.publish([this.myAudio, this.myVideo]);
|
|
|
|
+ }
|
|
|
|
+ catch (e) {
|
|
|
|
+ console.error('ALIX: could not publish media to media service!', e);
|
|
|
|
+ // TODO log
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
|
|
// utils - start ============================================================== //
|
|
// utils - start ============================================================== //
|
|
|
|
|