Skip to content

Commit fb564c2

Browse files
feat: add thumbnail generation hook and channel API
- Implemented `useThumbnailGenerator` hook for generating video thumbnails using FFmpeg. - Created `channelApi` for managing channel-related operations (create, update, delete, upload avatar/banner, etc.). - Removed deprecated FFmpeg utility functions from `ffmpeg.ts`. - Introduced Zustand store for managing channel state. - Deleted unused Kafka producer and FFmpeg wrapper files. - Added video processing worker for handling video transcoding, HLS generation, and metadata extraction. - Updated package dependencies to include `fs-extra` for file operations.
1 parent e091503 commit fb564c2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3572
-1218
lines changed

.github/workflows/ci.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Node.js CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
backend-test:
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
node-version: [18.x]
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v3
20+
21+
- name: Use Node.js ${{ matrix.node-version }}
22+
uses: actions/setup-node@v3
23+
with:
24+
node-version: ${{ matrix.node-version }}
25+
cache: 'npm'
26+
cache-dependency-path: BACKEND/package-lock.json
27+
28+
- name: Install Backend Dependencies
29+
run: cd BACKEND && npm install
30+
31+
- name: Run Backend Tests
32+
run: cd BACKEND && npm test
33+
env:
34+
MONGODB_URL: ${{ secrets.MONGODB_URL }}
35+
ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }}
36+
ACCESS_TOKEN_EXPIRY: "1d"
37+
CLOUDINARY_CLOUD_NAME: ${{ secrets.CLOUDINARY_CLOUD_NAME }}
38+
CLOUDINARY_API_KEY: ${{ secrets.CLOUDINARY_API_KEY }}
39+
CLOUDINARY_API_SECRET: ${{ secrets.CLOUDINARY_API_SECRET }}
40+
DB_NAME: "videostreaming" # You can specify a DB name for CI
41+
42+
frontend-build:
43+
runs-on: ubuntu-latest
44+
45+
strategy:
46+
matrix:
47+
node-version: [18.x]
48+
49+
steps:
50+
- name: Checkout repository
51+
uses: actions/checkout@v3
52+
53+
- name: Use Node.js ${{ matrix.node-version }}
54+
uses: actions/setup-node@v3
55+
with:
56+
node-version: ${{ matrix.node-version }}
57+
cache: 'npm'
58+
cache-dependency-path: frontend/package-lock.json
59+
60+
- name: Install Frontend Dependencies
61+
run: cd frontend && npm install
62+
63+
- name: Run Linter
64+
run: cd frontend && npm run lint
65+
66+
- name: Run Build
67+
run: cd frontend && npm run build

BACKEND

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit 86cf587f3133ac32701d580ee29cd0d8cdcfef8c
1+
Subproject commit ab44cb04b7bce5827aa96a28d0581297c61e1aa8

TODO.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1 @@
1-
# TODO : 18/7/25
2-
3-
# Backend of ffmpeg video streaming application
4-
## featurees in backend ( API related for video editing )
5-
### 1. Video upload
6-
### 2. Video quality change
7-
### 3. Video format change
8-
#
9-
10-
11-
12-
## Fixing some bug s in frontend
1+
# TODO implement net video option , add queue option

docker-compose-BACKEND.yml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1-
version: '3.4'
2-
31
services:
42
backends:
53
image: backends
64
build:
7-
context: BACKEND-IN-JS
5+
context: ./BACKEND
86
dockerfile: ./Dockerfile
97
environment:
108
NODE_ENV: production
11-
ports:
12-
- 3000:3000
9+
REDIS_HOST: 127.0.0.1
10+
REDIS_PORT: 6379
11+
KAFKAJS_NO_PARTITIONER_WARNING: 1
12+
CLOUDINARY_CLOUD_NAME: dfaytfq2n
13+
CLOUDINARY_API_KEY: 386834226673114
14+
CLOUDINARY_API_SECRET: uOVLF2th1fg8Hybk8f2OLFoKX0U
15+
MONGODB_URL: mongodb+srv://prakharchauhan179:pqvSLLv6ruESFI4b@cluster0.mr0wiih.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
16+
PORT: 8000
17+
depends_on:
18+
- redis
19+
network_mode: "host"
20+
21+
redis:
22+
image: redis:alpine
23+
network_mode: "host"
24+
25+
mongo:
26+
image: mongo
27+
network_mode: "host"

