FEAT: playlists player v1
This commit is contained in:
14
components/playlists-list.vue
Normal file
14
components/playlists-list.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<section>
|
||||
<div v-for="playlist in playlists.files" class="text-white">
|
||||
<NuxtLink :to="'/playlists/' + playlist"
|
||||
class="compilation mx-auto p-4 inline-flex hover:bg-esyellow hover:text-black">
|
||||
{{ playlist }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: playlists } = await useFetch('/api/playlists')
|
||||
</script>
|
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<div class="compilation mx-auto p-4 inline-flex">
|
||||
<atropos-component ref="atropos" class="my-atropos" active-offset="80" shadow-scale="1.05">
|
||||
<img src="/zero/sky-b.jpg" data-atropos-offset="-8" />
|
||||
<img src="/zero/propeller-b.png" data-atropos-offset="-3" class="absolute inset-0 object-cover" />
|
||||
<img src="/zero/zero-b.png" data-atropos-offset="0" class="absolute inset-0 object-cover" />
|
||||
<img src="/logo.svg" data-atropos-offset="0" width="70%" class="logo absolute inset-0" />
|
||||
</atropos-component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const atropos = ref(null)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* .my-atropos {
|
||||
width: 320px;
|
||||
height: 160px;
|
||||
} */
|
||||
.logo {
|
||||
filter: drop-shadow(4px 4px 0 rgb(0 0 0 / 0.5));
|
||||
left: 14%;
|
||||
top: 10%;
|
||||
}
|
||||
</style>
|
@@ -27,8 +27,9 @@
|
||||
</video>
|
||||
</section>
|
||||
<section class="flex justify-center">
|
||||
<div class="flex max-w-2xl">
|
||||
<div class="flex flex-col max-w-2xl">
|
||||
<compilationsList />
|
||||
<playlistsList />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
52
pages/playlists/[id].vue
Normal file
52
pages/playlists/[id].vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<section class="text-white flex flex-col sticky">
|
||||
<div class="player top-0 w-full bg-gray-500 p-10">
|
||||
<nuxt-link to="/" class="flex flex-col items-center">
|
||||
<img src="/logo.svg" class="w-44">
|
||||
</nuxt-link>
|
||||
<h1 class="text-4xl">{{ route.params.id }}</h1>
|
||||
<h2 class="text-2xl p-4 text-esyellow">{{ currentTrack }}</h2>
|
||||
<audio ref="playlistPlayer" :src="currentTrackUrl" @ended="onTrackEnd" controls></audio>
|
||||
</div>
|
||||
<button v-for="(track, index) in tracks.files"
|
||||
class="text-white p-4 inline-flex hover:bg-esyellow hover:text-black w-full" :index="index" @click="play(index)"
|
||||
:class="{ 'playing': index === currentTrackId }">
|
||||
{{ index }}
|
||||
{{ track }}
|
||||
</button>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router'
|
||||
const playlistPlayer = ref()
|
||||
const currentTrackId = ref(0)
|
||||
const route = useRoute()
|
||||
const { data: tracks } = await useFetch('/api/playlists/' + route.params.id)
|
||||
|
||||
const currentTrack = computed(() => tracks.value.files[currentTrackId.value])
|
||||
const currentTrackUrl = computed(() => 'https://files.erudi.fr/music/' + route.params.id + '/' + currentTrack.value)
|
||||
|
||||
const play = async (trackId) => {
|
||||
if (trackId != currentTrackId.value) {
|
||||
currentTrackId.value = trackId
|
||||
await playlistPlayer.value.load()
|
||||
}
|
||||
if (playlistPlayer.value.currentTime > 0 && !playlistPlayer.value.paused && !playlistPlayer.value.ended && playlistPlayer.value.readyState > 2) {
|
||||
playlistPlayer.value.pause()
|
||||
} else {
|
||||
playlistPlayer.value.play()
|
||||
}
|
||||
}
|
||||
|
||||
const onTrackEnd = () => {
|
||||
play(currentTrackId.value + 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.playing {
|
||||
@apply bg-esyellow text-black;
|
||||
}
|
||||
</style>
|
23
server/api/playlists/[id].ts
Normal file
23
server/api/playlists/[id].ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const id = event.context.params?.id || ''
|
||||
|
||||
const directoryPath = path.join(process.cwd(), 'media/files/music/' + id) // replace 'your-folder' with the folder you want to list
|
||||
|
||||
try {
|
||||
// Read the directory contents
|
||||
const files = await fs.promises.readdir(directoryPath)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
files: files.filter(file => !file.startsWith('.')) // optional: exclude unwanted files
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
})
|
21
server/api/playlists/index.ts
Normal file
21
server/api/playlists/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const directoryPath = path.join(process.cwd(), 'media/files/music')
|
||||
|
||||
try {
|
||||
// Read the directory contents
|
||||
const files = await fs.promises.readdir(directoryPath)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
files: files.filter(file => !file.startsWith('.')).reverse() // exclude unwanted files
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
})
|
Reference in New Issue
Block a user