first commit
This commit is contained in:
134
server.js
Normal file
134
server.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const dotenv = require('dotenv');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const { S3Client, ListObjectsV2Command, GetObjectCommand } = require('@aws-sdk/client-s3');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Configure AWS S3 Client
|
||||
const s3Client = new S3Client({
|
||||
region: process.env.AWS_REGION || 'us-east-1',
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
||||
}
|
||||
});
|
||||
|
||||
const BUCKET_NAME = process.env.S3_BUCKET_NAME;
|
||||
|
||||
// Endpoint to list videos in the bucket
|
||||
app.get('/api/videos', async (req, res) => {
|
||||
try {
|
||||
if (!BUCKET_NAME) {
|
||||
return res.status(500).json({ error: 'S3_BUCKET_NAME not configured' });
|
||||
}
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: BUCKET_NAME,
|
||||
});
|
||||
|
||||
const response = await s3Client.send(command);
|
||||
|
||||
// Filter out non-mp4 files for this boilerplate
|
||||
const videos = (response.Contents || [])
|
||||
.map(item => item.Key)
|
||||
.filter(key => key && key.toLowerCase().endsWith('.mp4'));
|
||||
|
||||
res.json({ videos });
|
||||
} catch (error) {
|
||||
console.error('Error fetching videos:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch videos from S3', detail: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint to transcode S3 video streaming to HLS
|
||||
app.post('/api/transcode', async (req, res) => {
|
||||
const { key } = req.body;
|
||||
|
||||
if (!key) {
|
||||
return res.status(400).json({ error: 'Video key is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const safeKeyName = key.replace(/[^a-zA-Z0-9_\-]/g, '');
|
||||
const outputDir = path.join(__dirname, 'public', 'hls', safeKeyName);
|
||||
const m3u8Path = path.join(outputDir, 'index.m3u8');
|
||||
const hlsUrl = `/hls/${safeKeyName}/index.m3u8`;
|
||||
|
||||
// If it already exists, just return the URL
|
||||
if (fs.existsSync(m3u8Path)) {
|
||||
return res.json({ message: 'Already transcoded', hlsUrl });
|
||||
}
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
// Get S3 stream
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: BUCKET_NAME,
|
||||
Key: key
|
||||
});
|
||||
|
||||
const response = await s3Client.send(command);
|
||||
const s3Stream = response.Body;
|
||||
|
||||
// Triggers fluent-ffmpeg to transcode to HLS
|
||||
console.log(`Starting transcoding for ${key}`);
|
||||
|
||||
ffmpeg(s3Stream)
|
||||
// Use hardware acceleration if available (optional for boilerplate)
|
||||
.outputOptions([
|
||||
'-codec:v copy', // Use copy if it's already h264, else change to libx264
|
||||
'-codec:a aac',
|
||||
'-hls_time 10', // 10 second segments
|
||||
'-hls_list_size 0', // keep all segments in playlist
|
||||
'-f hls'
|
||||
])
|
||||
.output(m3u8Path)
|
||||
.on('end', () => {
|
||||
console.log(`Finished transcoding ${key} to HLS`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
console.error(`Error transcoding ${key}:`, err);
|
||||
})
|
||||
.run();
|
||||
|
||||
// Return immediately so the client can start polling or waiting
|
||||
res.json({ message: 'Transcoding started', hlsUrl });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in transcode:', error);
|
||||
res.status(500).json({ error: 'Failed to initiate transcoding', detail: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Status check for HLS playlist availability
|
||||
app.get('/api/status', (req, res) => {
|
||||
const { key } = req.query;
|
||||
if (!key) return res.status(400).json({ error: 'Key is required' });
|
||||
|
||||
const safeKeyName = key.replace(/[^a-zA-Z0-9_\-]/g, '');
|
||||
const m3u8Path = path.join(__dirname, 'public', 'hls', safeKeyName, 'index.m3u8');
|
||||
|
||||
// Check if the playlist file exists
|
||||
if (fs.existsSync(m3u8Path)) {
|
||||
res.json({ ready: true, hlsUrl: `/hls/${safeKeyName}/index.m3u8` });
|
||||
} else {
|
||||
res.json({ ready: false });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user