Simple PHP hackjob to automatically record live streams from Restreamer. It is copying the HLS output with FFmpeg when the Restreamer instance report type connected.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
4.6 KiB

/* -------------------------------------------------------------- */
// Options
$opts = getopt('c:');
// Basic functions
function logger($tabs = 1, $txt, $lvl = 1, $exit = false)
global $_set;
if ($lvl >= $_set->loglevel)
echo "[".date('Y-m-d H:i:s')."]".str_repeat("\t", $tabs)."$txt\n";
if ($exit)
return true;
function is_dir_empty($dir)
if (!is_readable($dir)) return NULL;
return (count(scandir($dir)) == 2);
function notify($sub, $msg, $loglevel = 1)
global $_set;
if (is_array($_set->mail) && !empty($_set->mail))
foreach ($_set->mail AS $mail)
if ([email protected]mail($mail, "=?UTF-8?B?".base64_encode('[hls-recorder] '.$sub)."?=", $msg, "Content-type: text/plain; charset=utf-8"))
logger($loglevel, 'Unable to send notification E-mail');
// Check requirements
if (!function_exists('curl_init'))
logger(1, 'cURL is required but not available', 1, true);
if (!function_exists('json_decode'))
logger(1, 'json_decode is required but not available', 1, true);
// Settings
$config = isset($opts['c']) ? $opts['c'] : __DIR__.'/config.json';
if (!file_exists($config))
logger(1, 'Config file not found: '.$config, 1, true);
if (!$_set = @json_decode(@file_get_contents($config)))
logger(1, 'Unable to load or parse configuration file', 1, true);
// Check settings
$_set_def = [
'rtmp' => '',
'recordhls' => false
foreach ($_set_def AS $i => $v)
if (!isset($_set->{$i}))
$_set->{$i} = $v;
// Loop indefinitely
while (true) :
if ($api = @file_get_contents($_set->api.'/states'))
if ($api = @json_decode($api))
if (isset($api->repeat_to_local_nginx) && $api->repeat_to_local_nginx->type == 'connected')
logger(1, 'Repeater seems to be connected');
$handle = curl_init($_set->hls);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE);
$response = curl_exec($handle);
$httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
if ($httpCode == 200)
$output_directory = $_set->vod.'/vod-'.date('Ymd-His');
$exitcode = 0;
if (@mkdir($output_directory))
logger(2, 'All checks passed, starting ffmpeg');
notify('Recording', 'Restreamer API reported that repeating to local nginx is connected and the HLS endpoint is found. The HLS output is now being recorded. HLS URL is the following: '.$_set->hls.', Output directory is: '.$output_directory, 2);
if ($_set->recordhls || !$_set->rtmp)
exec('flock -n '.$_set->pid.' -c "ffmpeg -re -i '.$_set->hls.' -c copy -hls_list_size 0 '.$output_directory.'/index.m3u8 2> '.$output_directory.'/ffmpeg.log"', $output, $exitcode);
exec('flock -n '.$_set->pid.' -c "ffmpeg -re -i '.$_set->rtmp.' -c copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls '.$output_directory.'/index.m3u8 2> '.$output_directory.'/ffmpeg.log"', $output, $exitcode);
logger(2, 'ffmpeg exited with code '.$exitcode);
notify('Recording stopped', 'The FFMPEG process shut down with exit code: '.$exitcode, 2);
if ($exitcode)
logger(3, 'Non-clean exit (expected), checking if output directory is empty');
if (is_dir_empty($output_directory))
if (@rmdir($output_directory))
logger(4, 'Output directory removed');
logger(4, 'Unable to remove output directory');
notify('Error', 'The FFMPEG process shut down with exit code '.$exitcode.' and the output directory is empty (nothing recorded). Output directory should have been removed automatically, but the process failed. Remove the output directory manually: '.$output_directory, 4);
} else
logger(3, 'Unable to create output directory');
} else
logger(2, 'HLS inaccessible, HTTP Code '.$httpCode);
} else
logger(1, 'Repeater not in connected state', 0);
logger(1, 'API response unreadable');
logger(1, 'API can not be accessed');