initial commit
This commit is contained in:
1736
resources/js/views/videocall/new-videocall.vue
Normal file
1736
resources/js/views/videocall/new-videocall.vue
Normal file
File diff suppressed because it is too large
Load Diff
568
resources/js/views/videocall/videocall.vue
Normal file
568
resources/js/views/videocall/videocall.vue
Normal file
@@ -0,0 +1,568 @@
|
||||
<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>
|
Reference in New Issue
Block a user