add: 添加分辨率设置逻辑
This commit is contained in:
parent
b913f58f13
commit
352ee1f4f2
|
@ -33,6 +33,26 @@ public class VideoStreamController : ControllerBase
|
||||||
public int Port { get; set; }
|
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>
|
/// <summary>
|
||||||
/// 初始化HTTP视频流控制器
|
/// 初始化HTTP视频流控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -233,4 +253,116 @@ public class VideoStreamController : ControllerBase
|
||||||
return TypedResults.Ok(false);
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,19 +26,13 @@ class Camera
|
||||||
|
|
||||||
const uint CAM_I2C_ADDR = 0x3C;
|
const uint CAM_I2C_ADDR = 0x3C;
|
||||||
const Peripherals.I2cClient.I2cProtocol CAM_PROTO = Peripherals.I2cClient.I2cProtocol.SCCB;
|
const Peripherals.I2cClient.I2cProtocol CAM_PROTO = Peripherals.I2cClient.I2cProtocol.SCCB;
|
||||||
const UInt16 H_START = 0; //default: 0
|
|
||||||
const UInt16 V_START = 0; //default: 0
|
|
||||||
const UInt16 DVPHO = 640; //default: 2592 (0xA20)
|
|
||||||
const UInt16 DVPVO = 480; //default: 1944 (0x798)
|
|
||||||
const UInt16 H_END = H_START + 1500 - 1; //default: 2624-1 (0xA3F)
|
|
||||||
const UInt16 V_END = V_START + 1300 - 1; //default: 1951-1 (0x79F)
|
|
||||||
const UInt16 HTS = 1700; //default: 2844 (0xB1C)
|
|
||||||
const UInt16 VTS = 1500; //default: 1968 (0x7B0)
|
|
||||||
const UInt16 H_OFFSET = 16; //default: 16 (0x10)
|
|
||||||
const UInt16 V_OFFSET = 4; //default: 4 (0x04)
|
|
||||||
const byte PLL_MUX = 10;
|
const byte PLL_MUX = 10;
|
||||||
const UInt32 FrameAddr = 0x00;
|
const UInt32 FrameAddr = 0x00;
|
||||||
const UInt32 FrameLength = DVPHO * DVPVO * 16 / 32;
|
|
||||||
|
// 动态分辨率参数
|
||||||
|
private UInt16 _currentWidth = 640;
|
||||||
|
private UInt16 _currentHeight = 480;
|
||||||
|
private UInt32 _currentFrameLength = 640 * 480 * 2 / 4; // RGB565格式,2字节/像素,按4字节对齐
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -183,8 +177,7 @@ class Camera
|
||||||
this.ep,
|
this.ep,
|
||||||
this.taskID, // taskID
|
this.taskID, // taskID
|
||||||
FrameAddr,
|
FrameAddr,
|
||||||
// ((int)FrameLength),
|
(int)(_currentWidth * _currentHeight * 2), // 使用当前分辨率的动态大小
|
||||||
1280*720/2,
|
|
||||||
this.timeout);
|
this.timeout);
|
||||||
|
|
||||||
if (!result.IsSuccessful)
|
if (!result.IsSuccessful)
|
||||||
|
@ -423,6 +416,67 @@ class Camera
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换摄像头分辨率
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="width">宽度</param>
|
||||||
|
/// <param name="height">高度</param>
|
||||||
|
/// <returns>配置结果</returns>
|
||||||
|
public async ValueTask<Result<bool>> ChangeResolution(int width, int height)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.Info($"正在切换摄像头分辨率到 {width}x{height}");
|
||||||
|
|
||||||
|
Result<bool> result;
|
||||||
|
switch ($"{width}x{height}")
|
||||||
|
{
|
||||||
|
case "640x480":
|
||||||
|
result = await ConfigureResolution640x480();
|
||||||
|
break;
|
||||||
|
case "1280x720":
|
||||||
|
result = await ConfigureResolution1280x720();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.Error($"不支持的分辨率: {width}x{height}");
|
||||||
|
return new(new ArgumentException($"不支持的分辨率: {width}x{height}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.IsSuccessful)
|
||||||
|
{
|
||||||
|
_currentWidth = (UInt16)width;
|
||||||
|
_currentHeight = (UInt16)height;
|
||||||
|
_currentFrameLength = (UInt32)(width * height * 2 / 4); // RGB565格式,按4字节对齐
|
||||||
|
logger.Info($"摄像头分辨率已切换到 {width}x{height}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, $"切换分辨率到 {width}x{height} 时发生错误");
|
||||||
|
return new(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前分辨率
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>当前分辨率(宽度, 高度)</returns>
|
||||||
|
public (int Width, int Height) GetCurrentResolution()
|
||||||
|
{
|
||||||
|
return (_currentWidth, _currentHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前帧长度
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>当前帧长度</returns>
|
||||||
|
public UInt32 GetCurrentFrameLength()
|
||||||
|
{
|
||||||
|
return _currentFrameLength;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 复位摄像头
|
/// 复位摄像头
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -82,8 +82,11 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
private HttpListener? _httpListener;
|
private HttpListener? _httpListener;
|
||||||
private readonly int _serverPort = 8080;
|
private readonly int _serverPort = 8080;
|
||||||
private readonly int _frameRate = 30; // 30 FPS
|
private readonly int _frameRate = 30; // 30 FPS
|
||||||
private readonly int _frameWidth = 1280;
|
|
||||||
private readonly int _frameHeight = 720;
|
// 动态分辨率配置
|
||||||
|
private int _frameWidth = 640; // 默认640x480
|
||||||
|
private int _frameHeight = 480;
|
||||||
|
private readonly object _resolutionLock = new object();
|
||||||
|
|
||||||
// 摄像头客户端
|
// 摄像头客户端
|
||||||
private Camera? _camera;
|
private Camera? _camera;
|
||||||
|
@ -439,8 +442,16 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
// 获取当前帧
|
// 获取当前帧
|
||||||
var imageData = await GetFPGAImageData();
|
var imageData = await GetFPGAImageData();
|
||||||
|
|
||||||
|
// 获取当前分辨率
|
||||||
|
int currentWidth, currentHeight;
|
||||||
|
lock (_resolutionLock)
|
||||||
|
{
|
||||||
|
currentWidth = _frameWidth;
|
||||||
|
currentHeight = _frameHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
|
// 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
|
||||||
var jpegResult = Common.Image.ConvertRGB24ToJpeg(imageData, _frameWidth, _frameHeight, 80);
|
var jpegResult = Common.Image.ConvertRGB24ToJpeg(imageData, currentWidth, currentHeight, 80);
|
||||||
if (!jpegResult.IsSuccessful)
|
if (!jpegResult.IsSuccessful)
|
||||||
{
|
{
|
||||||
logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
|
logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
|
||||||
|
@ -647,6 +658,14 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 获取当前分辨率
|
||||||
|
int currentWidth, currentHeight;
|
||||||
|
lock (_resolutionLock)
|
||||||
|
{
|
||||||
|
currentWidth = _frameWidth;
|
||||||
|
currentHeight = _frameHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// 从摄像头读取帧数据
|
// 从摄像头读取帧数据
|
||||||
var readStartTime = DateTime.UtcNow;
|
var readStartTime = DateTime.UtcNow;
|
||||||
var result = await currentCamera.ReadFrame();
|
var result = await currentCamera.ReadFrame();
|
||||||
|
@ -662,15 +681,15 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
var rgb565Data = result.Value;
|
var rgb565Data = result.Value;
|
||||||
|
|
||||||
// 验证数据长度是否正确
|
// 验证数据长度是否正确
|
||||||
if (!Common.Image.ValidateImageDataLength(rgb565Data, _frameWidth, _frameHeight, 2))
|
if (!Common.Image.ValidateImageDataLength(rgb565Data, currentWidth, currentHeight, 2))
|
||||||
{
|
{
|
||||||
logger.Warn("摄像头数据长度不匹配,期望: {Expected}, 实际: {Actual}",
|
logger.Warn("摄像头数据长度不匹配,期望: {Expected}, 实际: {Actual}",
|
||||||
_frameWidth * _frameHeight * 2, rgb565Data.Length);
|
currentWidth * currentHeight * 2, rgb565Data.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 RGB565 转换为 RGB24
|
// 将 RGB565 转换为 RGB24
|
||||||
var convertStartTime = DateTime.UtcNow;
|
var convertStartTime = DateTime.UtcNow;
|
||||||
var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, _frameWidth, _frameHeight, isLittleEndian: false);
|
var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, currentWidth, currentHeight, isLittleEndian: false);
|
||||||
var convertEndTime = DateTime.UtcNow;
|
var convertEndTime = DateTime.UtcNow;
|
||||||
var convertTime = (convertEndTime - convertStartTime).TotalMilliseconds;
|
var convertTime = (convertEndTime - convertStartTime).TotalMilliseconds;
|
||||||
|
|
||||||
|
@ -708,8 +727,16 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前分辨率
|
||||||
|
int currentWidth, currentHeight;
|
||||||
|
lock (_resolutionLock)
|
||||||
|
{
|
||||||
|
currentWidth = _frameWidth;
|
||||||
|
currentHeight = _frameHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
|
// 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
|
||||||
var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameData, _frameWidth, _frameHeight, 80);
|
var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameData, currentWidth, currentHeight, 80);
|
||||||
if (!jpegResult.IsSuccessful)
|
if (!jpegResult.IsSuccessful)
|
||||||
{
|
{
|
||||||
logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
|
logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
|
||||||
|
@ -904,4 +931,102 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置视频流分辨率
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="width">宽度</param>
|
||||||
|
/// <param name="height">高度</param>
|
||||||
|
/// <returns>设置结果</returns>
|
||||||
|
public async Task<(bool IsSuccess, string Message)> SetResolutionAsync(int width, int height)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.Info($"正在设置视频流分辨率为 {width}x{height}");
|
||||||
|
|
||||||
|
// 验证分辨率
|
||||||
|
if (!IsSupportedResolution(width, height))
|
||||||
|
{
|
||||||
|
var message = $"不支持的分辨率: {width}x{height},支持的分辨率: 640x480, 1280x720";
|
||||||
|
logger.Error(message);
|
||||||
|
return (false, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera? currentCamera = null;
|
||||||
|
lock (_cameraLock)
|
||||||
|
{
|
||||||
|
currentCamera = _camera;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCamera == null)
|
||||||
|
{
|
||||||
|
var message = "摄像头未配置,无法设置分辨率";
|
||||||
|
logger.Error(message);
|
||||||
|
return (false, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置摄像头分辨率
|
||||||
|
var cameraResult = await currentCamera.ChangeResolution(width, height);
|
||||||
|
if (!cameraResult.IsSuccessful)
|
||||||
|
{
|
||||||
|
var message = $"设置摄像头分辨率失败: {cameraResult.Error}";
|
||||||
|
logger.Error(message);
|
||||||
|
return (false, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新HTTP服务的分辨率配置
|
||||||
|
lock (_resolutionLock)
|
||||||
|
{
|
||||||
|
_frameWidth = width;
|
||||||
|
_frameHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
var successMessage = $"视频流分辨率已成功设置为 {width}x{height}";
|
||||||
|
logger.Info(successMessage);
|
||||||
|
return (true, successMessage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var message = $"设置分辨率时发生错误: {ex.Message}";
|
||||||
|
logger.Error(ex, message);
|
||||||
|
return (false, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前分辨率
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>当前分辨率(宽度, 高度)</returns>
|
||||||
|
public (int Width, int Height) GetCurrentResolution()
|
||||||
|
{
|
||||||
|
lock (_resolutionLock)
|
||||||
|
{
|
||||||
|
return (_frameWidth, _frameHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查是否支持该分辨率
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="width">宽度</param>
|
||||||
|
/// <param name="height">高度</param>
|
||||||
|
/// <returns>是否支持</returns>
|
||||||
|
private bool IsSupportedResolution(int width, int height)
|
||||||
|
{
|
||||||
|
var resolution = $"{width}x{height}";
|
||||||
|
return resolution == "640x480" || resolution == "1280x720";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取支持的分辨率列表
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>支持的分辨率列表</returns>
|
||||||
|
public List<(int Width, int Height, string Name)> GetSupportedResolutions()
|
||||||
|
{
|
||||||
|
return new List<(int, int, string)>
|
||||||
|
{
|
||||||
|
(640, 480, "640x480 (VGA)"),
|
||||||
|
(1280, 720, "1280x720 (HD)")
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
144
src/APIClient.ts
144
src/APIClient.ts
|
@ -311,6 +311,150 @@ export class VideoStreamClient {
|
||||||
}
|
}
|
||||||
return Promise.resolve<boolean>(null as any);
|
return Promise.resolve<boolean>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置视频流分辨率
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
setResolution(width: number, height: number): Promise<boolean> {
|
||||||
|
let url_ = this.baseUrl + "/api/VideoStream/SetResolution";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
const content_ = JSON.stringify({ width: width, height: height });
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
body: content_,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processSetResolution(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processSetResolution(response: Response): Promise<boolean> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result500: any = null;
|
||||||
|
let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result500 = Exception.fromJS(resultData500);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result500);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<boolean>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前视频流分辨率
|
||||||
|
* @return 当前分辨率信息
|
||||||
|
*/
|
||||||
|
getCurrentResolution(): Promise<any> {
|
||||||
|
let url_ = this.baseUrl + "/api/VideoStream/GetCurrentResolution";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processGetCurrentResolution(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processGetCurrentResolution(response: Response): Promise<any> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result500: any = null;
|
||||||
|
let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result500 = Exception.fromJS(resultData500);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result500);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<any>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支持的视频流分辨率列表
|
||||||
|
* @return 支持的分辨率列表
|
||||||
|
*/
|
||||||
|
getSupportedResolutions(): Promise<any[]> {
|
||||||
|
let url_ = this.baseUrl + "/api/VideoStream/GetSupportedResolutions";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processGetSupportedResolutions(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processGetSupportedResolutions(response: Response): Promise<any[]> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result500: any = null;
|
||||||
|
let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result500 = Exception.fromJS(resultData500);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result500);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<any[]>(null as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BsdlParserClient {
|
export class BsdlParserClient {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
控制面板
|
控制面板
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<!-- 服务状态 -->
|
<!-- 服务状态 -->
|
||||||
<div class="stats shadow">
|
<div class="stats shadow">
|
||||||
<div class="stat bg-base-100">
|
<div class="stat bg-base-100">
|
||||||
|
@ -42,6 +42,38 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 分辨率控制 -->
|
||||||
|
<div class="stats shadow">
|
||||||
|
<div class="stat bg-base-100">
|
||||||
|
<div class="stat-figure text-info">
|
||||||
|
<Settings class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div class="stat-title">分辨率设置</div>
|
||||||
|
<div class="stat-value text-sm">
|
||||||
|
<select
|
||||||
|
class="select select-sm select-bordered max-w-xs"
|
||||||
|
v-model="selectedResolution"
|
||||||
|
@change="changeResolution"
|
||||||
|
:disabled="changingResolution"
|
||||||
|
>
|
||||||
|
<option v-for="res in supportedResolutions" :key="`${res.width}x${res.height}`" :value="res">
|
||||||
|
{{ res.width }}×{{ res.height }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="stat-desc">
|
||||||
|
<button
|
||||||
|
class="btn btn-xs btn-outline btn-info mt-1"
|
||||||
|
@click="refreshResolutions"
|
||||||
|
:disabled="loadingResolutions"
|
||||||
|
>
|
||||||
|
<RefreshCw v-if="loadingResolutions" class="animate-spin h-3 w-3" />
|
||||||
|
{{ loadingResolutions ? "刷新中..." : "刷新" }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 连接数 -->
|
<!-- 连接数 -->
|
||||||
<div class="stats shadow">
|
<div class="stats shadow">
|
||||||
<div class="stat bg-base-100 relative">
|
<div class="stat bg-base-100 relative">
|
||||||
|
@ -321,6 +353,15 @@ const isPlaying = ref(false);
|
||||||
const hasVideoError = ref(false);
|
const hasVideoError = ref(false);
|
||||||
const videoStatus = ref('点击"播放视频流"按钮开始查看实时视频');
|
const videoStatus = ref('点击"播放视频流"按钮开始查看实时视频');
|
||||||
|
|
||||||
|
// 分辨率相关状态
|
||||||
|
const changingResolution = ref(false);
|
||||||
|
const loadingResolutions = ref(false);
|
||||||
|
const selectedResolution = ref({ width: 640, height: 480 });
|
||||||
|
const supportedResolutions = ref([
|
||||||
|
{ width: 640, height: 480 },
|
||||||
|
{ width: 1280, height: 720 }
|
||||||
|
]);
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const statusInfo = ref({
|
const statusInfo = ref({
|
||||||
isRunning: false,
|
isRunning: false,
|
||||||
|
@ -549,6 +590,69 @@ const startStream = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 分辨率相关方法
|
||||||
|
// 获取支持的分辨率列表
|
||||||
|
const refreshResolutions = async () => {
|
||||||
|
loadingResolutions.value = true;
|
||||||
|
try {
|
||||||
|
addLog("info", "正在获取支持的分辨率列表...");
|
||||||
|
const resolutions = await videoClient.getSupportedResolutions();
|
||||||
|
supportedResolutions.value = resolutions;
|
||||||
|
|
||||||
|
// 获取当前分辨率
|
||||||
|
const currentRes = await videoClient.getCurrentResolution();
|
||||||
|
selectedResolution.value = currentRes;
|
||||||
|
|
||||||
|
addLog("success", "分辨率列表获取成功");
|
||||||
|
} catch (error) {
|
||||||
|
addLog("error", `获取分辨率列表失败: ${error}`);
|
||||||
|
console.error("获取分辨率列表失败:", error);
|
||||||
|
} finally {
|
||||||
|
loadingResolutions.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换分辨率
|
||||||
|
const changeResolution = async () => {
|
||||||
|
if (!selectedResolution.value) return;
|
||||||
|
|
||||||
|
changingResolution.value = true;
|
||||||
|
const wasPlaying = isPlaying.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
addLog("info", `正在切换分辨率到 ${selectedResolution.value.width}×${selectedResolution.value.height}...`);
|
||||||
|
|
||||||
|
// 如果正在播放,先停止视频流
|
||||||
|
if (wasPlaying) {
|
||||||
|
stopStream();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置新分辨率
|
||||||
|
const success = await videoClient.setResolution(selectedResolution.value.width, selectedResolution.value.height);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 刷新流信息
|
||||||
|
await refreshStatus();
|
||||||
|
|
||||||
|
// 如果之前在播放,重新启动视频流
|
||||||
|
if (wasPlaying) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500)); // 短暂延迟
|
||||||
|
await startStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
addLog("success", `分辨率已切换到 ${selectedResolution.value.width}×${selectedResolution.value.height}`);
|
||||||
|
} else {
|
||||||
|
addLog("error", "分辨率切换失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
addLog("error", `分辨率切换失败: ${error}`);
|
||||||
|
console.error("分辨率切换失败:", error);
|
||||||
|
} finally {
|
||||||
|
changingResolution.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 停止视频流
|
// 停止视频流
|
||||||
const stopStream = () => {
|
const stopStream = () => {
|
||||||
try {
|
try {
|
||||||
|
@ -574,6 +678,7 @@ const stopStream = () => {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
addLog("info", "HTTP 视频流页面已加载");
|
addLog("info", "HTTP 视频流页面已加载");
|
||||||
await refreshStatus();
|
await refreshStatus();
|
||||||
|
await refreshResolutions(); // 初始化分辨率信息
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
Loading…
Reference in New Issue