frontend/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12-
"@ffmpeg/ffmpeg": "^0.12.15",
13-
"@ffmpeg/util": "^0.12.2",
12+
"@ffmpeg/ffmpeg": "^0.12.10",
13+
"@ffmpeg/util": "^0.12.1",
1414
"@radix-ui/react-alert-dialog": "^1.1.15",
1515
"@radix-ui/react-avatar": "^1.1.11",
1616
"@radix-ui/react-dialog": "^1.1.15",
@@ -34,14 +34,18 @@
3434
"clsx": "^2.1.1",
3535
"date-fns": "^4.1.0",
3636
"framer-motion": "^11.18.2",
37+
"hls.js": "^1.5.8",
3738
"lucide-react": "^0.468.0",
3839
"next": "15.3.5",
3940
"react": "^19.0.0",
4041
"react-dom": "^19.0.0",
42+
"recharts": "^3.6.0",
4143
"socket.io-client": "^4.8.1",
44+
"sonner": "^2.0.7",
4245
"tailwind-merge": "^2.6.0",
4346
"tailwindcss-animate": "^1.0.7",
4447
"three": "^0.171.0",
48+
"video.js": "^8.12.0",
4549
"zustand": "^5.0.2"
4650
},
4751
"devDependencies": {

frontend/src/app/auth/login/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useAuthStore } from "@/store/authStore";
88
import { Button } from "@/components/ui/button";
99
import { useToast } from "@/hooks/use-toast";
1010
import { Mail, Lock } from "lucide-react";
11-
import { log } from "console";
11+
// import { log } from "console";
1212

1313
export default function LoginPage() {
1414
const [identifier, setIdentifier] = useState(""); // Can be email or username

frontend/src/app/channel/[id]/page.tsx

Lines changed: 19 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,9 @@ import { Button } from '@/components/ui/button';
77
import { useToast } from '@/hooks/use-toast';
88
import { useAuthStore } from '@/store/authStore';
99
import apiClient from '@/lib/api';
10-
import { Bell, BellOff, Video, Eye, Calendar } from 'lucide-react';
10+
import { Bell, BellOff, Video, Users, Play, Settings } from 'lucide-react';
1111
import Image from 'next/image';
12-
import Link from 'next/link';
13-
import { formatViewCount, formatTimeAgo } from '@/lib/utils';
14-
15-
interface Video {
16-
_id: string;
17-
title: string;
18-
description: string;
19-
thumbnail: string;
20-
videoFiles: string;
21-
duration: number;
22-
views: number;
23-
createdAt: string;
24-
}
12+
import { VideoGrid } from '@/components/video/VideoGrid';
2513

2614
interface Channel {
2715
_id: string;
@@ -39,15 +27,15 @@ export default function ChannelPage() {
3927
const { user } = useAuthStore();
4028

4129
const [channel, setChannel] = useState<Channel | null>(null);
42-
const [videos, setVideos] = useState<Video[]>([]);
30+
const [videoCount, setVideoCount] = useState(0);
4331
const [loading, setLoading] = useState(true);
4432
const [isSubscribed, setIsSubscribed] = useState(false);
4533
const [subscribersCount, setSubscribersCount] = useState(0);
4634

4735
useEffect(() => {
4836
if (params.id) {
4937
fetchChannelData();
50-
fetchChannelVideos();
38+
fetchVideoCount();
5139
checkSubscription();
5240
}
5341
}, [params.id]);
@@ -66,19 +54,19 @@ export default function ChannelPage() {
6654
variant: 'destructive',
6755
});
6856
router.push('/');
57+
} finally {
58+
setLoading(false);
6959
}
7060
};
7161

