Files
dsserver/app/controller/IndexController.php
T
zimoyin 13fd9c5f0a init
2026-04-06 20:48:32 +08:00

263 lines
8.2 KiB
PHP

<?php
namespace app\controller;
use app\config\Config;
use app\utils\ModelAutoGenerator;
use app\zlm\ZLMClient;
use support\Request;
use support\Response;
class IndexController
{
/**
* 页面1: 视频实时监控页面 - 返回静态HTML
*/
public function index(Request $request): Response
{
// 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)) {
// 记录错误日志
\plugin\admin\app\common\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) {
\plugin\admin\app\common\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)
{
return view('index/view', ['name' => 'webman']);
}
}