purityselect/resources/js/views/videocall/videocall.vue
2024-10-25 01:05:27 +05:00

569 lines
17 KiB
Vue

<script setup>
import axios from '@axios';
import {
LocalParticipant,
Room,
RoomEvent,
Track,
VideoPresets,
facingModeFromLocalTrack
} from 'livekit-client';
import { defineEmits, defineProps, nextTick, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const router = useRouter()
const route = useRoute()
const props = defineProps({
token: {
type: String,
required: true,
}, recording: {
type: Boolean,
default: false,
},
height: {
type: String,
required: false,
default: ''
}
})
const emit = defineEmits([
'update:token',
'update:transcriptData'
])
let room = null
const url = "wss://plugnmeet.codelfi.com"
const participants = ref([])
const cameras = ref([])
const cameraDefault = ref()
const microphones = ref([])
const microphoneDefault = ref()
const speakers = ref([])
const speakerDefault = ref()
const callStart = ref(false)
const userType = ref(localStorage.getItem('user_role'))
const recording = ref(false)
const isMobile = ref(window.innerWidth <= 768);
const transcriptData = ref('')
let rtCon = null
const appointmentId = localStorage.getItem('patient_appiontment_id')
const strData = JSON.stringify({ some: "data" })
const decoder = new TextDecoder()
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768; // Adjust the breakpoint as needed
};
onMounted(async () => {
window.addEventListener('resize', checkMobile);
document.addEventListener('mousemove', handleMouseMove)
connect();
// if (userType.value == 'agent')
// getAppointmentDetails();
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
document.removeEventListener('mousemove', handleMouseMove)
});
const x = ref(0)
const y = ref(0)
const dragging = ref(false)
const rect = ref(null)
const style = computed(() => {
return {
transform: `translate(${x.value}px, ${y.value}px)`
}
})
const handleMouseDown = e => {
rect.value = e.currentTarget.getBoundingClientRect()
dragging.value = true
}
const handleMouseMove = e => {
if (!dragging.value) return
x.value = Math.min(Math.max(0, e.clientX - rect.value.left), window.innerWidth - rect.value.width) - 25
y.value = Math.min(Math.max(0, e.clientY - rect.value.top), window.innerHeight - rect.value.height) - 25
}
const connect = async () => {
room = new Room({
// automatically manage subscribed video quality
adaptiveStream: true,
dynacast: true,
// default capture settings
videoCaptureDefaults: {
resolution: VideoPresets.h720.resolution,
},
});
// pre-warm connection, this can be called as early as your page is loaded
room.prepareConnection(url, props.token);
console.log(props.token)
// set up event listeners
room
.on(RoomEvent.TrackSubscribed, (track, publication, participant) => renderParticipant(participant))
.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => renderParticipant(participant, true))
// .on(RoomEvent.ActiveSpeakersChanged, (speakers) => console.log("handleTrackSubscribed"))
.on(RoomEvent.Disconnected, () => {
renderParticipant(room.localParticipant, true)
room.participants.forEach((p) => {
renderParticipant(p, true);
})
room = null
})
.on(RoomEvent.Reconnecting, () => appendLog('Reconnecting to room'))
.on(RoomEvent.Reconnected, async () => {
appendLog(
'Successfully reconnected. server',
await room.engine.getConnectedServerAddress(),
);
})
.on(RoomEvent.ParticipantConnected, async (participant) => {
console.log('ParticipantConnected')
callStart.value = true
})
.on(RoomEvent.DataReceived, async (payload, participant, kind) => {
const strData = decoder.decode(payload)
transcriptData.value = strData
emit('update:transcriptData', transcriptData.value);
console.log('strData', strData)
})
.on(RoomEvent.ParticipantDisconnected, (participant) => {
renderParticipant(participant, true);
console.log('part length ', room.participants.size)
setTimeout(() => {
if (room.participants.size == 0) {
console.log('disconnect')
room.disconnect();
callStart.value = false
// if (userType.value == 'agent')
// router.push('/queue-users');
if (userType.value == 'patient') {
localStorage.setItem('call-end-message', 'Password Sent to Email Address')
router.push('/overview');
}
}
}, 1000)
})
.on(RoomEvent.MediaDevicesChanged, async () => {
console.log('device changed')
})
.on(RoomEvent.LocalTrackUnpublished, () => renderParticipant(room.localParticipant))
.on(RoomEvent.LocalTrackPublished, () => renderParticipant(room.localParticipant));
// connect to room
await room.connect(url, props.token);
console.log("connected to room", room.name);
// publish local camera and mic tracks
await room.localParticipant.enableCameraAndMicrophone();
callStart.value = true
// connectSpeach();
if (props.recording == true) {
await axios.post('/agent/api/start-record/' + localStorage.getItem('patient_appiontment_id'))
.then(response => {
console.log('start rec', response.data.message)
if (response.data.message == 'Success')
recording.value = response.data.message
});
}
room.participants.forEach((participant) => {
renderParticipant(participant);
});
renderParticipant(room.localParticipant);
const videoDevices = await Room.getLocalDevices('videoinput');
const audioDevices = await Room.getLocalDevices('audioinput');
const audioOutputs = await Room.getLocalDevices('audiooutput');
callStart.value = true
let selectedVideoDevice = videoDevices[0];
let selectedMicDevice = audioDevices[0];
let selectedSpeakerDevice = audioOutputs[0];
cameras.value = videoDevices;
microphones.value = audioDevices;
speakers.value = audioOutputs;
console.log('camera ', cameras.value)
console.log('mic ', microphones.value)
console.log('speaker ', speakers.value)
// console.log('Devices ',videoDevices,audioDevices,audioOutputs)
if (videoDevices.length > 0) {
for (const videoDevice of videoDevices) {
if (videoDevice.deviceId == 'default') {
selectedVideoDevice = videoDevice
break;
}
}
cameraDefault.value = selectedVideoDevice.deviceId
console.log('default video', cameraDefault.value)
await room.switchActiveDevice('videoinput', cameraDefault.value);
}
if (audioDevices.length > 0) {
for (const audioDevice of audioDevices) {
if (audioDevice.deviceId == 'default') {
selectedMicDevice = audioDevice
break;
}
}
microphoneDefault.value = selectedMicDevice.deviceId
console.log('default mic', microphoneDefault.value)
await room.switchActiveDevice('audioinput', microphoneDefault.value);
}
if (audioOutputs.length > 0) {
for (const audioOutput of audioOutputs) {
if (audioOutput.deviceId == 'default') {
selectedSpeakerDevice = audioOutput
break;
}
}
speakerDefault.value = selectedSpeakerDevice.deviceId
console.log('default audio', speakerDefault.value)
await room.switchActiveDevice('audiooutput', speakerDefault.value);
}
return room;
}
const renderParticipant = async (participant, remove = false) => {
let _participant = participants.value.find(part => part.id == participant.identity)
if (!_participant) {
_participant = {
id: participant.identity,
type: (participant instanceof LocalParticipant) ? "local" : "remote",
video: null,
audio: null,
};
participants.value.push(_participant)
}
if (remove) {
participants.value.splice(participants.value.indexOf(_participant), 1)
return;
}
const cameraPub = participant.getTrack(Track.Source.Camera);
const micPub = participant.getTrack(Track.Source.Microphone);
await nextTick()
// if ((participant instanceof LocalParticipant) && micPub?.track.getProcessor()?.name != "local-processor") {
// micPub.track.setProcessor(localProcessor);
// }
// if (micPub?.getProcessor()?.name != "remote-processor" && !(participant instanceof LocalParticipant)) {
// micPub.setProcessor(remoteProcessor);
// }
window.r = room
_participant.video = document.getElementById("video-" + _participant.id)
_participant.audio = document.getElementById("audio-" + _participant.id)
console.log('video ', _participant.video)
const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
if (cameraEnabled) {
cameraPub?.videoTrack?.attach(_participant.video);
} else {
if (cameraPub?.videoTrack) {
// detach manually whenever possible
cameraPub.videoTrack?.detach(_participant.video);
} else if (_participant.video) {
_participant.video.src = '';
_participant.video.srcObject = null;
}
}
const micEnabled = micPub && micPub.isSubscribed && !micPub.isMuted;
if (micEnabled) {
if (!(participant instanceof LocalParticipant)) {
if (!rtCon) {
micPub?.audioTrack?.attach(_participant.audio);
console.log('audioTrack', _participant.audio)
}
}
}
}
const endCall = async () => {
room.disconnect()
callStart.value = false
if (userType.value == 'agent')
router.push('/provider/queue-users');
if (userType.value == 'patient') {
localStorage.setItem('call-end-message', 'Password Sent to Email Address')
router.push('/doctor-appiontments');
}
}
const getAppointmentDetails = async () => {
axios.post('/agent/api/appointment-detail/' + appointmentId)
.then(r => {
let appointmentAnalytics = r.data.agent_appointment.analytics
transcriptData.value = appointmentAnalytics
console.log('appointmentAnalytics ', transcriptData.value)
emit('update:transcriptData', transcriptData.value);
}).catch(error => {
console.log("Error", error);
});
}
const flipCamera = () => {
const videoPub = room?.localParticipant.getTrack(Track, Source.Camera);
if (!videoPub) {
return;
}
const track = videoPub.track;
const facingMode = track && facingModeFromLocalTrack(track);
const options = {
resolution: VideoPresets.h720.resolution,
facingMode: facingMode?.facingMode === 'environment' ? 'user' : 'environment',
}
videoPub.videoTrack?.restartTrack(options)
}
const selectCamera = async (deviceId) => {
await room.switchActiveDevice('videoinput', deviceId);
cameraDefault.value = deviceId
console.log('camera selected ', deviceId);
}
const selectMicrophone = async (deviceId) => {
await room.switchActiveDevice('audioinput', deviceId);
microphoneDefault.value = deviceId
console.log('mic selected ', deviceId);
}
const selectSpeaker = async (deviceId) => {
await room.switchActiveDevice('audiooutput', deviceId);
speakerDefault.value = deviceId
console.log('speaker selected ', deviceId);
}
</script>
<template>
<VCard class="text-center text-sm-start rounded-0">
<div v-if="audioDevices">{{ audioDevices }}</div>
<div class="livekit-container" @mouseup="dragging = false" @mouseleave="dragging = false"
:style="`height:` + height">
<div class="recording-badge" v-if="recording">Recording</div>
<!-- <v-card class="draggable-card" width="150px" height="150px" color="light" @mousedown=""></v-card> -->
<template v-for="(participant, index) in participants" :key="index" :value="index">
<div v-if="participant.type == 'local'" :style="style" @mousedown="handleMouseDown" class="box">
<video class="small-video" ref="participant.video" :id="`video-` + participant.id"></video>
<audio ref="participant.audio" :id="`audio-` + participant.id"></audio>
</div>
<div class="remote-box" v-if="participant.type == 'remote'">
<video class="large-video" ref="participant.video" :id="`video-` + participant.id"></video>
<audio ref="participant.audio" :id="`audio-` + participant.id"></audio>
</div>
</template>
</div>
<VRow style="background-color: black;" v-if="callStart">
<VCol cols="12" md="12">
<div class="text-center mb-2">
<v-btn-toggle v-if="!isMobile" v-model="toggle" divided rounded="xl">
<v-row no-gutters>
<v-btn class="btn btn-secondary waves-effect waves-light" color="white" icon="mdi-microphone"
style="background-color: white;color: black;"></v-btn>
<v-menu :location="top">
<template v-slot:activator="{ props }">
<v-btn color="white" v-bind="props" icon="mdi-chevron-up" title="Devices">
</v-btn>
</template>
<v-list>
<v-list-item>
<v-list-item-title>
<v-menu :location="top">
<template v-slot:activator="{ props }">
<v-btn color="white" v-bind="props">Camera
<VIcon icon="mdi-chevron-up"></VIcon>
</v-btn>
</template>
<v-list>
<v-list-item v-for="(camera, index) in cameras" :key="index" :value="camera.deviceId"
@click="selectCamera(camera.deviceId)">
<v-list-item-title>
<VIcon color="green" icon="mdi-check" v-if="cameraDefault == camera.deviceId"></VIcon>
{{ camera.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-list-item-title>
<v-list-item-title>
<v-menu :location="top">
<template v-slot:activator="{ props }">
<v-btn color="white" v-bind="props">Microphone
<VIcon icon="mdi-chevron-up"></VIcon>
</v-btn>
</template>
<v-list>
<v-list-item v-for="(microphone, index) in microphones" :key="index"
:value="microphone.deviceId" @click="selectMicrophone(microphone.deviceId)">
<v-list-item-title>
<VIcon color="green" icon="mdi-check" v-if="microphoneDefault == microphone.deviceId">
</VIcon>
{{ microphone.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-list-item-title>
<v-list-item-title>
<v-menu :location="top">
<template v-slot:activator="{ props }">
<v-btn color="white" v-bind="props">Speaker
<VIcon icon="mdi-chevron-up"></VIcon>
</v-btn>
</template>
<v-list>
<v-list-item v-for="(speaker, index) in speakers" :key="index" :value="speaker.deviceId"
@click="selectSpeaker(speaker.deviceId)">
<v-list-item-title>
<VIcon color="green" icon="mdi-check" v-if="speakerDefault == speaker.deviceId"></VIcon>
{{ speaker.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-btn class="btn btn-secondary waves-effect waves-light" color="white" icon="mdi-video"
style="background-color: white;color: black;"></v-btn>
</v-row>
</v-btn-toggle>
<v-btn v-if="isMobile" class="btn btn-secondary waves-effect waves-light" color="white" icon="mdi-camera-flip"
style="background-color: white;color: black;" @clicK="flipCamera"></v-btn>
<v-btn rounded="xl" class="btn btn-danger waves-effect waves-light ml-2" color="red" icon="mdi-phone-hangup"
@click="endCall()"></v-btn>
</div>
</VCol>
</VRow>
<!-- <div id="jaas-container" >
</div> -->
<!-- Show the iframe when countdown reaches 1 -->
<!-- <iframe v-if="showIframe" ref="myIframe" :src="responseUrl" width="100%" height="400" style="border:0"></iframe>
<div v-else>
Loading iframe...
</div> -->
</VCard>
</template>
<style scoped>
.recording-badge {
text-align: right;
color: white;
margin-right: 10px;
background: red;
min-width: 10%;
float: right;
border-radius: 25px;
padding-right: 3px;
z-index: 10;
position: relative;
}
.small-screen {
background-color: white;
min-height: 150px;
max-width: 24%;
}
.livekit-container {
min-height: 500px;
background-color: black;
position: relative;
}
.draggable-card {
cursor: move;
position: relative;
}
.box {
position: absolute;
cursor: move;
width: 150px;
height: 150px;
background: #313131;
border-radius: 10px;
margin-left: 10px;
z-index: 2;
}
.large-video {
height: 100%;
width: 100%;
}
.small-video {
max-width: 100%;
height: 100%;
max-height: 100%;
width: auto;
z-index: 9;
}
.remote-box {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
}
</style>