+
{thumbnail ? (

= ({
ref={slider}
className={`flex ${
wrap ? "flex-wrap" : "overflow-x-scroll whitespace-nowrap"
- } items-center justify-between scroll-smooth scrollbar-hide gap-5 py-[10px] px=[30px] mx-[30px]`}
+ } max-w-[95%] items-center justify-between scroll-smooth scrollbar-hide gap-5 mx-auto`}
>
{items.map((item) => (
@@ -93,7 +93,7 @@ const ListRow: React.FC
= ({
? onClick?.(item.streamer)
: onClick?.(item.title)
}
- extraClasses={`${itemExtraClasses} min-w-[25vw]`}
+ extraClasses={`${itemExtraClasses} min-w-[20vw]`}
/>
))}
diff --git a/frontend/src/components/Video/ChatPanel.tsx b/frontend/src/components/Stream/ChatPanel.tsx
similarity index 100%
rename from frontend/src/components/Video/ChatPanel.tsx
rename to frontend/src/components/Stream/ChatPanel.tsx
diff --git a/frontend/src/components/Stream/VideoPlayer.tsx b/frontend/src/components/Stream/VideoPlayer.tsx
new file mode 100644
index 0000000..f694b14
--- /dev/null
+++ b/frontend/src/components/Stream/VideoPlayer.tsx
@@ -0,0 +1,117 @@
+import React, { useEffect, useRef } from "react";
+import { useParams } from "react-router-dom";
+import videojs from "video.js";
+import type Player from "video.js/dist/types/player";
+import "video.js/dist/video-js.css";
+interface VideoPlayerProps {
+ streamer?: string;
+ extraClasses?: string;
+ onStreamDetected?: (isStreamAvailable: boolean) => void;
+}
+
+const VideoPlayer: React.FC
= ({
+ streamer,
+ extraClasses,
+ onStreamDetected,
+}) => {
+ const { streamerName: urlStreamerName } = useParams<{
+ streamerName: string;
+ }>();
+ const videoRef = useRef(null);
+ const playerRef = useRef(null);
+
+ // Use URL param if available, otherwise fall back to prop
+ const streamerName = urlStreamerName || streamer;
+
+ useEffect(() => {
+ if (!videoRef.current || !streamerName) {
+ console.log("No video ref or streamer name");
+ return;
+ }
+
+ const setupPlayer = async () => {
+ const streamKey = await fetchStreamKey();
+ const streamUrl = `/stream/${streamKey}/index.m3u8`;
+ console.log("Player created with src:", streamUrl);
+
+ if (!playerRef.current) {
+ const videoElement = document.createElement("video");
+ videoElement.classList.add(
+ "video-js",
+ "vjs-big-play-centered",
+ "w-full",
+ "h-full"
+ );
+ videoElement.setAttribute("playsinline", "true");
+ if (videoRef.current) {
+ videoRef.current.appendChild(videoElement);
+ }
+
+ playerRef.current = videojs(videoElement, {
+ controls: false,
+ autoplay: true,
+ muted: false,
+ fluid: true,
+ responsive: true,
+ aspectRatio: "16:9",
+ liveui: false,
+ sources: [
+ {
+ src: streamUrl,
+ type: "application/x-mpegURL",
+ },
+ ],
+ });
+
+ playerRef.current.on("loadeddata", () => {
+ if (onStreamDetected) onStreamDetected(true);
+ });
+
+ playerRef.current.on("error", () => {
+ console.error(`Stream failed to load: ${streamUrl}`);
+ if (onStreamDetected) onStreamDetected(false);
+ setTimeout(() => {
+ console.log("Retrying stream...");
+ playerRef.current?.src({
+ src: streamUrl,
+ type: "application/x-mpegURL",
+ });
+ playerRef.current?.play();
+ }, 5000);
+ });
+ } else {
+ playerRef.current.src({
+ src: streamUrl,
+ type: "application/x-mpegURL",
+ });
+ playerRef.current.play();
+ }
+ };
+
+ const fetchStreamKey = async () => {
+ const response = await fetch(`/api/user/${streamerName}/stream_key`);
+ const keyData = await response.json();
+ return keyData.stream_key;
+ };
+
+ setupPlayer();
+
+ return () => {
+ if (playerRef.current) {
+ playerRef.current.dispose();
+ playerRef.current = null;
+ }
+ };
+ }, [streamerName]);
+
+ return (
+
+ );
+};
+
+export default VideoPlayer;
diff --git a/frontend/src/components/Video/VideoPlayer.tsx b/frontend/src/components/Video/VideoPlayer.tsx
deleted file mode 100644
index de30a27..0000000
--- a/frontend/src/components/Video/VideoPlayer.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { useEffect, useRef } from "react";
-import { useParams } from "react-router-dom";
-import videojs from "video.js";
-import "video.js/dist/video-js.css";
-interface VideoPlayerProps {
- streamer?: string;
- extraClasses?: string;
- onStreamDetected?: (isStreamAvailable: boolean) => void;
-}
-
-const VideoPlayer: React.FC = ({
- streamer,
- extraClasses,
- onStreamDetected,
-}) => {
- const { streamerName: urlStreamerName } = useParams<{
- streamerName: string;
- }>();
- const videoRef = useRef(null);
- const playerRef = useRef(null);
-
- // Use URL param if available, otherwise fall back to prop
- const streamerName = urlStreamerName || streamer;
-
- useEffect(() => {
- if (!videoRef.current || !streamerName) return;
-
- const streamUrl = `/stream/${streamerName}/index.m3u8`;
-
- if (!playerRef.current) {
- const videoElement = document.createElement("video");
- videoElement.classList.add(
- "video-js",
- "vjs-big-play-centered",
- "w-full",
- "h-full"
- );
- videoElement.setAttribute("playsinline", "true");
- videoRef.current.appendChild(videoElement);
-
- playerRef.current = videojs(videoElement, {
- controls: false,
- autoplay: true,
- muted: false,
- fluid: true,
- responsive: true,
- aspectRatio: "16:9",
- liveui: false,
- sources: [
- {
- src: streamUrl,
- type: "application/x-mpegURL",
- },
- ],
- });
-
- playerRef.current.on('loadeddata', () => {
- if (onStreamDetected) onStreamDetected(true);
- });
-
- playerRef.current.on("error", () => {
- console.error(`Stream failed to load: ${streamUrl}`);
- if (onStreamDetected) onStreamDetected(false);
- setTimeout(() => {
- console.log("Retrying stream...");
- playerRef.current?.src({
- src: streamUrl,
- type: "application/x-mpegURL",
- });
- playerRef.current?.play();
- }, 5000);
- });
- } else {
- playerRef.current.src({ src: streamUrl, type: "application/x-mpegURL" });
- playerRef.current.play();
- }
-
- return () => {
- if (playerRef.current) {
- playerRef.current.dispose();
- playerRef.current = null;
- }
- };
- }, [streamerName]);
-
- return (
-
- );
-};
-
-export default VideoPlayer;
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index 156433a..7e681b3 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -44,10 +44,9 @@ const HomePage: React.FC = ({ variant = "default" }) => {
wrap={false}
onClick={handleStreamClick}
extraClasses="bg-[var(--liveNow)]"
-
>
{/* */}
@@ -73,7 +72,7 @@ const HomePage: React.FC = ({ variant = "default" }) => {
extraClasses="absolute right-10"
onClick={() => navigate("/categories")}
>
- Show More . . .
+ Show More
diff --git a/frontend/src/pages/VideoPage.tsx b/frontend/src/pages/VideoPage.tsx
index ba20c86..01c2575 100644
--- a/frontend/src/pages/VideoPage.tsx
+++ b/frontend/src/pages/VideoPage.tsx
@@ -1,11 +1,11 @@
import React, { useState, useEffect } from "react";
import { ToggleButton } from "../components/Input/Button";
-import ChatPanel from "../components/Video/ChatPanel";
+import ChatPanel from "../components/Stream/ChatPanel";
import { useNavigate, useParams } from "react-router-dom";
import { useAuthModal } from "../hooks/useAuthModal";
import { useAuth } from "../context/AuthContext";
import { useFollow } from "../hooks/useFollow";
-import VideoPlayer from "../components/Video/VideoPlayer";
+import VideoPlayer from "../components/Stream/VideoPlayer";
import { SocketProvider } from "../context/SocketContext";
import AuthModal from "../components/Auth/AuthModal";
import CheckoutForm, { Return } from "../components/Checkout/CheckoutForm";