1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-03 09:49:28 +02:00

Update playlist live playback

This commit is contained in:
Daniel Neto 2025-02-10 16:58:14 -03:00
parent de868fa249
commit ffb785e348
8 changed files with 362 additions and 8 deletions

View file

@ -687,6 +687,39 @@ function testFFMPEGRemote()
} }
} }
function listFFMPEGRemote($keyword = '')
{
$url = buildFFMPEGRemoteURL(['list' => 1, 'keyword' => $keyword, 'microtime' => microtime(true)]);
if ($url) {
_error_log("listFFMPEGRemote: URL $url");
return json_decode(url_get_contents($url));
} else {
return false;
}
}
function killFFMPEGRemote($pid)
{
$url = buildFFMPEGRemoteURL(['kill' => $pid, 'microtime' => microtime(true)]);
if ($url) {
_error_log("killFFMPEGRemote: URL $url");
return json_decode(url_get_contents($url));
} else {
return false;
}
}
function isKeywordRunningFFMPEGRemote($keyword)
{
$url = buildFFMPEGRemoteURL(['isKeywordRunning' => $keyword, 'microtime' => microtime(true)]);
if ($url) {
_error_log("isKeywordRunningFFMPEGRemote: URL $url");
return json_decode(url_get_contents($url));
} else {
return false;
}
}
function deleteFolderFFMPEGRemote($videoFilename) function deleteFolderFFMPEGRemote($videoFilename)
{ {
$url = buildFFMPEGRemoteURL(['deleteFolder' => $videoFilename]); $url = buildFFMPEGRemoteURL(['deleteFolder' => $videoFilename]);

139
plugin/API/ffmpeg.php Normal file
View file

@ -0,0 +1,139 @@
<?php
global $global, $config;
if (!isset($global['systemRootPath'])) {
require_once '../../videos/configuration.php';
}
if (!User::isAdmin()) {
forbiddenPage('Admin only');
}
require_once $global['systemRootPath'] . 'plugin/API/API.php';
$plugin = AVideoPlugin::loadPluginIfEnabled("API");
if (empty($plugin)) {
forbiddenPage('API Plugin disabled');
}
$obj = AVideoPlugin::getObjectData("API");
$_page = new Page(array('FFMPEG'));
?>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading"><?php echo __('FFmpeg Process Manager'); ?> </div>
<div class="panel-body">
<table class="table table-bordered table-striped" id="processTable">
<thead>
<tr>
<th>PID</th>
<th>Command</th>
<th>CPU (%)</th>
<th>Memory (%)</th>
<th>Running Time</th>
<th>Running Time (s)</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<!-- Data will be loaded here -->
</tbody>
</table>
</div>
<div class="panel-footer">
<button id="refreshBtn" class="btn btn-primary btn-block"><?php echo __('Refresh List'); ?></button>
</div>
</div>
</div>
<script>
$(document).ready(function() {
var loadProcessesModal = getPleaseWait();
function loadProcesses() {
loadProcessesModal.showPleaseWait();
$.ajax({
url: webSiteRootURL + "plugin/API/list.ffmpeg.json.php",
type: "GET",
dataType: "json",
success: function(data) {
let tableBody = $("#processTable tbody");
tableBody.empty();
if (data.error || !data.list || data.list.length === 0) {
tableBody.append("<tr><td colspan='7' class='text-center'>No FFmpeg processes running</td></tr>");
return;
}
$.each(data.list, function(index, process) {
let row = `<tr>
<td>${process.pid}</td>
<td style="word-break: break-word;">${process.command}</td>
<td>${process.cpu_usage}</td>
<td>${process.memory_usage}</td>
<td>${process.running_time}</td>
<td>${process.running_time_seconds}</td>
<td>
<button class="btn btn-danger btn-sm kill-btn btn-block" data-pid="${process.pid}">Kill</button>
</td>
</tr>`;
tableBody.append(row);
});
loadProcessesModal.hidePleaseWait();
},
complete: function(resp) {
response = resp.responseJSON
avideoResponse(response);
loadProcessesModal.hidePleaseWait();
}
});
}
// Load process list on page load
loadProcesses();
// Refresh button
$("#refreshBtn").click(function() {
loadProcesses();
});
// Handle process kill button click
$(document).on("click", ".kill-btn", function() {
let pid = $(this).data("pid");
avideoConfirm("Are you sure you want to kill process " + pid + "?").then(response => {
if (response) {
modal.showPleaseWait();
$.ajax({
url: webSiteRootURL + "plugin/API/kill.ffmpeg.json.php",
type: "POST",
data: {
pid: pid
},
dataType: "json",
success: function(response) {
avideoResponse(response);
loadProcesses(); // Refresh the list after a successful kill
modal.hidePleaseWait();
},
complete: function(resp) {
response = resp.responseJSON
avideoResponse(response);
modal.hidePleaseWait();
}
});
} else {
return false;
}
});
});
});
</script>
<?php
$_page->print();
?>

View file

@ -0,0 +1,18 @@
<?php
$configFile = __DIR__.'/../../videos/configuration.php';
require_once $configFile;
header('Content-Type: application/json');
if(!User::isAdmin()){
forbiddenPage('Must be admin');
}
$pid = intval($_REQUEST['pid']);
if(empty($pid)){
forbiddenPage('PID is invalid');
}
$obj = killFFMPEGRemote($pid);
die(json_encode($obj));

View file

@ -0,0 +1,12 @@
<?php
$configFile = __DIR__.'/../../videos/configuration.php';
require_once $configFile;
header('Content-Type: application/json');
if(!User::isAdmin()){
forbiddenPage('Must be admin');
}
$obj = listFFMPEGRemote();
die(json_encode($obj));

View file

@ -1,2 +1,3 @@
<button onclick="avideoModalIframeLarge(webSiteRootURL +'plugin/API/info.php');" class="btn btn-primary btn-sm btn-xs btn-block"><i class="fas fa-info-circle"></i> Info</button> <button onclick="avideoModalIframeLarge(webSiteRootURL +'plugin/API/info.php');" class="btn btn-primary btn-sm btn-xs btn-block"><i class="fas fa-info-circle"></i> Info</button>
<button onclick="avideoModalIframeLarge(webSiteRootURL +'plugin/API/ffmpeg.php');" class="btn btn-primary btn-sm btn-xs btn-block"><i class="fa fa-bolt"></i> FFmpeg Process</button>
<button onclick="avideoAlertAJAX(webSiteRootURL +'plugin/API/check.ffmpeg.json.php');" class="btn btn-primary btn-sm btn-xs btn-block"><i class="fas fa-terminal"></i> Test Remote FFmpeg</button> <button onclick="avideoAlertAJAX(webSiteRootURL +'plugin/API/check.ffmpeg.json.php');" class="btn btn-primary btn-sm btn-xs btn-block"><i class="fas fa-terminal"></i> Test Remote FFmpeg</button>

View file

@ -214,6 +214,25 @@ if (!empty($codeToExec->test)) {
'isActive' => $isActive, 'isActive' => $isActive,
]); ]);
exit; exit;
} else if (!empty($codeToExec->list)) {
_error_log("List mode triggered");
$list = listFFmpegProcesses($codeToExec->keyword);
echo json_encode([
'error' => false,
'msg' => '',
'list' => $list,
]);
exit;
} else if (!empty($codeToExec->kill) && is_numeric($codeToExec->kill)) {
_error_log("kill mode triggered");
$kill = killFFmpegProcess($codeToExec->kill);
echo json_encode([
'error' => empty($kill),
'msg' => '',
'kill' => $kill,
'pid' => $codeToExec->kill,
]);
exit;
} else if (!empty($codeToExec->stop) && !empty($keyword)) { } else if (!empty($codeToExec->stop) && !empty($keyword)) {
_error_log("Stop mode triggered for keyword: $keyword"); _error_log("Stop mode triggered for keyword: $keyword");
@ -314,6 +333,31 @@ if (!empty($codeToExec->test)) {
'unlink' => $unlink, 'unlink' => $unlink,
]); ]);
exit; exit;
}else if (!empty($codeToExec->isKeywordRunning)) {
_error_log("Checking for running FFmpeg process with keyword: $codeToExec->isKeywordRunning");
$list = listFFmpegProcesses($codeToExec->isKeywordRunning);
// If a process is found, return the full command
if (!empty($list)) {
echo json_encode([
'error' => false,
'msg' => 'FFmpeg process found',
'isRunning' => true,
'list' => $list,
'keyword' => $codeToExec->isKeywordRunning,
]);
exit;
}
// No process found
echo json_encode([
'error' => true,
'msg' => 'No FFmpeg process found with the given keyword',
'isRunning' => false,
'keyword' => $codeToExec->isKeywordRunning,
]);
exit;
} }
if (empty($ffmpegCommand)) { if (empty($ffmpegCommand)) {
@ -352,6 +396,8 @@ if (!empty($keyword)) {
//sleep(5); //sleep(5);
} }
//$ffmpegCommand = fixConcatFfmpegCommand($ffmpegCommand);
$ffmpegCommand = addKeywordToFFmpegCommand($ffmpegCommand, $keyword); $ffmpegCommand = addKeywordToFFmpegCommand($ffmpegCommand, $keyword);
$ffmpegCommand .= " > {$logFile} "; $ffmpegCommand .= " > {$logFile} ";

View file

@ -80,3 +80,102 @@ function sanitizeFFmpegCommand($command)
_error_log("Sanitization failed: Command does not start with an allowed prefix"); _error_log("Sanitization failed: Command does not start with an allowed prefix");
return ''; return '';
} }
function convertElapsedTimeToSeconds($elapsedTime)
{
$timeParts = explode('-', $elapsedTime);
if (count($timeParts) == 2) {
// Format: DD-HH:MM:SS
list($days, $hms) = $timeParts;
$days = intval($days) * 86400; // Convert days to seconds
} else {
// Format: HH:MM:SS
$days = 0;
$hms = $timeParts[0];
}
list($hours, $minutes, $seconds) = array_pad(explode(':', $hms), 3, '00');
return $days + (intval($hours) * 3600) + (intval($minutes) * 60) + intval($seconds);
}
function listFFmpegProcesses($keyword = '')
{
$command = "ps -eo pid,etime,%cpu,%mem,cmd | grep '[f]fmpeg'"; // Get PID, elapsed time, CPU & memory usage
if (!empty($keyword)) {
$command .= " | grep '$keyword'";
}
exec($command, $output, $status);
$processes = [];
foreach ($output as $line) {
preg_match('/^\s*(\d+)\s+([\d:-]+)\s+([\d.]+)\s+([\d.]+)\s+(.+)$/', $line, $matches);
if (!empty($matches)) {
$runningTime = $matches[2]; // Original elapsed time format
$runningTimeSeconds = convertElapsedTimeToSeconds($runningTime);
$processes[] = [
'pid' => intval($matches[1]),
'running_time' => $runningTime, // Formatted time (e.g., 02:15:30 or 1-12:45:20)
'running_time_seconds' => $runningTimeSeconds, // Time in seconds
'cpu_usage' => floatval($matches[3]), // CPU usage percentage
'memory_usage' => floatval($matches[4]), // Memory usage percentage
'command' => $matches[5] // Full command line
];
}
}
return $processes;
}
function killFFmpegProcess($pid)
{
if (!is_numeric($pid) || $pid <= 0) {
return [
'error' => true,
'msg' => 'Invalid PID'
];
}
$killCommand = "kill -9 " . escapeshellarg($pid);
exec($killCommand, $output, $status);
return [
'error' => $status !== 0,
'msg' => $status === 0 ? "Process $pid killed successfully." : "Failed to kill process $pid.",
'killCommand' => $killCommand,
'output' => $output,
'status' => $status
];
}
/*
function fixConcatFfmpegCommand($ffmpegCommand) {
$pattern = '/concat=([^\s]+)/';
if (preg_match($pattern, $ffmpegCommand, $matches)) {
$concatFiles = explode('|', $matches[1]);
$fixedFiles = [];
foreach ($concatFiles as $file) {
if (preg_match('/^https?:\/\//', $file)) {
$localFile = getTmpDir().'concat_'.uniqid();
$data = url_get_contents($file);
file_put_contents($localFile, $data);
$fixedFiles[] = $localFile;
} else {
$fixedFiles[] = $file;
}
}
$newConcat = implode('|', $fixedFiles);
$ffmpegCommand = str_replace($matches[1], $newConcat, $ffmpegCommand);
}
return $ffmpegCommand;
}
*/

