feat: add video player functionality and search feature

- Added `react-player` dependency for video playback.
- Implemented a modal for video playback with a close button.
- Integrated search functionality to filter videos by title or path.
- Updated CSS for video player modal and search bar styling.
- Enhanced video fetching logic to support search queries.
This commit is contained in:
tigeren 2025-08-23 15:49:17 +00:00
parent 27dd0bee39
commit 3782556c03
7 changed files with 731 additions and 82 deletions

64
README.md Normal file
View File

@ -0,0 +1,64 @@
# Video Organization App
This project consists of a backend API (FastAPI) and a frontend web application (React).
## Getting Started
Follow these instructions to set up and run the project on your local machine.
### Prerequisites
* Node.js (LTS version recommended)
* npm (comes with Node.js)
* Python 3.8+
* pip (comes with Python)
### Backend Setup and Run
The backend is a FastAPI application.
1. **Navigate to the backend directory:**
```bash
cd backend
```
2. **Install Python dependencies:**
It's recommended to use a virtual environment.
```bash
python -m venv venv
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
pip install -r requirements.txt
```
3. **Run the backend server:**
```bash
uvicorn main:app --host 0.0.0.0 --port 8000
```
The backend server will be accessible at `http://0.0.0.0:8000`.
### Frontend Setup and Run
The frontend is a React application built with Vite.
1. **Navigate to the frontend directory:**
```bash
cd frontend
```
2. **Install Node.js dependencies:**
```bash
npm install
```
3. **Run the frontend development server:**
```bash
npm run dev -- --host
```
The frontend development server will typically run on `http://localhost:5173` (or another available port).
## Project Structure
* `backend/`: Contains the FastAPI backend application.
* `frontend/`: Contains the React frontend application.
* `test_videos/`: Sample video files.
* `screenshot/`: Screenshots of the application.

View File

