464 lines
18 KiB
Vue
464 lines
18 KiB
Vue
<script setup>
|
|
import videoCameraOff from '@images/svg/video-camera-off.svg';
|
|
import Echo from 'laravel-echo';
|
|
import Pusher from 'pusher-js';
|
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useStore } from 'vuex';
|
|
const store = useStore()
|
|
const isLoadingVisible = ref(false);
|
|
const getIsTonalSnackbarVisible = ref(false);
|
|
const errorMessage = ref(null);
|
|
const videoElement = ref(null);
|
|
const isCameraEnable = ref(false);
|
|
const isMicrophoneEnable = ref(false);
|
|
const isCameraPermissionGranted = ref(false);
|
|
const isMicrophonePermissionGranted = ref(false);
|
|
const isCameraDisabled = ref(false);
|
|
const isMuted = ref(false);
|
|
const mediaStream = ref(null);
|
|
const enableCamBtn = ref(true)
|
|
const permissionModel = ref(false)
|
|
store.dispatch('updateCurrentPage', '/queue')
|
|
|
|
const camIcon = computed(() => {
|
|
return isCameraDisabled.value ? 'mdi-video-off' : 'mdi-video';
|
|
});
|
|
|
|
const camText = computed(() => {
|
|
return isCameraDisabled.value ? 'Turn on camera' : 'Turn off camera';
|
|
});
|
|
|
|
const camIconColor = computed(() => {
|
|
return isCameraDisabled.value ? 'red' : 'black';
|
|
});
|
|
|
|
const micIcon = computed(() => {
|
|
return isMuted.value ? 'mdi-microphone-off' : 'mdi-microphone';
|
|
});
|
|
|
|
const micText = computed(() => {
|
|
return isMuted.value ? 'Unmute Myself' : 'Mute Myself';
|
|
});
|
|
|
|
const micIconColor = computed(() => {
|
|
return isMuted.value ? 'red' : 'black';
|
|
});
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const queueUsers = ref([]);
|
|
const isDialogVisible = ref(true);
|
|
const patientId = ref(localStorage.getItem('patient_id'));
|
|
const currentQueue = computed(() => {
|
|
return queueUsers.value.findIndex(user => user.id == localStorage.getItem('patient_id')) + 1
|
|
});
|
|
const currentRoute = route.path;
|
|
console.log('currentRoute', currentRoute)
|
|
onMounted(async () => {
|
|
console.log('store mic cam permissions', store.getters.getIsMicEnabled, store.getters.getIsCamEnabled)
|
|
// permissionModel.value = true
|
|
// await checkPermissions();
|
|
// try {
|
|
// const camera = await navigator.permissions.query({ name: 'camera' });
|
|
// const microphone = await navigator.permissions.query({ name: 'microphone' });
|
|
// if (camera.state === 'granted') isCameraEnable.value = true;
|
|
// if (microphone.state === 'granted') isMicrophoneEnable.value = true;
|
|
// console.log(camera.state, microphone.state); // 'granted', 'denied', or 'prompt'
|
|
|
|
// } catch (error) { }
|
|
|
|
await checkMicCamPermissions();
|
|
window.Pusher = Pusher;
|
|
const key = 'bc8bffbbbc49cfa39818';
|
|
const cluster = 'mt1';
|
|
let echo = new Echo({
|
|
broadcaster: 'pusher',
|
|
|
|
key: key,
|
|
cluster: cluster,
|
|
forceTLS: true,
|
|
auth: {
|
|
headers: {
|
|
Authorization: 'Bearer ' + localStorage.getItem('access_token'),
|
|
},
|
|
},
|
|
});
|
|
//presence channel ((user bio show))
|
|
const presenceChannel = echo.join(`dhkjkiplqe84sdaqf17nqg`)
|
|
window.presenceChannel = presenceChannel
|
|
presenceChannel.here((users) => { //existing user/ including current user
|
|
// queueUsers.value = users.filter(user => user.type == "patient").sort((a, b) => a.time - b.time);
|
|
// queueUsers.value.forEach(user => {
|
|
// user.mic = isMicrophoneEnable.value;
|
|
// });
|
|
// console.log(queueUsers.value);
|
|
presenceChannel.whisper('DeviceCurrentStatus', {
|
|
mic: isMicrophoneEnable.value,
|
|
cam: isCameraEnable.value,
|
|
})
|
|
})
|
|
.joining((user) => {
|
|
//new user
|
|
console.log('joining', user.name);
|
|
queueUsers.value.push(user);
|
|
queueUsers.value.sort((a, b) => a.time - b.time);
|
|
console.log('queueUsers.value', queueUsers.value);
|
|
})
|
|
.leaving((user) => {
|
|
console.log('leaving', user);
|
|
queueUsers.value = queueUsers.value.filter(u => u.id !== user.id);
|
|
console.log("CurrentUser", queueUsers);
|
|
})
|
|
.listenForWhisper('getPermission-' + patientId.value, (e, metadata) => {
|
|
console.log('...', e, metadata)
|
|
presenceChannel.whisper('DeviceCurrentStatus-' + metadata.user_id, {
|
|
mic: store.getters.getIsMicEnabled,
|
|
cam: store.getters.getIsCamEnabled,
|
|
})
|
|
})
|
|
.error((error) => {
|
|
console.error(error);
|
|
});
|
|
|
|
// (private channel)
|
|
echo.private('patient-' + patientId.value)
|
|
.listen('AppointmentCreated', (e) => {
|
|
console.log("Patient AppointmentCreatedTest", e);
|
|
echo.leave('dhkjkiplqe84sdaqf17nqg')
|
|
localStorage.setItem('meeting_id', e.meeting_id);
|
|
localStorage.setItem('call_type', e.call_type);
|
|
console.log('Patient_meetingId', e.meeting_id);
|
|
if (!store.getters.getIsMicEnabled && !store.getters.getIsCamEnabled) {
|
|
permissionModel.value = true
|
|
} else {
|
|
router.push('/telehealth');
|
|
}
|
|
|
|
});
|
|
|
|
echo.private('patient-end-call-' + patientId.value)
|
|
.listen('AppointmentCallEnded', (e) => {
|
|
console.log("AppointmentCallEnded", e);
|
|
localStorage.setItem('call-end-message', 'Password Sent to Email Address')
|
|
router.push('/overview');
|
|
// echo.leave('dhkjkiplqe84sdaqf17nqg')
|
|
});
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (mediaStream.value) {
|
|
const tracks = mediaStream.value.getTracks();
|
|
tracks.forEach((track) => track.stop());
|
|
mediaStream.value = null;
|
|
}
|
|
});
|
|
const checkPermissionStatus = async (permissionName) => {
|
|
try {
|
|
const permissionStatus = await navigator.permissions.query({ name: permissionName });
|
|
return permissionStatus.state;
|
|
} catch (error) {
|
|
console.error(`Error querying ${permissionName} permission status:`, error);
|
|
return 'unknown';
|
|
}
|
|
};
|
|
|
|
const checkMicCamPermissions = async () => {
|
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
try {
|
|
const videoPermissionStatus = await checkPermissionStatus('camera');
|
|
const audioPermissionStatus = await checkPermissionStatus('microphone');
|
|
|
|
if (videoPermissionStatus === 'granted' && audioPermissionStatus === 'granted') {
|
|
console.log('Camera and microphone permissions already granted.');
|
|
isCameraEnable.value = true;
|
|
isMicrophoneEnable.value = true;
|
|
store.dispatch('updateIsCamEnabled', true);
|
|
store.dispatch('updateIsMicEnabled', true);
|
|
return;
|
|
}
|
|
|
|
const constraints = {
|
|
video: videoPermissionStatus !== 'granted' ? true : false,
|
|
audio: audioPermissionStatus !== 'granted' ? true : false
|
|
};
|
|
|
|
const mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
|
|
if (constraints.video && mediaStream.getVideoTracks().length > 0) {
|
|
isCameraEnable.value = true;
|
|
store.dispatch('updateIsCamEnabled', true);
|
|
console.log('Camera permission granted.');
|
|
mediaStream.getVideoTracks().forEach(track => track.stop());
|
|
}
|
|
|
|
if (constraints.audio && mediaStream.getAudioTracks().length > 0) {
|
|
isMicrophoneEnable.value = true;
|
|
store.dispatch('updateIsMicEnabled', true);
|
|
console.log('Microphone permission granted.');
|
|
mediaStream.getAudioTracks().forEach(track => track.stop());
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error requesting camera or microphone permissions:', error);
|
|
if (error.name === 'NotAllowedError') {
|
|
console.log('Camera or microphone permissions were not granted.');
|
|
}
|
|
}
|
|
} else {
|
|
console.error('navigator.mediaDevices is not supported in this browser.');
|
|
}
|
|
};
|
|
|
|
const checkPermissions = async () => {
|
|
try {
|
|
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
console.log('stream', stream)
|
|
if (stream) {
|
|
isCameraPermissionGranted.value = true;
|
|
isMicrophonePermissionGranted.value = true;
|
|
enableCamBtn.value = false
|
|
await nextTick();
|
|
videoElement.value.srcObject = stream;
|
|
mediaStream.value = stream;
|
|
|
|
}
|
|
} catch (error) {
|
|
isCameraDisabled.value = true;
|
|
enableCamBtn.value = false
|
|
getIsTonalSnackbarVisible.value = true;
|
|
errorMessage.value = "We can't access your camera and microphone.";
|
|
console.error('Error accessing media devices:', error);
|
|
}
|
|
};
|
|
|
|
const toggleCamera = () => {
|
|
if (!isCameraPermissionGranted.value) {
|
|
getIsTonalSnackbarVisible.value = true;
|
|
errorMessage.value = "We can't access your camera and microphone.";
|
|
} else {
|
|
isCameraDisabled.value = !isCameraDisabled.value;
|
|
|
|
// Stop the stream if the camera is being turned off
|
|
if (isCameraDisabled.value && mediaStream.value) {
|
|
const tracks = mediaStream.value.getTracks();
|
|
tracks.forEach((track) => track.stop());
|
|
mediaStream.value = null;
|
|
videoElement.value.srcObject = null;
|
|
}
|
|
|
|
// Start the stream if the camera is being turned on
|
|
if (!isCameraDisabled.value) {
|
|
checkPermissions();
|
|
}
|
|
}
|
|
};
|
|
|
|
const toggleMic = () => {
|
|
if (!isMicrophonePermissionGranted.value) {
|
|
getIsTonalSnackbarVisible.value = true;
|
|
errorMessage.value = "We can't access your camera and microphone.";
|
|
} else {
|
|
isMuted.value = !isMuted.value;
|
|
}
|
|
};
|
|
const enablePermission = async () => {
|
|
await checkMicCamPermissions();
|
|
await nextTick()
|
|
if (store.getters.getIsMicEnabled && store.getters.getIsCamEnabled) {
|
|
permissionModel.value = false
|
|
router.push('/telehealth');
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<VDialog v-model="isLoadingVisible" width="110" height="150" color="primary">
|
|
<VCardText class="" style="color: white !important;">
|
|
<div class="demo-space-x">
|
|
<VProgressCircular :size="40" color="primary" indeterminate />
|
|
</div>
|
|
</VCardText>
|
|
</VDialog>
|
|
|
|
<VDialog v-model="permissionModel" refs="myDialog" persistent width="500">
|
|
<!-- <template v-slot:default="{ isActive }"> -->
|
|
<v-card>
|
|
<v-card-text>
|
|
<div class="mt-2 mb-2">
|
|
<h4 class="text-center">Enable your Camera & Microphone permission from your browser</h4>
|
|
</div>
|
|
</v-card-text>
|
|
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn text="Grant Pesmission" @click="enablePermission()"></v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
<!-- </template> -->
|
|
</VDialog>
|
|
<VSnackbar v-model="getIsTonalSnackbarVisible" :timeout="5000" location="top end" variant="flat" color="red">
|
|
{{ errorMessage }}
|
|
</VSnackbar>
|
|
<VRow>
|
|
<VCol cols="12" md="9">
|
|
<VAlert border="start" color="red" variant="tonal" class="mb-2"
|
|
v-if="!store.getters.getIsMicEnabled && !store.getters.getIsCamEnabled">
|
|
<div class="">To proceed, please enable camera and microphone permissions in your browser's settings.
|
|
</div>
|
|
</VAlert>
|
|
<VCard>
|
|
<VCardText>
|
|
<p>
|
|
<b>Improved Access:</b><br>
|
|
Telehealth can provide access to healthcare for patients living in remote or underserved areas,
|
|
as well as those with mobility issues or busy schedules that make it difficult to attend
|
|
in-person appointments. This can be especially beneficial for individuals with chronic
|
|
conditions or those requiring specialized care that may not be readily available locally.
|
|
<br><br>
|
|
<b>Increased Convenience:</b><br>
|
|
Telehealth appointments allow patients to receive care from the comfort of their own homes,
|
|
eliminating the need for travel and reducing the time and costs associated with in-person
|
|
visits. This can be particularly advantageous for patients with limited transportation options
|
|
or those juggling work, family, and other commitments.
|
|
<br><br>
|
|
<b>Reduced Costs:</b><br>
|
|
Telehealth has the potential to lower healthcare costs by reducing the need for in-person
|
|
visits, transportation expenses, and time off work. This can benefit both patients and the
|
|
healthcare system as a whole.
|
|
<br><br>
|
|
<b>Expanded Care Options:</b><br>
|
|
Telehealth can enable access to specialists and healthcare providers who may not be available
|
|
locally, giving patients the opportunity to receive care from a broader range of medical
|
|
professionals.
|
|
<br><br>
|
|
<b>Increased Efficiency:</b><br>
|
|
Telehealth appointments can often be scheduled more easily and quickly than in-person visits,
|
|
potentially reducing wait times and improving the overall efficiency of the healthcare system.
|
|
</p>
|
|
</VCardText>
|
|
</VCard>
|
|
</VCol>
|
|
<VCol cols="12" md="3">
|
|
<VCard class="auth-card rounded-5 bg-black" :class="isCameraDisabled || enableCamBtn ? 'pa-2' : ''">
|
|
<div class="align-items-center justify-content-center" style="min-height:200px">
|
|
<!-- <v-menu v-if="isCameraPermissionGranted && isMicrophonePermissionGranted">
|
|
<template v-slot:activator="{ props }">
|
|
<v-btn icon="mdi-dots-horizontal" v-bind="props" class="menu-btn" size="small"
|
|
rounded="xl"></v-btn>
|
|
</template>
|
|
|
|
<v-list>
|
|
<v-list-item @click="toggleCamera()">
|
|
<v-list-item-title>
|
|
<VIcon :color="camIconColor" :icon="camIcon"></VIcon> {{ camText }}
|
|
</v-list-item-title>
|
|
</v-list-item>
|
|
<v-list-item @click="toggleMic()">
|
|
<v-list-item-title>
|
|
<VIcon :color="micIconColor" :icon="micIcon"></VIcon> {{ micText }}
|
|
</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu> -->
|
|
<div v-if="isCameraPermissionGranted && isMicrophonePermissionGranted" class="video-icons">
|
|
<v-btn @click="toggleCamera()" :class="isCameraDisabled ? 'video-icon-red' : 'video-icon-black'"
|
|
:color="camIconColor" :icon="camIcon" size="small" rounded="xl"></v-btn>
|
|
<v-btn @click="toggleMic()" class="ml-1"
|
|
:class="isMuted ? 'video-icon-red' : 'video-icon-black'" :color="micIconColor"
|
|
:icon="micIcon" size="small" rounded="xl"></v-btn>
|
|
</div>
|
|
<div class="text-center" :class="isCameraDisabled ? 'video-single-icon mb-1 pb-0 px-2' : ''">
|
|
<VImg v-if="isCameraDisabled" :src="videoCameraOff" height="50" width="55"
|
|
class="video-camera mb-1 mt-3" />
|
|
<div v-if="isCameraPermissionGranted">
|
|
<!-- <video :ref="!isCameraDisabled ? 'videoElement' : ''" autoplay
|
|
:style="{ display: !isCameraDisabled ? 'block' : 'none' }" :muted="isMuted"></video> -->
|
|
<video ref="videoElement" autoplay
|
|
:style="{ display: !isCameraDisabled ? 'block' : 'none' }" :muted="isMuted"></video>
|
|
</div>
|
|
<div v-if="!enableCamBtn">
|
|
<small v-if="!isCameraPermissionGranted && !isMicrophonePermissionGranted">Allow access to
|
|
your cam/mic by
|
|
clicking on blocked cam icon in address bar.</small>
|
|
</div>
|
|
<div v-if="enableCamBtn" class="text-center"
|
|
:class="enableCamBtn ? 'video-single-icon mb-1 pb-0 px-2' : ''">
|
|
<VImg v-if="enableCamBtn" :src="videoCameraOff" height="50" width="55"
|
|
class="video-camera mb-1 mt-3" />
|
|
<small>Your webcam isn't enabled yet.</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<v-btn v-if="enableCamBtn" class=" d-grid w-100 waves-effect waves-light text-capitalize" color="error"
|
|
variant="flat" @click="checkPermissions()">
|
|
Enable Camera
|
|
</v-btn>
|
|
</VCard>
|
|
<VCard v-if="isDialogVisible" class="mt-4">
|
|
<!-- <VCardItem class="justify-center">
|
|
<VCardTitle class="font-weight-bold text-primary">
|
|
HGH
|
|
</VCardTitle>
|
|
</VCardItem> -->
|
|
<v-sheet elevation="20" max-width="" rounded="lg" width="100%" class="pa-2 px-4">
|
|
<h4> Doctor is</h4>
|
|
<v-badge color="success" content="Online" inline></v-badge>
|
|
<p class="mb-3">Your call will start soon.<br></p>
|
|
<!-- <p>You are number {{ currentQueue }} in line. </p> -->
|
|
</v-sheet>
|
|
</VCard>
|
|
</VCol>
|
|
</VRow>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@use "@core/scss/template/pages/page-auth.scss";
|
|
</style>
|
|
<style>
|
|
.video-single-icon {
|
|
margin: 0 auto;
|
|
position: absolute;
|
|
top: calc(35% - 10px);
|
|
width: 100%;
|
|
}
|
|
|
|
.menu-btn {
|
|
position: absolute;
|
|
background-color: #0000009c !important;
|
|
left: 10px;
|
|
z-index: 999;
|
|
top: 5px;
|
|
}
|
|
|
|
.video-icons {
|
|
position: absolute;
|
|
right: 10px;
|
|
z-index: 9999;
|
|
top: 5px;
|
|
}
|
|
|
|
.video-icon-red {
|
|
background-color: red;
|
|
color: white !important;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.video-icon-black {
|
|
background-color: #0000009c !important;
|
|
color: white !important;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.video-camera {
|
|
display: block;
|
|
position: relative;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
video {
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
</style>
|