From 9cc5b40b25f1387c2cc5b2b2c2584f6bd32a76d8 Mon Sep 17 00:00:00 2001
From: Chris-1010 <122332721@umail.ucc.ie>
Date: Sat, 22 Feb 2025 12:05:20 +0000
Subject: [PATCH 1/3] UPDATE: Change to how streams are accessed, detected and
published to the application
---
nginx/nginx.conf | 48 ++++++++--------
web_server/blueprints/streams.py | 94 ++++++++++++++++++++++++++------
web_server/utils/stream_utils.py | 34 +-----------
3 files changed, 105 insertions(+), 71 deletions(-)
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index 008518e..f5c8bd2 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -1,4 +1,5 @@
worker_processes 1;
+error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
@@ -15,8 +16,8 @@ rtmp {
deny play all;
push rtmp://127.0.0.1:1935/hls-live;
- on_publish http://web_server:5000/publish_stream;
- on_publish_done http://web_server:5000/end_stream;
+ on_publish http://web_server:5000/init_stream; # if the stream is detected from OBS
+ on_publish_done http://web_server:5000/end_stream; # if the stream is ended on OBS
}
@@ -24,7 +25,7 @@ rtmp {
live on;
hls on;
- hls_path /stream_data/;
+ hls_path /stream_data/stream/;
allow publish 127.0.0.1;
deny publish all;
@@ -78,7 +79,7 @@ http {
# The MPEG-TS video chunks are stored in /tmp/hls
location ~ ^/stream/(.+)/(.+\.ts)$ {
- alias /stream_data/$1/stream/$2;
+ alias /stream_data/stream/$1/$2;
# Let the MPEG-TS video chunks be cacheable
expires max;
@@ -86,35 +87,36 @@ http {
# The M3U8 playlists location
location ~ ^/stream/(.+)/(.+\.m3u8)$ {
- alias /stream_data/$1/stream/$2;
+ alias /stream_data/stream/$1/$2;
# The M3U8 playlists should not be cacheable
expires -1d;
}
+ #! Unused right now so the following are inaccurate locations
# The thumbnails location
- location ~ ^/stream/(.+)/thumbnails/(.+\.jpg)$ {
- alias /stream_data/$1/thumbnails/$2;
+ # location ~ ^/stream/(.+)/thumbnails/(.+\.jpg)$ {
+ # alias /stream_data/$1/thumbnails/$2;
- # The thumbnails should not be cacheable
- expires -1d;
- }
+ # # The thumbnails should not be cacheable
+ # expires -1d;
+ # }
- # The vods location
- location ~ ^/stream/(.+)/vods/(.+\.mp4)$ {
- alias /stream_data/$1/vods/$2;
+ # # The vods location
+ # location ~ ^/stream/(.+)/vods/(.+\.mp4)$ {
+ # alias /stream_data/$1/vods/$2;
- # The vods should not be cacheable
- expires -1d;
- }
+ # # The vods should not be cacheable
+ # expires -1d;
+ # }
- location ~ ^/\?token=.*$ {
- proxy_pass http://frontend:5173;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $host;
- }
+ # location ~ ^/\?token=.*$ {
+ # proxy_pass http://frontend:5173;
+ # proxy_http_version 1.1;
+ # proxy_set_header Upgrade $http_upgrade;
+ # proxy_set_header Connection "upgrade";
+ # proxy_set_header Host $host;
+ # }
location / {
proxy_pass http://frontend:5173;
diff --git a/web_server/blueprints/streams.py b/web_server/blueprints/streams.py
index 9e96c25..f49c608 100644
--- a/web_server/blueprints/streams.py
+++ b/web_server/blueprints/streams.py
@@ -72,7 +72,7 @@ def stream_data(streamer_id) -> dict:
Returns a streamer's current stream data
"""
data = get_current_stream_data(streamer_id)
-
+
if session.get('user_id') == streamer_id:
with Database() as db:
stream_key = db.fetchone(
@@ -163,10 +163,39 @@ def vods(username):
# RTMP Server Routes
+
+@stream_bp.route("/init_stream", methods=["POST"])
+def init_stream():
+ """
+ Called by NGINX when OBS starts streaming.
+ Creates necessary directories and validates stream key.
+ """
+ stream_key = request.form.get("name")
+
+ print(f"Stream initialization requested in nginx with key: {stream_key}")
+
+ with Database() as db:
+ # Check if valid stream key and user is allowed to stream
+ user_info = db.fetchone("""SELECT user_id, username, is_live
+ FROM users
+ WHERE stream_key = ?""", (stream_key,))
+
+ if not user_info:
+ print("Unauthorized - Invalid stream key", flush=True)
+ return "Unauthorized - Invalid stream key", 403
+
+ # Create necessary directories
+ username = user_info["username"]
+ create_local_directories(username)
+
+ return "OK", 200
+
+
@stream_bp.route("/publish_stream", methods=["POST"])
def publish_stream():
"""
- Authenticates stream from streamer and publishes it to the site
+ Called when user clicks Start Stream in dashboard.
+ Sets up stream in database and starts thumbnail generation.
step-by-step:
fetch user info from stream key
@@ -174,27 +203,26 @@ def publish_stream():
set user as streaming
periodically update thumbnail
"""
- stream_key = request.form.get("key")
- print("Stream request received")
+ data = request.form.get("data")
- # Open database connection
with Database() as db:
- # Get user info from stream key
- user_info = db.fetchone("""SELECT user_id, username, current_stream_title, current_selected_category_id, is_live
+ user_info = db.fetchone("""SELECT user_id, username, current_stream_title,
+ current_selected_category_id, is_live
FROM users
- WHERE stream_key = ?""", (stream_key,))
+ WHERE stream_key = ?""", (data['stream_key'],))
- # If stream key is invalid, return unauthorized
if not user_info or user_info["is_live"]:
+ print(
+ "Unauthorized. No user found from Stream key or user is already streaming.", flush=True)
return "Unauthorized", 403
# Insert stream into database
db.execute("""INSERT INTO streams (user_id, title, start_time, num_viewers, category_id)
VALUES (?, ?, ?, ?, ?)""", (user_info["user_id"],
- user_info["current_stream_title"],
+ data["title"],
datetime.now(),
0,
- 1))
+ get_category_id(data['category_name'])))
# Set user as streaming
db.execute("""UPDATE users SET is_live = 1 WHERE user_id = ?""",
@@ -203,16 +231,41 @@ def publish_stream():
username = user_info["username"]
user_id = user_info["user_id"]
- # Local file creation
- create_local_directories(username)
-
# Update thumbnail periodically
update_thumbnail.delay(user_id,
path_manager.get_stream_file_path(username),
path_manager.get_thumbnail_file_path(username),
THUMBNAIL_GENERATION_INTERVAL)
- return redirect(f"/{user_info['username']}/stream/")
+ return "OK", 200
+
+
+@stream_bp.route("/update_stream", methods=["POST"])
+def update_stream():
+ """
+ Called by StreamDashboard to update stream info
+ """
+ # TODO: Add thumbnails (paths) to table, allow user to update thumbnail
+
+ stream_key = request.form.get("key")
+ title = request.form.get("title")
+ category_name = request.form.get("category_name")
+
+ with Database() as db:
+ user_info = db.fetchone("""SELECT user_id, username, is_live
+ FROM users
+ WHERE stream_key = ?""", (stream_key,))
+
+ if not user_info or not user_info["is_live"]:
+ print(
+ "Unauthorized - No user found from stream key or user is not streaming", flush=True)
+ return "Unauthorized", 403
+
+ db.execute("""UPDATE streams
+ SET title = ?, category_id = ?
+ WHERE user_id = ?""", (title, get_category_id(category_name), user_info["user_id"]))
+
+ return "Stream updated", 200
@stream_bp.route("/end_stream", methods=["POST"])
@@ -230,6 +283,12 @@ def end_stream():
"""
stream_key = request.form.get("key")
+ if stream_key is None:
+ stream_key = request.form.get("name")
+
+ if stream_key is None:
+ print("Unauthorized - No stream key provided", flush=True)
+ return "Unauthorized", 403
# Open database connection
with Database() as db:
@@ -244,6 +303,7 @@ def end_stream():
# If stream key is invalid, return unauthorized
if not user_info:
+ print("Unauthorized - No user found from stream key", flush=True)
return "Unauthorized", 403
# Remove stream from database
@@ -256,9 +316,9 @@ def end_stream():
db.execute("""INSERT INTO vods (user_id, title, datetime, category_id, length, views)
VALUES (?, ?, ?, ?, ?, ?)""", (user_info["user_id"],
- user_info["current_stream_title"],
+ stream_info["title"],
stream_info["start_time"],
- user_info["current_selected_category_id"],
+ stream_info["category_id"],
stream_length,
0))
diff --git a/web_server/utils/stream_utils.py b/web_server/utils/stream_utils.py
index 52581de..8e40830 100644
--- a/web_server/utils/stream_utils.py
+++ b/web_server/utils/stream_utils.py
@@ -142,41 +142,14 @@ def get_vod_tags(vod_id: int):
""", (vod_id,))
return tags
-def transfer_stream_to_vod(user_id: int):
- """
- Deletes stream from stream table and moves it to VoD table
- TODO: Add functionaliy to save stream permanently
- """
-
- with Database() as db:
- stream = db.fetchone("""
- SELECT * FROM streams WHERE user_id = ?;
- """, (user_id,))
-
- if not stream:
- return False
-
- ## TODO: calculate length in seconds, currently using temp value
-
- db.execute("""
- INSERT INTO vods (user_id, title, datetime, category_id, length, views)
- VALUES (?, ?, ?, ?, ?, ?);
- """, (stream["user_id"], stream["title"], stream["datetime"], stream["category_id"], 10, stream["num_viewers"]))
-
- db.execute("""
- DELETE FROM streams WHERE user_id = ?;
- """, (user_id,))
-
- return True
-
def create_local_directories(username: str):
"""
Create directories for user stream data if they do not exist
"""
- vods_path = f"stream_data/{username}/vods"
- stream_path = f"stream_data/{username}/stream"
- thumbnail_path = f"stream_data/{username}/thumbnails"
+ vods_path = f"stream_data/vods/{username}"
+ stream_path = f"stream_data/stream"
+ thumbnail_path = f"stream_data/thumbnails/{username}"
if not os.path.exists(vods_path):
os.makedirs(vods_path)
@@ -188,7 +161,6 @@ def create_local_directories(username: str):
os.makedirs(thumbnail_path)
# Fix permissions
- os.chmod(f"stream_data/{username}", 0o777)
os.chmod(vods_path, 0o777)
os.chmod(stream_path, 0o777)
os.chmod(thumbnail_path, 0o777)
From af24bfa7d37c7925072167bdb2cffd8c8429d622 Mon Sep 17 00:00:00 2001
From: Chris-1010 <122332721@umail.ucc.ie>
Date: Sat, 22 Feb 2025 12:14:00 +0000
Subject: [PATCH 2/3] REFACTOR: Relocate components to appropriate folders +
minor style adjustments
---
frontend/index.html | 4 +-
frontend/src/components/Auth/LoginForm.tsx | 2 +-
frontend/src/components/Layout/ListItem.tsx | 2 +-
frontend/src/components/Layout/ListRow.tsx | 4 +-
.../{Video => Stream}/ChatPanel.tsx | 0
.../src/components/Stream/VideoPlayer.tsx | 117 ++++++++++++++++++
frontend/src/components/Video/VideoPlayer.tsx | 96 --------------
frontend/src/pages/HomePage.tsx | 5 +-
frontend/src/pages/VideoPage.tsx | 4 +-
9 files changed, 127 insertions(+), 107 deletions(-)
rename frontend/src/components/{Video => Stream}/ChatPanel.tsx (100%)
create mode 100644 frontend/src/components/Stream/VideoPlayer.tsx
delete mode 100644 frontend/src/components/Video/VideoPlayer.tsx
diff --git a/frontend/index.html b/frontend/index.html
index 2f8fa8d..8989d4f 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,5 +1,5 @@
-
+
@@ -7,7 +7,7 @@
Team Software Project
-
+
diff --git a/frontend/src/components/Auth/LoginForm.tsx b/frontend/src/components/Auth/LoginForm.tsx
index 1d9f558..4fb6457 100644
--- a/frontend/src/components/Auth/LoginForm.tsx
+++ b/frontend/src/components/Auth/LoginForm.tsx
@@ -148,7 +148,7 @@ const LoginForm: React.FC = ({ onSubmit, onForgotPassword }) => {
errors.password ? "border-red-500" : ""
}`}
>
-
+