mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 11:42:02 +08:00
fix: 优化视频切换流畅性;修复 OTG HID 功能无法一次保存成功和页面未即刻生效问题
This commit is contained in:
@@ -844,7 +844,7 @@ impl SharedVideoPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the pipeline
|
||||
/// Stop the pipeline (non-blocking, does not wait for capture thread to exit)
|
||||
pub fn stop(&self) {
|
||||
if *self.running_rx.borrow() {
|
||||
let _ = self.running.send(false);
|
||||
@@ -854,6 +854,39 @@ impl SharedVideoPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the pipeline and wait for the capture thread to fully exit.
|
||||
///
|
||||
/// This ensures the V4L2 device is released before returning, which is
|
||||
/// necessary when another consumer (e.g. MJPEG streamer) needs to open
|
||||
/// the same device immediately after.
|
||||
pub async fn stop_and_wait(&self, timeout: std::time::Duration) {
|
||||
self.stop();
|
||||
let mut rx = self.running_watch();
|
||||
if !*rx.borrow() {
|
||||
// Capture thread may still be running from a previous `stop()` call.
|
||||
// Wait for the "Video pipeline stopped" log (thread sets running=false
|
||||
// at exit), unless it already happened.
|
||||
}
|
||||
let deadline = tokio::time::Instant::now() + timeout;
|
||||
loop {
|
||||
if !self.running_flag.load(Ordering::Acquire) {
|
||||
// Flag is cleared, but the capture thread may still be unwinding
|
||||
// (dropping the V4L2 stream). Give it a brief moment.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
break;
|
||||
}
|
||||
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
|
||||
if remaining.is_zero() {
|
||||
warn!(
|
||||
"Timed out waiting for video pipeline to stop after {:?}",
|
||||
timeout
|
||||
);
|
||||
break;
|
||||
}
|
||||
let _ = tokio::time::timeout(remaining, rx.changed()).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set bitrate using preset
|
||||
pub async fn set_bitrate_preset(
|
||||
&self,
|
||||
|
||||
@@ -404,8 +404,11 @@ impl VideoStreamManager {
|
||||
}
|
||||
}
|
||||
StreamMode::WebRTC => {
|
||||
info!("Closing all WebRTC sessions");
|
||||
let closed = self.webrtc_streamer.close_all_sessions().await;
|
||||
info!("Closing all WebRTC sessions and releasing capture device");
|
||||
let closed = self
|
||||
.webrtc_streamer
|
||||
.close_all_sessions_and_release_device()
|
||||
.await;
|
||||
if closed > 0 {
|
||||
info!("Closed {} WebRTC sessions", closed);
|
||||
}
|
||||
@@ -781,6 +784,61 @@ impl VideoStreamManager {
|
||||
self.webrtc_streamer.request_keyframe().await
|
||||
}
|
||||
|
||||
/// Notify frontend about a codec-only switch (WebRTC mode unchanged, codec changed).
|
||||
///
|
||||
/// `set_video_codec` already rebuilt the pipeline synchronously, so we just
|
||||
/// emit the events the frontend waits on: `StreamModeChanged`, `WebRTCReady`,
|
||||
/// and `StreamModeReady`.
|
||||
///
|
||||
/// Events are spawned asynchronously so the HTTP response (carrying the
|
||||
/// `transition_id`) reaches the client before the WebSocket events, giving
|
||||
/// the frontend time to call `registerTransition()` first.
|
||||
pub async fn notify_codec_switch(
|
||||
self: &Arc<Self>,
|
||||
transition_id: &str,
|
||||
new_codec_str: &str,
|
||||
previous_codec_str: &str,
|
||||
) {
|
||||
let manager = Arc::clone(self);
|
||||
let transition_id = transition_id.to_string();
|
||||
let new_codec = new_codec_str.to_string();
|
||||
let prev_codec = previous_codec_str.to_string();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Small yield to ensure the HTTP response is flushed first.
|
||||
tokio::task::yield_now().await;
|
||||
|
||||
manager
|
||||
.publish_event(SystemEvent::StreamModeChanged {
|
||||
transition_id: Some(transition_id.clone()),
|
||||
mode: new_codec.clone(),
|
||||
previous_mode: prev_codec.clone(),
|
||||
})
|
||||
.await;
|
||||
|
||||
let is_hardware = manager.webrtc_streamer.is_hardware_encoding().await;
|
||||
manager
|
||||
.publish_event(SystemEvent::WebRTCReady {
|
||||
transition_id: Some(transition_id.clone()),
|
||||
codec: new_codec.clone(),
|
||||
hardware: is_hardware,
|
||||
})
|
||||
.await;
|
||||
|
||||
manager
|
||||
.publish_event(SystemEvent::StreamModeReady {
|
||||
transition_id: transition_id.clone(),
|
||||
mode: new_codec.clone(),
|
||||
})
|
||||
.await;
|
||||
|
||||
info!(
|
||||
"Codec switch notified: {} -> {} (transition: {})",
|
||||
prev_codec, new_codec, transition_id
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Publish event to event bus
|
||||
async fn publish_event(&self, event: SystemEvent) {
|
||||
if let Some(ref events) = *self.events.read().await {
|
||||
|
||||
Reference in New Issue
Block a user