View file

@ -909,7 +909,13 @@ class PlayLists extends PluginAbstract
$labelText = ''; $labelText = '';
} }
return "<button class=\"{$class}\" onclick=\"avideoModalIframe('$liveLink');\" data-toggle=\"tooltip\" title=\"$label\" ><i class=\"fas fa-broadcast-tower\"></i> $labelText</button>"; $btn = "<button class=\"{$class}\" onclick=\"avideoModalIframe('$liveLink');\" data-toggle=\"tooltip\" title=\"$label\" ><i class=\"fas fa-broadcast-tower\"></i> $labelText</button>";
if(AVideoPlugin::isEnabledByName('VideoPlaylistScheduler')){
$liveLink = "{$global['webSiteRootURL']}plugin/VideoPlaylistScheduler/playLiveInLoop.php";
$liveLink = addQueryStringParameter($liveLink, 'playlists_id', $playlists_id);
$btn .= "<button class=\"{$class}\" onclick=\"avideoModalIframe('{$liveLink}');\" data-toggle=\"tooltip\" title=\"$label Loop\" ><i class=\"fa-solid fa-infinity\"></i> $labelText</button>";
}
return $btn;
} }
static function getVideosIdFromPlaylist($playlists_id) static function getVideosIdFromPlaylist($playlists_id)