diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 09adb15..e8e4760 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -11,7 +11,8 @@ import { useAudio } from './composables/useAudio.ts' const showNowPlaying = ref(true); const route = useRoute(); -const audio = useAudio(); +const audioRef = ref(null) +const audio = useAudio(audioRef); watch(route, async (to) => { @@ -61,32 +62,29 @@ onMounted(() => { onUnmounted(() => { window.removeEventListener('resize', checkScreenSize); }); - - + \ No newline at end of file diff --git a/frontend/src/components/NowPlaying.vue b/frontend/src/components/NowPlaying.vue index 6f0f818..cd7a88b 100644 --- a/frontend/src/components/NowPlaying.vue +++ b/frontend/src/components/NowPlaying.vue @@ -1,15 +1,23 @@ +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() +}) + diff --git a/frontend/src/components/SongItem.vue b/frontend/src/components/SongItem.vue index 514e739..a52ae8c 100644 --- a/frontend/src/components/SongItem.vue +++ b/frontend/src/components/SongItem.vue @@ -13,6 +13,7 @@ const audioStore = useAudio(); function updateSong() { let updated = props.song; + console.log("updating song:", updated) audioStore.setSong(updated) } diff --git a/frontend/src/components/icons/IconCommunity.vue b/frontend/src/components/icons/IconCommunity.vue deleted file mode 100644 index 2dc8b05..0000000 --- a/frontend/src/components/icons/IconCommunity.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/frontend/src/components/icons/IconDocumentation.vue b/frontend/src/components/icons/IconDocumentation.vue deleted file mode 100644 index 6d4791c..0000000 --- a/frontend/src/components/icons/IconDocumentation.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/frontend/src/components/icons/IconEcosystem.vue b/frontend/src/components/icons/IconEcosystem.vue deleted file mode 100644 index c3a4f07..0000000 --- a/frontend/src/components/icons/IconEcosystem.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/frontend/src/components/icons/IconSupport.vue b/frontend/src/components/icons/IconSupport.vue deleted file mode 100644 index 7452834..0000000 --- a/frontend/src/components/icons/IconSupport.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/frontend/src/components/icons/IconTooling.vue b/frontend/src/components/icons/IconTooling.vue deleted file mode 100644 index 660598d..0000000 --- a/frontend/src/components/icons/IconTooling.vue +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/frontend/src/composables/useAudio.ts b/frontend/src/composables/useAudio.ts index 4e9ddc4..8f76e0b 100644 --- a/frontend/src/composables/useAudio.ts +++ b/frontend/src/composables/useAudio.ts @@ -7,214 +7,149 @@ let audioInstance: ReturnType | null = null function createAudio() { const { musicApi } = useApi() - const songSrc = ref('https://cdn.pixabay.com/audio/2024/05/24/audio_46382ae035.mp3') - const artist = ref('Artist') - const title = ref('Title') - const bgimg = ref('https://assets.ppy.sh/beatmaps/2197744/covers/cover@2x.jpg?1722207959') - const hash = ref('0000') + const audioElement = document.createElement('audio') + audioElement.setAttribute('id', 'global-audio') + audioElement.setAttribute('controls', '') + audioElement.classList.add('hidden') + document.body.appendChild(audioElement) - const isPlaying = ref(false) - const duration = ref('0:00') - const currentTime = ref('0:00') - const percentDone = ref(0) - - const shuffle = ref(false) - const repeat = ref(false) - - const activeCollection = ref([]) - const currentSong = ref(null) - - function saveSongToLocalStorage(song: Song) { - console.log(song) - localStorage.setItem('lastPlayedSong', JSON.stringify(song)) + const state = { + isPlaying: ref(false), + duration: ref('0:00'), + currentTime: ref('0:00'), + percentDone: ref(0), + shuffle: ref(false), + repeat: ref(false), + activeSongs: ref([]), + currentSong: ref(null), } - function loadSongFromLocalStorage(): Song | null { - const song = localStorage.getItem('lastPlayedSong') - return song ? JSON.parse(song) : null + function saveToLocalStorage(key: string, data: any) { + localStorage.setItem(key, JSON.stringify(data)) } - function saveCollectionToLocalStorage(collection: Song[]) { - localStorage.setItem('lastActiveCollection', JSON.stringify(collection)) - } - - 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 loadFromLocalStorage(key: string): T | null { + const item = localStorage.getItem(key) + return item ? JSON.parse(item) as T : null } function setSong(song: Song | null) { + console.log(song) if (!song) return + state.currentSong.value = song + saveToLocalStorage('lastPlayedSong', song) - songSrc.value = song.url - artist.value = song.artist - title.value = song.name - bgimg.value = song.previewimage - hash.value = song.hash - - 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 }) + audioElement.pause() + audioElement.src = song.url + audioElement.addEventListener( + 'canplaythrough', + () => audioElement.play().catch(console.error), + { once: true } + ) } - function setCollection(songs: Song[] | null) { - if (!songs) return - activeCollection.value = songs - saveCollectionToLocalStorage(songs) + function setCollection(song: Song[] | null) { + if (!song) return + state.activeSongs.value = song + 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 { const api = musicApi() - const response = await api.musicBackendRecent() - if (response.data.songs) { - setCollection(mapApiToSongs(response.data.songs)) + const res = await api.musicBackendRecent() + let songs = mapApiToSongs(res.data.songs); + if (res.data?.songs?.length) { + setSong(songs[0]) } - } catch (error) { - console.error('Failed to load songs:', error) + } catch (err) { + console.error('Failed to load song:', err) } } function init() { - setSong(loadSongFromLocalStorage()) - setCollection(loadCollectionFromLocalStorage()) + setSong(loadFromLocalStorage('lastPlayedSong')) + setCollection(loadFromLocalStorage('activeCollection')) - console.log(activeCollection.value) - console.log(currentSong.value) - if (!currentSong.value || activeCollection.value.length === 0) { - loadInitialCollection() + if (!state.currentSong.value) { + loadInitialSong() } + + audioElement.addEventListener('timeupdate', update) } - onMounted(() => { - init() - }) + onMounted(init) return { - songSrc, - artist, - title, - bgimg, - hash, - isPlaying, - duration, - currentTime, - percentDone, - shuffle, - repeat, - activeCollection, - currentSong, + ...state, togglePlay, - update, - next, - updateTime, - togglePrev, + toggleShuffle: () => (state.shuffle.value = !state.shuffle.value), + toggleRepeat: () => (state.repeat.value = !state.repeat.value), toggleNext, - toggleShuffle, - toggleRepeat, + togglePrevious, + updateTime, setSong, setCollection, init, diff --git a/frontend/src/script/types.ts b/frontend/src/script/types.ts index 6701aeb..8436166 100644 --- a/frontend/src/script/types.ts +++ b/frontend/src/script/types.ts @@ -13,13 +13,18 @@ export type Song = { const basePath = import.meta.env.BACKEND_URL || 'http://localhost:8080'; export function mapToSong(apiSong: Apiv1Song): Song { + const image = apiSong.image; + const imageIsMissing = !image || image === "404.png"; + return { hash: apiSong.md5Hash, name: apiSong.title, artist: apiSong.artist, length: Number(apiSong.totalTime), 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, }; } @@ -40,7 +45,7 @@ export function mapToCollectionPreview( index, name: apiCollection.name, length: apiCollection.items, - previewimage: `${basePath}/api/v1/images/${apiCollection.image}`, + previewimage: `${basePath}/api/v1/image/${apiCollection.image}`, }; } diff --git a/frontend/src/stores/headerStore.ts b/frontend/src/stores/headerStore.ts deleted file mode 100644 index dba1fcd..0000000 --- a/frontend/src/stores/headerStore.ts +++ /dev/null @@ -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 } -}) diff --git a/frontend/src/stores/userStore.ts b/frontend/src/stores/userStore.ts deleted file mode 100644 index 5f2838a..0000000 --- a/frontend/src/stores/userStore.ts +++ /dev/null @@ -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(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 { - 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(cacheKey: string, url: string, cacheDuration: number = 24 * 60 * 60 * 1): Promise { - 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 { - const cacheKey = `collections_cache_${offset}_${limit}`; - const url = `${baseUrl.value}/api/v1/collections/?offset=${offset}&limit=${limit}`; - return fetchWithCache(cacheKey, url); - } - - async function fetchCollection(index: number): Promise { - const cacheKey = `collection_${index}_cache`; - const url = `${baseUrl.value}/api/v1/collection/${index}`; - return fetchWithCache(cacheKey, url); - } - - async function fetchFavorites(limit: number, offset: number): Promise { - const cacheKey = `favorites_${limit}_${offset}_cache`; - const url = `${baseUrl.value}/api/v1/recent?limit=${limit}&offset=${offset}`; - return fetchWithCache(cacheKey, url); - } - - async function fetchRecent(limit: number, offset: number): Promise { - const cacheKey = `recent_${limit}_${offset}_cache`; - const url = `${baseUrl.value}/api/v1/songs/recent?limit=${limit}&offset=${offset}`; - return fetchWithCache(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 { - const cacheKey = `collections_artist_${query}`; - const url = `${baseUrl.value}/api/v1/search/artist?q=${query}`; - return fetchWithCache(cacheKey, url); - } - - async function fetchMe(): Promise { - 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 } -}) diff --git a/frontend/src/views/CollectionView.vue b/frontend/src/views/CollectionView.vue index db38d24..1f69dbb 100644 --- a/frontend/src/views/CollectionView.vue +++ b/frontend/src/views/CollectionView.vue @@ -1,10 +1,13 @@ diff --git a/frontend/src/views/RecentView.vue b/frontend/src/views/RecentView.vue index 38371e0..f8c873c 100644 --- a/frontend/src/views/RecentView.vue +++ b/frontend/src/views/RecentView.vue @@ -33,8 +33,6 @@ const fetchRecent = async () => { offset.value += limit.value; songs.value = [...songs.value, ...newSongs]; - console.log(offset.value) - isLoading.value = false; audioStore.setCollection(songs.value); } diff --git a/frontend/src/views/SearchView.vue b/frontend/src/views/SearchView.vue index 16f6f09..e42ee27 100644 --- a/frontend/src/views/SearchView.vue +++ b/frontend/src/views/SearchView.vue @@ -1,12 +1,11 @@