fix(musik): fix seek slider reset ke 0 - root cause: useEffect dependency

ROOT CAUSE:
- filteredMusik di-calculate ulang setiap render (.filter() tanpa memoization)
- currentSong = filteredMusik[currentSongIndex] → object reference baru setiap render
- useEffect dependency [currentSong, currentSongIndex] trigger setiap render
- useEffect reset setCurrentTime(0) → slider kembali ke awal

FIX:
1. useMemo untuk filteredMusik - mencegah re-calculate setiap render
2. useEffect dependency [currentSong?.id, currentSongIndex] - hanya trigger saat lagu benar-benar berubah
3. Hapus semua debug console.log yang tidak diperlukan
4. Simplifikasi seekTo function

File Changed:
- src/app/darmasaba/(pages)/musik/musik-desa/page.tsx
- src/app/darmasaba/(pages)/musik/lib/seek.ts

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-02 12:03:26 +08:00
parent 4d03908f23
commit 91e32f3f1c
2 changed files with 17 additions and 28 deletions

View File

@@ -3,23 +3,13 @@ export function seekTo(
time: number, time: number,
setCurrentTime?: (v: number) => void setCurrentTime?: (v: number) => void
) { ) {
if (!audioRef.current) { if (!audioRef.current) return;
console.error('[seekTo] Audio element tidak ada!');
return;
}
console.log('[seekTo] Before seek - currentTime:', audioRef.current.currentTime, 'Target:', time);
// Set waktu audio // Set waktu audio
audioRef.current.currentTime = time; audioRef.current.currentTime = time;
console.log('[seekTo] After seek - currentTime:', audioRef.current.currentTime);
// Update state jika provided // Update state jika provided
if (setCurrentTime) { if (setCurrentTime) {
setCurrentTime(Math.round(time)); setCurrentTime(Math.round(time));
} }
// Debug log
console.log('[seekTo] Seek to:', time, 'seconds');
} }

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import { ActionIcon, Avatar, Badge, Box, Card, Flex, Grid, Group, Paper, ScrollArea, Slider, Stack, Text, TextInput } from '@mantine/core'; import { ActionIcon, Avatar, Badge, Box, Card, Flex, Grid, Group, Paper, ScrollArea, Slider, Stack, Text, TextInput } from '@mantine/core';
import { IconArrowsShuffle, IconPlayerPauseFilled, IconPlayerPlayFilled, IconPlayerSkipBackFilled, IconPlayerSkipForwardFilled, IconRepeat, IconRepeatOff, IconSearch, IconVolume, IconVolumeOff, IconX } from '@tabler/icons-react'; import { IconArrowsShuffle, IconPlayerPauseFilled, IconPlayerPlayFilled, IconPlayerSkipBackFilled, IconPlayerSkipForwardFilled, IconRepeat, IconRepeatOff, IconSearch, IconVolume, IconVolumeOff, IconX } from '@tabler/icons-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import BackButton from '../../desa/layanan/_com/BackButto'; import BackButton from '../../desa/layanan/_com/BackButto';
import { togglePlayPause } from '../lib/playPause'; import { togglePlayPause } from '../lib/playPause';
import { getNextIndex, getPrevIndex } from '../lib/nextPrev'; import { getNextIndex, getPrevIndex } from '../lib/nextPrev';
@@ -68,12 +68,14 @@ const MusicPlayer = () => {
fetchMusik(); fetchMusik();
}, []); }, []);
// Filter musik based on search // Filter musik based on search - gunakan useMemo untuk mencegah re-calculate setiap render
const filteredMusik = musikData.filter(musik => const filteredMusik = useMemo(() => {
musik.judul.toLowerCase().includes(search.toLowerCase()) || return musikData.filter(musik =>
musik.artis.toLowerCase().includes(search.toLowerCase()) || musik.judul.toLowerCase().includes(search.toLowerCase()) ||
(musik.genre && musik.genre.toLowerCase().includes(search.toLowerCase())) musik.artis.toLowerCase().includes(search.toLowerCase()) ||
); (musik.genre && musik.genre.toLowerCase().includes(search.toLowerCase()))
);
}, [musikData, search]);
const currentSong = currentSongIndex >= 0 && currentSongIndex < filteredMusik.length const currentSong = currentSongIndex >= 0 && currentSongIndex < filteredMusik.length
? filteredMusik[currentSongIndex] ? filteredMusik[currentSongIndex]
@@ -103,12 +105,15 @@ const MusicPlayer = () => {
// Update duration when song changes // Update duration when song changes
useEffect(() => { useEffect(() => {
if (currentSong && audioRef.current) { if (currentSong && audioRef.current) {
console.log('[useEffect Song Change] currentSong:', currentSong.judul, 'Index:', currentSongIndex);
// Gunakan durasi dari database sebagai acuan utama // Gunakan durasi dari database sebagai acuan utama
const durationParts = currentSong.durasi.split(':'); const durationParts = currentSong.durasi.split(':');
const durationInSeconds = parseInt(durationParts[0]) * 60 + parseInt(durationParts[1]); const durationInSeconds = parseInt(durationParts[0]) * 60 + parseInt(durationParts[1]);
setDuration(durationInSeconds); setDuration(durationInSeconds);
// Reset audio currentTime ke 0 hanya untuk lagu baru
audioRef.current.currentTime = 0;
setCurrentTime(0); setCurrentTime(0);
if (isPlaying) { if (isPlaying) {
audioRef.current.play().catch(err => { audioRef.current.play().catch(err => {
console.error('Error playing audio:', err); console.error('Error playing audio:', err);
@@ -116,7 +121,7 @@ const MusicPlayer = () => {
}); });
} }
} }
}, [currentSongIndex, currentSong, isPlaying]); }, [currentSong?.id, currentSongIndex]);
const formatTime = (seconds: number) => { const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60); const mins = Math.floor(seconds / 60);
@@ -162,7 +167,6 @@ const MusicPlayer = () => {
const skipBack = () => { const skipBack = () => {
const prevIndex = getPrevIndex(currentSongIndex, filteredMusik.length, isShuffle); const prevIndex = getPrevIndex(currentSongIndex, filteredMusik.length, isShuffle);
if (prevIndex >= 0) { if (prevIndex >= 0) {
console.log('[skipBack] Changing song to index:', prevIndex);
playSong(prevIndex); playSong(prevIndex);
} }
}; };
@@ -170,7 +174,6 @@ const MusicPlayer = () => {
const skipForward = () => { const skipForward = () => {
const nextIndex = getNextIndex(currentSongIndex, filteredMusik.length, isShuffle); const nextIndex = getNextIndex(currentSongIndex, filteredMusik.length, isShuffle);
if (nextIndex >= 0) { if (nextIndex >= 0) {
console.log('[skipForward] Changing song to index:', nextIndex);
playSong(nextIndex); playSong(nextIndex);
} }
}; };
@@ -204,7 +207,6 @@ const MusicPlayer = () => {
src={currentSong.audioFile.link} src={currentSong.audioFile.link}
muted={isMuted} muted={isMuted}
onLoadedMetadata={(e) => { onLoadedMetadata={(e) => {
console.log('[onLoadedMetadata] Audio loaded, duration:', e.currentTarget.duration);
// Jangan override duration dari database // Jangan override duration dari database
// Audio element duration bisa berbeda beberapa ms // Audio element duration bisa berbeda beberapa ms
if (audioRef.current) { if (audioRef.current) {
@@ -216,7 +218,6 @@ const MusicPlayer = () => {
// Gunakan pembulatan yang lebih smooth // Gunakan pembulatan yang lebih smooth
const time = audioRef.current.currentTime; const time = audioRef.current.currentTime;
const roundedTime = Math.round(time); const roundedTime = Math.round(time);
console.log('[onTimeUpdate] currentTime:', time, 'rounded:', roundedTime, 'isSeeking:', isSeeking);
setCurrentTime(roundedTime); setCurrentTime(roundedTime);
}} }}
onEnded={handleSongEnd} onEnded={handleSongEnd}
@@ -281,12 +282,10 @@ const MusicPlayer = () => {
setCurrentTime(v); setCurrentTime(v);
}} }}
onChangeEnd={(v) => { onChangeEnd={(v) => {
console.log('[Slider Atas onChangeEnd] START - value:', v);
// Validasi: jangan seek melebihi durasi // Validasi: jangan seek melebihi durasi
const seekTime = Math.min(Math.max(0, v), duration); const seekTime = Math.min(Math.max(0, v), duration);
// Set seeking false DULUAN sebelum seekTo // Set seeking false DULUAN sebelum seekTo
setIsSeeking(false); setIsSeeking(false);
console.log('[Slider Atas onChangeEnd] Calling seekTo with:', seekTime);
seekTo(audioRef, seekTime, setCurrentTime); seekTo(audioRef, seekTime, setCurrentTime);
}} }}
color="#0B4F78" color="#0B4F78"
@@ -428,11 +427,11 @@ const MusicPlayer = () => {
setCurrentTime(v); // preview - update UI saja setCurrentTime(v); // preview - update UI saja
}} }}
onChangeEnd={(v) => { onChangeEnd={(v) => {
setIsSeeking(false);
// Validasi: jangan seek melebihi durasi // Validasi: jangan seek melebihi durasi
const seekTime = Math.min(Math.max(0, v), duration); const seekTime = Math.min(Math.max(0, v), duration);
// Set seeking false DULUAN sebelum seekTo
setIsSeeking(false);
seekTo(audioRef, seekTime, setCurrentTime); seekTo(audioRef, seekTime, setCurrentTime);
console.log('[Slider Bawah] Seek end:', seekTime, 'Duration:', duration);
}} }}
color="#0B4F78" color="#0B4F78"
size="xs" size="xs"