add working player
This commit is contained in:
		| @@ -31,6 +31,8 @@ const props = withDefaults( | ||||
|   { BoxState: 'list' } | ||||
| ) | ||||
|  | ||||
| const isDraggable = computed(() => !['list', 'hidden'].includes(BoxState.value())) | ||||
|  | ||||
| // --- Réfs --- | ||||
| const scene = ref<HTMLElement>() | ||||
| const box = ref<HTMLElement>() | ||||
| @@ -123,61 +125,81 @@ function tickInertia() { | ||||
| } | ||||
|  | ||||
| // --- Pointer events --- | ||||
| let listenersAttached = false | ||||
|  | ||||
| const down = (ev: PointerEvent) => { | ||||
|   ev.preventDefault() | ||||
|   dragging = true | ||||
|   box.value?.setPointerCapture(ev.pointerId) | ||||
|   lastPointer = { x: ev.clientX, y: ev.clientY, time: performance.now() } | ||||
|   velocity = { x: 0, y: 0 } | ||||
|   if (raf) { cancelAnimationFrame(raf); raf = null } | ||||
| } | ||||
|  | ||||
| const move = (ev: PointerEvent) => { | ||||
|   if (!dragging) return | ||||
|   ev.preventDefault() | ||||
|   const now = performance.now() | ||||
|   const dx = ev.clientX - lastPointer.x | ||||
|   const dy = ev.clientY - lastPointer.y | ||||
|   const dt = Math.max(1, now - lastPointer.time) | ||||
|  | ||||
|   rotateY.value += dx * sensitivity | ||||
|   rotateX.value -= dy * sensitivity | ||||
|   rotateX.value = Math.max(-80, Math.min(80, rotateX.value)) | ||||
|  | ||||
|   velocity.x = (dx / dt) * 16 * sensitivity | ||||
|   velocity.y = (-dy / dt) * 16 * sensitivity | ||||
|  | ||||
|   lastPointer = { x: ev.clientX, y: ev.clientY, time: now } | ||||
|   applyTransform(0) // immédiat pendant drag | ||||
| } | ||||
|  | ||||
| const end = (ev: PointerEvent) => { | ||||
|   if (!dragging) return | ||||
|   dragging = false | ||||
|   try { box.value?.releasePointerCapture(ev.pointerId) } catch { } | ||||
|   if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) { | ||||
|     if (!raf) raf = requestAnimationFrame(tickInertia) | ||||
|   } | ||||
| } | ||||
|  | ||||
| function addListeners() { | ||||
|   if (!box.value || listenersAttached) return | ||||
|   box.value.addEventListener('pointerdown', down) | ||||
|   box.value.addEventListener('pointermove', move) | ||||
|   box.value.addEventListener('pointerup', end) | ||||
|   box.value.addEventListener('pointercancel', end) | ||||
|   box.value.addEventListener('pointerleave', end) | ||||
|   listenersAttached = true | ||||
| } | ||||
|  | ||||
| function removeListeners() { | ||||
|   if (!box.value || !listenersAttached) return | ||||
|   box.value.removeEventListener('pointerdown', down) | ||||
|   box.value.removeEventListener('pointermove', move) | ||||
|   box.value.removeEventListener('pointerup', end) | ||||
|   box.value.removeEventListener('pointercancel', end) | ||||
|   box.value.removeEventListener('pointerleave', end) | ||||
|   listenersAttached = false | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   applyColor() | ||||
|   applyBoxState() | ||||
|   if (isDraggable) addListeners() | ||||
| }) | ||||
|  | ||||
|   const down = (ev: PointerEvent) => { | ||||
|     ev.preventDefault() | ||||
|     dragging = true | ||||
|     box.value?.setPointerCapture(ev.pointerId) | ||||
|     lastPointer = { x: ev.clientX, y: ev.clientY, time: performance.now() } | ||||
|     velocity = { x: 0, y: 0 } | ||||
|     if (raf) { cancelAnimationFrame(raf); raf = null } | ||||
|   } | ||||
|  | ||||
|   const move = (ev: PointerEvent) => { | ||||
|     if (!dragging) return | ||||
|     ev.preventDefault() | ||||
|     const now = performance.now() | ||||
|     const dx = ev.clientX - lastPointer.x | ||||
|     const dy = ev.clientY - lastPointer.y | ||||
|     const dt = Math.max(1, now - lastPointer.time) | ||||
|  | ||||
|     rotateY.value += dx * sensitivity | ||||
|     rotateX.value -= dy * sensitivity | ||||
|     rotateX.value = Math.max(-80, Math.min(80, rotateX.value)) | ||||
|  | ||||
|     velocity.x = (dx / dt) * 16 * sensitivity | ||||
|     velocity.y = (-dy / dt) * 16 * sensitivity | ||||
|  | ||||
|     lastPointer = { x: ev.clientX, y: ev.clientY, time: now } | ||||
|     applyTransform(0) // immédiat pendant drag | ||||
|   } | ||||
|  | ||||
|   const end = (ev: PointerEvent) => { | ||||
|     if (!dragging) return | ||||
|     dragging = false | ||||
|     try { box.value?.releasePointerCapture(ev.pointerId) } catch { } | ||||
|     if (enableInertia && (Math.abs(velocity.x) > minVelocity || Math.abs(velocity.y) > minVelocity)) { | ||||
|       if (!raf) raf = requestAnimationFrame(tickInertia) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   box.value?.addEventListener('pointerdown', down) | ||||
|   box.value?.addEventListener('pointermove', move) | ||||
|   box.value?.addEventListener('pointerup', end) | ||||
|   box.value?.addEventListener('pointercancel', end) | ||||
|   box.value?.addEventListener('pointerleave', end) | ||||
|  | ||||
|   onBeforeUnmount(() => { | ||||
|     cancelAnimationFrame(raf!) | ||||
|   }) | ||||
| onBeforeUnmount(() => { | ||||
|   cancelAnimationFrame(raf!) | ||||
|   removeListeners() | ||||
| }) | ||||
|  | ||||
| // --- Watchers --- | ||||
| watch(() => props.BoxState, () => applyBoxState()) | ||||
| watch(() => props.compilation, () => applyColor(), { deep: true }) | ||||
| watch(() => isDraggable, (enabled) => enabled ? addListeners() : removeListeners()) | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <article class="flip-card w-56 h-80" :data-flipped="props.isflipped"> | ||||
|   <article class="flip-card w-56 h-80"> | ||||
|     <div class="flip-inner"> | ||||
|       <main | ||||
|         class="flip-front backdrop-blur-sm border-2 -mt-12 z-10 card w-56 h-80 p-3 bg-opacity-40 hover:bg-opacity-80 hover:shadow-xl transition-all bg-white rounded-2xl shadow-lg flex flex-col overflow-hidden"> | ||||
| @@ -39,7 +39,7 @@ | ||||
| import type { Track } from '~~/types/types' | ||||
| import { usePlayerStore } from '~/store/player' | ||||
|  | ||||
| const props = defineProps<{ track: Track, isflipped: false }>() | ||||
| const props = defineProps<{ track: Track }>() | ||||
| const playerStore = usePlayerStore() | ||||
| const coverUrl = props.track.coverId.startsWith('http') | ||||
|   ? props.track.coverId | ||||
| @@ -66,7 +66,7 @@ const coverUrl = props.track.coverId.startsWith('http') | ||||
|   transform-style: preserve-3d; | ||||
| } | ||||
|  | ||||
| .flip-card[data-flipped=false] .flip-inner { | ||||
| .flipped .flip-inner { | ||||
|   transform: rotateY(180deg); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,14 @@ | ||||
| <template> | ||||
|   <audio ref="audioRef" class="fixed z-50 bottom-0 left-1/2 -translate-x-1/2 w-1/2" | ||||
|     :src="playerStore.currentTrack ? playerStore.getCompilationUrlFromTrack(playerStore.currentTrack) : ''" controls | ||||
|     @timeupdate="updatePosition" @ended="onEnded" /> | ||||
|   <div class="fixed left-0 bottom-0 opacity-1 z-50 w-full bg-white transition-all" | ||||
|     :class="{ '-bottom-20 opacity-0': !playerStore.currentTrack }"> | ||||
|     <!-- <p class="hidden"> | ||||
|       {{ Math.round(currentTime) }} | ||||
|       {{ Math.round(currentProgression) }}% | ||||
|     </p> --> | ||||
|     <audio ref="audioRef" class="w-full" | ||||
|       :src="playerStore.currentTrack ? playerStore.getCompilationUrlFromTrack(playerStore.currentTrack) : ''" | ||||
|       controls /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| @@ -10,39 +17,36 @@ import { usePlayerStore } from '~/store/player' | ||||
|  | ||||
| const playerStore = usePlayerStore() | ||||
| const audioRef = ref<HTMLAudioElement | null>(null) | ||||
| const currentTime = ref(0) | ||||
| const lastValidProgression = ref(0) | ||||
|  | ||||
| onMounted(() => { | ||||
|   if (audioRef.value) playerStore.audio = audioRef.value | ||||
| const currentProgression = computed(() => { | ||||
|   if (!audioRef.value) return 0 | ||||
|   const progression = (currentTime.value / audioRef.value.duration) * 100 | ||||
|  | ||||
|   if (!isNaN(progression)) { | ||||
|     lastValidProgression.value = progression | ||||
|   } | ||||
|  | ||||
|   return lastValidProgression.value | ||||
| }) | ||||
|  | ||||
| // Mettre à jour la position | ||||
| function updatePosition() { | ||||
|   if (audioRef.value) playerStore.position = audioRef.value.currentTime | ||||
| } | ||||
|  | ||||
| function onEnded() { | ||||
|   playerStore.isPlaying = false | ||||
| } | ||||
|  | ||||
| // Si la track change, mettre à jour le src et le start | ||||
| watch( | ||||
|   () => playerStore.currentTrack, | ||||
|   (newTrack) => { | ||||
|     if (newTrack && audioRef.value) { | ||||
|       audioRef.value.src = newTrack.url | ||||
|       audioRef.value.currentTime = newTrack.start || 0 | ||||
|       if (playerStore.isPlaying) audioRef.value.play() | ||||
|     } | ||||
| function updateTime() { | ||||
|   if (audioRef.value) { | ||||
|     currentTime.value = audioRef.value.currentTime | ||||
|   } | ||||
| ) | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   if (audioRef.value) { | ||||
|     playerStore.audio = audioRef.value | ||||
|     audioRef.value.addEventListener("timeupdate", updateTime) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| onUnmounted(() => { | ||||
|   if (audioRef.value) { | ||||
|     audioRef.value.removeEventListener("timeupdate", updateTime) | ||||
|   } | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| audio { | ||||
|   transition: all 1s; | ||||
| } | ||||
|  | ||||
| audio[src=""] { | ||||
|   @apply -bottom-1.5 opacity-0 | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user