mira, tengo esto, pero necesito que pongas algo referencia a que esta es mi playlist original: https://open.spotify.com/playlist/39fLwIyFpofxhYSCyyYKpC

asi como de esta es mi playlist original, mira el codigo completo


import React, {
  useState,
  useEffect,
  useRef,
  useMemo,
  useCallback,
} from "react";
import YouTube, { YouTubeProps, YouTubePlayer } from "react-youtube";
import Navbar from "./components/Navbar";
import PlaylistInfo from "./components/PlaylistInfo";
import PlaybackControls from "./components/PlaybackControls";
import SongList from "./components/SongList";
import NowPlayingBar from "./components/NowPlayingBar";
import FullScreenPlayer from "./components/FullScreenPlayer";
import PlaybackBar from "./components/PlaybackBar";
import { songs as initialSongs, Song } from "./songs";

const formatTotalDuration = (totalSeconds: number): string => {
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  return `${hours} h ${minutes} min`;
};

const App: React.FC = () => {
  const [currentSong, setCurrentSong] = useState<Song | null>(null);
  const [isPlaying, setIsPlaying] = useState<boolean>(false);
  const [isMobile, setIsMobile] = useState<boolean>(window.innerWidth < 1024);
  const [showNavbar, setShowNavbar] = useState<boolean>(true);
  const [isFullScreenPlayer, setIsFullScreenPlayer] = useState<boolean>(false);

  const playerRef = useRef<YouTubePlayer | null>(null);

  const [currentTime, setCurrentTime] = useState<number>(0);
  const [duration, setDuration] = useState<number>(0);

  const [volume, setVolume] = useState<number>(100);

  const lastScrollY = useRef<number>(0);

  const [searchValue, setSearchValue] = useState<string>("");

  // Estado de Paginación
  const [currentPage, setCurrentPage] = useState<number>(1);
  const songsPerPage = 100; // Puedes ajustar este valor

  const handleSearchChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setSearchValue(e.target.value);
      setCurrentPage(1); // Reiniciar a la primera página al cambiar la búsqueda
    },
    []
  );

  const handleClearSearch = useCallback(() => {
    setSearchValue("");
    setCurrentPage(1); // Reiniciar a la primera página al limpiar la búsqueda
  }, []);

  const [sortOrder, setSortOrder] = useState<"asc" | "desc" | null>(null);

  const sortAscending = useCallback(() => {
    setSortOrder("asc");
  }, []);

  const sortDescending = useCallback(() => {
    setSortOrder("desc");
  }, []);

  const [isShuffle, setIsShuffle] = useState<boolean>(false);

  // Memoiza las canciones filtradas
  const filteredSongs = useMemo(() => {
    return searchValue
      ? initialSongs.filter(
          (song) =>
            song.title.toLowerCase().includes(searchValue.toLowerCase()) ||
            song.artist.toLowerCase().includes(searchValue.toLowerCase())
        )
      : initialSongs;
  }, [searchValue]);

  // Memoiza las canciones ordenadas
  const sortedSongs = useMemo(() => {
    const sorted = [...filteredSongs];
    if (sortOrder === "asc") {
      sorted.reverse();
    }
    return sorted;
  }, [filteredSongs, sortOrder]);

  // Cálculo de la duración total
  const totalDurationInSeconds = useMemo(
    () => sortedSongs.reduce((sum, song) => sum + (song.duration || 0), 0),
    [sortedSongs]
  );
  const totalDuration = useMemo(
    () => formatTotalDuration(totalDurationInSeconds),
    [totalDurationInSeconds]
  );

  const playlist = useMemo(
    () => ({
      name: "Time Free",
      description: "Relajao",
      owner: "Johan Morales",
      songsCount: sortedSongs.length,
      totalDuration: totalDuration,
      coverImage: "https://i.postimg.cc/y8KhsW1p/imagen-blessd.jpg",
    }),
    [sortedSongs.length, totalDuration]
  );

  useEffect(() => {
    const handleResize = () => {
      setIsMobile(window.innerWidth < 1024);
    };

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  useEffect(() => {
    const handleScroll = () => {
      if (!isFullScreenPlayer) {
        if (window.scrollY > lastScrollY.current && window.scrollY > 100) {
          setShowNavbar(false);
        } else {
          setShowNavbar(true);
        }
        lastScrollY.current = window.scrollY;
      }
    };

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, [isFullScreenPlayer]);

  const getCurrentIndex = useCallback((): number => {
    if (!currentSong?.youtubeId) return -1;
    return sortedSongs.findIndex(
      (song) => song.youtubeId === currentSong.youtubeId
    );
  }, [currentSong, sortedSongs]);

  useEffect(() => {
    if (playerRef.current && currentSong?.youtubeId) {
      try {
        playerRef.current.loadVideoById(currentSong.youtubeId);
        setCurrentTime(0);
        setDuration(0);
      } catch (error) {
        console.error("Error al cargar el video:", error);
      }
    }
  }, [currentSong]);

  // Controlar la reproducción/pausa del video
  useEffect(() => {
    if (playerRef.current) {
      if (isPlaying) {
        try {
          playerRef.current.playVideo();
        } catch (error) {
          console.error("Error intentando reproducir el video:", error);
        }
      } else {
        playerRef.current.pauseVideo();
      }
    }
  }, [isPlaying]);

  // Actualizar el tiempo y la duración del video cada segundo
  useEffect(() => {
    let intervalId: number | null = null;

    if (playerRef.current && isPlaying) {
      intervalId = window.setInterval(() => {
        if (playerRef.current) {
          const current = playerRef.current.getCurrentTime();
          const dura = playerRef.current.getDuration();
          setCurrentTime(current);
          setDuration(dura);
        }
      }, 1000);
    }

    return () => {
      if (intervalId !== null) {
        window.clearInterval(intervalId);
      }
    };
  }, [isPlaying]);

  const handlePlayPause = useCallback(() => {
    if (isPlaying) {
      playerRef.current?.pauseVideo();
      setIsPlaying(false);
    } else {
      if (currentSong?.youtubeId) {
        try {
          playerRef.current?.playVideo();
          setIsPlaying(true);
        } catch (error) {
          console.error("Error intentando reproducir el video:", error);
        }
      }
    }
  }, [isPlaying, currentSong]);

  const handleNextSong = useCallback(() => {
    if (!currentSong) return;
    let nextIndex: number;

    if (isShuffle) {
      do {
        nextIndex = Math.floor(Math.random() * sortedSongs.length);
      } while (nextIndex === getCurrentIndex());
    } else {
      const currentIndex = getCurrentIndex();
      nextIndex = (currentIndex + 1) % sortedSongs.length;
    }

    const nextSong = sortedSongs[nextIndex];
    setCurrentSong(nextSong);
    setIsPlaying(true);
  }, [currentSong, isShuffle, sortedSongs, getCurrentIndex]);

  const handlePreviousSong = useCallback(() => {
    if (!currentSong) return;
    let previousIndex: number;

    if (isShuffle) {
      do {
        previousIndex = Math.floor(Math.random() * sortedSongs.length);
      } while (previousIndex === getCurrentIndex());
    } else {
      const currentIndex = getCurrentIndex();
      previousIndex =
        (currentIndex - 1 + sortedSongs.length) % sortedSongs.length;
    }

    const previousSong = sortedSongs[previousIndex];
    setCurrentSong(previousSong);
    setIsPlaying(true);
  }, [currentSong, isShuffle, sortedSongs, getCurrentIndex]);

  const onPlayerReady: YouTubeProps["onReady"] = useCallback(
    (event: YT.PlayerEvent) => {
      playerRef.current = event.target;
      setDuration(event.target.getDuration());

      event.target.setVolume(volume);

      if (isPlaying) {
        try {
          event.target.playVideo();
        } catch (error) {
          console.error("Error intentando reproducir el video:", error);
        }
      }
    },
    [isPlaying, volume]
  );

  const onPlayerEnd: YouTubeProps["onEnd"] = useCallback(() => {
    handleNextSong();
  }, [handleNextSong]);

  const onPlayerError: YouTubeProps["onError"] = useCallback(
    (event: YT.OnErrorEvent) => {
      console.error("Error del reproductor de YouTube:", event.data);
    },
    []
  );

  const onPlayerStateChange: YouTubeProps["onStateChange"] = useCallback(
    (event: YT.OnStateChangeEvent) => {
      if (window.YT && event.data === YT.PlayerState.ENDED) {
        handleNextSong();
      } else if (
        window.YT &&
        (event.data === YT.PlayerState.CUED ||
          event.data === YT.PlayerState.BUFFERING) &&
        isPlaying
      ) {
        try {
          playerRef.current?.playVideo();
        } catch (error) {
          console.error("Error intentando reproducir el video:", error);
        }
      }
    },
    [handleNextSong, isPlaying]
  );

  const opts: YouTubeProps["opts"] = useMemo(
    () => ({
      height: "0",
      width: "0",
      playerVars: {
        autoplay: 0,
        controls: 0,
        disablekb: 1,
        fs: 0,
        modestbranding: 1,
        rel: 0,
      },
    }),
    []
  );

  const handleProgressChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const newTime = Number(e.target.value);
      if (playerRef.current) {
        try {
          playerRef.current.seekTo(newTime, true);
          setCurrentTime(newTime);
        } catch (error) {
          console.error("Error al cambiar el progreso del video:", error);
        }
      }
    },
    []
  );

  const handleVolumeChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const newVolume = Number(e.target.value);
      setVolume(newVolume);
      if (playerRef.current) {
        try {
          playerRef.current.setVolume(newVolume);
        } catch (error) {
          console.error("Error al cambiar el volumen:", error);
        }
      }
    },
    []
  );

  const toggleMute = useCallback(() => {
    if (playerRef.current) {
      try {
        if (playerRef.current.isMuted()) {
          playerRef.current.unMute();
          setVolume(playerRef.current.getVolume());
        } else {
          playerRef.current.mute();
          setVolume(0);
        }
      } catch (error) {
        console.error("Error al alternar mute/unmute:", error);
      }
    }
  }, []);

  const toggleFullScreenPlayer = useCallback(() => {
    setIsFullScreenPlayer((prev) => !prev);
    if (!isFullScreenPlayer) {
      setShowNavbar(false);
    } else {
      setShowNavbar(true);
    }
  }, [isFullScreenPlayer]);

  const ensurePlayback = useCallback(() => {
    if (isPlaying && playerRef.current) {
      try {
        playerRef.current.playVideo();
        console.log("Reproducción iniciada automáticamente.");
      } catch (error) {
        console.error("Error al intentar reproducir automáticamente:", error);
      }
    }
  }, [isPlaying]);

  useEffect(() => {
    if (currentSong && isPlaying) {
      const timeoutId = setTimeout(() => {
        ensurePlayback();
      }, 1000);

      return () => clearTimeout(timeoutId);
    }
  }, [currentSong, isPlaying, ensurePlayback]);

  const handleSongSelect = useCallback((song: Song) => {
    setCurrentSong(song);
    setIsPlaying(true);
  }, []);

  const handleShuffle = useCallback(() => {
    setIsShuffle((prev) => !prev);
  }, []);

  const handleRepeat = useCallback(() => {
    if (currentSong) {
      setIsPlaying(false);
      setCurrentSong(currentSong);
      setIsPlaying(true);
    }
  }, [currentSong]);

  // Manejo de paginación
  const indexOfLastSong = currentPage * songsPerPage;
  const indexOfFirstSong = indexOfLastSong - songsPerPage;
  const currentSongs = useMemo(
    () => sortedSongs.slice(indexOfFirstSong, indexOfLastSong),
    [sortedSongs, indexOfFirstSong, indexOfLastSong]
  );

  const totalPages = useMemo(
    () => Math.ceil(sortedSongs.length / songsPerPage),
    [sortedSongs.length, songsPerPage]
  );

  const handleNextPage = useCallback(() => {
    setCurrentPage((prev) => Math.min(prev + 1, totalPages));
  }, [totalPages]);

  const handlePrevPage = useCallback(() => {
    setCurrentPage((prev) => Math.max(prev - 1, 1));
  }, []);

  return (
    <div className="flex flex-col h-screen text-gray-300 bg-black">
      {currentSong?.youtubeId && (
        <YouTube
          videoId={currentSong.youtubeId}
          opts={opts}
          onReady={onPlayerReady}
          onEnd={onPlayerEnd}
          onStateChange={onPlayerStateChange}
          onError={onPlayerError}
        />
      )}

      {showNavbar && (
        <Navbar
          searchValue={searchValue}
          onSearchChange={handleSearchChange}
          onClear={handleClearSearch}
          onSortAscending={sortAscending}
          onSortDescending={sortDescending}
        />
      )}

      <div
        className={`pt-20 flex-1 overflow-y-auto bg-gradient-to-b from-red-900 to-gray-900 ${
          isFullScreenPlayer ? "hidden" : ""
        } pb-24`}
      >
        <PlaylistInfo playlist={playlist} />

        {currentSong && (
          <PlaybackControls
            isPlaying={isPlaying}
            onPlayPause={handlePlayPause}
            onAddToQueue={() => {
              console.log("Agregar a la cola");
            }}
            onMoreOptions={() => {
              console.log("Más opciones");
            }}
          />
        )}

        <SongList
          songs={currentSongs}
          currentSong={currentSong}
          onSongSelect={handleSongSelect}
        />

        {/* Controles de Paginación */}
        <div className="flex justify-between items-center mt-4 px-6">
          <button
            onClick={handlePrevPage}
            disabled={currentPage === 1}
            className={`px-4 py-2 bg-gray-700 text-white rounded ${
              currentPage === 1
                ? "opacity-50 cursor-not-allowed"
                : "hover:bg-gray-600"
            }`}
          >
            Anterior
          </button>
          <span className="text-sm text-gray-400">
            Página {currentPage} de {totalPages}
          </span>
          <button
            onClick={handleNextPage}
            disabled={currentPage === totalPages}
            className={`px-4 py-2 bg-red-700 text-white rounded ${
              currentPage === totalPages
                ? "opacity-50 cursor-not-allowed"
                : "hover:bg-gray-600"
            }`}
          >
            Siguiente
          </button>
        </div>
      </div>

      {isMobile && currentSong && !isFullScreenPlayer && (
        <NowPlayingBar
          currentSong={currentSong}
          isPlaying={isPlaying}
          onPlayPause={(e) => {
            e.preventDefault();
            handlePlayPause();
          }}
          onToggleFullScreen={toggleFullScreenPlayer}
        />
      )}

      {isMobile && isFullScreenPlayer && currentSong && (
        <FullScreenPlayer
          currentSong={currentSong}
          isPlaying={isPlaying}
          onPlayPause={handlePlayPause}
          onNextSong={handleNextSong}
          onPreviousSong={handlePreviousSong}
          onShuffle={handleShuffle}
          onRepeat={handleRepeat}
          onVolumeChange={handleVolumeChange}
          volume={volume}
          onProgressChange={(values) => {
            const newTime = (values[0] / 100) * duration;
            if (playerRef.current) {
              try {
                playerRef.current.seekTo(newTime, true);
                setCurrentTime(newTime);
              } catch (error) {
                console.error("Error al cambiar el progreso del video:", error);
              }
            }
          }}
          currentTime={currentTime}
          duration={duration}
          onClose={toggleFullScreenPlayer}
          isShuffle={isShuffle}
        />
      )}

      {!isMobile && currentSong && (
        <PlaybackBar
          currentSong={currentSong}
          isPlaying={isPlaying}
          onPlayPause={handlePlayPause}
          onNextSong={handleNextSong}
          onPreviousSong={handlePreviousSong}
          onShuffle={handleShuffle}
          onRepeat={handleRepeat}
          onVolumeChange={handleVolumeChange}
          volume={volume}
          onFullScreen={toggleFullScreenPlayer}
          currentTime={currentTime}
          duration={duration}
          onProgressChange={handleProgressChange}
          isShuffle={isShuffle}
        />
      )}
    </div>
  );
};

export default App;