refactor: delegate Miruro playback resolution to Terebi

This commit is contained in:
2026-10-22 09:45:03 +02:00
parent f21a1fbe63
commit 3fda432dbd
15 changed files with 248 additions and 825 deletions
+16 -47
View File
@@ -265,7 +265,7 @@ impl Screen {
self.rows = videos.iter().map(|video| {
let identity = Identity::from_key(&video.id).expect("provider-scoped collection");
let title = safe(&video.title);
let metadata = format!("{title}\n\nEpisode {} · Miruro ID {}\n\nStored offline episode metadata. Enter resolves a fresh direct Sun source; no catalog slug is reconstructed from this title.", identity.episode, identity.media_id);
let metadata = format!("{title}\n\nEpisode {} · Miruro ID {}\n\nStored offline episode metadata. Enter sends the episode URL to Terebi, which resolves sources and playback metadata.", identity.episode, identity.media_id);
DisplayRow { key: Some(video.id.clone()), title, details: video.publication_date.as_deref().map(safe).unwrap_or_default(), metadata }
}).collect();
}
@@ -317,7 +317,6 @@ struct Browser {
enum Intent {
Catalog,
Series(String),
Source(String),
}
enum LibraryModal {
@@ -526,12 +525,20 @@ impl Browser {
self.push(Screen::new(Content::Series { media, data: None }));
self.load_series();
} else if let Some(video) = self.screen().selected_video() {
if Identity::from_key(&video.id).is_err() {
return;
self.invalidate();
self.screen_mut().error = None;
let result = self.state.progress_file(&video).and_then(|progress_file| {
// Dropping the launcher cancels only its CLI child, not the native player.
self.player = None;
PlayerLaunch::start(&video.id, &self.options.player, &progress_file)
});
match result {
Ok(player) => {
self.player = Some(player);
self.status = format!("Opening {} in Terebi…", safe(&video.title));
}
Err(error) => self.fail(error),
}
let id = video.id.clone();
self.request(Request::Source(video), Intent::Source(id));
self.status = "Resolving fresh direct Sun source…".into();
}
}
fn fail(&mut self, error: impl std::fmt::Display) {
@@ -595,34 +602,6 @@ impl Browser {
self.status.clear();
}
}
(Intent::Source(id), Ok(Response::Source(video, source))) => {
if video.id != id || self.screen().selected_key() != Some(id.as_str()) {
return;
}
if source.identity.key() != id {
self.fail(io::Error::other(
"Miruro source identity did not match the selected episode",
));
return;
}
match self.state.miruro_session(&video, &source).and_then(
|(source_file, progress_file)| {
// Dropping the launcher cancels only its CLI child, not the native player.
self.player = None;
PlayerLaunch::start_source(
&source_file,
&self.options.player,
&progress_file,
)
},
) {
Ok(player) => {
self.player = Some(player);
self.status = format!("Opening {} in Terebi…", safe(&video.title));
}
Err(error) => self.fail(error),
}
}
(_, Err(error)) => self.fail(error),
_ => self.fail(io::Error::other(
"Miruro response did not match the current request",
@@ -910,12 +889,6 @@ impl Browser {
screen.clamp();
screen.rebuild_display();
}
// Progress may remove the originating currently-watching row during resolution.
if let Some(Intent::Source(id)) = &self.intent
&& self.screen().selected_key() != Some(id.as_str())
{
self.invalidate();
}
}
fn save(&mut self, kind: CollectionKind) {
let Some(video) = self.screen().selected_video() else {
@@ -1064,10 +1037,6 @@ impl Browser {
}
}
fn select(&mut self, action: Action) {
if matches!(self.intent, Some(Intent::Source(_))) {
self.invalidate();
self.status.clear();
}
let screen = self.screen_mut();
let last = screen.len().saturating_sub(1);
let selected = screen.selection.selected().unwrap_or(0);
@@ -1292,7 +1261,7 @@ impl Browser {
) {
"Open series"
} else {
"Play episode (fresh source)"
"Play episode in Terebi"
},
),
(Action::VideoNext, "Next title/episode"),
@@ -1804,7 +1773,7 @@ impl Browser {
let mut lines = vec![
Line::from("Miruro catalog → series → actual episodes").bold(),
Line::from(
"No provider/track fallback; fresh direct ssub/Sun HLS + VTT on every play.",
"Terebi resolves episode URLs, sources, subtitles and playback metadata.",
),
Line::from("✓ is acknowledged playback progress, never CLI completion."),
Line::default(),