72-
const fetchChannelVideos = async () => {
62+
const fetchVideoCount = async () => {
7363
try {
7464
const response = await apiClient.get(`/videos?page=1&limit=100`);
7565
const allVideos = response.data.data || [];
7666
const channelVideos = allVideos.filter((v: any) => v.owner?._id === params.id);
77-
setVideos(channelVideos);
67+
setVideoCount(channelVideos.length);
7868
} catch (error) {
79-
console.error('Error fetching videos:', error);
80-
} finally {
81-
setLoading(false);
69+
console.error('Error fetching video count:', error);
8270
}
8371
};
8472

@@ -187,12 +175,12 @@ export default function ChannelPage() {
187175
<span className="text-lg">@{channel.username}</span>
188176
<span></span>
189177
<span className="flex items-center gap-1">
190-
<Video className="w-4 h-4" />
191-
{videos.length} videos
178+
<Play className="w-4 h-4" />
179+
{videoCount} videos
192180
</span>
193181
<span></span>
194182
<span className="flex items-center gap-1">
195-
<Bell className="w-4 h-4" />
183+
<Users className="w-4 h-4" />
196184
{subscribersCount} subscribers
197185
</span>
198186
</div>
@@ -227,11 +215,14 @@ export default function ChannelPage() {
227215
{/* Videos Section */}
228216
<div className="py-8">
229217
<div className="flex items-center justify-between mb-6">
230-
<h2 className="text-2xl font-bold">Videos</h2>
231-
<span className="text-muted-foreground">{videos.length} uploads</span>
218+
<h2 className="text-2xl font-bold flex items-center gap-2">
219+
<Play className="w-6 h-6 text-orange-500" />
220+
Channel Videos
221+
</h2>
222+
<span className="text-muted-foreground">{videoCount} uploads</span>
232223
</div>
233224

234-
{videos.length === 0 ? (
225+
{videoCount === 0 ? (
235226
<div className="text-center py-16 bg-card border rounded-xl">
236227
<Video className="w-16 h-16 mx-auto mb-4 text-muted-foreground" />
237228
<h3 className="text-xl font-semibold mb-2">No videos yet</h3>
@@ -240,42 +231,7 @@ export default function ChannelPage() {
240231
</p>
241232
</div>
242233
) : (
243-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
244-
{videos.map((video) => (
245-
<Link key={video._id} href={`/video/${video._id}`}>
246-
<div className="group cursor-pointer">
247-
<div className="relative aspect-video bg-muted rounded-xl overflow-hidden mb-3">
248-
<Image
249-
src={video.thumbnail}
250-
alt={video.title}
251-
fill
252-
className="object-cover transition-transform duration-300 group-hover:scale-110"
253-
/>
254-
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors" />
255-
<div className="absolute bottom-2 right-2 bg-black/80 px-2 py-1 rounded text-sm font-semibold">
256-
{Math.floor(video.duration / 60)}:{(video.duration % 60).toString().padStart(2, '0')}
257-
</div>
258-
</div>
259-
<div className="space-y-1">
260-
<h3 className="font-semibold line-clamp-2 text-lg group-hover:text-orange-500 transition-colors">
261-
{video.title}
262-
</h3>
263-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
264-
<span className="flex items-center gap-1">
265-
<Eye className="w-4 h-4" />
266-
{formatViewCount(video.views)}
267-
</span>
268-
<span></span>
269-
<span className="flex items-center gap-1">
270-
<Calendar className="w-4 h-4" />
271-
{formatTimeAgo(video.createdAt)}
272-
</span>
273-
</div>
274-
</div>
275-
</div>
276-
</Link>
277-
))}
278-
</div>
234+
<VideoGrid channelId={params.id as string} sortBy="recent" />
279235
)}
280236
</div>
281237
</div>

0 commit comments

Comments
 (0)