To update your code so that the video files are stored in Firebase Storage instead of the local filesystem, you'll need to:
- Remove the local filesystem handling logic.
- Replace it with Firebase Storage operations.
- Ensure that you assemble and upload the final video directly to Firebase Storage.
Here's the updated code:
Updated Code:
javascriptconst multer = require('multer');
const path = require('path');
const fs = require('fs-extra');
const { bucket } = require('./firebaseConfig'); // Import the Firebase Storage bucket
// Multer storage for handling chunk uploads in memory (since we won't store them locally)
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
router.post('/upload-video', Authorization.authorized, upload.single('video'), Product.uploadVideo);
class Product {
async uploadVideo(req, res) {
try {
const { title, description, course, is_free, instructorId, chunkNumber, totalChunks } = req.body;
if (!title || !description || !course || !instructorId || !totalChunks) {
return res.status(400).json({ errors: [{ msg: 'Missing required fields' }] });
}
const instructor = await Instructor.findById(instructorId);
if (!instructor) {
return res.status(400).json({ errors: [{ msg: 'Instructor not found' }] });
}
const selectedCourse = await ProductModel.findById(course);
if (!selectedCourse || selectedCourse.instructor_id.toString() !== instructorId) {
return res.status(400).json({ errors: [{ msg: 'Invalid course selection' }] });
}
if (!req.file) {
return res.status(400).json({ errors: [{ msg: 'No video file uploaded' }] });
}
const chunkFileName = `${instructorId}-${course}-${chunkNumber}-${Date.now()}`;
// Upload chunk to Firebase Storage
const chunkFile = bucket.file(`chunks/${chunkFileName}`);
await chunkFile.save(req.file.buffer, {
contentType: req.file.mimetype,
});
// Check if all chunks have been uploaded
const [files] = await bucket.getFiles({ prefix: `chunks/${instructorId}-${course}-` });
const sortedChunks = files.sort((a, b) => parseInt(a.name.split('-').pop()) - parseInt(b.name.split('-').pop()));
if (sortedChunks.length === parseInt(totalChunks)) {
// Assemble the video from the chunks
const finalFileName = `${instructorId}-${course}-${Date.now()}.mp4`;
const finalFile = bucket.file(`videos/${finalFileName}`);
const writeStream = finalFile.createWriteStream({ contentType: 'video/mp4' });
for (const file of sortedChunks) {
const [chunkBuffer] = await file.download();
writeStream.write(chunkBuffer);
}
writeStream.end();
writeStream.on('finish', async () => {
// Clean up chunk files from Firebase Storage
for (const file of sortedChunks) {
await file.delete();
}
// Save video info to the database
const video = new Video({
title,
description,
course: selectedCourse._id,
instructor: instructor._id,
is_free: is_free === 'true' || is_free === true,
video_url: await finalFile.getSignedUrl({
action: 'read',
expires: '03-09-2491', // Set expiration as needed
}),
});
const savedVideo = await video.save();
// Update ProductModel with new video information
selectedCourse.video_list.push(savedVideo._id);
selectedCourse.video_count += 1;
if (is_free === 'true') {
selectedCourse.free_video_count += 1;
}
await selectedCourse.save();
return res.status(201).json({ msg: 'Video uploaded successfully', video: savedVideo });
});
writeStream.on('error', (err) => {
console.error('Error writing final video:', err);
res.status(500).json({ errors: [{ msg: 'Error assembling video' }] });
});
} else {
return res.status(200).json({ msg: 'Chunk uploaded successfully, waiting for more chunks to complete the video' });
}
} catch (error) {
console.error('Video upload error:', error);
return res.status(500).json({ errors: [{ msg: 'Internal server error' }] });
}
}
}
module.exports = new Product();
Explanation:
Multer Memory Storage:
- Instead of saving the chunks locally, we're using
multer.memoryStorage()
to handle file uploads in memory.
- Instead of saving the chunks locally, we're using
Firebase Storage Upload:
- Each chunk is uploaded to Firebase Storage as soon as it's received.
- The chunk files are stored under a
chunks/
directory in Firebase Storage.
Assemble Video in Firebase:
- When all chunks are uploaded, we assemble the video by downloading each chunk from Firebase Storage and writing it to a new file in the
videos/
directory within Firebase Storage.
- When all chunks are uploaded, we assemble the video by downloading each chunk from Firebase Storage and writing it to a new file in the
Cleanup:
- Once the video is successfully assembled, the chunk files are deleted from Firebase Storage.
Video URL:
- The
video_url
stored in the database is a signed URL from Firebase Storage, allowing users to stream the video.
- The
Testing & Deployment:
- Deploy this backend to Vercel or any other platform supporting serverless functions.
- Make sure your Firebase credentials are correctly set up in the environment variables on Vercel.
- Test the entire flow, from uploading video chunks to assembling and streaming the final video from Firebase Storage.
This setup ensures that your videos are securely stored in Firebase, and it scales well with large files and high traffic.