REFACTOR: Relocate components to appropriate folders + minor style adjustments
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="min-w-[650px]">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
@@ -7,7 +7,7 @@
|
||||
<title>Team Software Project</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="min-h-screen h-full bg-gradient-to-tr from-[#2043ba] via-[#0026a6] to-[#63007a] overflow-x-hidden"></div>
|
||||
<div id="root" class="min-h-screen h-full min-w-[650px] bg-gradient-to-tr from-[#2043ba] via-[#0026a6] to-[#63007a] overflow-x-hidden"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -148,7 +148,7 @@ const LoginForm: React.FC<SubmitProps> = ({ onSubmit, onForgotPassword }) => {
|
||||
errors.password ? "border-red-500" : ""
|
||||
}`}
|
||||
></Input>
|
||||
<div className="flex flex-row">
|
||||
<div className="flex">
|
||||
<label className="flex w-full items-center justify-start cursor-pointer w-10px">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -28,7 +28,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||
className={`${extraClasses} overflow-hidden flex-shrink-0 flex flex-col bg-purple-900 rounded-lg cursor-pointer mx-auto hover:bg-pink-700 hover:scale-105 transition-all`}
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div className="relative w-full pt-[56.25%] overflow-hidden rounded-t-lg">
|
||||
<div className="relative w-full aspect-video overflow-hidden rounded-t-lg">
|
||||
{thumbnail ? (
|
||||
<img
|
||||
src={thumbnail}
|
||||
|
||||
@@ -73,7 +73,7 @@ const ListRow: React.FC<ListRowProps> = ({
|
||||
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<ListRowProps> = ({
|
||||
? onClick?.(item.streamer)
|
||||
: onClick?.(item.title)
|
||||
}
|
||||
extraClasses={`${itemExtraClasses} min-w-[25vw]`}
|
||||
extraClasses={`${itemExtraClasses} min-w-[20vw]`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
117
frontend/src/components/Stream/VideoPlayer.tsx
Normal file
117
frontend/src/components/Stream/VideoPlayer.tsx
Normal file
@@ -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<VideoPlayerProps> = ({
|
||||
streamer,
|
||||
extraClasses,
|
||||
onStreamDetected,
|
||||
}) => {
|
||||
const { streamerName: urlStreamerName } = useParams<{
|
||||
streamerName: string;
|
||||
}>();
|
||||
const videoRef = useRef<HTMLDivElement>(null);
|
||||
const playerRef = useRef<Player | null>(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 (
|
||||
<div
|
||||
id="video-player"
|
||||
className={`${extraClasses} w-full h-full mx-auto flex justify-center items-center bg-gray-900 rounded-lg`}
|
||||
>
|
||||
<div ref={videoRef} className="w-full max-w-[160vh] self-center" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoPlayer;
|
||||
@@ -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<VideoPlayerProps> = ({
|
||||
streamer,
|
||||
extraClasses,
|
||||
onStreamDetected,
|
||||
}) => {
|
||||
const { streamerName: urlStreamerName } = useParams<{
|
||||
streamerName: string;
|
||||
}>();
|
||||
const videoRef = useRef<HTMLDivElement>(null);
|
||||
const playerRef = useRef<videojs.Player | null>(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 (
|
||||
<div
|
||||
id="video-player"
|
||||
className={`${extraClasses} w-full h-full mx-auto flex justify-center items-center bg-gray-900 rounded-lg`}
|
||||
>
|
||||
<div ref={videoRef} className="w-full max-w-[160vh] self-center" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoPlayer;
|
||||
@@ -44,10 +44,9 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
wrap={false}
|
||||
onClick={handleStreamClick}
|
||||
extraClasses="bg-[var(--liveNow)]"
|
||||
|
||||
>
|
||||
{/* <Button extraClasses="absolute right-10" onClick={() => navigate("/")}>
|
||||
Show More . . .
|
||||
Show More
|
||||
</Button> */}
|
||||
</ListRow>
|
||||
|
||||
@@ -73,7 +72,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
extraClasses="absolute right-10"
|
||||
onClick={() => navigate("/categories")}
|
||||
>
|
||||
Show More . . .
|
||||
Show More
|
||||
</Button>
|
||||
</ListRow>
|
||||
</DynamicPageContent>
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user