@ -1,34 +1,3 @@
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from typing import List
import os
from models import Base, Video
from schemas import VideoCreate, VideoInDB
from database import engine, get_db
from video_scanner import scan_video_directory
import json
# Create database tables
Base.metadata.create_all(bind=engine)
app = FastAPI(title="Video Organization API", version="0.1.0")
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# In-memory storage for video paths (in a real app, this would be in a database)
video_paths = []
@app.get("/")
def read_root():
return {"message": "Welcome to Video Organization API"}
from fastapi import FastAPI, HTTPException, Query, Depends
from fastapi.middleware.cors import CORSMiddleware
from typing import List
@ -38,6 +7,7 @@ from models import Base, Video
from schemas import VideoCreate, VideoInDB
from database import engine, get_db
from video_scanner import scan_video_directory
from fastapi.responses import FileResponse
import json
# Create database tables
@ -108,48 +78,33 @@ def scan_videos(db: Session = Depends(get_db)):
return {"message": f"Scan complete. Added {added_count} new videos.", "count": added_count}
@app.get("/videos/", response_model=List[VideoInDB])
def get_videos(db: Session = Depends(get_db)):
def get_videos(db: Session = Depends(get_db), search: str = Query(None)):
"""
Get all videos in the library
Get all videos in the library, optionally filtered by search query
"""
videos = db.query(Video).all()
query = db.query(Video)
if search:
query = query.filter(
(Video.title.ilike(f"%{search}%")) |
(Video.path.ilike(f"%{search}%"))
)
videos = query.all()
return videos
@app.get("/videos/{video_id}/stream")
def stream_video(video_id: int, db: Session = Depends(get_db)):
"""
Stream a video file by its ID
"""
video = db.query(Video).filter(Video.id == video_id).first()
if not video:
raise HTTPException(status_code=404, detail="Video not found")
if not os.path.exists(video.path):
raise HTTPException(status_code=404, detail="Video file not found on server")
return FileResponse(video.path, media_type="video/mp4")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
@app.get("/video-paths/")
def get_video_paths():
"""
Get all video paths in the library
"""
return {"paths": video_paths}
@app.post("/scan-videos/")
def scan_videos():
"""
Scan all video paths and return the videos found
"""
all_videos = []
for path in video_paths:
try:
videos = scan_video_directory(path)
all_videos.extend(videos)
except FileNotFoundError as e:
raise HTTPException(status_code=404, detail=str(e))
return {"videos": all_videos, "count": len(all_videos)}
@app.get("/videos/")
def get_videos():
"""
Get all videos in the library
"""
# This is a placeholder - in a real implementation, we would query the database
return {"message": "List of videos"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@ -10,3 +10,32 @@ INFO: 192.168.2.244:49178 - "POST /video-paths/?path=%2Fmnt%2Fdata1%2FPorn%2
INFO: 192.168.2.244:49178 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49178 - "POST /scan-videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49178 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49365 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49366 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49377 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49378 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49377 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49378 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49456 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49457 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49456 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49457 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49594 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49594 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49595 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49594 - "GET /videos/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49618 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49618 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49618 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49638 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49639 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49638 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49640 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49640 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49645 - "GET /videos/?search=shi HTTP/1.1" 200 OK
INFO: 192.168.2.244:49645 - "GET /videos/?search=shit HTTP/1.1" 200 OK
INFO: 192.168.2.244:49768 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49769 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49768 - "GET /video-paths/ HTTP/1.1" 200 OK
INFO: 192.168.2.244:49769 - "GET /videos/?search= HTTP/1.1" 200 OK
INFO: 192.168.2.244:49769 - "GET /videos/?search= HTTP/1.1" 200 OK

View File

@ -12,6 +12,7 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-player": "^3.3.1",
"react-pro-sidebar": "^1.1.0"
},
"devDependencies": {
@ -1170,6 +1171,74 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@mux/mux-data-google-ima": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/@mux/mux-data-google-ima/-/mux-data-google-ima-0.2.8.tgz",
"integrity": "sha512-0ZEkHdcZ6bS8QtcjFcoJeZxJTpX7qRIledf4q1trMWPznugvtajCjCM2kieK/pzkZj1JM6liDRFs1PJSfVUs2A==",
"license": "MIT",
"dependencies": {
"mux-embed": "5.9.0"
}
},
"node_modules/@mux/mux-player": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/@mux/mux-player/-/mux-player-3.5.3.tgz",
"integrity": "sha512-uXKFXbdtioAi+clSVfD60Rw4r7OvA62u2jV6aar9loW9qMsmKv8LU+8uaIaWQjyAORp6E0S37GOVjo72T6O2eQ==",
"license": "MIT",
"dependencies": {
"@mux/mux-video": "0.26.1",
"@mux/playback-core": "0.30.1",
"media-chrome": "~4.11.1",
"player.style": "^0.1.9"
}
},
"node_modules/@mux/mux-player-react": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/@mux/mux-player-react/-/mux-player-react-3.5.3.tgz",
"integrity": "sha512-f0McZbIXYDkzecFwhhkf0JgEInPnsOClgBqBhkdhRlLRdrAzMATib+D3Di3rPkRHNH7rc/WWORvSxgJz6m6zkA==",
"license": "MIT",
"dependencies": {
"@mux/mux-player": "3.5.3",
"@mux/playback-core": "0.30.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0",
"react": "^17.0.2 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0",
"react-dom": "^17.0.2 || ^17.0.2-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@mux/mux-video": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/@mux/mux-video/-/mux-video-0.26.1.tgz",
"integrity": "sha512-gkMdBAgNlB4+krANZHkQFzYWjWeNsJz69y1/hnPtmNQnpvW+O7oc71OffcZrbblyibSxWMQ6MQpYmBVjXlp6sA==",
"license": "MIT",
"dependencies": {
"@mux/mux-data-google-ima": "0.2.8",
"@mux/playback-core": "0.30.1",
"castable-video": "~1.1.10",
"custom-media-element": "~1.4.5",
"media-tracks": "~0.3.3"
}
},
"node_modules/@mux/playback-core": {
"version": "0.30.1",
"resolved": "https://registry.npmjs.org/@mux/playback-core/-/playback-core-0.30.1.tgz",
"integrity": "sha512-rnO1NE9xHDyzbAkmE6ygJYcD7cyyMt7xXqWTykxlceaoSXLjUqgp42HDio7Lcidto4x/O4FIa7ztjV2aCBCXgQ==",
"license": "MIT",
"dependencies": {
"hls.js": "~1.6.6",
"mux-embed": "^5.8.3"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@ -1467,6 +1536,12 @@
"win32"
]
},
"node_modules/@svta/common-media-library": {
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.12.4.tgz",
"integrity": "sha512-9EuOoaNmz7JrfGwjsrD9SxF9otU5TNMnbLu1yU4BeLK0W5cDxVXXR58Z89q9u2AnHjIctscjMTYdlqQ1gojTuw==",
"license": "Apache-2.0"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -1536,7 +1611,6 @@
"version": "19.1.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@ -1552,6 +1626,22 @@
"@types/react": "^19.0.0"
}
},
"node_modules/@vercel/edge": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.2.2.tgz",
"integrity": "sha512-1+y+f6rk0Yc9ss9bRDgz/gdpLimwoRteKHhrcgHvEpjbP1nyT3ByqEMWm2BTcpIO5UtDmIFXc8zdq4LR190PDA==",
"license": "Apache-2.0"
},
"node_modules/@vimeo/player": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/@vimeo/player/-/player-2.29.0.tgz",
"integrity": "sha512-9JjvjeqUndb9otCCFd0/+2ESsLk7VkDE6sxOBy9iy2ukezuQbplVRi+g9g59yAurKofbmTi/KcKxBGO/22zWRw==",
"license": "MIT",
"dependencies": {
"native-promise-only": "0.8.1",
"weakmap-polyfill": "2.0.4"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.1.tgz",
@ -1675,6 +1765,45 @@
"dev": true,
"license": "MIT"
},
"node_modules/bcp-47": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz",
"integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^2.0.0",
"is-alphanumerical": "^2.0.0",
"is-decimal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/bcp-47-match": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
"integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/bcp-47-normalize": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz",
"integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==",
"license": "MIT",
"dependencies": {
"bcp-47": "^2.0.0",
"bcp-47-match": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@ -1762,6 +1891,24 @@
],
"license": "CC-BY-4.0"
},
"node_modules/castable-video": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/castable-video/-/castable-video-1.1.10.tgz",
"integrity": "sha512-/T1I0A4VG769wTEZ8gWuy1Crn9saAfRTd1UYTb8xbOPlN78+zOi/1nU2dD5koNkfE5VWvgabkIqrGKmyNXOjSQ==",
"license": "MIT",
"dependencies": {
"custom-media-element": "~1.4.5"
}
},
"node_modules/ce-la-react": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/ce-la-react/-/ce-la-react-0.3.1.tgz",
"integrity": "sha512-g0YwpZDPIwTwFumGTzNHcgJA6VhFfFCJkSNdUdC04br2UfU+56JDrJrJva3FZ7MToB4NDHAFBiPE/PZdNl1mQA==",
"license": "BSD-3-Clause",
"peerDependencies": {
"react": ">=17.0.0"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@ -1785,6 +1932,18 @@
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/cloudflare-video-element": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/cloudflare-video-element/-/cloudflare-video-element-1.3.4.tgz",
"integrity": "sha512-F9g+tXzGEXI6v6L48qXxr8vnR8+L6yy7IhpJxK++lpzuVekMHTixxH7/dzLuq6OacVGziU4RB5pzZYJ7/LYtJg==",
"license": "MIT"
},
"node_modules/codem-isoboxer": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.10.tgz",
"integrity": "sha512-eNk3TRV+xQMJ1PEj0FQGY8KD4m0GPxT487XJ+Iftm7mVa9WpPFDMWqPt+46buiP5j5Wzqe5oMIhqBcAeKfygSA==",
"license": "MIT"
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1877,6 +2036,40 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/custom-media-element": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/custom-media-element/-/custom-media-element-1.4.5.tgz",
"integrity": "sha512-cjrsQufETwxjvwZbYbKBCJNvmQ2++G9AvT45zDi7NXL9k2PdVcs2h0jQz96J6G4TMKRCcEsoJ+QTgQD00Igtjw==",
"license": "MIT"
},
"node_modules/dash-video-element": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/dash-video-element/-/dash-video-element-0.1.6.tgz",
"integrity": "sha512-4gHShaQjcFv6diX5EzB6qAdUGKlIUGGZY8J8yp2pQkWqR0jX4c6plYy0cFraN7mr0DZINe8ujDN1fssDYxJjcg==",
"license": "MIT",
"dependencies": {
"custom-media-element": "^1.4.5",
"dashjs": "^5.0.3"
}
},
"node_modules/dashjs": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/dashjs/-/dashjs-5.0.3.tgz",
"integrity": "sha512-TXndNnCUjFjF2nYBxDVba+hWRpVkadkQ8flLp7kHkem+5+wZTfRShJCnVkPUosmjS0YPE9fVNLbYPJxHBeQZvA==",
"license": "BSD-3-Clause",
"dependencies": {
"@svta/common-media-library": "^0.12.4",
"bcp-47-match": "^2.0.3",
"bcp-47-normalize": "^2.3.0",
"codem-isoboxer": "0.3.10",
"fast-deep-equal": "3.1.3",
"html-entities": "^2.5.2",
"imsc": "^1.1.5",
"localforage": "^1.10.0",
"path-browserify": "^1.0.1",
"ua-parser-js": "^1.0.37"
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@ -2231,7 +2424,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
@ -2517,6 +2709,23 @@
"node": ">= 0.4"
}
},
"node_modules/hls-video-element": {
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/hls-video-element/-/hls-video-element-1.5.7.tgz",
"integrity": "sha512-R+uYimNZQndT2iqBgW7Gm0KiHT6pmlt5tnT63rYIcqOEcKD59M6pmdwqtX2vKPfHo+1ACM14Fy9JF1YMwlrLdQ==",
"license": "MIT",
"dependencies": {
"custom-media-element": "^1.4.5",
"hls.js": "^1.6.5",
"media-tracks": "^0.3.3"
}
},
"node_modules/hls.js": {
"version": "1.6.10",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.10.tgz",
"integrity": "sha512-16XHorwFNh+hYazYxDNXBLEm5aRoU+oxMX6qVnkbGH3hJil4xLav3/M6NH92VkD1qSOGKXeSm+5unuawPXK6OQ==",
"license": "Apache-2.0"
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -2526,6 +2735,22 @@
"react-is": "^16.7.0"
}
},
"node_modules/html-entities": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/mdevils"
},
{
"type": "patreon",
"url": "https://patreon.com/mdevils"
}
],
"license": "MIT"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -2536,6 +2761,12 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@ -2552,6 +2783,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imsc": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.5.tgz",
"integrity": "sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ==",
"license": "BSD-2-Clause",
"dependencies": {
"sax": "1.2.1"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -2562,6 +2802,30 @@
"node": ">=0.8.19"
}
},
"node_modules/is-alphabetical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
"integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-alphanumerical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
"integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^2.0.0",
"is-decimal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -2583,6 +2847,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-decimal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
"integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -2708,12 +2982,30 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/localforage": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
"license": "Apache-2.0",
"dependencies": {
"lie": "3.1.1"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -2737,6 +3029,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -2756,6 +3060,22 @@
"node": ">= 0.4"
}
},
"node_modules/media-chrome": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/media-chrome/-/media-chrome-4.11.1.tgz",
"integrity": "sha512-+2niDc4qOwlpFAjwxg1OaizK/zKV6y7QqGm4nBFEVlSaG0ZBgOmfc4IXAPiirZqAlZGaFFUaMqCl1SpGU0/naA==",
"license": "MIT",
"dependencies": {
"@vercel/edge": "^1.2.1",
"ce-la-react": "^0.3.0"
}
},
"node_modules/media-tracks": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/media-tracks/-/media-tracks-0.3.3.tgz",
"integrity": "sha512-9P2FuUHnZZ3iji+2RQk7Zkh5AmZTnOG5fODACnjhCVveX1McY3jmCRHofIEI+yTBqplz7LXy48c7fQ3Uigp88w==",
"license": "MIT"
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@ -2796,6 +3116,12 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/mux-embed": {
"version": "5.9.0",
"resolved": "https://registry.npmjs.org/mux-embed/-/mux-embed-5.9.0.tgz",
"integrity": "sha512-wmunL3uoPhma/tWy8PrDPZkvJpXvSFBwbD3KkC4PG8Ztjfb1X3hRJwGUAQyRz7z99b/ovLm2UTTitrkvStjH4w==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@ -2815,6 +3141,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/native-promise-only": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz",
"integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==",
"license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -2829,6 +3161,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -2909,6 +3250,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"license": "MIT"
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -2963,6 +3310,22 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/player.style": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/player.style/-/player.style-0.1.10.tgz",
"integrity": "sha512-Jxv7tlaQ3SFCddsN35jzoGnCHB3/xMTbJOgn4zcsmF0lcZvRPq5UkRRAD5tZm8CvzKndUvtoDlG6GSPL/N/SrA==",
"license": "MIT",
"workspaces": [
".",
"site",
"examples/*",
"scripts/*",
"themes/*"
],
"dependencies": {
"media-chrome": "~4.11.0"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@ -3002,6 +3365,17 @@
"node": ">= 0.8.0"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -3054,6 +3428,29 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-player": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/react-player/-/react-player-3.3.1.tgz",
"integrity": "sha512-wE/xLloneXZ1keelFCaNeIFVNUp4/7YoUjfHjwF945aQzsbDKiIB0LQuCchGL+la0Y1IybxnR0R6Cm3AiqInMw==",
"license": "MIT",
"dependencies": {
"@mux/mux-player-react": "^3.5.1",
"cloudflare-video-element": "^1.3.3",
"dash-video-element": "^0.1.6",
"hls-video-element": "^1.5.6",
"spotify-audio-element": "^1.0.2",
"tiktok-video-element": "^0.1.0",
"twitch-video-element": "^0.1.2",
"vimeo-video-element": "^1.5.3",
"wistia-video-element": "^1.3.3",
"youtube-video-element": "^1.6.1"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18 || ^19",
"react": "^17.0.2 || ^18 || ^19",
"react-dom": "^17.0.2 || ^18 || ^19"
}
},
"node_modules/react-pro-sidebar": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-pro-sidebar/-/react-pro-sidebar-1.1.0.tgz",
@ -3149,6 +3546,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==",
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@ -3207,6 +3610,12 @@
"node": ">=0.10.0"
}
},
"node_modules/spotify-audio-element": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/spotify-audio-element/-/spotify-audio-element-1.0.3.tgz",
"integrity": "sha512-I1/qD8cg/UnTlCIMiKSdZUJTyYfYhaqFK7LIVElc48eOqUUbVCaw1bqL8I6mJzdMJTh3eoNyF/ewvB7NoS/g9A==",
"license": "MIT"
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@ -3226,6 +3635,12 @@
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
"license": "MIT"
},
"node_modules/super-media-element": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/super-media-element/-/super-media-element-1.4.2.tgz",
"integrity": "sha512-9pP/CVNp4NF2MNlRzLwQkjiTgKKe9WYXrLh9+8QokWmMxz+zt2mf1utkWLco26IuA3AfVcTb//qtlTIjY3VHxA==",
"license": "MIT"
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -3251,6 +3666,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tiktok-video-element": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/tiktok-video-element/-/tiktok-video-element-0.1.1.tgz",
"integrity": "sha512-BaiVzvNz2UXDKTdSrXzrNf4q6Ecc+/utYUh7zdEu2jzYcJVDoqYbVfUl0bCfMoOeeAqg28vD/yN63Y3E9jOrlA==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
@ -3268,6 +3689,12 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/twitch-video-element": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/twitch-video-element/-/twitch-video-element-0.1.4.tgz",
"integrity": "sha512-SDpZ4f7sZmwHF6XG5PF0KWuP18pH/kNG04MhTcpqJby7Lk/D3TS/lCYd+RSg0rIAAVi1LDgSIo1yJs9kmHlhgw==",
"license": "MIT"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -3281,6 +3708,32 @@
"node": ">= 0.8.0"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.41",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
"integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "MIT",
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@ -3322,6 +3775,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/vimeo-video-element": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/vimeo-video-element/-/vimeo-video-element-1.5.4.tgz",
"integrity": "sha512-4C9+Gnac7gOVNNu3tWQgzuwG4mFVaiCmUz8RtV1l+xkirgcZ0kEJOSIblXx/Y7DIfM+BbeepptxL9SP/ZrskJA==",
"license": "MIT",
"dependencies": {
"@vimeo/player": "2.29.0"
}
},
"node_modules/vite": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
@ -3397,6 +3859,15 @@
}
}
},
"node_modules/weakmap-polyfill": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/weakmap-polyfill/-/weakmap-polyfill-2.0.4.tgz",
"integrity": "sha512-ZzxBf288iALJseijWelmECm/1x7ZwQn3sMYIkDr2VvZp7r6SEKuT8D0O9Wiq6L9Nl5mazrOMcmiZE/2NCenaxw==",
"license": "MIT",
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -3413,6 +3884,15 @@
"node": ">= 8"
}
},
"node_modules/wistia-video-element": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/wistia-video-element/-/wistia-video-element-1.3.4.tgz",
"integrity": "sha512-2l22oaQe4jUfi3yvsh2m2oCEgvbqTzaSYx6aJnZAvV5hlMUJlyZheFUnaj0JU2wGlHdVGV7xNY+5KpKu+ruLYA==",
"license": "MIT",
"dependencies": {
"super-media-element": "~1.4.2"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@ -3442,6 +3922,12 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/youtube-video-element": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/youtube-video-element/-/youtube-video-element-1.6.2.tgz",
"integrity": "sha512-YHDIOAqgRpfl1Ois9HcB8UFtWOxK8KJrV5TXpImj4BKYP1rWT04f/fMM9tQ9SYZlBKukT7NR+9wcI3UpB5BMDQ==",
"license": "MIT"
}
}
}

