Initial Push
This commit is contained in:
3
API/DB.js
Normal file
3
API/DB.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
DB: 'mongodb://localhost:27017/videoStreamer' // Connection to DB
|
||||
}
|
||||
91
API/index.js
Normal file
91
API/index.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
NodeJS Video Streamer - index.js
|
||||
By: Johnathon Slightham
|
||||
*/
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const fs = require("fs");
|
||||
const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
const mongoose = require('mongoose');
|
||||
const config = require('./DB.js');
|
||||
const postRoute = require('./post.route');
|
||||
const fileUpload = require('express-fileupload');
|
||||
|
||||
printWelcome();
|
||||
// Connect to database
|
||||
mongoose.Promise = global.Promise;
|
||||
mongoose.connect(config.DB, { useNewUrlParser: true, useUnifiedTopology: true}).then(
|
||||
() => {console.log('Connected to database') },
|
||||
err => { console.log('Can not connect to the database: '+ err)}
|
||||
);
|
||||
|
||||
// Express
|
||||
app.use(cors());
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(bodyParser.json());
|
||||
app.use(fileUpload());
|
||||
app.use('/posts', postRoute);
|
||||
app.listen(8000, function () {
|
||||
console.log("Express listening on port 8000");
|
||||
});
|
||||
|
||||
/*
|
||||
Get thumbnail for video with :id
|
||||
*/
|
||||
app.get("/thumbnails/:id", (req, res) => {
|
||||
let id = req.params.id;
|
||||
if(id)
|
||||
res.sendFile(__dirname + "/thumbnails/" + id + ".jpg");
|
||||
else
|
||||
res.send(500);
|
||||
});
|
||||
|
||||
/*
|
||||
Stream video with :id
|
||||
*/
|
||||
app.get("/video2/:id", (req, res) => {
|
||||
|
||||
let id = req.params.id; // ID of video to be streamed
|
||||
|
||||
// Check if the header includes range
|
||||
const range = req.headers.range;
|
||||
if (!range) {
|
||||
res.status(400).send("Missing range header");
|
||||
}
|
||||
|
||||
const videoPath = "videos/" + id + ".mp4"; // path of the video
|
||||
const videoSize = fs.statSync("videos/" + id + ".mp4").size; // size of the video
|
||||
|
||||
// Parse Range
|
||||
const CHUNK_SIZE = 5 ** 6; // Half megabyte
|
||||
let start = Number(range.replace(/\D/g, ""));
|
||||
let end = Math.min(start + CHUNK_SIZE, videoSize - 1);
|
||||
|
||||
// Create headers
|
||||
const contentLength = end - start + 1;
|
||||
const headers = {
|
||||
"Content-Range": `bytes ${start}-${end}/${videoSize}`,
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Length": contentLength,
|
||||
"Content-Type": "video/mp4",
|
||||
};
|
||||
|
||||
// HTTP Status 206 for Partial Content
|
||||
res.writeHead(206, headers);
|
||||
|
||||
// create video read stream for this particular chunk
|
||||
const videoStream = fs.createReadStream(videoPath, { start, end });
|
||||
|
||||
// Stream the video chunk to the client
|
||||
videoStream.pipe(res);
|
||||
});
|
||||
|
||||
|
||||
function printWelcome(){
|
||||
console.log("-----------------------------------");
|
||||
console.log("NodeJS-Video-Streamer by jslightham");
|
||||
console.log("Version 1.0");
|
||||
console.log("-----------------------------------");
|
||||
console.log();
|
||||
}
|
||||
1927
API/package-lock.json
generated
Normal file
1927
API/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
API/package.json
Normal file
21
API/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "open-video-stream",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "nodemon index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-fileupload": "^1.2.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"handbrake-js": "^5.0.2",
|
||||
"mongoose": "^5.11.8",
|
||||
"nodemon": "^2.0.6"
|
||||
}
|
||||
}
|
||||
35
API/post.model.js
Normal file
35
API/post.model.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
NodeJS-Video-Streamer - post.model.js
|
||||
|
||||
*/
|
||||
// Schema for a video post
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
let Post = new Schema({
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
comments: {
|
||||
type: Array
|
||||
},
|
||||
transcoding: {
|
||||
type: Boolean
|
||||
},
|
||||
progress: {
|
||||
type: Number
|
||||
},
|
||||
eta:{
|
||||
type: String
|
||||
},
|
||||
likes:{
|
||||
type: Number
|
||||
},
|
||||
},{
|
||||
collection: 'posts'
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Post', Post);
|
||||
182
API/post.route.js
Normal file
182
API/post.route.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const express = require('express');
|
||||
const postRoutes = express.Router();
|
||||
const hbjs = require('handbrake-js')
|
||||
const fs = require("fs");
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
|
||||
// Require Post model in our routes module
|
||||
let Post = require('./post.model');
|
||||
|
||||
/*
|
||||
Route for uploading a video
|
||||
First save the video to API/toTranscode/id.mp4, then use ffmpeg to get a screenshot
|
||||
and save the screenshot to API/thumbnails/id.jpg. Then, use handbrake to transcode
|
||||
the video to a compressed mp4 for easier streaming.
|
||||
*/
|
||||
postRoutes.route('/upload').post((req, res) => {
|
||||
// Create the post object and initiate the values
|
||||
let post = new Post();
|
||||
console.log(req.body);
|
||||
post.title = req.body.title;
|
||||
post.description = req.body.description;
|
||||
post.transcoding = true;
|
||||
post.progress = 0;
|
||||
post.likes = 0;
|
||||
post.comments = [];
|
||||
|
||||
// Check if a file was included in the uplaod
|
||||
if (!req.files) {
|
||||
return res.status(500).send({ msg: "file is not found" })
|
||||
}
|
||||
|
||||
const myFile = req.files.file;
|
||||
|
||||
// Place the file into toTranscode directory
|
||||
myFile.mv(`${__dirname}/toTranscode/${post._id}.mp4`, err => {
|
||||
|
||||
// If there was an error, print it to the console
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return res.status(500).send({ msg: "Error occured" });
|
||||
}
|
||||
|
||||
// Save the post to the databse
|
||||
post.save();
|
||||
|
||||
// Take a 480p screenshot of the video 50% through, and save it to the
|
||||
// thumbnails folder.
|
||||
ffmpeg(`${__dirname}/toTranscode/${post._id}.mp4`)
|
||||
.screenshots({
|
||||
timestamps: ['50%'],
|
||||
filename: `${post._id}.jpg`,
|
||||
folder: `${__dirname}/thumbnails`,
|
||||
size: '704x480'
|
||||
});
|
||||
|
||||
// Transcode the video in the toTranscode directory to an mp4 using Very Fast 1080p30 preset, and save to videos directory
|
||||
hbjs.spawn({ input: `${__dirname}/toTranscode/${post._id}.mp4`, output: `${__dirname}/videos/${post._id}.mp4`, preset: "Very Fast 1080p30" })
|
||||
.on('error', err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
// Save the progress and eta to the database
|
||||
.on('progress', prog => {
|
||||
post.progress = prog.percentComplete;
|
||||
|
||||
// if the eta is empty, leave it the same
|
||||
if (prog.eta) {
|
||||
post.eta = prog.eta;
|
||||
}
|
||||
post.save()
|
||||
})
|
||||
|
||||
// When done transcoding, delete the old file, and change status of transcoding to false
|
||||
.on('end', () => {
|
||||
post.transcoding = false;
|
||||
post.save
|
||||
|
||||
// Delete file in toTranscode directory
|
||||
fs.unlink(`${__dirname}/toTranscode/${post._id}.mp4`, (err) => {
|
||||
// Log deletion error to console
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return;
|
||||
}
|
||||
})
|
||||
})
|
||||
return res.send({ name: myFile.name, path: `/${post._id}` });
|
||||
});
|
||||
})
|
||||
|
||||
/*
|
||||
Route to add a like to video with id of :id
|
||||
*/
|
||||
postRoutes.route('/like/:id').get(function (req, res) {
|
||||
let id = req.params.id;
|
||||
|
||||
// Find the post that has the id
|
||||
Post.findById(id, function (err, post) {
|
||||
if (err) {
|
||||
res.json(err);
|
||||
}
|
||||
|
||||
// Add a like and save to database
|
||||
post.likes++;
|
||||
post.save();
|
||||
res.json(post);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
Route to add a comment to the video with an id of :id
|
||||
*/
|
||||
postRoutes.route('/postComment/:id').post(function (req, res) {
|
||||
let id = req.params.id;
|
||||
|
||||
// Find the post that has the id
|
||||
Post.findById(id, function (err, post) {
|
||||
if (err) {
|
||||
res.json(err);
|
||||
}
|
||||
|
||||
// Push comment on to the comments array, and save it
|
||||
post.comments.push(req.body.comment);
|
||||
post.save();
|
||||
res.json(post);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
Get the entry for the video with id of :id
|
||||
*/
|
||||
postRoutes.route('/vinfo/:id').get(function (req, res) {
|
||||
let id = req.params.id;
|
||||
|
||||
// Find the video with id
|
||||
Post.findById(id, function (err, post) {
|
||||
if (err) {
|
||||
res.json(err);
|
||||
}
|
||||
res.json(post);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
Search for all videos that contain query in their title
|
||||
*/
|
||||
postRoutes.route('/search').post(function (req, res) {
|
||||
let query = req.body.query;
|
||||
|
||||
// Get all posts
|
||||
Post.find(function (err, posts) {
|
||||
if (err) {
|
||||
res.json(err);
|
||||
}
|
||||
else {
|
||||
// Filter for posts that have a title containing query
|
||||
posts = posts.filter(post => {
|
||||
return post.title.toLowerCase().includes(query.toLowerCase());
|
||||
});
|
||||
res.json(posts);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
Get info for all videos
|
||||
*/
|
||||
postRoutes.route('/').get(function (req, res) {
|
||||
Post.find(function (err, posts) {
|
||||
if (err) {
|
||||
res.json(err);
|
||||
}
|
||||
else {
|
||||
res.json(posts);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = postRoutes;
|
||||
Reference in New Issue
Block a user