mirror of
https://github.com/JuLi0n21/pwa-player.git
synced 2026-04-19 23:40:05 +00:00
update shit code (trash) :3
This commit is contained in:
@@ -11,7 +11,8 @@ import { useAudio } from './composables/useAudio.ts'
|
|||||||
|
|
||||||
const showNowPlaying = ref(true);
|
const showNowPlaying = ref(true);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const audio = useAudio();
|
const audioRef = ref<HTMLAudioElement | null>(null)
|
||||||
|
const audio = useAudio(audioRef);
|
||||||
|
|
||||||
watch(route, async (to) => {
|
watch(route, async (to) => {
|
||||||
|
|
||||||
@@ -61,32 +62,29 @@ onMounted(() => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', checkScreenSize);
|
window.removeEventListener('resize', checkScreenSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="screenInfo.isSmall" class="flex flex-col h-screen max-h-screen wrapper info text-xl ">
|
<div v-if="screenInfo.isSmall" class="flex flex-col h-screen max-h-screen wrapper info text-xl">
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
|
||||||
<NowPlaying v-show="showNowPlaying" />
|
<NowPlaying v-show="showNowPlaying" />
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!screenInfo.isSmall" class="flex flex-col h-screen max-h-screen wrapper info text-xl ">
|
|
||||||
|
<div v-else class="flex flex-col h-screen max-h-screen wrapper info text-xl">
|
||||||
<main class="flex flex-1 w-full h-full overflow-y-scroll">
|
<main class="flex flex-1 w-full h-full overflow-y-scroll">
|
||||||
<div class="w-1/12">
|
|
||||||
<MenuView />
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 overflow-y-scroll">
|
|
||||||
<RouterView />
|
|
||||||
</div>
|
|
||||||
<div class="w-1/5">
|
|
||||||
<NowPlayingView />
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
|
<aside class="w-1/12 bg-primary p-4">
|
||||||
|
<p>Sidebar content</p>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="flex-1 overflow-y-scroll p-4">
|
||||||
|
<RouterView />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="w-1/5 p-4">
|
||||||
|
<NowPlayingView />
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAudio } from '@/composables/useAudio';
|
import { useAudio } from '@/composables/useAudio';
|
||||||
|
import { onMounted, computed } from 'vue'
|
||||||
|
|
||||||
const audioStore = useAudio();
|
const audioStore = useAudio();
|
||||||
</script>
|
|
||||||
|
|
||||||
|
const title = computed(() => audioStore.currentSong.value?.name || 'Unknown Title')
|
||||||
|
const artist = computed(() => audioStore.currentSong.value?.artist || 'Unknown Artist')
|
||||||
|
const bgimg = computed(() => audioStore.currentSong.value?.previewimage || '/default-bg.jpg')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
audioStore.init()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="relative wrapper p-1 grow action">
|
<div class="relative wrapper p-1 grow action">
|
||||||
<img :src="encodeURI(audioStore.bgimg.value + '?h=150&w=400')" class="w-full absolute top-0 left-0 right-0 h-full"
|
<img :src="encodeURI(bgimg + '?h=150&w=400')" class="w-full absolute top-0 left-0 right-0 h-full"
|
||||||
:style="{ 'filter': 'blur(2px)', 'opacity': '0.5' }" alt="Background Image" />
|
:style="{ 'filter': 'blur(2px)', 'opacity': '0.5' }" alt="Background Image" />
|
||||||
|
|
||||||
<nav class="relative flex-col z-10">
|
<nav class="relative flex-col z-10">
|
||||||
@@ -17,15 +25,15 @@ const audioStore = useAudio();
|
|||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<RouterLink to="/nowplaying" class="grow overflow-hidden">
|
<RouterLink to="/nowplaying" class="grow overflow-hidden">
|
||||||
<p class="relative text-sm text-left font-bold info overflow-hidden text-ellipsis text-nowrap">
|
<p class="relative text-sm text-left font-bold info overflow-hidden text-ellipsis text-nowrap">
|
||||||
{{ audioStore.title.value }}
|
{{ title }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="relative text-sm text-left font-bold info text-nowrap">
|
<p class="relative text-sm text-left font-bold info text-nowrap">
|
||||||
{{ audioStore.artist.value }}
|
{{ artist }}
|
||||||
</p>
|
</p>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div class="flex flex-col text-center justify-center px-2" @click="audioStore.togglePlay">
|
<div class="flex flex-col text-center justify-center px-2" @click="audioStore.togglePlay">
|
||||||
<i :class="[audioStore.isPlaying.value ? ' fa-circle-play' : 'fa-circle-pause']" class="text-3xl fa-regular"></i>
|
<i :class="[audioStore.isPlaying.value ? ' fa-circle-pause' : 'fa-circle-play']" class="text-3xl fa-regular"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -37,8 +45,6 @@ const audioStore = useAudio();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
<audio controls class="hidden" id="audio-player" :src="audioStore.songSrc.value"
|
|
||||||
@timeupdate="audioStore.update"></audio>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const audioStore = useAudio();
|
|||||||
function updateSong() {
|
function updateSong() {
|
||||||
|
|
||||||
let updated = props.song;
|
let updated = props.song;
|
||||||
|
console.log("updating song:", updated)
|
||||||
audioStore.setSong(updated)
|
audioStore.setSong(updated)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
class="iconify iconify--mdi"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
|
||||||
fill="currentColor"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -7,214 +7,149 @@ let audioInstance: ReturnType<typeof createAudio> | null = null
|
|||||||
function createAudio() {
|
function createAudio() {
|
||||||
const { musicApi } = useApi()
|
const { musicApi } = useApi()
|
||||||
|
|
||||||
const songSrc = ref('https://cdn.pixabay.com/audio/2024/05/24/audio_46382ae035.mp3')
|
const audioElement = document.createElement('audio')
|
||||||
const artist = ref('Artist')
|
audioElement.setAttribute('id', 'global-audio')
|
||||||
const title = ref('Title')
|
audioElement.setAttribute('controls', '')
|
||||||
const bgimg = ref('https://assets.ppy.sh/beatmaps/2197744/covers/cover@2x.jpg?1722207959')
|
audioElement.classList.add('hidden')
|
||||||
const hash = ref('0000')
|
document.body.appendChild(audioElement)
|
||||||
|
|
||||||
const isPlaying = ref(false)
|
const state = {
|
||||||
const duration = ref('0:00')
|
isPlaying: ref(false),
|
||||||
const currentTime = ref('0:00')
|
duration: ref('0:00'),
|
||||||
const percentDone = ref(0)
|
currentTime: ref('0:00'),
|
||||||
|
percentDone: ref(0),
|
||||||
const shuffle = ref(false)
|
shuffle: ref(false),
|
||||||
const repeat = ref(false)
|
repeat: ref(false),
|
||||||
|
activeSongs: ref<Song[] | null>([]),
|
||||||
const activeCollection = ref<Song[]>([])
|
currentSong: ref<Song | null>(null),
|
||||||
const currentSong = ref<Song | null>(null)
|
|
||||||
|
|
||||||
function saveSongToLocalStorage(song: Song) {
|
|
||||||
console.log(song)
|
|
||||||
localStorage.setItem('lastPlayedSong', JSON.stringify(song))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSongFromLocalStorage(): Song | null {
|
function saveToLocalStorage(key: string, data: any) {
|
||||||
const song = localStorage.getItem('lastPlayedSong')
|
localStorage.setItem(key, JSON.stringify(data))
|
||||||
return song ? JSON.parse(song) : null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCollectionToLocalStorage(collection: Song[]) {
|
function loadFromLocalStorage<T>(key: string): T | null {
|
||||||
localStorage.setItem('lastActiveCollection', JSON.stringify(collection))
|
const item = localStorage.getItem(key)
|
||||||
}
|
return item ? JSON.parse(item) as T : null
|
||||||
|
|
||||||
function loadCollectionFromLocalStorage(): Song[] | null {
|
|
||||||
const collection = localStorage.getItem('lastActiveCollection')
|
|
||||||
return collection ? JSON.parse(collection) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePlay() {
|
|
||||||
const audio = document.getElementById('audio-player') as HTMLAudioElement
|
|
||||||
if (!audio) return
|
|
||||||
if (audio.paused) {
|
|
||||||
audio.play()
|
|
||||||
} else {
|
|
||||||
audio.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
const audio = document.getElementById('audio-player') as HTMLAudioElement
|
|
||||||
if (!audio) return
|
|
||||||
|
|
||||||
isPlaying.value = !audio.paused
|
|
||||||
|
|
||||||
const current_min = Math.floor(audio.currentTime / 60)
|
|
||||||
const current_sec = Math.floor(audio.currentTime % 60)
|
|
||||||
if (!isNaN(current_min) && !isNaN(current_sec)) {
|
|
||||||
currentTime.value = `${current_min}:${current_sec.toString().padStart(2, '0')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration_min = Math.floor(audio.duration / 60)
|
|
||||||
const duration_sec = Math.floor(audio.duration % 60)
|
|
||||||
if (!isNaN(duration_min) && !isNaN(duration_sec)) {
|
|
||||||
duration.value = `${duration_min}:${duration_sec.toString().padStart(2, '0')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const percent = (audio.currentTime / audio.duration) * 100
|
|
||||||
if (!isNaN(percent)) {
|
|
||||||
percentDone.value = percent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio.ended) {
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function next() {
|
|
||||||
const audio = document.getElementById('audio-player') as HTMLAudioElement
|
|
||||||
if (!audio) return
|
|
||||||
|
|
||||||
if (repeat.value) {
|
|
||||||
audio.pause()
|
|
||||||
audio.currentTime = 0
|
|
||||||
audio.play()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shuffle.value) {
|
|
||||||
audio.pause()
|
|
||||||
const randomSong = activeCollection.value[Math.floor(Math.random() * activeCollection.value.length)]
|
|
||||||
setSong(randomSong)
|
|
||||||
audio.play()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTime() {
|
|
||||||
const audio = document.getElementById('audio-player') as HTMLAudioElement
|
|
||||||
const slider = document.getElementById('audio-slider') as HTMLInputElement
|
|
||||||
if (!audio || !slider) return
|
|
||||||
|
|
||||||
audio.currentTime = (Number(slider.value) / 100) * audio.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePrev() {
|
|
||||||
const index = activeCollection.value.findIndex((s) => s.hash === hash.value)
|
|
||||||
if (index === -1) return
|
|
||||||
const prevIndex = (index - 1 + activeCollection.value.length) % activeCollection.value.length
|
|
||||||
setSong(activeCollection.value[prevIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleNext() {
|
|
||||||
let index = 0
|
|
||||||
if (shuffle.value) {
|
|
||||||
index = Math.floor(Math.random() * activeCollection.value.length)
|
|
||||||
} else {
|
|
||||||
index = activeCollection.value.findIndex((s) => s.hash === hash.value)
|
|
||||||
if (index === -1) return
|
|
||||||
index = (index + 1) % activeCollection.value.length
|
|
||||||
}
|
|
||||||
setSong(activeCollection.value[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleShuffle() {
|
|
||||||
shuffle.value = !shuffle.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleRepeat() {
|
|
||||||
repeat.value = !repeat.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSong(song: Song | null) {
|
function setSong(song: Song | null) {
|
||||||
|
console.log(song)
|
||||||
if (!song) return
|
if (!song) return
|
||||||
|
state.currentSong.value = song
|
||||||
|
saveToLocalStorage('lastPlayedSong', song)
|
||||||
|
|
||||||
songSrc.value = song.url
|
audioElement.pause()
|
||||||
artist.value = song.artist
|
audioElement.src = song.url
|
||||||
title.value = song.name
|
audioElement.addEventListener(
|
||||||
bgimg.value = song.previewimage
|
'canplaythrough',
|
||||||
hash.value = song.hash
|
() => audioElement.play().catch(console.error),
|
||||||
|
{ once: true }
|
||||||
currentSong.value = song
|
)
|
||||||
saveSongToLocalStorage(song)
|
|
||||||
|
|
||||||
const audio = document.getElementById('audio-player') as HTMLAudioElement
|
|
||||||
if (!audio) return
|
|
||||||
if (!audio.paused) audio.pause()
|
|
||||||
audio.src = song.url
|
|
||||||
|
|
||||||
audio.addEventListener('canplaythrough', () => {
|
|
||||||
audio.play().catch(console.error)
|
|
||||||
}, { once: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCollection(songs: Song[] | null) {
|
function setCollection(song: Song[] | null) {
|
||||||
if (!songs) return
|
if (!song) return
|
||||||
activeCollection.value = songs
|
state.activeSongs.value = song
|
||||||
saveCollectionToLocalStorage(songs)
|
saveToLocalStorage('activeCollection', song)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadInitialCollection() {
|
function togglePlay() {
|
||||||
|
if (audioElement.paused) {
|
||||||
|
audioElement.play().catch(console.error)
|
||||||
|
} else {
|
||||||
|
audioElement.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleNext() {
|
||||||
|
if (!state.activeSongs.value || !state.currentSong.value) return;
|
||||||
|
|
||||||
|
const songs = state.activeSongs.value;
|
||||||
|
const currentHash = state.currentSong.value.hash;
|
||||||
|
const currentIndex = songs.findIndex(song => song.hash === currentHash);
|
||||||
|
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
const nextIndex = (currentIndex + 1) % songs.length;
|
||||||
|
setSong(songs[nextIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePrevious() {
|
||||||
|
if (!state.activeSongs.value || !state.currentSong.value) return;
|
||||||
|
|
||||||
|
const songs = state.activeSongs.value;
|
||||||
|
const currentHash = state.currentSong.value.hash;
|
||||||
|
const currentIndex = songs.findIndex(song => song.hash === currentHash);
|
||||||
|
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
const prevIndex = (currentIndex - 1 + songs.length) % songs.length;
|
||||||
|
setSong(songs[prevIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const { currentTime: ct, duration: dur } = audioElement
|
||||||
|
state.isPlaying.value = !audioElement.paused
|
||||||
|
state.currentTime.value = formatTime(ct)
|
||||||
|
state.duration.value = formatTime(dur)
|
||||||
|
state.percentDone.value = isNaN(dur) ? 0 : (ct / dur) * 100
|
||||||
|
|
||||||
|
if (audioElement.ended) {
|
||||||
|
if (state.repeat.value) {
|
||||||
|
audioElement.currentTime = 0
|
||||||
|
audioElement.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds: number): string {
|
||||||
|
const min = Math.floor(seconds / 60)
|
||||||
|
const sec = Math.floor(seconds % 60).toString().padStart(2, '0')
|
||||||
|
return `${min}:${sec}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTime() {
|
||||||
|
const slider = document.getElementById('audio-slider') as HTMLInputElement
|
||||||
|
if (slider) {
|
||||||
|
audioElement.currentTime = (Number(slider.value) / 100) * audioElement.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadInitialSong() {
|
||||||
try {
|
try {
|
||||||
const api = musicApi()
|
const api = musicApi()
|
||||||
const response = await api.musicBackendRecent()
|
const res = await api.musicBackendRecent()
|
||||||
if (response.data.songs) {
|
let songs = mapApiToSongs(res.data.songs);
|
||||||
setCollection(mapApiToSongs(response.data.songs))
|
if (res.data?.songs?.length) {
|
||||||
|
setSong(songs[0])
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('Failed to load songs:', error)
|
console.error('Failed to load song:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
setSong(loadSongFromLocalStorage())
|
setSong(loadFromLocalStorage<Song>('lastPlayedSong'))
|
||||||
setCollection(loadCollectionFromLocalStorage())
|
setCollection(loadFromLocalStorage<Song[]>('activeCollection'))
|
||||||
|
|
||||||
console.log(activeCollection.value)
|
if (!state.currentSong.value) {
|
||||||
console.log(currentSong.value)
|
loadInitialSong()
|
||||||
if (!currentSong.value || activeCollection.value.length === 0) {
|
|
||||||
loadInitialCollection()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioElement.addEventListener('timeupdate', update)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(init)
|
||||||
init()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
songSrc,
|
...state,
|
||||||
artist,
|
|
||||||
title,
|
|
||||||
bgimg,
|
|
||||||
hash,
|
|
||||||
isPlaying,
|
|
||||||
duration,
|
|
||||||
currentTime,
|
|
||||||
percentDone,
|
|
||||||
shuffle,
|
|
||||||
repeat,
|
|
||||||
activeCollection,
|
|
||||||
currentSong,
|
|
||||||
togglePlay,
|
togglePlay,
|
||||||
update,
|
toggleShuffle: () => (state.shuffle.value = !state.shuffle.value),
|
||||||
next,
|
toggleRepeat: () => (state.repeat.value = !state.repeat.value),
|
||||||
updateTime,
|
|
||||||
togglePrev,
|
|
||||||
toggleNext,
|
toggleNext,
|
||||||
toggleShuffle,
|
togglePrevious,
|
||||||
toggleRepeat,
|
updateTime,
|
||||||
setSong,
|
setSong,
|
||||||
setCollection,
|
setCollection,
|
||||||
init,
|
init,
|
||||||
|
|||||||
@@ -13,13 +13,18 @@ export type Song = {
|
|||||||
const basePath = import.meta.env.BACKEND_URL || 'http://localhost:8080';
|
const basePath = import.meta.env.BACKEND_URL || 'http://localhost:8080';
|
||||||
|
|
||||||
export function mapToSong(apiSong: Apiv1Song): Song {
|
export function mapToSong(apiSong: Apiv1Song): Song {
|
||||||
|
const image = apiSong.image;
|
||||||
|
const imageIsMissing = !image || image === "404.png";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hash: apiSong.md5Hash,
|
hash: apiSong.md5Hash,
|
||||||
name: apiSong.title,
|
name: apiSong.title,
|
||||||
artist: apiSong.artist,
|
artist: apiSong.artist,
|
||||||
length: Number(apiSong.totalTime),
|
length: Number(apiSong.totalTime),
|
||||||
url: `${basePath}/api/v1/audio/${btoa(apiSong.folder + "/" + apiSong.audio).replace(/=+$/, '')}`,
|
url: `${basePath}/api/v1/audio/${btoa(apiSong.folder + "/" + apiSong.audio).replace(/=+$/, '')}`,
|
||||||
previewimage: `${basePath}/api/v1/image/${btoa(apiSong.image === "" || apiSong.image === undefined ? "404.png" : apiSong.image).replace(/=+$/, '')}`,
|
previewimage: imageIsMissing
|
||||||
|
? "/404.gif"
|
||||||
|
: `${basePath}/api/v1/image/${btoa(image).replace(/=+$/, '')}`,
|
||||||
mapper: apiSong.creator,
|
mapper: apiSong.creator,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -40,7 +45,7 @@ export function mapToCollectionPreview(
|
|||||||
index,
|
index,
|
||||||
name: apiCollection.name,
|
name: apiCollection.name,
|
||||||
length: apiCollection.items,
|
length: apiCollection.items,
|
||||||
previewimage: `${basePath}/api/v1/images/${apiCollection.image}`,
|
previewimage: `${basePath}/api/v1/image/${apiCollection.image}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { ref, computed } from 'vue'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useHeaderStore = defineStore('headerStore', () => {
|
|
||||||
const showheader = ref(true)
|
|
||||||
function show() {
|
|
||||||
showheader.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
|
||||||
showheader.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { showheader, show, hide }
|
|
||||||
})
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import type { Song, CollectionPreview, Me } from '@/script/types';
|
|
||||||
import { useApi } from '@/composables/useApi';
|
|
||||||
|
|
||||||
export const useUserStore = defineStore('userStore', () => {
|
|
||||||
const { musicApi } = useApi();
|
|
||||||
|
|
||||||
const userId = ref(null)
|
|
||||||
const baseUrl = ref('https://service.illegalesachen.download')
|
|
||||||
const proxyUrl = ref('https://proxy.illegalesachen.download')
|
|
||||||
|
|
||||||
const User = ref<Me | null>(null)
|
|
||||||
|
|
||||||
function saveUser(user: Me | null) {
|
|
||||||
localStorage.setItem('activeUser', JSON.stringify(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUser(): Me | null {
|
|
||||||
const user = localStorage.getItem('activeUser');
|
|
||||||
return user ? JSON.parse(user) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function setBaseUrl(url: string) {
|
|
||||||
localStorage.setItem('baseUrl', url);
|
|
||||||
baseUrl.value = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadBaseUrl(): string | null {
|
|
||||||
const url = localStorage.getItem('baseUrl');
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setUser(user: Me | null) {
|
|
||||||
User.value = user;
|
|
||||||
saveUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchSong(hash: string): Promise<Song> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/api/v1/songs/${hash}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: Song = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch songs:', error);
|
|
||||||
return {
|
|
||||||
hash: "-1",
|
|
||||||
name: "song name",
|
|
||||||
artist: "artist name",
|
|
||||||
length: 0,
|
|
||||||
url: "song.mp3",
|
|
||||||
previewimage: "404.im5",
|
|
||||||
mapper: "map",
|
|
||||||
} as Song;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchWithCache<T>(cacheKey: string, url: string, cacheDuration: number = 24 * 60 * 60 * 1): Promise<T> {
|
|
||||||
const cacheTimestampKey = `${cacheKey}_timestamp`;
|
|
||||||
|
|
||||||
const cachedData = localStorage.getItem(cacheKey);
|
|
||||||
const cachedTimestamp = localStorage.getItem(cacheTimestampKey);
|
|
||||||
|
|
||||||
if (cachedData && cachedTimestamp && (Date.now() - parseInt(cachedTimestamp)) < cacheDuration) {
|
|
||||||
console.log(`Returning cached data for ${cacheKey}`);
|
|
||||||
return JSON.parse(cachedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: T = await response.json();
|
|
||||||
|
|
||||||
localStorage.setItem(cacheKey, JSON.stringify(data));
|
|
||||||
localStorage.setItem(cacheTimestampKey, Date.now().toString());
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to fetch data from ${url}:`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function fetchCollections(offset: number, limit: number): Promise<CollectionPreview[]> {
|
|
||||||
const cacheKey = `collections_cache_${offset}_${limit}`;
|
|
||||||
const url = `${baseUrl.value}/api/v1/collections/?offset=${offset}&limit=${limit}`;
|
|
||||||
return fetchWithCache<CollectionPreview[]>(cacheKey, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchCollection(index: number): Promise<Song[]> {
|
|
||||||
const cacheKey = `collection_${index}_cache`;
|
|
||||||
const url = `${baseUrl.value}/api/v1/collection/${index}`;
|
|
||||||
return fetchWithCache<Song[]>(cacheKey, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchFavorites(limit: number, offset: number): Promise<Song[]> {
|
|
||||||
const cacheKey = `favorites_${limit}_${offset}_cache`;
|
|
||||||
const url = `${baseUrl.value}/api/v1/recent?limit=${limit}&offset=${offset}`;
|
|
||||||
return fetchWithCache<Song[]>(cacheKey, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchRecent(limit: number, offset: number): Promise<Song[]> {
|
|
||||||
const cacheKey = `recent_${limit}_${offset}_cache`;
|
|
||||||
const url = `${baseUrl.value}/api/v1/songs/recent?limit=${limit}&offset=${offset}`;
|
|
||||||
return fetchWithCache<Song[]>(cacheKey, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchActiveSearch(query: string): Promise<{}> {
|
|
||||||
const cacheKey = `collections_activeSearch_${query}`;
|
|
||||||
const url = `${baseUrl.value}/api/v1/search/active?q=${query}`;
|
|
||||||
return fetchWithCache(cacheKey, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchSearchArtist(query: string): Promise<Song[]> {
|
|
||||||
const cacheKey = `collections_artist_${query}`;
|
|
||||||
const url = `${baseUrl.value}/api/v1/search/artist?q=${query}`;
|
|
||||||
return fetchWithCache<Song[]>(cacheKey, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMe(): Promise<Me | {}> {
|
|
||||||
const url = `${proxyUrl.value}/me`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
if (response.redirected) {
|
|
||||||
window.open(response.url, '_blank');
|
|
||||||
return { "redirected": true };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(`Fetch failed with status: ${response.status} ${response.statusText}`);
|
|
||||||
return { id: -1 } as Me;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fetch error:', error);
|
|
||||||
return {} as Me;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setUser(loadUser());
|
|
||||||
baseUrl.value = loadBaseUrl();
|
|
||||||
|
|
||||||
return { fetchSong, fetchActiveSearch, fetchSearchArtist, fetchCollections, fetchCollection, fetchRecent, fetchFavorites, fetchMe, userId, baseUrl, proxyUrl, User, setUser, setBaseUrl }
|
|
||||||
})
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Song, CollectionPreview } from '../script/types'
|
import { type Song, type CollectionPreview, mapApiToCollectionPreview } from '../script/types'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import CollectionListItem from '../components/CollectionListItem.vue'
|
import CollectionListItem from '../components/CollectionListItem.vue'
|
||||||
import { useUser } from '@/composables/useUser';
|
import { useUser } from '@/composables/useUser';
|
||||||
|
import { useApi } from '@/composables/useApi';
|
||||||
|
|
||||||
const userStore = useUser();
|
const userStore = useUser();
|
||||||
|
const { musicApi } = useApi()
|
||||||
|
const api = musicApi()
|
||||||
|
|
||||||
const collections = ref<CollectionPreview[]>([]);
|
const collections = ref<CollectionPreview[]>([]);
|
||||||
const limit = ref(10);
|
const limit = ref(10);
|
||||||
@@ -15,12 +18,11 @@ const fetchCollections = async () => {
|
|||||||
if (isLoading.value) return;
|
if (isLoading.value) return;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
const data = await userStore.fetchCollections(offset.value, limit.value);
|
const response = await api.musicBackendCollections(offset.value, limit.value);
|
||||||
data.forEach(song => {
|
console.log(response.data.songs.length)
|
||||||
song.previewimage = `${userStore.baseUrl}/api/v1/images/${song.previewimage}?h=80&w=80`;
|
let songs = mapApiToCollectionPreview(response.data.songs)
|
||||||
});
|
console.log(songs)
|
||||||
|
collections.value = [...collections.value, ...songs];
|
||||||
collections.value = [...collections.value, ...data];
|
|
||||||
offset.value += limit.value;
|
offset.value += limit.value;
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ function reset() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex-1 flex flex-col overflow-scroll">
|
<main class="flex-1 flex flex-col overflow-scroll">
|
||||||
<h1> Meeeeee </h1>
|
|
||||||
<input @change="update" type="text" id="url-input" :value="userStore.baseUrl.value" disabled />
|
<input @change="update" type="text" id="url-input" :value="userStore.baseUrl.value" disabled />
|
||||||
<br>
|
<br>
|
||||||
<button v-if="!userStore.user.value" @click="getMe" class="border bordercolor rounded-lg p-0.5">{{ loginStatus }}</button>
|
<button v-if="!userStore.user.value" @click="getMe" class="border bordercolor rounded-lg p-0.5">{{ loginStatus }}</button>
|
||||||
@@ -108,25 +107,25 @@ function reset() {
|
|||||||
<div class="flex flex-1 justify-between">
|
<div class="flex flex-1 justify-between">
|
||||||
<p>Background:</p>
|
<p>Background:</p>
|
||||||
<input type="color" id="bgPicker" v-model="bgColor" @input="save()"
|
<input type="color" id="bgPicker" v-model="bgColor" @input="save()"
|
||||||
class="appearance-none w-8 h-8 border border-2 p-0 overflow-hidden cursor-pointer">
|
class="appearance-none w-8 h-8 border-2 p-0 overflow-hidden cursor-pointer">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-1 justify-between">
|
<div class="flex flex-1 justify-between">
|
||||||
<p>Main:</p>
|
<p>Main:</p>
|
||||||
<input type="color" id="actionPicker" v-model="actionColor" @input="save()"
|
<input type="color" id="actionPicker" v-model="actionColor" @input="save()"
|
||||||
class="appearance-none w-8 h-8 border border-2 p-0 overflow-hidden cursor-pointer">
|
class="appearance-none w-8 h-8 border-2 p-0 overflow-hidden cursor-pointer">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-1 justify-between">
|
<div class="flex flex-1 justify-between">
|
||||||
<p>Secondary:</p>
|
<p>Secondary:</p>
|
||||||
<input type="color" id="infoPicker" v-model="infoColor" @input="save()"
|
<input type="color" id="infoPicker" v-model="infoColor" @input="save()"
|
||||||
class="appearance-none w-8 h-8 border border-2 p-0 overflow-hidden cursor-pointer">
|
class="appearance-none w-8 h-8 border-2 p-0 overflow-hidden cursor-pointer">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-1 justify-between">
|
<div class="flex flex-1 justify-between">
|
||||||
<p>Border:</p>
|
<p>Border:</p>
|
||||||
<input type="color" id="borderPicker" v-model="borderColor" @input="save()"
|
<input type="color" id="borderPicker" v-model="borderColor" @input="save()"
|
||||||
class="appearance-none w-8 h-8 border border-2 p-0 overflow-hidden cursor-pointer">
|
class="appearance-none w-8 h-8 border-2 p-0 overflow-hidden cursor-pointer">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
import { useAudio } from '@/composables/useAudio';
|
import { useAudio } from '@/composables/useAudio';
|
||||||
|
|
||||||
const audioStore = useAudio();
|
const audioStore = useAudio();
|
||||||
|
|
||||||
|
const title = computed(() => audioStore.currentSong.value?.name || 'Unknown Title')
|
||||||
|
const artist = computed(() => audioStore.currentSong.value?.artist || 'Unknown Artist')
|
||||||
|
const bgimg = computed(() => audioStore.currentSong.value?.previewimage || '/default-bg.jpg')
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -18,55 +24,66 @@ const audioStore = useAudio();
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex-1 flex justify-center text-center action">
|
<main class="flex-1 flex flex-col items-center justify-center text-center px-4">
|
||||||
<div class="flex flex-col justify-around">
|
<div class="flex flex-col items-center w-full max-w-md space-y-6">
|
||||||
<div class="relative">
|
|
||||||
<i class="relative p-36 fa-solid fa-play">
|
|
||||||
|
|
||||||
<img class="absolute top-4 left-0 bottom-0 right-0 bg-center bg-cover rounded-lg"
|
<div class="relative w-full aspect-square">
|
||||||
:src="encodeURI(audioStore.bgimg.value + '?h=320&w=320')" :key="audioStore.bgimg.value" />
|
<img
|
||||||
</i>
|
class="absolute inset-0 w-full h-full object-cover rounded-lg shadow-lg"
|
||||||
</div>
|
:src="encodeURI(bgimg + '?h=320&w=320')"
|
||||||
|
:key="bgimg"
|
||||||
<div class="h-1/3 flex flex-col justify-center">
|
alt="Album Art"
|
||||||
<div class="flex-1"></div>
|
/>
|
||||||
<div>
|
<i class="absolute inset-0 flex items-center justify-center text-white text-5xl">
|
||||||
<div class="flex w-full justify-around">
|
<i class="fa-solid fa-play bg-black bg-opacity-50 p-4 rounded-full"></i>
|
||||||
<i class="fa-solid fa-backward-step text-5xl self-center" @click="audioStore.togglePrev"></i>
|
</i>
|
||||||
<i :class="[audioStore.isPlaying.value ? 'fa-circle-play' : 'fa-circle-pause']" class="fa-regular text-7xl "
|
|
||||||
@click="audioStore.togglePlay"></i>
|
|
||||||
<i class="fa-solid fa-forward-step text-5xl self-center" @click="audioStore.toggleNext"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-1 justify-around ml-4">
|
|
||||||
<i @click="audioStore.toggleShuffle" :class="[audioStore.shuffle.value ? 'info' : '']"
|
|
||||||
class="fa-solid fa-shuffle"></i>
|
|
||||||
|
|
||||||
<div class="m-4 info flex-1 overflow-hidden">
|
|
||||||
<p>{{ audioStore.title.value }}</p>
|
|
||||||
<RouterLink :to="'search?a=' + audioStore.artist.value">
|
|
||||||
|
|
||||||
{{ audioStore.artist.value }}
|
|
||||||
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col justify-between mb-4 mr-4">
|
|
||||||
<i @click="audioStore.toggleRepeat" :class="[audioStore.repeat.value ? 'info' : '']"
|
|
||||||
class="fa-solid fa-repeat"></i>
|
|
||||||
<i @click="$router.go(-1);" class="fa-solid fa-arrow-down"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<input
|
|
||||||
class="appearance-none mx-4 flex-1 bg-yellow-200 bg-opacity-20 accent-yellow-600 rounded-lg outline-none slider "
|
|
||||||
type="range" id="audio-slider" @change="audioStore.updateTime" max="100" :value="audioStore.percentDone.value">
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between mx-4">
|
|
||||||
<span id="current-time" class="time">{{ audioStore.currentTime.value }}</span>
|
|
||||||
<span id="duration" class="time ">{{ audioStore.duration.value }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
<div class="flex justify-between items-center w-full text-3xl space-x-6">
|
||||||
|
<i class="fa-solid fa-backward-step" @click="audioStore.togglePrevious"></i>
|
||||||
|
<i
|
||||||
|
:class="[audioStore.isPlaying.value ? 'fa-circle-pause' : 'fa-circle-play']"
|
||||||
|
class="fa-regular text-5xl"
|
||||||
|
@click="audioStore.togglePlay"
|
||||||
|
></i>
|
||||||
|
<i class="fa-solid fa-forward-step" @click="audioStore.toggleNext"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center w-full px-2">
|
||||||
|
<p class="truncate text-lg font-semibold">{{ title }}</p>
|
||||||
|
<RouterLink :to="'search?a=' + artist" class="block text-sm text-blue-500 truncate">
|
||||||
|
{{ artist }}
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center w-full px-4">
|
||||||
|
<i
|
||||||
|
@click="audioStore.toggleShuffle"
|
||||||
|
:class="[audioStore.shuffle.value ? 'text-yellow-500' : '']"
|
||||||
|
class="fa-solid fa-shuffle"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
@click="audioStore.toggleRepeat"
|
||||||
|
:class="[audioStore.repeat.value ? 'text-yellow-500' : '']"
|
||||||
|
class="fa-solid fa-repeat"
|
||||||
|
></i>
|
||||||
|
<i @click="$router.go(-1)" class="fa-solid fa-arrow-down"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full px-4">
|
||||||
|
<input
|
||||||
|
class="w-full appearance-none h-2 rounded-full bg-yellow-200 bg-opacity-20 accent-yellow-600 outline-none"
|
||||||
|
type="range"
|
||||||
|
@change="audioStore.updateTime"
|
||||||
|
:max="100"
|
||||||
|
:value="audioStore.percentDone.value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between text-sm w-full px-4">
|
||||||
|
<span>{{ audioStore.currentTime.value }}</span>
|
||||||
|
<span>{{ audioStore.duration.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ const fetchRecent = async () => {
|
|||||||
offset.value += limit.value;
|
offset.value += limit.value;
|
||||||
songs.value = [...songs.value, ...newSongs];
|
songs.value = [...songs.value, ...newSongs];
|
||||||
|
|
||||||
console.log(offset.value)
|
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
audioStore.setCollection(songs.value);
|
audioStore.setCollection(songs.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { mapApiToSongs, type Song } from '../script/types'
|
import { mapApiToSongs, mapToSong, type Song } from '../script/types'
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, onMounted, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import ActiveSearchList from '../components/ActiveSearchList.vue'
|
import ActiveSearchList from '../components/ActiveSearchList.vue'
|
||||||
import SongItem from '../components/SongItem.vue'
|
import SongItem from '../components/SongItem.vue'
|
||||||
import { useAudio } from '@/composables/useAudio'
|
import { useAudio } from '@/composables/useAudio'
|
||||||
import { useUser } from '@/composables/useUser'
|
import { useUser } from '@/composables/useUser'
|
||||||
import { MusicBackendApi } from '@/generated'
|
|
||||||
import { useApi } from '@/composables/useApi'
|
import { useApi } from '@/composables/useApi'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -43,7 +42,9 @@ async function fetchActiveSearch(term: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchSearchArtist(artist: string) {
|
async function fetchSearchArtist(artist: string) {
|
||||||
const data = await musicApi.MusicBackend_SearchArtists(artist)
|
const response = await api.musicBackendArtist(artist)
|
||||||
|
|
||||||
|
const data = mapApiToSongs(response.data.songs)
|
||||||
|
|
||||||
data.forEach((song: Song) => {
|
data.forEach((song: Song) => {
|
||||||
song.previewimage = `${userStore.baseUrl.value}/api/v1/images/${song.previewimage}`
|
song.previewimage = `${userStore.baseUrl.value}/api/v1/images/${song.previewimage}`
|
||||||
@@ -72,7 +73,6 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch for artist query changes
|
|
||||||
watch(() => route.query.a, async (newArtist) => {
|
watch(() => route.query.a, async (newArtist) => {
|
||||||
if (newArtist) {
|
if (newArtist) {
|
||||||
await fetchSearchArtist(newArtist as string)
|
await fetchSearchArtist(newArtist as string)
|
||||||
@@ -81,7 +81,6 @@ watch(() => route.query.a, async (newArtist) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Search input model
|
|
||||||
const searchInput = ref(searchTerm.value)
|
const searchInput = ref(searchTerm.value)
|
||||||
|
|
||||||
watch(searchInput, async (val) => {
|
watch(searchInput, async (val) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user