ahora reproduce canciones
This commit is contained in:
@@ -72,11 +72,26 @@ class SongController extends Controller
|
||||
return response()->noContent(200);
|
||||
}
|
||||
/**
|
||||
* @return void
|
||||
* @param mixed $id
|
||||
* @param int $id
|
||||
*/
|
||||
public function stream($id):void
|
||||
public function stream($id)
|
||||
{
|
||||
$song = Song::find($id);
|
||||
|
||||
// Stream specified song
|
||||
$this->authorize('view', $song);
|
||||
|
||||
if ($song && Storage::exists($song->path)) {
|
||||
$file = Storage::path($song->path);
|
||||
return response()->stream(function () use ($file) {
|
||||
$stream = fopen($file, 'rb');
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, [
|
||||
'Content-Type' => 'audio/mpeg',
|
||||
'Content-Disposition' => 'inline; filename="' . $file . '"',
|
||||
'Accept-Ranges' => 'bytes',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cancion } from '@/types/types';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import PauseIcon from './Icons/PauseIcon';
|
||||
import PlayIcon from './Icons/PlayIcon';
|
||||
|
||||
export default function Reproductor({ titulo }) {
|
||||
export default function Reproductor({
|
||||
song = {
|
||||
id: 0,
|
||||
title: 'Sin título',
|
||||
artist: 'Artista desconocido',
|
||||
path: '',
|
||||
cover: null,
|
||||
},
|
||||
setSong,
|
||||
playlist = [],
|
||||
setplaylist,
|
||||
}: {
|
||||
song: Cancion;
|
||||
setSong: (arg0: Cancion) => void;
|
||||
playlist: Cancion[];
|
||||
setplaylist: (arg0: Cancion[]) => void;
|
||||
}) {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [timeLeft, setTimeLeft] = useState('0:00');
|
||||
|
||||
const [isPlaylistVisible, setIsPlaylistVisible] = useState(false);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (event.code === 'Space') {
|
||||
@@ -21,27 +41,163 @@ export default function Reproductor({ titulo }) {
|
||||
};
|
||||
}, [isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const playlistElement = document.querySelector('div[tabIndex="0"]');
|
||||
const toggleButton = event.target as HTMLElement;
|
||||
|
||||
if (
|
||||
playlistElement &&
|
||||
!playlistElement.contains(event.target as Node) &&
|
||||
!toggleButton.closest('button')
|
||||
) {
|
||||
setIsPlaylistVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioRef.current) return;
|
||||
if (isPlaying) {
|
||||
audioRef.current.play();
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
const playNextSong = () => {
|
||||
const currentIndex = playlist.findIndex((s) => s.id === song.id);
|
||||
if (currentIndex > -1 && currentIndex < playlist.length - 1) {
|
||||
setSong(playlist[currentIndex + 1]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (song.id === 0) {
|
||||
setIsPlaying(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsPlaying(true);
|
||||
|
||||
if (!audioRef.current) {
|
||||
audioRef.current = new Audio();
|
||||
}
|
||||
const audio = audioRef.current;
|
||||
audio.src = `/canciones/stream/${song.id}`;
|
||||
audio.load();
|
||||
|
||||
if (timeLeft === '0:00' && song.id !== 0) {
|
||||
setIsPlaying(false);
|
||||
setTimeLeft('0:00');
|
||||
setProgress(0);
|
||||
song = {
|
||||
id: 0,
|
||||
title: 'Sin título',
|
||||
artist: 'Artista desconocido',
|
||||
path: '',
|
||||
cover: null,
|
||||
};
|
||||
}
|
||||
|
||||
setIsPlaying(true);
|
||||
|
||||
audio.addEventListener('timeupdate', () => {
|
||||
const duration = audio.duration;
|
||||
const currentTime = audio.currentTime;
|
||||
const progress = (currentTime / duration) * 100;
|
||||
const timeLeft = Math.floor(duration - currentTime);
|
||||
const minutes = Math.floor(timeLeft / 60);
|
||||
const seconds = Math.floor(timeLeft % 60);
|
||||
|
||||
setProgress(progress);
|
||||
setTimeLeft(`${minutes}:${seconds.toString().padStart(2, '0')}`);
|
||||
});
|
||||
|
||||
audio.addEventListener('ended', () => {
|
||||
playNextSong();
|
||||
});
|
||||
|
||||
if (isPlaying) {
|
||||
audio.play();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
audioRef.current.src = '';
|
||||
}
|
||||
};
|
||||
}, [song.id]);
|
||||
|
||||
const handleSelectSong = (selectedSong: Cancion) => {
|
||||
if (selectedSong == null) return;
|
||||
setSong(selectedSong);
|
||||
|
||||
setIsPlaying(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-gray-800 p-4">
|
||||
<>
|
||||
{isPlaylistVisible && (
|
||||
<div className="fixed inset-0 z-40 bg-black bg-opacity-50" />
|
||||
)}
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 rounded-t-xl bg-gray-800 p-4">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="mb-4 h-2 w-full rounded-full bg-gray-600">
|
||||
<div
|
||||
className="h-2 rounded-full bg-blue-500"
|
||||
className="h-2 rounded-full bg-green-500"
|
||||
style={{ width: `${progress}%` }}
|
||||
></div>
|
||||
{playlist.length > 0 && (
|
||||
<div
|
||||
className={`fixed bottom-0 left-0 right-0 transform rounded-t-xl bg-gray-700 transition-transform duration-300 ease-in-out ${
|
||||
!isPlaylistVisible
|
||||
? 'translate-y-full bg-opacity-30'
|
||||
: 'translate-y-0'
|
||||
} max-h-[50%] overflow-y-auto p-2`}
|
||||
onBlur={() => setIsPlaylistVisible(false)}
|
||||
tabIndex={0}
|
||||
>
|
||||
{playlist.map((song) => (
|
||||
<div
|
||||
key={song.id}
|
||||
className="cursor-pointer p-2 text-white hover:bg-gray-600"
|
||||
onClick={() => handleSelectSong(song)}
|
||||
>
|
||||
{song.title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div>
|
||||
<button
|
||||
className="mx-4 text-white"
|
||||
onClick={() => setIsPlaying(!isPlaying)}
|
||||
disabled={song.id === 0}
|
||||
>
|
||||
{!isPlaying ? <PlayIcon /> : <PauseIcon />}
|
||||
</button>
|
||||
<span className="text-white">{titulo}</span>
|
||||
<span className="text-white">{timeLeft}</span>
|
||||
<button
|
||||
className="mx-4 text-white"
|
||||
onClick={() => setIsPlaylistVisible(!isPlaylistVisible)}
|
||||
>
|
||||
{isPlaylistVisible ? '▼' : '▲'}
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-white">{song.title}</span>
|
||||
<span className="w-16 text-right text-white">{timeLeft}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,17 @@ import { useState } from 'react';
|
||||
export default function Gallery({ songs }: { songs: Cancion[] }) {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [cancionSeleccionada, setCancion] = useState({ title: '' });
|
||||
const [queue, setQueue] = useState<Cancion[]>([]);
|
||||
const [currentSong, setCurrentSong] = useState<Cancion>();
|
||||
|
||||
const addToQueue = (song: Cancion) => {
|
||||
if (queue.length === 0) {
|
||||
setQueue([song]);
|
||||
} else {
|
||||
setQueue([...queue, song]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Authenticated
|
||||
header={
|
||||
@@ -40,10 +50,10 @@ export default function Gallery({ songs }: { songs: Cancion[] }) {
|
||||
{song.artist}
|
||||
</p>
|
||||
<div className="mt-1 flex gap-1">
|
||||
<PrimaryButton>
|
||||
<PrimaryButton onClick={() => setCurrentSong(song)}>
|
||||
<AddIcon />
|
||||
</PrimaryButton>
|
||||
<SecondaryButton>
|
||||
<SecondaryButton onClick={() => addToQueue(song)}>
|
||||
<AddStackIcon />
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
@@ -52,9 +62,13 @@ export default function Gallery({ songs }: { songs: Cancion[] }) {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Reproductor />
|
||||
<Reproductor
|
||||
song={currentSong}
|
||||
setSong={setCurrentSong}
|
||||
playlist={queue}
|
||||
setplaylist={setQueue}
|
||||
/>
|
||||
</div>
|
||||
c
|
||||
</Authenticated>
|
||||
);
|
||||
}
|
||||
|
||||
1
resources/js/types/types.d.ts
vendored
1
resources/js/types/types.d.ts
vendored
@@ -1,5 +1,4 @@
|
||||
export type Cancion = {
|
||||
name: ReactNode;
|
||||
id: number;
|
||||
title: string;
|
||||
artist: string | null;
|
||||
|
||||
Reference in New Issue
Block a user