mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
refactor(web): 前端代码规范化重构
- 集中化 HID 类型定义到 types/hid.ts,消除重复代码 - 统一 WebSocket 连接管理,提取共享工具到 types/websocket.ts - 拆分 ConsoleView.vue 关注点,创建 useVideoStream、useHidInput、useConsoleEvents composables - 添加 useConfigPopover 抽象配置弹窗公共逻辑 - 优化视频容器布局,支持动态比例自适应
This commit is contained in:
@@ -225,6 +225,16 @@ impl WsHidHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset HID state when client disconnects to release any held keys/buttons
|
||||
let hid = self.hid_controller.read().clone();
|
||||
if let Some(hid) = hid {
|
||||
if let Err(e) = hid.reset().await {
|
||||
warn!("WsHidHandler: Failed to reset HID on client {} disconnect: {}", client_id, e);
|
||||
} else {
|
||||
debug!("WsHidHandler: HID reset on client {} disconnect", client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle binary HID message
|
||||
|
||||
@@ -138,6 +138,34 @@ impl PixelFormat {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get recommended format for video encoding (WebRTC)
|
||||
///
|
||||
/// Hardware encoding prefers: NV12 > YUYV
|
||||
/// Software encoding prefers: YUYV > NV12
|
||||
///
|
||||
/// Returns None if no suitable format is available
|
||||
pub fn recommended_for_encoding(available: &[PixelFormat], is_hardware: bool) -> Option<PixelFormat> {
|
||||
if is_hardware {
|
||||
// Hardware encoding: NV12 > YUYV
|
||||
if available.contains(&PixelFormat::Nv12) {
|
||||
return Some(PixelFormat::Nv12);
|
||||
}
|
||||
if available.contains(&PixelFormat::Yuyv) {
|
||||
return Some(PixelFormat::Yuyv);
|
||||
}
|
||||
} else {
|
||||
// Software encoding: YUYV > NV12
|
||||
if available.contains(&PixelFormat::Yuyv) {
|
||||
return Some(PixelFormat::Yuyv);
|
||||
}
|
||||
if available.contains(&PixelFormat::Nv12) {
|
||||
return Some(PixelFormat::Nv12);
|
||||
}
|
||||
}
|
||||
// Fallback to any non-compressed format
|
||||
available.iter().find(|f| !f.is_compressed()).copied()
|
||||
}
|
||||
|
||||
/// Get all supported formats
|
||||
pub fn all() -> &'static [PixelFormat] {
|
||||
&[
|
||||
|
||||
@@ -208,6 +208,10 @@ impl VideoStreamManager {
|
||||
|
||||
if current_mode == new_mode {
|
||||
debug!("Already in {:?} mode, no switch needed", new_mode);
|
||||
// Even if mode is the same, ensure video capture is running for WebRTC
|
||||
if new_mode == StreamMode::WebRTC {
|
||||
self.ensure_video_capture_running().await?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -223,6 +227,43 @@ impl VideoStreamManager {
|
||||
result
|
||||
}
|
||||
|
||||
/// Ensure video capture is running (for WebRTC mode)
|
||||
async fn ensure_video_capture_running(self: &Arc<Self>) -> Result<()> {
|
||||
// Initialize streamer if not already initialized
|
||||
if self.streamer.state().await == StreamerState::Uninitialized {
|
||||
info!("Initializing video capture for WebRTC (ensure)");
|
||||
if let Err(e) = self.streamer.init_auto().await {
|
||||
error!("Failed to initialize video capture: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Start video capture if not streaming
|
||||
if self.streamer.state().await != StreamerState::Streaming {
|
||||
info!("Starting video capture for WebRTC (ensure)");
|
||||
if let Err(e) = self.streamer.start().await {
|
||||
error!("Failed to start video capture: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Wait a bit for capture to stabilize
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
// Reconnect frame source to WebRTC
|
||||
if let Some(frame_tx) = self.streamer.frame_sender().await {
|
||||
let (format, resolution, fps) = self.streamer.current_video_config().await;
|
||||
info!(
|
||||
"Reconnecting frame source to WebRTC: {}x{} {:?} @ {}fps",
|
||||
resolution.width, resolution.height, format, fps
|
||||
);
|
||||
self.webrtc_streamer.update_video_config(resolution, format, fps).await;
|
||||
self.webrtc_streamer.set_video_source(frame_tx).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Internal implementation of mode switching (called with lock held)
|
||||
async fn do_switch_mode(self: &Arc<Self>, current_mode: StreamMode, new_mode: StreamMode) -> Result<()> {
|
||||
info!("Switching video mode: {:?} -> {:?}", current_mode, new_mode);
|
||||
@@ -276,6 +317,22 @@ impl VideoStreamManager {
|
||||
match new_mode {
|
||||
StreamMode::Mjpeg => {
|
||||
info!("Starting MJPEG streaming");
|
||||
|
||||
// Auto-switch to MJPEG format if device supports it
|
||||
if let Some(device) = self.streamer.current_device().await {
|
||||
let (current_format, resolution, fps) = self.streamer.current_video_config().await;
|
||||
let available_formats: Vec<PixelFormat> = device.formats.iter().map(|f| f.format).collect();
|
||||
|
||||
// If current format is not MJPEG and device supports MJPEG, switch to it
|
||||
if current_format != PixelFormat::Mjpeg && available_formats.contains(&PixelFormat::Mjpeg) {
|
||||
info!("Auto-switching to MJPEG format for MJPEG mode");
|
||||
let device_path = device.path.to_string_lossy().to_string();
|
||||
if let Err(e) = self.streamer.apply_video_config(&device_path, PixelFormat::Mjpeg, resolution, fps).await {
|
||||
warn!("Failed to auto-switch to MJPEG format: {}, keeping current format", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.streamer.start().await {
|
||||
error!("Failed to start MJPEG streamer: {}", e);
|
||||
return Err(e);
|
||||
@@ -294,6 +351,29 @@ impl VideoStreamManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-switch to non-compressed format if current format is MJPEG/JPEG
|
||||
if let Some(device) = self.streamer.current_device().await {
|
||||
let (current_format, resolution, fps) = self.streamer.current_video_config().await;
|
||||
|
||||
if current_format.is_compressed() {
|
||||
let available_formats: Vec<PixelFormat> = device.formats.iter().map(|f| f.format).collect();
|
||||
|
||||
// Determine if using hardware encoding
|
||||
let is_hardware = self.webrtc_streamer.is_hardware_encoding().await;
|
||||
|
||||
if let Some(recommended) = PixelFormat::recommended_for_encoding(&available_formats, is_hardware) {
|
||||
info!(
|
||||
"Auto-switching from {:?} to {:?} for WebRTC encoding (hardware={})",
|
||||
current_format, recommended, is_hardware
|
||||
);
|
||||
let device_path = device.path.to_string_lossy().to_string();
|
||||
if let Err(e) = self.streamer.apply_video_config(&device_path, recommended, resolution, fps).await {
|
||||
warn!("Failed to auto-switch format for WebRTC: {}, keeping current format", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start video capture if not streaming
|
||||
if self.streamer.state().await != StreamerState::Streaming {
|
||||
info!("Starting video capture for WebRTC");
|
||||
|
||||
@@ -351,6 +351,15 @@ impl PeerConnection {
|
||||
|
||||
/// Close the connection
|
||||
pub async fn close(&self) -> Result<()> {
|
||||
// Reset HID state to release any held keys/buttons
|
||||
if let Some(ref hid) = self.hid_controller {
|
||||
if let Err(e) = hid.reset().await {
|
||||
tracing::warn!("Failed to reset HID on peer {} close: {}", self.session_id, e);
|
||||
} else {
|
||||
tracing::debug!("HID reset on peer {} close", self.session_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref track) = self.video_track {
|
||||
track.stop();
|
||||
}
|
||||
|
||||
@@ -568,6 +568,32 @@ impl WebRtcStreamer {
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if current encoder configuration uses hardware encoding
|
||||
///
|
||||
/// Returns true if:
|
||||
/// - A specific hardware backend is configured, OR
|
||||
/// - Auto mode is used and hardware encoders are available
|
||||
pub async fn is_hardware_encoding(&self) -> bool {
|
||||
let config = self.config.read().await;
|
||||
match config.encoder_backend {
|
||||
Some(backend) => backend.is_hardware(),
|
||||
None => {
|
||||
// Auto mode: check if hardware encoder is available for current codec
|
||||
use crate::video::encoder::registry::{EncoderRegistry, VideoEncoderType};
|
||||
let codec_type = match *self.video_codec.read().await {
|
||||
VideoCodecType::H264 => VideoEncoderType::H264,
|
||||
VideoCodecType::H265 => VideoEncoderType::H265,
|
||||
VideoCodecType::VP8 => VideoEncoderType::VP8,
|
||||
VideoCodecType::VP9 => VideoEncoderType::VP9,
|
||||
};
|
||||
EncoderRegistry::global()
|
||||
.best_encoder(codec_type, false)
|
||||
.map(|e| e.is_hardware)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update ICE configuration (STUN/TURN servers)
|
||||
///
|
||||
/// Note: Changes take effect for new sessions only.
|
||||
|
||||
Reference in New Issue
Block a user