263 lines
8.2 KiB
PHP
263 lines
8.2 KiB
PHP
<?php
|
|
|
|
namespace app\controller;
|
|
|
|
use app\config\Config;
|
|
use plugin\admin\app\common\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']);
|
|
}
|
|
|
|
}
|