View File

@ -14,6 +14,7 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-player": "^3.3.1",
"react-pro-sidebar": "^1.1.0"
},
"devDependencies": {

View File

@ -24,8 +24,6 @@
margin-left: 80px; /* Same as collapsed sidebar width */
}
/* Sidebar Styles */
.path-section {
padding: 10px 20px;
@ -184,15 +182,77 @@
text-overflow: ellipsis;
}
.video-channel, .video-stats {
margin: 0;
font-size: 14px;
color: #606060;
}
.video-path, .video-size {
margin: 4px 0;
font-size: 12px;
color: #606060;
word-break: break-all;
}
/* Video Player Modal */
.video-player-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
}
.video-player-modal-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
position: relative;
width: 90%;
max-width: 900px;
max-height: 90%;
overflow-y: auto;
}
.close-player-modal {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #333;
}
.video-player-modal-content h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 22px;
color: #333;
}
.player-wrapper {
position: relative;
padding-top: 56.25%; /* 16:9 Aspect Ratio */
margin-bottom: 15px;
}
.react-player {
position: absolute;
top: 0;
left: 0;
}
/* Search Bar */
.search-bar {
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Sidebar, Menu, MenuItem, SubMenu } from 'react-pro-sidebar';
import { FaBars, FaHome, FaFolder, FaVideo, FaPlus, FaList } from 'react-icons/fa';
import ReactPlayer from 'react-player';
import './App.css';
const API_BASE_URL = 'http://192.168.2.220:8000';
@ -12,6 +13,9 @@ function App() {
const [videos, setVideos] = useState([]);
const [loading, setLoading] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const [selectedVideo, setSelectedVideo] = useState(null);
const [showPlayerModal, setShowPlayerModal] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
// Fetch initial data on component mount
useEffect(() => {
@ -28,15 +32,26 @@ function App() {
}
};
const fetchVideos = async () => {
const fetchVideos = async (search = '') => {
try {
const response = await axios.get(`${API_BASE_URL}/videos/`);
const response = await axios.get(`${API_BASE_URL}/videos/`, {
params: { search: search }
});
setVideos(response.data);
} catch (error) {
console.error('Error fetching videos:', error);
}
};
// Add a new useEffect to trigger search when searchQuery changes
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
fetchVideos(searchQuery);
}, 500); // Debounce for 500ms
return () => clearTimeout(delayDebounceFn);
}, [searchQuery]); // Re-run when searchQuery changes
const addVideoPath = async () => {
if (!newPath.trim()) return;
@ -66,6 +81,16 @@ function App() {
}
};
const openVideoPlayer = (video) => {
setSelectedVideo(video);
setShowPlayerModal(true);
};
const closeVideoPlayer = () => {
setSelectedVideo(null);
setShowPlayerModal(false);
};
return (
<div className={`App ${collapsed ? 'collapsed' : ''}`}>
<Sidebar collapsed={collapsed} className="app-sidebar">
@ -118,10 +143,19 @@ function App() {
<main className="app-main">
<section className="videos-section">
<h2>Video Library</h2>
<div className="search-bar">
<input
type="text"
placeholder="Search videos by title or path..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="search-input"
/>
</div>
{videos.length > 0 ? (
<div className="video-grid">
{videos.map((video) => (
<div key={video.id} className="video-card">
<div key={video.id} className="video-card" onClick={() => openVideoPlayer(video)}>
<div className="video-thumbnail">
<div className="thumbnail-placeholder"></div>
</div>
@ -141,6 +175,26 @@ function App() {
)}
</section>
</main>
{showPlayerModal && selectedVideo && (
<div className="video-player-modal-overlay" onClick={closeVideoPlayer}>
<div className="video-player-modal-content" onClick={(e) => e.stopPropagation()}>
<button className="close-player-modal" onClick={closeVideoPlayer}>&times;</button>
<h3>{selectedVideo.title}</h3>
<div className="player-wrapper">
<ReactPlayer
url={`${API_BASE_URL}/videos/${selectedVideo.id}/stream`}
className="react-player"
width="100%"
height="100%"
controls={true}
playing={true}
/>
</div>
<p>{selectedVideo.path}</p>
</div>
</div>
)}
</div>
);
}