docs: 战略规划和管理架构优化
- 新增 STRATEGY.md: 三年战略规划、技术路线、团队策略 - 新增 MILESTONES.md: 详细里程碑和时间表(M1.1-M1.4) - 新增 CODE_REVIEW.md: 代码审核标准和流程 - 组建管理班子: 新增 PM 刘建国,优化管理架构 - 丰富团队成员背景: 补充所有成员的教育经历、工作经验、技能树 - 解锁多线程思考能力: 团队成员可使用 kilo 命令并行探索 - 更新工作流程: CEO → PM → 开发团队,两级审核制度 - 修正 kilo 调用方式: 不使用 -f 参数,在消息中指示读取文件
This commit is contained in:
@@ -1,27 +1,91 @@
|
||||
//! VideoPlugin — 视频播放引擎
|
||||
//!
|
||||
//! 基于 OpenCV 的视频播放,支持状态机驱动、帧变换、过渡效果。
|
||||
//! Phase 1 核心:迁移旧 video_processor.rs + state_machine.rs
|
||||
|
||||
pub mod processor;
|
||||
pub mod state_machine;
|
||||
|
||||
use crate::core::message::Message;
|
||||
use crate::core::plugin::{Plugin, PluginContext, PluginInfo, Platform};
|
||||
use anyhow::Result;
|
||||
use crate::core::message::{Destination, Envelope, Message, PlayerCommand, PlayerStatusData};
|
||||
use crate::core::plugin::{Platform, Plugin, PluginContext, PluginInfo};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use opencv::highgui;
|
||||
use processor::VideoProcessor;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
pub struct VideoPlugin {
|
||||
ctx: Option<PluginContext>,
|
||||
processor: Option<Arc<Mutex<VideoProcessor>>>,
|
||||
worker: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl VideoPlugin {
|
||||
pub fn new() -> Self {
|
||||
Self { ctx: None }
|
||||
Self {
|
||||
ctx: None,
|
||||
processor: None,
|
||||
worker: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn processor(&self) -> Result<&Arc<Mutex<VideoProcessor>>> {
|
||||
self.processor
|
||||
.as_ref()
|
||||
.context("video processor is not initialized")
|
||||
}
|
||||
|
||||
fn publish_status(&self) {
|
||||
let Some(ctx) = &self.ctx else {
|
||||
return;
|
||||
};
|
||||
let Some(processor) = &self.processor else {
|
||||
return;
|
||||
};
|
||||
|
||||
let status = match processor.lock() {
|
||||
Ok(processor) => processor.status(),
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if let Err(error) = ctx.tx.send(Envelope {
|
||||
from: self.id(),
|
||||
to: Destination::Broadcast,
|
||||
message: Message::PlayerStatus(status),
|
||||
}) {
|
||||
eprintln!("[VideoPlugin] failed to publish status: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn publish_state_changed(&self, old_state: Option<String>, new_state: Option<String>) {
|
||||
let Some(ctx) = &self.ctx else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (Some(old_state), Some(new_state)) = (old_state, new_state) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if old_state == new_state {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(error) = ctx.tx.send(Envelope {
|
||||
from: self.id(),
|
||||
to: Destination::Broadcast,
|
||||
message: Message::StateChanged {
|
||||
old_state,
|
||||
new_state,
|
||||
},
|
||||
}) {
|
||||
eprintln!("[VideoPlugin] failed to publish state change: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for VideoPlugin {
|
||||
fn id(&self) -> &'static str { "video" }
|
||||
fn id(&self) -> &'static str {
|
||||
"video"
|
||||
}
|
||||
|
||||
fn info(&self) -> PluginInfo {
|
||||
PluginInfo {
|
||||
@@ -38,17 +102,246 @@ impl Plugin for VideoPlugin {
|
||||
}
|
||||
|
||||
fn start(&mut self) -> Result<()> {
|
||||
// TODO: Commit 4 实现
|
||||
let ctx = self
|
||||
.ctx
|
||||
.as_ref()
|
||||
.context("video plugin context is not initialized")?;
|
||||
|
||||
let processor = Arc::new(Mutex::new(VideoProcessor::new((*ctx.config).clone())?));
|
||||
let worker_processor = Arc::clone(&processor);
|
||||
let tx = ctx.tx.clone();
|
||||
let handle = std::thread::spawn(move || {
|
||||
if let Err(error) = run_processor_loop(worker_processor, tx) {
|
||||
eprintln!("[VideoPlugin] playback loop failed: {error}");
|
||||
}
|
||||
});
|
||||
|
||||
self.processor = Some(processor);
|
||||
self.worker = Some(handle);
|
||||
|
||||
ctx.tx.send(Envelope {
|
||||
from: self.id(),
|
||||
to: Destination::Manager,
|
||||
message: Message::PluginReady(self.id()),
|
||||
})?;
|
||||
|
||||
self.publish_status();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, _msg: Message) -> Result<()> {
|
||||
// TODO: Commit 4 实现
|
||||
fn handle_message(&mut self, msg: Message) -> Result<()> {
|
||||
match msg {
|
||||
Message::PlayerCommand(command) => {
|
||||
let processor = Arc::clone(self.processor()?);
|
||||
let mut processor = lock_processor(&processor)?;
|
||||
match command {
|
||||
PlayerCommand::Play => {
|
||||
processor.play()?;
|
||||
}
|
||||
PlayerCommand::Pause => {
|
||||
processor.pause();
|
||||
}
|
||||
PlayerCommand::Next => {
|
||||
processor.next()?;
|
||||
}
|
||||
PlayerCommand::Previous => {
|
||||
processor.previous()?;
|
||||
}
|
||||
PlayerCommand::Goto(index) => {
|
||||
processor.goto(index)?;
|
||||
}
|
||||
PlayerCommand::ChangeScene(name) => {
|
||||
processor.change_scene(&name)?;
|
||||
}
|
||||
}
|
||||
drop(processor);
|
||||
self.publish_status();
|
||||
}
|
||||
Message::Trigger { name, value } => {
|
||||
let processor = Arc::clone(self.processor()?);
|
||||
let mut processor = lock_processor(&processor)?;
|
||||
let old_state = processor.current_state().map(str::to_owned);
|
||||
processor.trigger(&name, &value)?;
|
||||
let new_state = processor.current_state().map(str::to_owned);
|
||||
drop(processor);
|
||||
self.publish_state_changed(old_state, new_state);
|
||||
self.publish_status();
|
||||
}
|
||||
Message::ConfigReloaded(config) => {
|
||||
let processor = Arc::new(Mutex::new(VideoProcessor::new((*config).clone())?));
|
||||
if let Some(old) = self.processor.replace(Arc::clone(&processor)) {
|
||||
if let Ok(mut old) = old.lock() {
|
||||
let _ = old.stop();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(handle) = self.worker.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
|
||||
let worker_processor = Arc::clone(&processor);
|
||||
let tx = self
|
||||
.ctx
|
||||
.as_ref()
|
||||
.context("video plugin context is not initialized")?
|
||||
.tx
|
||||
.clone();
|
||||
self.worker = Some(std::thread::spawn(move || {
|
||||
if let Err(error) = run_processor_loop(worker_processor, tx) {
|
||||
eprintln!("[VideoPlugin] playback loop failed after reload: {error}");
|
||||
}
|
||||
}));
|
||||
self.publish_status();
|
||||
}
|
||||
Message::Shutdown => {
|
||||
self.stop()?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> Result<()> {
|
||||
// TODO: Commit 4 实现
|
||||
if let Some(processor) = &self.processor {
|
||||
if let Ok(mut processor) = processor.lock() {
|
||||
let _ = processor.stop();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(handle) = self.worker.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
|
||||
self.publish_status();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_processor_loop(
|
||||
processor: Arc<Mutex<VideoProcessor>>,
|
||||
tx: std::sync::mpsc::Sender<Envelope>,
|
||||
) -> Result<()> {
|
||||
{
|
||||
let mut processor = lock_processor(&processor)?;
|
||||
processor.start()?;
|
||||
}
|
||||
|
||||
publish_processor_status(&tx, &processor)?;
|
||||
|
||||
loop {
|
||||
let (outcome, old_state, new_state, old_status, new_status) = {
|
||||
let mut processor = lock_processor(&processor)?;
|
||||
let old_state = processor.current_state().map(str::to_owned);
|
||||
let old_status = processor.status();
|
||||
let outcome = processor.step()?;
|
||||
let new_state = processor.current_state().map(str::to_owned);
|
||||
let new_status = processor.status();
|
||||
(outcome, old_state, new_state, old_status, new_status)
|
||||
};
|
||||
|
||||
if let Some(frame) = outcome.frame {
|
||||
let processor = lock_processor(&processor)?;
|
||||
processor.display_frame(&outcome.window_name, &frame)?;
|
||||
}
|
||||
|
||||
if old_state != new_state {
|
||||
publish_state_changed(&tx, old_state, new_state)?;
|
||||
}
|
||||
|
||||
if status_changed(&old_status, &new_status) {
|
||||
publish_status_message(&tx, new_status.clone())?;
|
||||
}
|
||||
|
||||
if !outcome.keep_running {
|
||||
break;
|
||||
}
|
||||
|
||||
let key = highgui::wait_key(outcome.delay)?;
|
||||
let (old_state, new_state, old_status, new_status) = {
|
||||
let mut processor = lock_processor(&processor)?;
|
||||
let old_state = processor.current_state().map(str::to_owned);
|
||||
let old_status = processor.status();
|
||||
processor.handle_key_code(key)?;
|
||||
let new_state = processor.current_state().map(str::to_owned);
|
||||
let new_status = processor.status();
|
||||
(old_state, new_state, old_status, new_status)
|
||||
};
|
||||
|
||||
if old_state != new_state {
|
||||
publish_state_changed(&tx, old_state, new_state)?;
|
||||
}
|
||||
|
||||
if status_changed(&old_status, &new_status) {
|
||||
publish_status_message(&tx, new_status.clone())?;
|
||||
}
|
||||
|
||||
if !new_status.running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut processor = lock_processor(&processor)?;
|
||||
processor.stop()
|
||||
}
|
||||
|
||||
fn publish_processor_status(
|
||||
tx: &std::sync::mpsc::Sender<Envelope>,
|
||||
processor: &Arc<Mutex<VideoProcessor>>,
|
||||
) -> Result<()> {
|
||||
let status = lock_processor(processor)?.status();
|
||||
publish_status_message(tx, status)
|
||||
}
|
||||
|
||||
fn publish_status_message(
|
||||
tx: &std::sync::mpsc::Sender<Envelope>,
|
||||
status: PlayerStatusData,
|
||||
) -> Result<()> {
|
||||
tx.send(Envelope {
|
||||
from: "video",
|
||||
to: Destination::Broadcast,
|
||||
message: Message::PlayerStatus(status),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_state_changed(
|
||||
tx: &std::sync::mpsc::Sender<Envelope>,
|
||||
old_state: Option<String>,
|
||||
new_state: Option<String>,
|
||||
) -> Result<()> {
|
||||
let (Some(old_state), Some(new_state)) = (old_state, new_state) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if old_state == new_state {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tx.send(Envelope {
|
||||
from: "video",
|
||||
to: Destination::Broadcast,
|
||||
message: Message::StateChanged {
|
||||
old_state,
|
||||
new_state,
|
||||
},
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn status_changed(old: &PlayerStatusData, new: &PlayerStatusData) -> bool {
|
||||
old.running != new.running
|
||||
|| old.paused != new.paused
|
||||
|| old.in_transition != new.in_transition
|
||||
|| old.current_index != new.current_index
|
||||
|| old.playlist_length != new.playlist_length
|
||||
|| old.current_video != new.current_video
|
||||
}
|
||||
|
||||
fn lock_processor(
|
||||
processor: &Arc<Mutex<VideoProcessor>>,
|
||||
) -> Result<MutexGuard<'_, VideoProcessor>> {
|
||||
processor
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("video processor lock poisoned"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user