-
+
{/*//TODO Extract StreamListRow away to ListRow so that it makes sense for categories to be there also */}
{
- return (
-
-
- );
-};
-
-export default LoginPage;
\ No newline at end of file
diff --git a/frontend/src/pages/SignupPage.tsx b/frontend/src/pages/SignupPage.tsx
deleted file mode 100644
index e361516..0000000
--- a/frontend/src/pages/SignupPage.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-
-const SignupPage: React.FC = () => {
- return (
-
-
- );
-};
-
-export default SignupPage;
\ No newline at end of file
diff --git a/frontend/src/pages/UserPage.tsx b/frontend/src/pages/UserPage.tsx
new file mode 100644
index 0000000..692aed3
--- /dev/null
+++ b/frontend/src/pages/UserPage.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const UserPage: React.FC = () => {
+ return ;
+};
+
+export default UserPage;
diff --git a/frontend/src/pages/VideoPage.tsx b/frontend/src/pages/VideoPage.tsx
index fc84a81..9d99fbe 100644
--- a/frontend/src/pages/VideoPage.tsx
+++ b/frontend/src/pages/VideoPage.tsx
@@ -1,13 +1,21 @@
import React, { useState, useEffect } from "react";
import Navbar from "../components/Layout/Navbar";
import Button from "../components/Layout/Button";
+import ChatPanel from "../components/Video/ChatPanel";
import CheckoutForm, { Return } from "../components/Checkout/CheckoutForm";
import { useParams } from "react-router-dom";
+import { useAuth } from "../context/AuthContext";
+import VideoPlayer from "../components/Video/VideoPlayer";
-const VideoPage: React.FC = () => {
+interface VideoPageProps {
+ streamId: number;
+}
+
+const VideoPage: React.FC = ({ streamId }) => {
const [showCheckout, setShowCheckout] = useState(false);
const showReturn = window.location.search.includes("session_id");
const { streamerName } = useParams<{ streamerName: string }>();
+ const { isLoggedIn } = useAuth();
useEffect(() => {
// Prevent scrolling when checkout is open
@@ -25,19 +33,33 @@ const VideoPage: React.FC = () => {
if (streamerName) {
// Fetch stream data for this streamer
console.log(`Loading stream for ${streamerName}`);
- // fetch(`/api/get_stream_data/${streamerName}`)
+ // fetch(`/api/get_stream_data/${streamId}`)
}
}, [streamerName]);
return (
-
+
-
- Hello! Welcome to the soon-to-be-awesome Video Page where you'll watch
- the best streams ever!
-
-
+
+
+
+ {isLoggedIn ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
{showCheckout &&
setShowCheckout(false)} />}
{showReturn && }
diff --git a/package-lock.json b/package-lock.json
index 4360cf6..1f14298 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,33 @@
{
- "name": "Software Project",
+ "name": "Team-Software-Project",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
+ "@types/video.js": "^7.3.58",
"axios": "^1.7.9",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "video.js": "^8.21.0"
},
"devDependencies": {
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
+ "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@types/react": {
"version": "19.0.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
@@ -34,6 +48,81 @@
"@types/react": "^19.0.0"
}
},
+ "node_modules/@types/video.js": {
+ "version": "7.3.58",
+ "resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.58.tgz",
+ "integrity": "sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ==",
+ "license": "MIT"
+ },
+ "node_modules/@videojs/http-streaming": {
+ "version": "3.16.2",
+ "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.16.2.tgz",
+ "integrity": "sha512-fvt4ko7FknxiT9FnjyNQt6q2px+awrkM+Orv7IB/4gldvj94u4fowGfmNHynnvNTPgPkdxHklGmFLGfclYw8HA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^4.1.1",
+ "aes-decrypter": "^4.0.2",
+ "global": "^4.4.0",
+ "m3u8-parser": "^7.2.0",
+ "mpd-parser": "^1.3.1",
+ "mux.js": "7.1.0",
+ "video.js": "^7 || ^8"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ },
+ "peerDependencies": {
+ "video.js": "^8.19.0"
+ }
+ },
+ "node_modules/@videojs/vhs-utils": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz",
+ "integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "global": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ }
+ },
+ "node_modules/@videojs/xhr": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz",
+ "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "global": "~4.4.0",
+ "is-function": "^1.0.1"
+ }
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.10",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+ "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/aes-decrypter": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz",
+ "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^4.1.1",
+ "global": "^4.4.0",
+ "pkcs7": "^1.0.4"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -79,6 +168,11 @@
"node": ">=0.4.0"
}
},
+ "node_modules/dom-walk": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
+ "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
+ },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -113,6 +207,33 @@
"node": ">= 6"
}
},
+ "node_modules/global": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
+ "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
+ "license": "MIT",
+ "dependencies": {
+ "min-document": "^2.19.0",
+ "process": "^0.11.10"
+ }
+ },
+ "node_modules/is-function": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
+ "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
+ "license": "MIT"
+ },
+ "node_modules/m3u8-parser": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz",
+ "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^4.1.1",
+ "global": "^4.4.0"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -134,6 +255,67 @@
"node": ">= 0.6"
}
},
+ "node_modules/min-document": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
+ "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
+ "dependencies": {
+ "dom-walk": "^0.1.0"
+ }
+ },
+ "node_modules/mpd-parser": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz",
+ "integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^4.0.0",
+ "@xmldom/xmldom": "^0.8.3",
+ "global": "^4.4.0"
+ },
+ "bin": {
+ "mpd-to-m3u8-json": "bin/parse.js"
+ }
+ },
+ "node_modules/mux.js": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz",
+ "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "global": "^4.4.0"
+ },
+ "bin": {
+ "muxjs-transmux": "bin/transmux.js"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ }
+ },
+ "node_modules/pkcs7": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
+ "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5"
+ },
+ "bin": {
+ "pkcs7": "bin/cli.js"
+ }
+ },
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -161,11 +343,68 @@
"react": "^19.0.0"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT"
+ },
+ "node_modules/video.js": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.21.0.tgz",
+ "integrity": "sha512-zcwerRb257QAuWfi8NH9yEX7vrGKFthjfcONmOQ4lxFRpDAbAi+u5LAjCjMWqhJda6zEmxkgdDpOMW3Y21QpXA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/http-streaming": "^3.16.2",
+ "@videojs/vhs-utils": "^4.1.1",
+ "@videojs/xhr": "2.7.0",
+ "aes-decrypter": "^4.0.2",
+ "global": "4.4.0",
+ "m3u8-parser": "^7.2.0",
+ "mpd-parser": "^1.3.1",
+ "mux.js": "^7.0.1",
+ "videojs-contrib-quality-levels": "4.1.0",
+ "videojs-font": "4.2.0",
+ "videojs-vtt.js": "0.15.5"
+ }
+ },
+ "node_modules/videojs-contrib-quality-levels": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz",
+ "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "global": "^4.4.0"
+ },
+ "engines": {
+ "node": ">=16",
+ "npm": ">=8"
+ },
+ "peerDependencies": {
+ "video.js": "^8"
+ }
+ },
+ "node_modules/videojs-font": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz",
+ "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/videojs-vtt.js": {
+ "version": "0.15.5",
+ "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz",
+ "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "global": "^4.3.1"
+ }
}
}
}
diff --git a/package.json b/package.json
index 7fad102..4c6d8ed 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,10 @@
{
"dependencies": {
+ "@types/video.js": "^7.3.58",
"axios": "^1.7.9",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "video.js": "^8.21.0"
},
"devDependencies": {
"@types/react": "^19.0.8",
diff --git a/web_server/blueprints/__init__.py b/web_server/blueprints/__init__.py
index 794b94a..a9be838 100644
--- a/web_server/blueprints/__init__.py
+++ b/web_server/blueprints/__init__.py
@@ -30,11 +30,13 @@ def create_app():
from blueprints.stripe import stripe_bp
from blueprints.user import user_bp
from blueprints.streams import stream_bp
+ from blueprints.chat import chat_bp
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(stripe_bp)
app.register_blueprint(user_bp)
app.register_blueprint(stream_bp)
+ app.register_blueprint(chat_bp)
return app
diff --git a/web_server/blueprints/authentication.py b/web_server/blueprints/authentication.py
index 65b8bcc..0012fbc 100644
--- a/web_server/blueprints/authentication.py
+++ b/web_server/blueprints/authentication.py
@@ -22,8 +22,13 @@ def signup():
# Basic server-side validation
if not all([username, email, password]):
+ fields = ["username", "email", "password"]
+ for x in fields:
+ if not [username, email, password][fields.index(x)]:
+ fields.remove(x)
return jsonify({
"account_created": False,
+ "error_fields": fields,
"message": "Missing required fields"
}), 400
@@ -45,13 +50,15 @@ def signup():
if dup_email is not None:
return jsonify({
"account_created": False,
- "errors": {"email": "Email already taken"}
+ "error_fields": ["email"],
+ "message": "Email already taken"
}), 400
if dup_username is not None:
return jsonify({
"account_created": False,
- "errors": {"username": "Username already taken"}
+ "error_fields": ["username"],
+ "message": "Username already taken"
}), 400
# Create new user
@@ -122,14 +129,16 @@ def login():
if not user:
return jsonify({
"logged_in": False,
- "errors": {"general": "Invalid username or password"}
+ "error_fields": ["username", "password"],
+ "message": "Invalid username or password"
}), 401
# Verify password
if not check_password_hash(user["password"], password):
return jsonify({
"logged_in": False,
- "errors": {"general": "Invalid username or password"}
+ "error_fields": ["username", "password"],
+ "message": "Invalid username or password"
}), 401
# Set up session
diff --git a/web_server/blueprints/streams.py b/web_server/blueprints/streams.py
index 4a3a450..f5e8b65 100644
--- a/web_server/blueprints/streams.py
+++ b/web_server/blueprints/streams.py
@@ -139,9 +139,16 @@ def get_streamer(streamer_id):
"""
return
+@stream_bp.route('/streamer//status')
+def get_streamer_status(streamerName):
+ """
+ Returns a streamer's status, if they are live or not
+ """
+ return {"status": "live", "streamId": 1}
-@stream_bp.route('/get_stream_data/', methods=['GET'])
-def get_stream(streamer_id):
+
+@stream_bp.route('/get_stream_data/', methods=['GET'])
+def get_stream(stream_id):
"""
Returns a streamer's stream data
"""