@@ -203,6 +203,12 @@ pub fn run_screen_worker(
203203 }
204204 }
205205 }
206+ // v0.7.2(REQ-151):暂停态也探测播放器信息(时间文本仍在画面)——
207+ // 会话开始时视频已暂停的场景,时长/集号识别不因此缺席
208+ if last_info_probe_at. elapsed ( ) >= Duration :: from_secs ( 10 ) {
209+ last_info_probe_at = Instant :: now ( ) ;
210+ probe_player_info ( & app, & engines, & session_info, & roi_tracker, & latest_frame) ;
211+ }
206212 }
207213 }
208214 std:: thread:: sleep ( Duration :: from_millis ( WORKER_POLL_MS ) ) ;
@@ -354,61 +360,7 @@ pub fn run_screen_worker(
354360 // 失败 → 静默跳过(诚实:不猜不填;下轮再试)
355361 if last_info_probe_at. elapsed ( ) >= Duration :: from_secs ( 10 ) {
356362 last_info_probe_at = Instant :: now ( ) ;
357- let mut probe =
358- latest_frame. lock ( ) . ok ( ) . and_then ( |g| g. clone ( ) ) . unwrap_or ( LatestCapturedFrame {
359- timestamp_ms : 0 ,
360- bgraw : Vec :: new ( ) ,
361- width : 0 ,
362- height : 0 ,
363- } ) ;
364- if !probe. bgraw . is_empty ( ) {
365- if let Some ( rect) = roi_tracker. playback_rect ( ) {
366- let w = probe. width as i32 ;
367- let h = probe. height as i32 ;
368- let q = crate :: capture:: frame_diff:: Rect {
369- left : rect. left . clamp ( 0 , w) ,
370- top : rect. top . clamp ( 0 , h) ,
371- right : rect. right . clamp ( 0 , w) ,
372- bottom : rect. bottom . clamp ( 0 , h) ,
373- } ;
374- if q. width ( ) > 0 && q. height ( ) > 0 {
375- crate :: capture:: frame_diff:: crop_frame (
376- & mut probe. bgraw ,
377- & mut probe. width ,
378- & mut probe. height ,
379- Some ( & q) ,
380- ) ;
381- }
382- }
383- if !probe. bgraw . is_empty ( ) {
384- // P4:OCR 输入缩小(播放器 UI 文字大,质量无损)
385- crate :: capture:: frame_diff:: downscale_bgra (
386- & mut probe. bgraw ,
387- & mut probe. width ,
388- & mut probe. height ,
389- 960 ,
390- ) ;
391- if let Some ( img) = crate :: region_ocr:: bgra_to_rgb_image (
392- & probe. bgraw ,
393- probe. width ,
394- probe. height ,
395- ) {
396- if let Ok ( blocks) = engines. recognize_image ( img) {
397- let text = blocks
398- . iter ( )
399- . map ( |b| b. text . as_str ( ) )
400- . collect :: < Vec < _ > > ( )
401- . join ( " " ) ;
402- if session_info. observe_player_text ( & text) {
403- let _ = app. emit (
404- "live:session-info" ,
405- session_info. snapshot ( ) ,
406- ) ;
407- }
408- }
409- }
410- }
411- }
363+ probe_player_info ( & app, & engines, & session_info, & roi_tracker, & latest_frame) ;
412364 }
413365 }
414366 // 诊断:每 15s 打印采样统计(会话无 OCR 时定位失败阶段;静默失败可见化)
@@ -439,6 +391,62 @@ pub fn run_screen_worker(
439391 eprintln ! ( "[ScreenWorker] 屏幕采样线程退出(会话 {})" , session_id) ;
440392}
441393
394+ /// 播放器信息探测(REQ-151,v0.7.2):播放器区域 OCR 文本(时间对/分P)→
395+ /// 会话信息更新 → 值变化才 emit live:session-info(防 IPC 风暴)。
396+ ///
397+ /// @ai-context: 主采样循环与自动暂停轻量轮询共用——暂停时播放器时间文本仍
398+ /// 在画面,时长/集号识别不因暂停缺席(10s 节流由调用方控制);
399+ /// 无播放区域/OCR 失败 → 静默跳过(诚实:不猜不填,下轮再试)。
400+ fn probe_player_info (
401+ app : & tauri:: AppHandle ,
402+ engines : & crate :: engine:: EnginePool ,
403+ session_info : & crate :: session_info:: SessionInfoCollector ,
404+ roi_tracker : & crate :: region_tracker:: RoiTracker ,
405+ latest_frame : & Arc < Mutex < Option < LatestCapturedFrame > > > ,
406+ ) {
407+ let mut probe = latest_frame
408+ . lock ( )
409+ . ok ( )
410+ . and_then ( |g| g. clone ( ) )
411+ . unwrap_or ( LatestCapturedFrame { timestamp_ms : 0 , bgraw : Vec :: new ( ) , width : 0 , height : 0 } ) ;
412+ if probe. bgraw . is_empty ( ) {
413+ return ;
414+ }
415+ if let Some ( rect) = roi_tracker. playback_rect ( ) {
416+ let w = probe. width as i32 ;
417+ let h = probe. height as i32 ;
418+ let q = crate :: capture:: frame_diff:: Rect {
419+ left : rect. left . clamp ( 0 , w) ,
420+ top : rect. top . clamp ( 0 , h) ,
421+ right : rect. right . clamp ( 0 , w) ,
422+ bottom : rect. bottom . clamp ( 0 , h) ,
423+ } ;
424+ if q. width ( ) > 0 && q. height ( ) > 0 {
425+ crate :: capture:: frame_diff:: crop_frame (
426+ & mut probe. bgraw ,
427+ & mut probe. width ,
428+ & mut probe. height ,
429+ Some ( & q) ,
430+ ) ;
431+ }
432+ }
433+ if probe. bgraw . is_empty ( ) {
434+ return ;
435+ }
436+ // P4:OCR 输入缩小(播放器 UI 文字大,质量无损)
437+ crate :: capture:: frame_diff:: downscale_bgra ( & mut probe. bgraw , & mut probe. width , & mut probe. height , 960 ) ;
438+ let Some ( img) =
439+ crate :: region_ocr:: bgra_to_rgb_image ( & probe. bgraw , probe. width , probe. height )
440+ else {
441+ return ;
442+ } ;
443+ let Ok ( blocks) = engines. recognize_image ( img) else { return } ;
444+ let text = blocks. iter ( ) . map ( |b| b. text . as_str ( ) ) . collect :: < Vec < _ > > ( ) . join ( " " ) ;
445+ if session_info. observe_player_text ( & text) {
446+ let _ = app. emit ( "live:session-info" , session_info. snapshot ( ) ) ;
447+ }
448+ }
449+
442450/// 单测独立文件(保持本文件 ≤300 行,AGENTS.md §3)。
443451#[ cfg( test) ]
444452#[ path = "live_session_frame_tests.rs" ]
0 commit comments