init
This commit is contained in:
@@ -2,31 +2,256 @@
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\config\Config;
|
||||
use app\utils\ModelAutoGenerator;
|
||||
use app\zlm\ZLMClient;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
public function index(Request $request)
|
||||
/**
|
||||
* 页面1: 视频实时监控页面 - 返回静态HTML
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return <<<EOF
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
iframe {
|
||||
border: none;
|
||||
overflow: scroll;
|
||||
}
|
||||
</style>
|
||||
<iframe
|
||||
src="https://www.workerman.net/wellcome"
|
||||
width="100%"
|
||||
height="100%"
|
||||
allow="clipboard-write"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-downloads"
|
||||
></iframe>
|
||||
EOF;
|
||||
// ModelAutoGenerator::generate_all();
|
||||
return redirect('/static/html/monitor.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面2: 检测区域绘制页面 - 返回静态HTML
|
||||
*/
|
||||
public function roi(Request $request): Response
|
||||
{
|
||||
return redirect('/static/html/roi.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* API: 获取视频流列表
|
||||
*/
|
||||
public function streams(Request $request)
|
||||
{
|
||||
try {
|
||||
$zlmClient = new ZLMClient();
|
||||
$streams = $zlmClient->getMediaList();
|
||||
return json(['code' => 0, 'msg' => 'ok', 'data' => $streams]);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['code' => 500, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API: 获取检测状态
|
||||
*/
|
||||
public function status(Request $request)
|
||||
{
|
||||
$status = [
|
||||
'detection_enabled' => true,
|
||||
'check_interval' => 500,
|
||||
'detect_threshold' => 3,
|
||||
'cameras' => []
|
||||
];
|
||||
return json(['code' => 0, 'msg' => 'ok', 'data' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* API: 保存ROI配置
|
||||
*/
|
||||
public function saveRoi(Request $request)
|
||||
{
|
||||
$streamKey = $request->post('stream_key');
|
||||
$roi = $request->post('roi');
|
||||
|
||||
if (empty($streamKey) || empty($roi)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
$configFile = runtime_path() . '/roi_config/' . $streamKey . '.json';
|
||||
if (!is_dir(dirname($configFile))) {
|
||||
mkdir(dirname($configFile), 0777, true);
|
||||
}
|
||||
file_put_contents($configFile, json_encode($roi));
|
||||
|
||||
return json(['code' => 0, 'msg' => '保存成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
* API: 获取ROI配置
|
||||
*/
|
||||
public function getRoi(Request $request)
|
||||
{
|
||||
$streamKey = $request->get('stream_key');
|
||||
|
||||
if (empty($streamKey)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
$configFile = runtime_path() . '/roi_config/' . $streamKey . '.json';
|
||||
if (file_exists($configFile)) {
|
||||
$roi = json_decode(file_get_contents($configFile), true);
|
||||
return json(['code' => 0, 'msg' => 'ok', 'data' => $roi]);
|
||||
}
|
||||
|
||||
// 默认ROI
|
||||
return json(['code' => 0, 'msg' => 'ok', 'data' => [
|
||||
['x' => 0, 'y' => 0],
|
||||
['x' => 640, 'y' => 0],
|
||||
['x' => 640, 'y' => 480],
|
||||
['x' => 0, 'y' => 480]
|
||||
]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* API: 代理ZLM截图请求 - 解决跨域问题
|
||||
*/
|
||||
public function proxyZlmSnap(Request $request)
|
||||
{
|
||||
$app = $request->get('app');
|
||||
$stream = $request->get('stream');
|
||||
$processed = $request->get('processed'); // 是否获取处理后的帧
|
||||
|
||||
if (empty($app) || empty($stream)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
try {
|
||||
$zlmConfig = Config::getInstance()->zlm;
|
||||
$secret = $zlmConfig->secret;
|
||||
|
||||
// 构建ZLM截图URL
|
||||
$snapUrl = "{$zlmConfig->apiBaseUrl}/index/api/getSnap";
|
||||
$params = [
|
||||
'secret' => $secret,
|
||||
'app' => $app,
|
||||
'stream' => $stream,
|
||||
'timeout_sec' => 5
|
||||
];
|
||||
|
||||
$url = $snapUrl . '?' . http_build_query($params);
|
||||
|
||||
// 获取截图数据
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
$imageData = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || empty($imageData)) {
|
||||
// 记录错误日志
|
||||
\app\utils\Logger::error("ZLM截图失败: HTTP {$httpCode}, 错误: {$error}, URL: {$url}");
|
||||
// 返回错误提示图
|
||||
return $this->generateErrorImage("截图失败: HTTP {$httpCode}");
|
||||
}
|
||||
|
||||
// 如果是处理后的帧,添加标记(后续可替换为实际处理逻辑)
|
||||
if ($processed) {
|
||||
// TODO: 调用PHP处理流程处理图像
|
||||
// 目前先返回原图,但添加水印表示是"处理后"的
|
||||
$imageData = $this->addProcessedWatermark($imageData);
|
||||
}
|
||||
|
||||
// 返回图片
|
||||
return response($imageData, 200, [
|
||||
'Content-Type' => $contentType ?: 'image/jpeg',
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
\app\utils\Logger::error("ZLM截图异常: " . $e->getMessage());
|
||||
return $this->generateErrorImage("截图异常: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加"已处理"水印
|
||||
*/
|
||||
private function addProcessedWatermark(string $imageData): string
|
||||
{
|
||||
// 如果安装了GD库,添加水印
|
||||
if (extension_loaded('gd')) {
|
||||
try {
|
||||
$image = imagecreatefromstring($imageData);
|
||||
if ($image) {
|
||||
$color = imagecolorallocate($image, 233, 69, 96);
|
||||
$font = 5; // 内置字体
|
||||
$text = "PROCESSED";
|
||||
imagestring($image, $font, 10, 10, $text, $color);
|
||||
|
||||
ob_start();
|
||||
imagejpeg($image, null, 85);
|
||||
$watermarked = ob_get_clean();
|
||||
imagedestroy($image);
|
||||
|
||||
return $watermarked ?: $imageData;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 失败返回原图
|
||||
}
|
||||
}
|
||||
return $imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成错误提示图
|
||||
*/
|
||||
private function generateErrorImage(string $message): Response
|
||||
{
|
||||
// 创建带错误信息的图片
|
||||
if (extension_loaded('gd')) {
|
||||
try {
|
||||
$width = 640;
|
||||
$height = 480;
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
$bgColor = imagecolorallocate($image, 10, 10, 10);
|
||||
$textColor = imagecolorallocate($image, 233, 69, 96);
|
||||
$white = imagecolorallocate($image, 255, 255, 255);
|
||||
|
||||
imagefill($image, 0, 0, $bgColor);
|
||||
|
||||
// 绘制边框
|
||||
imagerectangle($image, 0, 0, $width - 1, $height - 1, $textColor);
|
||||
|
||||
// 绘制文字
|
||||
$lines = [
|
||||
"视频加载失败",
|
||||
"",
|
||||
$message,
|
||||
"",
|
||||
"请检查:",
|
||||
"1. ZLM服务是否运行",
|
||||
"2. 视频流是否正常",
|
||||
"3. 配置是否正确"
|
||||
];
|
||||
|
||||
$y = $height / 2 - count($lines) * 10;
|
||||
foreach ($lines as $line) {
|
||||
$x = ($width - strlen($line) * 8) / 2;
|
||||
imagestring($image, 3, (int)$x, (int)$y, $line, $white);
|
||||
$y += 20;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
imagejpeg($image, null, 85);
|
||||
$imageData = ob_get_clean();
|
||||
imagedestroy($image);
|
||||
|
||||
return response($imageData, 200, ['Content-Type' => 'image/jpeg']);
|
||||
} catch (\Throwable $e) {
|
||||
// 失败返回简单占位图
|
||||
}
|
||||
}
|
||||
|
||||
// 返回1x1透明像素
|
||||
$pixel = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
|
||||
return response($pixel, 200, ['Content-Type' => 'image/png']);
|
||||
}
|
||||
|
||||
public function view(Request $request)
|
||||
@@ -34,9 +259,4 @@ EOF;
|
||||
return view('index/view', ['name' => 'webman']);
|
||||
}
|
||||
|
||||
public function json(Request $request)
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user