TikTok's power to w sounds - audio tracks używane przez miliony wideo. Plus advanced video creation z effects!
1interface Sound {
2 id: string;
3 name: string;
4 artist: string;
5 duration: number;
6 thumbnailUrl: string;
7
8 // Stats
9 useCount: number; // ile videos używa tego sound
10 favorites: number;
11 isTrending: boolean;
12
13 // Categories
14 category: 'original' | 'music' | 'trending' | 'effects';
15 tags: string[];
16
17 createdAt: Date;
18}
19
20interface SoundUsage {
21 soundId: string;
22 startTime: number; // which second of sound to start (0-based)
23 volume: number; // 0-1
24}1'use client';
2
3import { useState } from 'react';
4
5interface SoundsPageProps {
6 sounds: Sound[];
7 onUseSound: (soundId: string) => void;
8 onFavorite: (soundId: string) => void;
9}
10
11export default function SoundsPage({
12 sounds,
13 onUseSound,
14 onFavorite
15}: SoundsPageProps) {
16 const [activeTab, setActiveTab] = useState<'trending' | 'favorites' | 'discover'>('trending');
17 const [searchQuery, setSearchQuery] = useState('');
18
19 const filteredSounds = sounds.filter(sound => {
20 if (searchQuery) {
21 return sound.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
22 sound.artist.toLowerCase().includes(searchQuery.toLowerCase());
23 }
24
25 if (activeTab === 'trending') return sound.isTrending;
26 if (activeTab === 'favorites') return sound.favorites > 1000;
27 return true;
28 });
29
30 return (
31 <div className="min-h-screen bg-black text-white">
32 {/* Header */}
33 <div className="sticky top-0 bg-black z-10 border-b border-gray-800">
34 <div className="p-4">
35 <h1 className="text-2xl font-bold mb-4">🎵 Sounds</h1>
36
37 {/* Search */}
38 <input
39 type="text"
40 value={searchQuery}
41 onChange={(e) => setSearchQuery(e.target.value)}
42 placeholder="Szukaj dźwięków..."
43 className="w-full bg-gray-900 text-white px-4 py-3 rounded-lg outline-none"
44 />
45 </div>
46
47 {/* Tabs */}
48 <div className="flex border-b border-gray-800">
49 {['trending', 'favorites', 'discover'].map(tab => (
50 <button
51 key={tab}
52 onClick={() => setActiveTab(tab as any)}
53 className={`flex-1 py-3 text-sm font-semibold capitalize ${
54 activeTab === tab
55 ? 'border-b-2 border-white'
56 : 'text-gray-500'
57 }`}
58 >
59 {tab}
60 </button>
61 ))}
62 </div>
63 </div>
64
65 {/* Sounds List */}
66 <div className="p-4 space-y-3">
67 {filteredSounds.map(sound => (
68 <SoundCard
69 key={sound.id}
70 sound={sound}
71 onUse={() => onUseSound(sound.id)}
72 onFavorite={() => onFavorite(sound.id)}
73 />
74 ))}
75 </div>
76 </div>
77 );
78}
79
80function SoundCard({
81 sound,
82 onUse,
83 onFavorite
84}: {
85 sound: Sound;
86 onUse: () => void;
87 onFavorite: () => void;
88}) {
89 const [isPlaying, setIsPlaying] = useState(false);
90
91 return (
92 <div className="bg-gray-900 rounded-lg p-4 flex items-center gap-4">
93 {/* Thumbnail */}
94 <button
95 onClick={() => setIsPlaying(!isPlaying)}
96 className="relative w-16 h-16 rounded-lg overflow-hidden flex-shrink-0"
97 >
98 <img
99 src={sound.thumbnailUrl}
100 alt={sound.name}
101 className={`w-full h-full object-cover ${isPlaying ? 'animate-pulse' : ''}`}
102 />
103 <div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-30">
104 <span className="text-white text-2xl">
105 {isPlaying ? '⏸' : '▶'}
106 </span>
107 </div>
108 </button>
109
110 {/* Info */}
111 <div className="flex-1 min-w-0">
112 <h3 className="font-bold text-white truncate">{sound.name}</h3>
113 <p className="text-sm text-gray-400 truncate">{sound.artist}</p>
114
115 <div className="flex items-center gap-3 mt-2 text-xs text-gray-500">
116 <span>{formatCount(sound.useCount)} videos</span>
117 {sound.isTrending && (
118 <span className="flex items-center gap-1 text-pink-500">
119 🔥 Trending
120 </span>
121 )}
122 </div>
123 </div>
124
125 {/* Actions */}
126 <div className="flex flex-col gap-2">
127 <button
128 onClick={onUse}
129 className="px-4 py-2 bg-white text-black rounded-full text-sm font-semibold hover:bg-gray-200"
130 >
131 Use Sound
132 </button>
133 <button
134 onClick={onFavorite}
135 className="p-2 hover:bg-gray-800 rounded-full"
136 >
137 ⭐
138 </button>
139 </div>
140 </div>
141 );
142}
143
144function formatCount(count: number): string {
145 if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
146 if (count >= 1000) return `${(count / 1000).toFixed(1)}K`;
147 return count.toString();
148}1interface VideoCreationState {
2 // Recording
3 recordedChunks: Blob[];
4 isRecording: boolean;
5 recordingDuration: number;
6 maxDuration: number; // 15, 60, or 180 seconds
7
8 // Editing
9 selectedSound?: Sound;
10 soundVolume: number;
11 selectedEffect?: Effect;
12 filters: Filter[];
13 trimStart: number;
14 trimEnd: number;
15
16 // Caption
17 caption: string;
18 hashtags: string[];
19 mentions: string[];
20
21 // Privacy
22 privacy: 'public' | 'friends' | 'private';
23 allowComments: boolean;
24 allowDuet: boolean;
25 allowStitch: boolean;
26}
27
28interface Effect {
29 id: string;
30 name: string;
31 type: 'beauty' | 'ar' | 'green-screen' | 'time';
32 thumbnailUrl: string;
33 isPopular: boolean;
34}
35
36interface Filter {
37 id: string;
38 name: string;
39 intensity: number; // 0-1
40}1'use client';
2
3import { useState, useRef, useEffect } from 'react';
4
5export default function VideoRecorder({
6 maxDuration = 60,
7 onComplete: (videoBlob: Blob, metadata: any) => void
8}: {
9 maxDuration?: number;
10 onComplete: (videoBlob: Blob, metadata: any) => void;
11}) {
12 const [isRecording, setIsRecording] = useState(false);
13 const [duration, setDuration] = useState(0);
14 const [selectedSound, setSelectedSound] = useState<Sound | null>(null);
15 const [selectedEffect, setSelectedEffect] = useState<Effect | null>(null);
16
17 const videoRef = useRef<HTMLVideoElement>(null);
18 const mediaRecorderRef = useRef<MediaRecorder | null>(null);
19 const chunksRef = useRef<Blob[]>([]);
20
21 useEffect(() => {
22 // Initialize camera
23 navigator.mediaDevices.getUserMedia({ video: true, audio: true })
24 .then(stream => {
25 if (videoRef.current) {
26 videoRef.current.srcObject = stream;
27 }
28 });
29 }, []);
30
31 const startRecording = () => {
32 if (!videoRef.current?.srcObject) return;
33
34 const stream = videoRef.current.srcObject as MediaStream;
35 const mediaRecorder = new MediaRecorder(stream);
36
37 mediaRecorder.ondataavailable = (e) => {
38 if (e.data.size > 0) {
39 chunksRef.current.push(e.data);
40 }
41 };
42
43 mediaRecorder.onstop = () => {
44 const blob = new Blob(chunksRef.current, { type: 'video/webm' });
45 onComplete(blob, {
46 duration,
47 sound: selectedSound,
48 effect: selectedEffect
49 });
50 };
51
52 chunksRef.current = [];
53 mediaRecorder.start();
54 mediaRecorderRef.current = mediaRecorder;
55 setIsRecording(true);
56
57 // Duration counter
58 const interval = setInterval(() => {
59 setDuration(d => {
60 if (d >= maxDuration) {
61 stopRecording();
62 clearInterval(interval);
63 return d;
64 }
65 return d + 0.1;
66 });
67 }, 100);
68 };
69
70 const stopRecording = () => {
71 if (mediaRecorderRef.current && isRecording) {
72 mediaRecorderRef.current.stop();
73 setIsRecording(false);
74 setDuration(0);
75 }
76 };
77
78 return (
79 <div className="relative h-screen bg-black flex flex-col">
80 {/* Video Preview */}
81 <div className="flex-1 relative">
82 <video
83 ref={videoRef}
84 autoPlay
85 playsInline
86 muted
87 className="w-full h-full object-cover"
88 />
89
90 {/* Effect Overlay */}
91 {selectedEffect && (
92 <div className="absolute top-4 left-4 bg-black bg-opacity-50 px-3 py-2 rounded-full text-white text-sm">
93 ✨ {selectedEffect.name}
94 </div>
95 )}
96
97 {/* Sound Indicator */}
98 {selectedSound && (
99 <div className="absolute top-4 right-4 bg-black bg-opacity-50 px-3 py-2 rounded-full text-white text-sm flex items-center gap-2">
100 🎵 {selectedSound.name}
101 </div>
102 )}
103
104 {/* Recording Indicator */}
105 {isRecording && (
106 <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
107 <div className="w-20 h-20 border-4 border-red-500 rounded-full animate-pulse"></div>
108 </div>
109 )}
110 </div>
111
112 {/* Controls */}
113 <div className="bg-black border-t border-gray-800 p-6">
114 {/* Duration Bar */}
115 <div className="mb-6">
116 <div className="h-1 bg-gray-700 rounded-full overflow-hidden">
117 <div
118 className={`h-full transition-all ${
119 isRecording ? 'bg-red-500' : 'bg-gray-500'
120 }`}
121 style={{ width: `${(duration / maxDuration) * 100}%` }}
122 />
123 </div>
124 <div className="flex justify-between mt-2 text-xs text-gray-400">
125 <span>{duration.toFixed(1)}s</span>
126 <span>{maxDuration}s</span>
127 </div>
128 </div>
129
130 {/* Actions */}
131 <div className="flex items-center justify-around">
132 {/* Effects */}
133 <button className="flex flex-col items-center gap-1">
134 <div className="w-12 h-12 bg-gray-800 rounded-full flex items-center justify-center text-2xl">
135 ✨
136 </div>
137 <span className="text-xs text-white">Effects</span>
138 </button>
139
140 {/* Record Button */}
141 <button
142 onClick={isRecording ? stopRecording : startRecording}
143 className={`w-20 h-20 rounded-full border-4 flex items-center justify-center transition ${
144 isRecording
145 ? 'border-red-500 bg-red-500'
146 : 'border-white bg-transparent'
147 }`}
148 >
149 {isRecording && (
150 <div className="w-8 h-8 bg-white rounded"></div>
151 )}
152 </button>
153
154 {/* Sounds */}
155 <button className="flex flex-col items-center gap-1">
156 <div className="w-12 h-12 bg-gray-800 rounded-full flex items-center justify-center text-2xl">
157 🎵
158 </div>
159 <span className="text-xs text-white">Sounds</span>
160 </button>
161 </div>
162 </div>
163 </div>
164 );
165}✅ Sounds library - discover, search, trending sounds ✅ Sound card - preview, play, use sound ✅ Use count - ile wideo używa sound ✅ Video recorder - camera access, recording, duration ✅ Effects - AR effects, filters, beauty mode ✅ Recording controls - start/stop, duration bar ✅ Sound selection - pick audio track for video ✅ Duration limits - 15s, 60s, 180s options
W następnym ćwiczeniu dodamy duet, stitch i advanced editing!
Do zobaczenia! 🚀