Add multi-platform RTMP streaming support#60
Add multi-platform RTMP streaming support#60PankajSingh34 wants to merge 2 commits intoanothercoder-nik:masterfrom
Conversation
Introduces RTMP streaming service and frontend modals to support live streaming to YouTube, Twitch, and Facebook. Refactors backend controller to use a generic RTMP service, adds new API utilities, and updates the studio UI to allow platform selection and stream configuration.
Introduce generic RTMP streaming so users can broadcast to YouTube, Twitch, and Facebook. Adds new documentation and environment examples, refactors the YouTube-only flow into backend/services/rtmpStreaming.service.js (start/stop, status, health, chunk processing), updates backend/controllers/youtubeController.js to accept/validate a platform parameter, and integrates frontend support (RTMPLiveModal, rtmp.api, StudioRoomComplete) with start/stop API calls and user toasts. Also updates frontend .env.example with Twitch/Facebook RTMP URLs and includes testing/deployment guidance in new docs (CONTRIBUTION_SUMMARY.md, NEXT_STEPS.md, RTMP_STREAMING_FEATURE.md). Ensure env variables for VITE_* and backend RTMP URLs are set and FFmpeg is available on the server.
There was a problem hiding this comment.
Pull request overview
This pull request adds multi-platform RTMP streaming support to FinalCast, enabling users to stream their studio sessions to YouTube, Twitch, and Facebook Live. The implementation refactors the existing YouTube-only streaming service into a generic RTMP service and updates the frontend with a new multi-platform selection modal.
Changes:
- Refactored backend streaming service from YouTube-specific to generic multi-platform RTMP service
- Created new frontend modal components with platform selection UI for YouTube, Twitch, and Facebook
- Updated API client and studio integration to support configurable platform streaming
- Added environment variables and comprehensive documentation for all supported platforms
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 23 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/services/rtmpStreaming.service.js | New generic RTMP streaming service supporting multiple platforms with FFmpeg-based encoding |
| backend/controllers/youtubeController.js | Updated to accept platform parameter and route to generic RTMP service |
| backend/.env.example | Added Twitch and Facebook RTMP URL configuration |
| frontend/src/components/studio/RTMPModal.jsx | State management wrapper for multi-platform streaming modal |
| frontend/src/components/studio/RTMPLiveModal.jsx | UI component with platform selection tabs and stream configuration |
| frontend/src/components/Main/StudioRoomComplete.jsx | Integration of RTMP modal with start/stop streaming functionality |
| frontend/src/api/rtmp.api.js | API client methods for starting, stopping, and monitoring RTMP streams |
| frontend/.env.example | Added environment variables for Twitch and Facebook RTMP URLs |
| RTMP_STREAMING_FEATURE.md | Comprehensive feature documentation |
| NEXT_STEPS.md | Testing and deployment guide |
| CONTRIBUTION_SUMMARY.md | Contribution overview and summary |
Comments suppressed due to low confidence (1)
backend/controllers/youtubeController.js:48
- Inconsistent parameter handling: The controller accepts both
platformandrtmpUrlparameters, but the newRTMPStreamingService.startStream()method only acceptsplatformand derives the RTMP URL from it. Whenplatformis not provided, the code defaults to 'youtube' (line 42), but thertmpUrlparameter passed from the frontend is completely ignored. This creates a discrepancy where users might provide a customrtmpUrlexpecting it to be used, but it will be silently ignored in favor of the platform-specific URL. Either removertmpUrlfrom the accepted parameters and update the frontend, or modify the service to use custom RTMP URLs when provided.
const {
sessionId,
rtmpUrl,
platform,
streamKey,
title,
videoConfig,
hasVideoCapture,
inputMode
} = req.body;
if (!sessionId || !streamKey) {
return res.status(400).json({
success: false,
message: "Missing required fields"
});
}
// If platform is provided, use it; otherwise use rtmpUrl directly
if (platform && !['youtube', 'twitch', 'facebook'].includes(platform)) {
return res.status(400).json({
success: false,
message: "Invalid platform. Must be youtube, twitch, or facebook"
});
}
if (rtmpUrl && !rtmpUrl.startsWith("rtmp://")) {
return res.status(400).json({
success: false,
message: "Invalid RTMP URL"
});
}
const result = await RTMPStreamingService.startStream({
sessionId,
platform: platform || 'youtube',
streamKey,
title,
videoConfig,
hasVideoCapture: hasVideoCapture === true,
inputMode: inputMode || "webm"
});
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| if (!session?._id) { | ||
| toast.error('Session not found'); | ||
| return; | ||
| } | ||
|
|
||
| const streamData = { | ||
| sessionId: session._id, | ||
| platform: config.platform || 'youtube', | ||
| streamKey: config.streamKey, | ||
| rtmpUrl: config.rtmpUrl, | ||
| title: config.title || session.title, | ||
| hasVideoCapture: true, | ||
| inputMode: 'webm' | ||
| }; | ||
|
|
||
| await startRTMPStream(streamData); | ||
| setShowRTMPModal(false); | ||
| setIsGridStreaming(true); | ||
| toast.success(`Started streaming to ${config.platform || 'YouTube'}!`); | ||
| } catch (error) { | ||
| console.error('Failed to start stream:', error); | ||
| toast.error(error.message || 'Failed to start stream'); | ||
| } |
There was a problem hiding this comment.
Triple error handling and unnecessary parameter: The error handling is nested three levels deep: StudioRoomComplete (lines 449-472), RTMPModal (lines 41-50), and RTMPLiveModal (lines 58-68). Each level has its own try-catch, causing errors to be caught and re-thrown multiple times. Additionally, rtmpUrl is being sent in streamData (line 459) but is ignored by the backend service which only uses the platform parameter to determine the RTMP URL. Consider simplifying the error handling to a single level and removing the unused rtmpUrl parameter from the request.
| try { | |
| if (!session?._id) { | |
| toast.error('Session not found'); | |
| return; | |
| } | |
| const streamData = { | |
| sessionId: session._id, | |
| platform: config.platform || 'youtube', | |
| streamKey: config.streamKey, | |
| rtmpUrl: config.rtmpUrl, | |
| title: config.title || session.title, | |
| hasVideoCapture: true, | |
| inputMode: 'webm' | |
| }; | |
| await startRTMPStream(streamData); | |
| setShowRTMPModal(false); | |
| setIsGridStreaming(true); | |
| toast.success(`Started streaming to ${config.platform || 'YouTube'}!`); | |
| } catch (error) { | |
| console.error('Failed to start stream:', error); | |
| toast.error(error.message || 'Failed to start stream'); | |
| } | |
| if (!session?._id) { | |
| toast.error('Session not found'); | |
| return; | |
| } | |
| const streamData = { | |
| sessionId: session._id, | |
| platform: config.platform || 'youtube', | |
| streamKey: config.streamKey, | |
| title: config.title || session.title, | |
| hasVideoCapture: true, | |
| inputMode: 'webm' | |
| }; | |
| await startRTMPStream(streamData); | |
| setShowRTMPModal(false); | |
| setIsGridStreaming(true); | |
| toast.success(`Started streaming to ${config.platform || 'YouTube'}!`); |
| title: '' | ||
| }, | ||
| twitch: { | ||
| rtmpUrl: process.env.TWITCH_RTMP_URL || 'rtmp://live.twitch.tv/app/', |
There was a problem hiding this comment.
Environment variable name inconsistency: This line uses process.env.TWITCH_RTMP_URL but the .env.example file specifies it as VITE_TWITCH_RTMP_URL. In Vite, all environment variables exposed to client-side code must be prefixed with VITE_. This should be process.env.VITE_TWITCH_RTMP_URL to match the environment variable naming convention.
| rtmpUrl: process.env.TWITCH_RTMP_URL || 'rtmp://live.twitch.tv/app/', | |
| rtmpUrl: process.env.VITE_TWITCH_RTMP_URL || 'rtmp://live.twitch.tv/app/', |
| // Process audio chunk (implementation depends on your needs) | ||
| return { success: true }; |
There was a problem hiding this comment.
Incomplete implementation: The processAudioChunk() method has a placeholder comment "Process audio chunk (implementation depends on your needs)" but doesn't actually process the audio chunk. It just returns success without doing anything with the chunkData. This means audio data won't be included in the stream. The FFmpeg configuration already sets up audio with anullsrc (silent audio source), but if the intention is to include actual audio from participants, this method needs to be properly implemented to mix and write audio data to the FFmpeg process.
| // Process audio chunk (implementation depends on your needs) | |
| return { success: true }; | |
| if (chunkData == null) { | |
| throw new Error('No audio chunk data provided'); | |
| } | |
| // Normalize chunkData to a Buffer. If it's not already a Buffer, | |
| // assume it is a base64-encoded string representation of the audio data. | |
| const audioBuffer = Buffer.isBuffer(chunkData) | |
| ? chunkData | |
| : Buffer.from(chunkData, 'base64'); | |
| // Write the audio data into the FFmpeg process stdin so it can be | |
| // included in the RTMP stream. | |
| const canWrite = stream.stdin.write(audioBuffer); | |
| // Optionally, callers could use backpressure info from `canWrite`. | |
| return { success: true, bytesWritten: audioBuffer.length, backpressure: !canWrite }; |
| onStartStream | ||
| }) => { | ||
| const [platform, setPlatform] = useState('youtube'); |
There was a problem hiding this comment.
State synchronization issue: RTMPLiveModal maintains a local platform state that always initializes to 'youtube' (line 42), but the parent RTMPModal component can accept a defaultPlatform prop and initializes streamConfig based on it. This creates a potential mismatch where the parent's config is for one platform but the child's UI shows a different platform as selected. Consider passing the defaultPlatform as a prop to RTMPLiveModal and initializing the local platform state with it, or passing the platform state management to the parent component entirely.
| onStartStream | |
| }) => { | |
| const [platform, setPlatform] = useState('youtube'); | |
| onStartStream, | |
| defaultPlatform, | |
| }) => { | |
| const [platform, setPlatform] = useState(defaultPlatform || 'youtube'); |
| title: '' | ||
| }, | ||
| facebook: { | ||
| rtmpUrl: process.env.FACEBOOK_RTMP_URL || 'rtmp://live-api-s.facebook.com:80/rtmp/', |
There was a problem hiding this comment.
Environment variable name inconsistency: This line uses process.env.FACEBOOK_RTMP_URL but the .env.example file specifies it as VITE_FACEBOOK_RTMP_URL. In Vite, all environment variables exposed to client-side code must be prefixed with VITE_. This should be process.env.VITE_FACEBOOK_RTMP_URL to match the environment variable naming convention.
| rtmpUrl: process.env.FACEBOOK_RTMP_URL || 'rtmp://live-api-s.facebook.com:80/rtmp/', | |
| rtmpUrl: process.env.VITE_FACEBOOK_RTMP_URL || 'rtmp://live-api-s.facebook.com:80/rtmp/', |
|
|
||
| const { | ||
| width = 1280, | ||
| height = 720, |
There was a problem hiding this comment.
Unused variable height.
| height = 720, |
| # Default YouTube RTMP URL for live streaming | ||
| VITE_YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2/ | ||
|
|
||
| # Default Twitch RTMP URL for live streaming | ||
| VITE_TWITCH_RTMP_URL=rtmp://live.twitch.tv/app/ | ||
|
|
||
| # Default Facebook RTMP URL for live streaming | ||
| VITE_FACEBOOK_RTMP_URL=rtmp://live-api-s.facebook.com:80/rtmp/ |
There was a problem hiding this comment.
The frontend sample configuration here also hard-codes unencrypted rtmp:// URLs for YouTube, Twitch, and Facebook, mirroring the backend defaults. If these values are copied directly into production, the app will send users� stream keys and live video over plaintext RTMP, allowing any on-path attacker to capture the keys and take over or spoof live streams. Change these defaults to the platforms� rtmps:// endpoints so new deployments use encrypted transport for live streaming.
| 3. **Environment Variables** (`backend/.env.example`) | ||
| ```bash | ||
| YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2/ | ||
| TWITCH_RTMP_URL=rtmp://live.twitch.tv/app/ | ||
| FACEBOOK_RTMP_URL=rtmp://live-api-s.facebook.com:80/rtmp/ | ||
| ``` | ||
|
|
||
| ### Frontend Changes | ||
|
|
||
| 1. **New RTMP Live Modal** (`frontend/src/components/studio/RTMPLiveModal.jsx`) | ||
| - Platform selection UI (YouTube, Twitch, Facebook) | ||
| - Dynamic RTMP URL and help text based on selected platform | ||
| - Stream key input with platform-specific guidance | ||
| - Visual platform icons and branding | ||
|
|
||
| 2. **RTMP Modal Wrapper** (`frontend/src/components/studio/RTMPModal.jsx`) | ||
| - State management for multi-platform streaming | ||
| - Platform switching logic | ||
| - Form validation and error handling | ||
|
|
||
| 3. **Updated Studio Room** (`frontend/src/components/Main/StudioRoomComplete.jsx`) | ||
| - Integrated new RTMP modal | ||
| - API calls to start/stop streaming | ||
| - Toast notifications for stream status | ||
| - Platform-aware streaming controls | ||
|
|
||
| 4. **RTMP API Client** (`frontend/src/api/rtmp.api.js`) | ||
| - `startRTMPStream()` - Start streaming to any platform | ||
| - `stopRTMPStream()` - Stop active stream | ||
| - `getRTMPStreamStatus()` - Check stream status | ||
| - `getActiveRTMPStreams()` - List all active streams | ||
|
|
||
| 5. **Environment Variables** (`frontend/.env.example`) | ||
| ```bash | ||
| VITE_YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2/ | ||
| VITE_TWITCH_RTMP_URL=rtmp://live.twitch.tv/app/ | ||
| VITE_FACEBOOK_RTMP_URL=rtmp://live-api-s.facebook.com:80/rtmp/ | ||
| ``` |
There was a problem hiding this comment.
This documentation section recommends backend environment variables using plain rtmp:// URLs for YouTube, Twitch, and Facebook, and mirrors the same unencrypted URLs for the frontend. Following these examples in production will cause stream keys and live video to be sent over plaintext RTMP, which lets any on-path attacker intercept the streamKey and hijack or spoof broadcasts. Update the documented examples to use each platform's rtmps:// endpoints so readers are guided toward encrypted RTMPS streaming.
| #### Backend (.env) | ||
| ```bash | ||
| # Required | ||
| YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2/ | ||
| TWITCH_RTMP_URL=rtmp://live.twitch.tv/app/ | ||
| FACEBOOK_RTMP_URL=rtmp://live-api-s.facebook.com:80/rtmp/ | ||
| ``` | ||
|
|
||
| #### Frontend (.env) | ||
| ```bash | ||
| # Required | ||
| VITE_YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2/ | ||
| VITE_TWITCH_RTMP_URL=rtmp://live.twitch.tv/app/ | ||
| VITE_FACEBOOK_RTMP_URL=rtmp://live-api-s.facebook.com:80/rtmp/ | ||
| ``` |
There was a problem hiding this comment.
The deployment checklist here marks RTMP environment variables with rtmp:// URLs as "Required", which promotes deploying the app with unencrypted RTMP connections to YouTube, Twitch, and Facebook. Using these values in production exposes stream keys and content over plaintext so a network attacker can sniff the streamKey and push arbitrary streams to the victim�s channels. Recommend documenting rtmps:// URLs instead (and noting RTMPS as the default) to ensure deployments use encrypted transport for live streaming.
|
hy @anothercoder-nik , |
Introduces RTMP streaming service and frontend modals to support live streaming to YouTube, Twitch, and Facebook. Refactors backend controller to use a generic RTMP service, adds new API utilities, and updates the studio UI to allow platform selection and stream configuration.