353 lines
12 KiB
C#
353 lines
12 KiB
C#
using System.ComponentModel.DataAnnotations;
|
||
using Microsoft.AspNetCore.Cors;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using System.Security.Claims;
|
||
using Database;
|
||
using DotNext;
|
||
|
||
/// <summary>
|
||
/// 视频流控制器,支持动态配置摄像头连接
|
||
/// </summary>
|
||
[ApiController]
|
||
[Authorize]
|
||
[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 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; }
|
||
}
|
||
|
||
public class AvailableResolutionsResponse
|
||
{
|
||
public int Width { get; set; }
|
||
public int Height { get; set; }
|
||
public string Name { get; set; } = string.Empty;
|
||
public string Value => $"{Width}x{Height}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化HTTP视频流控制器
|
||
/// </summary>
|
||
/// <param name="videoStreamService">HTTP视频流服务</param>
|
||
public VideoStreamController(server.Services.HttpVideoStreamService videoStreamService)
|
||
{
|
||
logger.Info("创建VideoStreamController,命名空间:{Namespace}", this.GetType().Namespace);
|
||
_videoStreamService = videoStreamService;
|
||
}
|
||
|
||
private Optional<string> TryGetBoardId()
|
||
{
|
||
var userName = User.FindFirstValue(ClaimTypes.Name);
|
||
if (string.IsNullOrEmpty(userName))
|
||
{
|
||
logger.Error("User name not found in claims.");
|
||
return Optional<string>.None;
|
||
}
|
||
|
||
var db = new AppDataConnection();
|
||
if (db == null)
|
||
{
|
||
logger.Error("Database connection failed.");
|
||
return Optional<string>.None;
|
||
}
|
||
|
||
var userRet = db.GetUserByName(userName);
|
||
if (!userRet.IsSuccessful || !userRet.Value.HasValue)
|
||
{
|
||
logger.Error("User not found.");
|
||
return Optional<string>.None;
|
||
}
|
||
|
||
var user = userRet.Value.Value;
|
||
var boardId = user.BoardID;
|
||
if (boardId == Guid.Empty)
|
||
{
|
||
logger.Error("No board bound to this user.");
|
||
return Optional<string>.None;
|
||
}
|
||
|
||
return boardId.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 HTTP 视频流服务状态
|
||
/// </summary>
|
||
/// <returns>服务状态信息</returns>
|
||
[HttpGet("Status")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public IResult GetStatus()
|
||
{
|
||
try
|
||
{
|
||
// 使用HttpVideoStreamService提供的状态信息
|
||
var status = _videoStreamService.GetServiceStatus();
|
||
|
||
// 转换为小写首字母的JSON属性(符合前端惯例)
|
||
return TypedResults.Ok(status);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取 HTTP 视频流服务状态失败");
|
||
return TypedResults.InternalServerError(ex.Message);
|
||
}
|
||
}
|
||
|
||
[HttpGet("MyEndpoint")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||
public IResult MyEndpoint()
|
||
{
|
||
try
|
||
{
|
||
var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
|
||
var endpoint = _videoStreamService.GetVideoEndpoint(boardId);
|
||
|
||
return TypedResults.Ok(endpoint);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取摄像头配置失败");
|
||
return TypedResults.InternalServerError(ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
|
||
var endpoint = _videoStreamService.GetVideoEndpoint(boardId);
|
||
|
||
// 尝试通过HTTP请求检查视频流服务是否可访问
|
||
bool isConnected = false;
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
httpClient.Timeout = TimeSpan.FromSeconds(2); // 设置较短的超时时间
|
||
var response = await httpClient.GetAsync(endpoint.MjpegUrl);
|
||
|
||
// 只要能连接上就认为成功,不管返回状态
|
||
isConnected = response.IsSuccessStatusCode;
|
||
}
|
||
|
||
var ret = await _videoStreamService.TestCameraConnection(boardId);
|
||
|
||
return TypedResults.Ok(ret);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "HTTP 视频流连接测试失败");
|
||
// 连接失败但不抛出异常,而是返回连接失败的结果
|
||
return TypedResults.Ok(false);
|
||
}
|
||
}
|
||
|
||
[HttpPost("DisableTransmission")]
|
||
public async Task<IActionResult> DisableHdmiTransmission()
|
||
{
|
||
try
|
||
{
|
||
var boardId = TryGetBoardId().OrThrow(() => new ArgumentException("Board ID is required"));
|
||
|
||
await _videoStreamService.DisableHdmiTransmissionAsync(boardId.ToString());
|
||
return Ok($"HDMI transmission for board {boardId} disabled.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, $"Failed to disable HDMI transmission for board");
|
||
return StatusCode(500, $"Error disabling HDMI transmission: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
|
||
|
||
var ret = await _videoStreamService.SetResolutionAsync(boardId, request.Width, request.Height);
|
||
|
||
if (ret.IsSuccessful && ret.Value)
|
||
{
|
||
return TypedResults.Ok(new
|
||
{
|
||
success = true,
|
||
message = $"成功设置分辨率为 {request.Width}x{request.Height}",
|
||
width = request.Width,
|
||
height = request.Height,
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
else
|
||
{
|
||
return TypedResults.BadRequest(new
|
||
{
|
||
success = false,
|
||
message = ret.Error?.ToString() ?? "未知错误",
|
||
timestamp = DateTime.Now
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, $"设置分辨率为 {request.Width}x{request.Height} 失败");
|
||
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()
|
||
{
|
||
// (640, 480, "640x480 (VGA)"),
|
||
// (960, 540, "960x540 (qHD)"),
|
||
// (1280, 720, "1280x720 (HD)"),
|
||
// (1280, 960, "1280x960 (SXGA)"),
|
||
// (1920, 1080, "1920x1080 (Full HD)")
|
||
return TypedResults.Ok(new AvailableResolutionsResponse[]
|
||
{
|
||
new AvailableResolutionsResponse { Width = 640, Height = 480, Name = "640x480(VGA)" },
|
||
new AvailableResolutionsResponse { Width = 960, Height = 480, Name = "960x480(qHD)" },
|
||
new AvailableResolutionsResponse { Width = 1280, Height = 720, Name = "1280x720(HD)" },
|
||
new AvailableResolutionsResponse { Width = 1280, Height = 960, Name = "1280x960(SXGA)" },
|
||
new AvailableResolutionsResponse { Width = 1920, Height = 1080, Name = "1920x1080(Full HD)" }
|
||
});
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
|
||
|
||
var result = await _videoStreamService.InitAutoFocusAsync(boardId);
|
||
|
||
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
|
||
{
|
||
var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
|
||
|
||
var result = await _videoStreamService.PerformAutoFocusAsync(boardId);
|
||
|
||
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}");
|
||
}
|
||
}
|
||
}
|