557 lines
18 KiB
C#
557 lines
18 KiB
C#
using System.ComponentModel.DataAnnotations;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.AspNetCore.Cors;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
|
||
/// <summary>
|
||
/// 视频流控制器,支持动态配置摄像头连接
|
||
/// </summary>
|
||
[ApiController]
|
||
[Route("api/[controller]")]
|
||
public class VideoStreamController : ControllerBase
|
||
{
|
||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||
private readonly server.Services.HttpVideoStreamService _videoStreamService;
|
||
|
||
/// <summary>
|
||
/// 视频流信息结构体
|
||
/// </summary>
|
||
public class StreamInfoResult
|
||
{
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public int FrameRate { get; set; }
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public int FrameWidth { get; set; }
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public int FrameHeight { get; set; }
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public string Format { get; set; } = "MJPEG";
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public string HtmlUrl { get; set; } = "";
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public string MjpegUrl { get; set; } = "";
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public string SnapshotUrl { get; set; } = "";
|
||
/// <summary>
|
||
/// TODO:
|
||
/// </summary>
|
||
public string UsbCameraUrl { get; set; } = "";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 摄像头配置请求模型
|
||
/// </summary>
|
||
public class CameraConfigRequest
|
||
{
|
||
/// <summary>
|
||
/// 摄像头地址
|
||
/// </summary>
|
||
[Required]
|
||
[RegularExpression(@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", ErrorMessage = "请输入有效的IP地址")]
|
||
public string Address { get; set; } = "";
|
||
|
||
/// <summary>
|
||
/// 摄像头端口
|
||
/// </summary>
|
||
[Required]
|
||
[Range(1, 65535, ErrorMessage = "端口必须在1-65535范围内")]
|
||
public int Port { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分辨率配置请求模型
|
||
/// </summary>
|
||
public class ResolutionConfigRequest
|
||
{
|
||
/// <summary>
|
||
/// 宽度
|
||
/// </summary>
|
||
[Required]
|
||
[Range(640, 1920, ErrorMessage = "宽度必须在640-1920范围内")]
|
||
public int Width { get; set; }
|
||
|
||
/// <summary>
|
||
/// 高度
|
||
/// </summary>
|
||
[Required]
|
||
[Range(480, 1080, ErrorMessage = "高度必须在480-1080范围内")]
|
||
public int Height { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化HTTP视频流控制器
|
||
/// </summary>
|
||
/// <param name="videoStreamService">HTTP视频流服务</param>
|
||
public VideoStreamController(server.Services.HttpVideoStreamService videoStreamService)
|
||
{
|
||
logger.Info("创建VideoStreamController,命名空间:{Namespace}", this.GetType().Namespace);
|
||
_videoStreamService = videoStreamService;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 HTTP 视频流服务状态
|
||
/// </summary>
|
||
/// <returns>服务状态信息</returns>
|
||
[HttpGet("Status")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public IResult GetStatus()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("GetStatus方法被调用,控制器:{Controller},路径:api/VideoStream/Status", this.GetType().Name);
|
||
|
||
// 使用HttpVideoStreamService提供的状态信息
|
||
var status = _videoStreamService.GetServiceStatus();
|
||
|
||
// 转换为小写首字母的JSON属性(符合前端惯例)
|
||
return TypedResults.Ok(status);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取 HTTP 视频流服务状态失败");
|
||
return TypedResults.InternalServerError(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 HTTP 视频流信息
|
||
/// </summary>
|
||
/// <returns>流信息</returns>
|
||
[HttpGet("StreamInfo")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(StreamInfoResult), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public IResult GetStreamInfo()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("获取 HTTP 视频流信息");
|
||
var result = new StreamInfoResult
|
||
{
|
||
FrameRate = _videoStreamService.FrameRate,
|
||
FrameWidth = _videoStreamService.FrameWidth,
|
||
FrameHeight = _videoStreamService.FrameHeight,
|
||
Format = "MJPEG",
|
||
HtmlUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/video-feed.html",
|
||
MjpegUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/video-stream",
|
||
SnapshotUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/snapshot",
|
||
UsbCameraUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/usb-camera"
|
||
};
|
||
return TypedResults.Ok(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取 HTTP 视频流信息失败");
|
||
return TypedResults.InternalServerError(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置摄像头连接参数
|
||
/// </summary>
|
||
/// <param name="config">摄像头配置</param>
|
||
/// <returns>配置结果</returns>
|
||
[HttpPost("ConfigureCamera")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> ConfigureCamera([FromBody] CameraConfigRequest config)
|
||
{
|
||
try
|
||
{
|
||
logger.Info("配置摄像头连接: {Address}:{Port}", config.Address, config.Port);
|
||
|
||
var success = await _videoStreamService.ConfigureCameraAsync(config.Address, config.Port);
|
||
|
||
if (success)
|
||
{
|
||
return TypedResults.Ok(new
|
||
{
|
||
success = true,
|
||
message = "摄像头配置成功",
|
||
cameraAddress = config.Address,
|
||
cameraPort = config.Port
|
||
});
|
||
}
|
||
else
|
||
{
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = "摄像头配置失败",
|
||
cameraAddress = config.Address,
|
||
cameraPort = config.Port
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "配置摄像头连接失败");
|
||
return TypedResults.InternalServerError(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前摄像头配置
|
||
/// </summary>
|
||
/// <returns>摄像头配置信息</returns>
|
||
[HttpGet("CameraConfig")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public IResult GetCameraConfig()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("获取摄像头配置");
|
||
var cameraStatus = _videoStreamService.GetCameraStatus();
|
||
|
||
return TypedResults.Ok(cameraStatus);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取摄像头配置失败");
|
||
return TypedResults.InternalServerError(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 控制 HTTP 视频流服务开关
|
||
/// </summary>
|
||
/// <param name="enabled">是否启用服务</param>
|
||
/// <returns>操作结果</returns>
|
||
[HttpPost("SetEnabled")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> SetEnabled([FromQuery] bool enabled)
|
||
{
|
||
logger.Info("设置视频流服务开关: {Enabled}", enabled);
|
||
await _videoStreamService.SetEnable(enabled);
|
||
return TypedResults.Ok();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试 HTTP 视频流连接
|
||
/// </summary>
|
||
/// <returns>连接测试结果</returns>
|
||
[HttpPost("TestConnection")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> TestConnection()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("测试 HTTP 视频流连接");
|
||
|
||
// 尝试通过HTTP请求检查视频流服务是否可访问
|
||
bool isConnected = false;
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
httpClient.Timeout = TimeSpan.FromSeconds(2); // 设置较短的超时时间
|
||
var response = await httpClient.GetAsync($"http://{Global.localhost}:{_videoStreamService.ServerPort}/");
|
||
|
||
// 只要能连接上就认为成功,不管返回状态
|
||
isConnected = response.IsSuccessStatusCode;
|
||
}
|
||
|
||
logger.Info("测试摄像头连接");
|
||
|
||
var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync();
|
||
|
||
return TypedResults.Ok(new
|
||
{
|
||
isConnected = isConnected,
|
||
success = isSuccess,
|
||
message = message,
|
||
cameraAddress = _videoStreamService.CameraAddress,
|
||
cameraPort = _videoStreamService.CameraPort,
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "HTTP 视频流连接测试失败");
|
||
// 连接失败但不抛出异常,而是返回连接失败的结果
|
||
return TypedResults.Ok(false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置视频流分辨率
|
||
/// </summary>
|
||
/// <param name="request">分辨率配置请求</param>
|
||
/// <returns>设置结果</returns>
|
||
[HttpPost("Resolution")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> SetResolution([FromBody] ResolutionConfigRequest request)
|
||
{
|
||
try
|
||
{
|
||
logger.Info($"设置视频流分辨率为 {request.Width}x{request.Height}");
|
||
|
||
var (isSuccess, message) = await _videoStreamService.SetResolutionAsync(request.Width, request.Height);
|
||
|
||
if (isSuccess)
|
||
{
|
||
return TypedResults.Ok(new
|
||
{
|
||
success = true,
|
||
message = message,
|
||
width = request.Width,
|
||
height = request.Height,
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
else
|
||
{
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = message,
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, $"设置分辨率为 {request.Width}x{request.Height} 失败");
|
||
return TypedResults.InternalServerError($"设置分辨率失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前分辨率
|
||
/// </summary>
|
||
/// <returns>当前分辨率信息</returns>
|
||
[HttpGet("Resolution")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
|
||
public IResult GetCurrentResolution()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("获取当前视频流分辨率");
|
||
|
||
var (width, height) = _videoStreamService.GetCurrentResolution();
|
||
|
||
return TypedResults.Ok(new
|
||
{
|
||
width = width,
|
||
height = height,
|
||
resolution = $"{width}x{height}",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取当前分辨率失败");
|
||
return TypedResults.InternalServerError($"获取当前分辨率失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取支持的分辨率列表
|
||
/// </summary>
|
||
/// <returns>支持的分辨率列表</returns>
|
||
[HttpGet("SupportedResolutions")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
|
||
public IResult GetSupportedResolutions()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("获取支持的分辨率列表");
|
||
|
||
var resolutions = _videoStreamService.GetSupportedResolutions();
|
||
|
||
return TypedResults.Ok(new
|
||
{
|
||
resolutions = resolutions.Select(r => new
|
||
{
|
||
width = r.Width,
|
||
height = r.Height,
|
||
name = r.Name,
|
||
value = $"{r.Width}x{r.Height}"
|
||
}),
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取支持的分辨率列表失败");
|
||
return TypedResults.InternalServerError($"获取支持的分辨率列表失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化摄像头自动对焦功能
|
||
/// </summary>
|
||
/// <returns>初始化结果</returns>
|
||
[HttpPost("InitAutoFocus")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> InitAutoFocus()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("收到初始化自动对焦请求");
|
||
|
||
var result = await _videoStreamService.InitAutoFocusAsync();
|
||
|
||
if (result)
|
||
{
|
||
logger.Info("自动对焦初始化成功");
|
||
return TypedResults.Ok(new
|
||
{
|
||
success = true,
|
||
message = "自动对焦初始化成功",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
else
|
||
{
|
||
logger.Warn("自动对焦初始化失败");
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = "自动对焦初始化失败",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "初始化自动对焦时发生异常");
|
||
return TypedResults.InternalServerError($"初始化自动对焦失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行自动对焦
|
||
/// </summary>
|
||
/// <returns>对焦结果</returns>
|
||
[HttpPost("AutoFocus")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> AutoFocus()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("收到执行自动对焦请求");
|
||
|
||
var result = await _videoStreamService.PerformAutoFocusAsync();
|
||
|
||
if (result)
|
||
{
|
||
logger.Info("自动对焦执行成功");
|
||
return TypedResults.Ok(new
|
||
{
|
||
success = true,
|
||
message = "自动对焦执行成功",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
else
|
||
{
|
||
logger.Warn("自动对焦执行失败");
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = "自动对焦执行失败",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "执行自动对焦时发生异常");
|
||
return TypedResults.InternalServerError($"执行自动对焦失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行一次自动对焦 (GET方式)
|
||
/// </summary>
|
||
/// <returns>对焦结果</returns>
|
||
[HttpGet("Focus")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
|
||
public async Task<IResult> Focus()
|
||
{
|
||
try
|
||
{
|
||
logger.Info("收到执行一次对焦请求 (GET)");
|
||
|
||
// 检查摄像头是否已配置
|
||
if (!_videoStreamService.IsCameraConfigured())
|
||
{
|
||
logger.Warn("摄像头未配置,无法执行对焦");
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = "摄像头未配置,请先配置摄像头连接",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
|
||
var result = await _videoStreamService.PerformAutoFocusAsync();
|
||
|
||
if (result)
|
||
{
|
||
logger.Info("对焦执行成功");
|
||
return TypedResults.Ok(new
|
||
{
|
||
success = true,
|
||
message = "对焦执行成功",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
else
|
||
{
|
||
logger.Warn("对焦执行失败");
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = "对焦执行失败",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "执行对焦时发生异常");
|
||
return TypedResults.InternalServerError($"执行对焦失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|