feat: added AudioPlayer component
This commit is contained in:
parent
b12ed81aff
commit
9c98dc82b3
@ -165,9 +165,7 @@
|
||||
class="relative flex justify-center after:absolute after:left-[50%] after:top-0 after:-z-10 after:border-l after:border-gray-200"
|
||||
:class="i != activities.length - 1 ? 'after:h-full' : 'after:h-4'"
|
||||
>
|
||||
<div
|
||||
class="z-10 flex h-7 w-7 items-center justify-center bg-white"
|
||||
>
|
||||
<div class="z-10 flex h-7 w-7 items-center justify-center bg-white">
|
||||
<CommentIcon class="text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
@ -373,6 +371,7 @@
|
||||
v-if="call.show_recording && call.recording_url"
|
||||
class="flex items-center justify-between rounded border"
|
||||
>
|
||||
<AudioPlayer :src="call.recording_url" />
|
||||
<audio class="audio-control" controls :src="call.recording_url" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
@ -698,13 +697,9 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="activity.show_recording && activity.recording_url"
|
||||
class="flex items-center justify-between"
|
||||
class="flex flex-col items-center justify-between"
|
||||
>
|
||||
<audio
|
||||
class="audio-control"
|
||||
controls
|
||||
:src="activity.recording_url"
|
||||
></audio>
|
||||
<AudioPlayer :src="activity.recording_url" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -930,6 +925,7 @@
|
||||
<script setup>
|
||||
import MultipleAvatar from '@/components/MultipleAvatar.vue'
|
||||
import EmailContent from '@/components/Activities/EmailContent.vue'
|
||||
import AudioPlayer from '@/components/Activities/AudioPlayer.vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
|
||||
178
frontend/src/components/Activities/AudioPlayer.vue
Normal file
178
frontend/src/components/Activities/AudioPlayer.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="player w-full text-sm text-gray-600">
|
||||
<div class="player-controls flex items-center gap-2">
|
||||
<Button variant="ghost" @click="playPause">
|
||||
<template #icon>
|
||||
<FeatherIcon class="size-4" :name="isPaused ? 'play' : 'pause'" />
|
||||
</template>
|
||||
</Button>
|
||||
<div
|
||||
class="player-timeline flex gap-2 items-center justify-between flex-1"
|
||||
>
|
||||
<input
|
||||
class="w-full slider !h-[0.5] bg-gray-200 [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb:hover]:outline [&::-webkit-slider-thumb:hover]:outline-[0.5px]"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, #171717 ${progress}%, #ededed ${progress}%)`,
|
||||
}"
|
||||
type="range"
|
||||
id="track"
|
||||
min="0"
|
||||
:max="duration"
|
||||
:value="currentTime"
|
||||
step="0.01"
|
||||
@input="(e) => (audio.currentTime = e.target.value)"
|
||||
/>
|
||||
<div class="shrink-0">
|
||||
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex group gap-2 items-center">
|
||||
<input
|
||||
class="slider opacity-0 group-hover:opacity-100 w-0 group-hover:w-20 !h-[0.5] [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb:hover]:outline [&::-webkit-slider-thumb:hover]:outline-[0.5px]"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, #171717 ${volumnProgress}%, #ededed ${volumnProgress}%)`,
|
||||
}"
|
||||
type="range"
|
||||
id="volume"
|
||||
min="0"
|
||||
max="1"
|
||||
:value="currentVolumn"
|
||||
step="0.01"
|
||||
@input="(e) => updateVolumnProgress(e.target.value)"
|
||||
/>
|
||||
<Button variant="ghost">
|
||||
<template #icon>
|
||||
<MuteIcon
|
||||
v-if="volumnProgress == 0"
|
||||
class="size-4"
|
||||
@click="updateVolumnProgress('1')"
|
||||
/>
|
||||
<VolumnLowIcon
|
||||
v-else-if="volumnProgress <= 40"
|
||||
class="size-4"
|
||||
@click="updateVolumnProgress('0')"
|
||||
/>
|
||||
<VolumnHighIcon
|
||||
v-else-if="volumnProgress > 20"
|
||||
class="size-4"
|
||||
@click="updateVolumnProgress('0')"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="ghost">
|
||||
<template #icon>
|
||||
<FeatherIcon class="size-4" name="more-horizontal" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<audio
|
||||
ref="audio"
|
||||
:src="src"
|
||||
crossorigin="anonymous"
|
||||
@loadedmetadata="setupDuration"
|
||||
@timeupdate="updateCurrentTime"
|
||||
@ended="isPaused = true"
|
||||
></audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import VolumnLowIcon from '@/components/Icons/VolumnLowIcon.vue'
|
||||
import VolumnHighIcon from '@/components/Icons/VolumnHighIcon.vue'
|
||||
import MuteIcon from '@/components/Icons/MuteIcon.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
src: String,
|
||||
})
|
||||
|
||||
const audio = ref(null)
|
||||
const isPaused = ref(true)
|
||||
|
||||
const duration = ref(0)
|
||||
const currentTime = ref(0)
|
||||
const progress = computed(() => (currentTime.value / duration.value) * 100)
|
||||
const currentVolumn = ref(1)
|
||||
const volumnProgress = ref(100)
|
||||
|
||||
function setupDuration() {
|
||||
duration.value = audio.value.duration
|
||||
}
|
||||
|
||||
function updateCurrentTime() {
|
||||
currentTime.value = audio.value.currentTime
|
||||
}
|
||||
|
||||
function playPause() {
|
||||
if (audio.value.paused) {
|
||||
audio.value.play()
|
||||
isPaused.value = false
|
||||
} else {
|
||||
audio.value.pause()
|
||||
isPaused.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
if (isNaN(time)) return '00:00'
|
||||
const minutes = Math.floor(time / 60)
|
||||
const seconds = Math.floor(time % 60)
|
||||
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function updateVolumnProgress(value) {
|
||||
audio.value.volume = value
|
||||
currentVolumn.value = value
|
||||
volumnProgress.value = value * 100
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.slider {
|
||||
--trackHeight: 2px;
|
||||
--thumbRadius: 14px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 100px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-runnable-track {
|
||||
appearance: none;
|
||||
height: var(--trackHeight);
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.slider:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
width: var(--thumbRadius);
|
||||
height: var(--thumbRadius);
|
||||
margin-top: calc((var(--trackHeight) - var(--thumbRadius)) / 2);
|
||||
background: #fff;
|
||||
border-radius: 100px;
|
||||
pointer-events: all;
|
||||
appearance: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
width: var(--thumbRadius);
|
||||
height: var(--thumbRadius);
|
||||
margin-top: calc((var(--trackHeight) - var(--thumbRadius)) / 2);
|
||||
background: #fff;
|
||||
border-radius: 100px;
|
||||
pointer-events: all;
|
||||
appearance: none;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
38
frontend/src/components/Icons/MuteIcon.vue
Normal file
38
frontend/src/components/Icons/MuteIcon.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.05142 5.27273H2.6926C2.3798 5.27273 2.07981 5.38766 1.85863 5.59225C1.63744 5.79683 1.51318 6.07431 1.51318 6.36364V9.63636C1.51318 9.92569 1.63744 10.2032 1.85863 10.4078C2.07981 10.6123 2.3798 10.7273 2.6926 10.7273H5.05142L9.76908 14V2L5.05142 5.27273Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.5 5.5C11.8143 5.81281 12.066 6.1975 12.2384 6.62854C12.4108 7.05958 12.5 7.52708 12.5 8C12.5 8.47292 12.4108 8.94042 12.2384 9.37146C12.066 9.8025 11.8143 10.1872 11.5 10.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 3C13.1286 3.62561 13.632 4.39501 13.9768 5.25708C14.3217 6.11915 14.5 7.05416 14.5 8C14.5 8.94584 14.3217 9.88085 13.9768 10.7429C13.632 11.605 13.1286 12.3744 12.5 13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 3.07596L13.7286 12.2609"
|
||||
stroke="white"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M2.97913 2.0192L14.0209 13.9808"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
28
frontend/src/components/Icons/VolumnHighIcon.vue
Normal file
28
frontend/src/components/Icons/VolumnHighIcon.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.05142 5.27273H2.6926C2.3798 5.27273 2.07981 5.38766 1.85863 5.59225C1.63744 5.79683 1.51318 6.07431 1.51318 6.36364V9.63636C1.51318 9.92569 1.63744 10.2032 1.85863 10.4078C2.07981 10.6123 2.3798 10.7273 2.6926 10.7273H5.05142L9.76908 14V2L5.05142 5.27273Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.5 5.5C11.8143 5.81281 12.066 6.1975 12.2384 6.62854C12.4108 7.05958 12.5 7.52708 12.5 8C12.5 8.47292 12.4108 8.94042 12.2384 9.37146C12.066 9.8025 11.8143 10.1872 11.5 10.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 3C13.1286 3.62561 13.632 4.39501 13.9768 5.25708C14.3217 6.11915 14.5 7.05416 14.5 8C14.5 8.94584 14.3217 9.88085 13.9768 10.7429C13.632 11.605 13.1286 12.3744 12.5 13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
22
frontend/src/components/Icons/VolumnLowIcon.vue
Normal file
22
frontend/src/components/Icons/VolumnLowIcon.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.05142 5.27273H2.6926C2.3798 5.27273 2.07981 5.38766 1.85863 5.59225C1.63744 5.79683 1.51318 6.07431 1.51318 6.36364V9.63636C1.51318 9.92569 1.63744 10.2032 1.85863 10.4078C2.07981 10.6123 2.3798 10.7273 2.6926 10.7273H5.05142L9.76908 14V2L5.05142 5.27273Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11.5 5.5C11.8143 5.81281 12.066 6.1975 12.2384 6.62854C12.4108 7.05958 12.5 7.52708 12.5 8C12.5 8.47292 12.4108 8.94042 12.2384 9.37146C12.066 9.8025 11.8143 10.1872 11.5 10.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
Loading…
x
Reference in New Issue
Block a user