1
0
mirror of https://git.waldn.net/git/knotteye/satyr.git synced 2025-05-07 02:59:25 +00:00

Switch from transcode server in node-media-server to spawning ffmpeg processes

Change config to reflect that
ffmpeg processes cleanup after themselves even on SIGINT now, cleanup.ts only cleans the database now
Adaptive livestreaming!
This commit is contained in:
knotteye 2019-11-16 11:34:16 -06:00
parent f8b197502a
commit 7983b60f8d
5 changed files with 29 additions and 36 deletions

@ -50,10 +50,4 @@ port = 8000
record = false
publicEndpoint = 'live'
privateEndpoint = 'stream'
ffmpeg = ''
[transcode]
hls = false
hlsFlags = '[hls_time=2:hls_list_size=3:hls_flags=delete_segments]'
dash = true
dashFlags = '[f=dash:window_size=3:extra_window_size=5]'
ffmpeg = ''

@ -1,23 +1,10 @@
import * as db from "./database";
import * as read from "recursive-readdir";
import * as fs from "fs";
async function init(siteDir: string) {
async function init() {
//If satyr is restarted in the middle of a stream
//it causes problems
//Live flags in the database stay live
await db.query('update user_meta set live=false');
//and stray m3u8 files will play the last
//few seconds of a stream back
try {
var files = await read(siteDir+'/live', ['!*.m3u8']);
}
catch (error) {}
if(files === undefined || files.length == 0) return;
for(let i=0;i<files.length;i++){
fs.unlinkSync(files[i]);
}
return;
}
export { init };
export { init };

@ -10,6 +10,7 @@ async function run() {
const bcryptcfg: object = config.bcrypt;
const satyr: object = {
privateEndpoint: config.media.privateEndpoint,
publicEndpoint: config.media.publicEndpoint,
record: config.media.record,
registration: config.satyr.registration,
webFormat: config.satyr.webFormat,
@ -18,7 +19,8 @@ async function run() {
domain: config.satyr.domain,
email: config.satyr.email,
rootredirect: config.satyr.rootredirect,
version: process.env.npm_package_version
version: process.env.npm_package_version,
directory: config.server.http.directory
};
const nms: object = {
logType: config.server.logs,
@ -29,7 +31,7 @@ async function run() {
ping: config.server.rtmp.ping,
ping_timeout: config.server.rtmp.ping_timeout,
},
http: {
/*http: {
port: config.server.http.port + 1,
mediaroot: config.server.http.directory,
allow_origin: config.server.http.allow_origin
@ -45,7 +47,7 @@ async function run() {
dashFlags: config.transcode.dashFlags
}
]
},
},*/
auth: {
api: config.server.api,
api_user: config.server.api_user,
@ -54,7 +56,7 @@ async function run() {
};
db.init(dbcfg, bcryptcfg);
await cleanup.init(config.server.http.directory);
await cleanup.init();
api.init(satyr);
http.init(satyr, config.server.http.port, config.ircd);
mediaserver.init(nms, satyr);

@ -1,7 +1,8 @@
import * as NodeMediaServer from "node-media-server";
import { mkdir } from "fs";
import { mkdir, fstat, access } from "fs";
import * as db from "./database";
const { exec } = require('child_process');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const { exec, execFile } = require('child_process');
function init (mediaconfig: any, satyrconfig: any) {
const nms = new NodeMediaServer(mediaconfig);
@ -18,7 +19,7 @@ function init (mediaconfig: any, satyrconfig: any) {
session.reject();
return false;
}
if(app === mediaconfig.trans.tasks[0].app) {
if(app === satyrconfig.publicEndpoint) {
if(session.ip.includes('127.0.0.1') || session.ip === '::1') {
//only allow publish to public endpoint from localhost
//this is NOT a comprehensive way of doing this, but I'm ignoring it
@ -37,15 +38,15 @@ function init (mediaconfig: any, satyrconfig: any) {
return db.query('select username,record_flag from users where username=\''+key+'\' limit 1').then((results) => {
if(results[0].record_flag && satyrconfig.record){
console.log('[NodeMediaServer] Initiating recording for stream:',id);
mkdir(mediaconfig.http.mediaroot+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username, { recursive : true }, (err) => {
mkdir(satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username, { recursive : true }, (err) => {
if (err) throw err;
let subprocess = exec('ffmpeg -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username+' -vcodec copy -acodec copy '+mediaconfig.http.mediaroot+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username+'/$(date +%d%b%Y-%H%M).mp4',{
let subprocess = exec('ffmpeg -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.publicEndpoint+'/'+results[0].username+' -vcodec copy -acodec copy '+satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username+'/$(date +%d%b%Y-%H%M).mp4',{
detached : true,
stdio : 'inherit'
});
subprocess.unref();
//spawn an ffmpeg process to record the stream, then detach it completely
//ffmpeg can then finalize the recording if satyr crashes mid-stream
//ffmpeg can then (probably) finalize the recording if satyr crashes mid-stream
});
}
else {
@ -67,9 +68,18 @@ function init (mediaconfig: any, satyrconfig: any) {
session.reject();
return false;
}
db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then((results) => {
db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => {
if(results[0]){
exec('ffmpeg -analyzeduration 0 -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key+' -vcodec copy -acodec copy -crf 18 -f flv rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+mediaconfig.trans.tasks[0].app+'/'+results[0].username);
//push to rtmp
exec('ffmpeg -analyzeduration 0 -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key+' -vcodec copy -acodec copy -crf 18 -f flv rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.publicEndpoint+'/'+results[0].username);
//push to mpd after making sure directory exists
mkdir(satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username, { recursive : true }, (err) => {;});
sleep(5000).then( () => {
//wait for stream to initialize, but i'm not happy about this
exec('ffmpeg -y -i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key+' -map 0:2 -map 0:2 -map 0:2 -map 0:1 -c:a copy -c:v:0 copy -c:v:1 libx264 -c:v:2 libx264 -crf:1 33 -crf:2 40 -b:v:1 3000K -b:v:2 1500K -remove_at_exit 1 -seg_duration 1 -window_size 30 -f dash '+satyrconfig.directory+'/'+satyrconfig.publicEndpoint+'/'+results[0].username+'/index.mpd');
});
//switch to execFile at some point, it's safer
//execFile('/usr/bin/ffmpeg',['-analyzeduration 0', '-i rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.privateEndpoint+'/'+key, '-vcodec copy', '-acodec copy', '-crf 18', '-f flv', 'rtmp://127.0.0.1:'+mediaconfig.rtmp.port+'/'+satyrconfig.publicEndpoint+'/'+results[0].username]);
console.log('[NodeMediaServer] Stream key okay for stream:',id);
}
else{
@ -81,7 +91,7 @@ function init (mediaconfig: any, satyrconfig: any) {
nms.on('donePublish', (id, StreamPath, args) => {
let app: string = StreamPath.split("/")[1];
let key: string = StreamPath.split("/")[2];
if(app === mediaconfig.trans.tasks[0].app) {
if(app === satyrconfig.publicEndpoint) {
db.query('update user_meta set live=false where username=\''+key+'\' limit 1');
}
});

@ -7,7 +7,7 @@ function newPopup(url) {
}
</script>
</br>
<span style="float: left;font-size: large;"><a href="/live/{{ user }}/index.m3u8">{{ user }}</a> | {{ streamtitle | escape }}</b></span><span style="float: right;font-size: large;"> Links | <a href="rtmp://{{ domain }}/live/{{ user }}">Watch</a> <a href="JavaScript:newPopup('/chat?room={{ user }}');">Chat</a> <a href="/vods/{{ user }}">VODs</a></span>
<span style="float: left;font-size: large;"><a href="/live/{{ user }}/index.mpd">{{ user }}</a> | {{ streamtitle | escape }}</b></span><span style="float: right;font-size: large;"> Links | <a href="rtmp://{{ domain }}/live/{{ user }}">Watch</a> <a href="JavaScript:newPopup('/chat?room={{ user }}');">Chat</a> <a href="/vods/{{ user }}">VODs</a></span>
<div id="jscontainer">
<div id="jschild" style="width: 70%;height: 100%;">
<video controls poster="/thumbnail.jpg" class="video-js vjs-default-skin" id="live-video" style="width:100%;height:100%;"></video>