Select the types of activity you want to include in your feed.
Hide Videos tab on Subsonic servers
Subsonic has no video API, so the Videos tab only exists on Jellyfin: the tab bar drops it, Tab/Shift+Tab skip past it, and the `2` key shows a hint instead. Switching servers while on Videos bounces to Music.
···1616 queue later.
17171818### Changed
1919+- **Videos tab is now Jellyfin-only** — Subsonic has no video API, so on a
2020+ Subsonic server the tab is hidden from the bar, `Tab`/`Shift+Tab` skip it,
2121+ and `2` shows a hint. Switching servers while on Videos bounces to Music.
1922- **ReplayGain now runs in the Rockbox DSP** — upgraded `rockbox-dsp` to
2023 0.2.0 and moved gain application into its pre-gain (PGA) stage, the same
2124 fixed-point pipeline as the EQ and tone controls. Tag extraction stays in
+4
README.md
···330330331331 `1` Music • `2` Videos • `3` Playlists • `4` Favorites • `5` Queue • `6` Search • `7` Devices • `8` Settings
332332333333+The **Videos** tab is Jellyfin-only — Subsonic has no video API, so on a
334334+Subsonic server the tab is hidden, `Tab`/`Shift+Tab` skip past it, and `2`
335335+shows a hint instead. The other number keys keep their meanings.
336336+333337| Key | Action |
334338|------------------------------|-------------------------------------|
335339| `?` | show / hide the full keyboard-shortcuts help modal |
+58-11
crates/fin-tui/src/app.rs
···955955 }
956956 }
957957958958+ /// Whether the active backend serves video at all. Subsonic is
959959+ /// music-only, so the Videos tab exists only on Jellyfin.
960960+ fn videos_available(&self) -> bool {
961961+ self.jf().kind() == fin_config::ServerKind::Jellyfin
962962+ }
963963+964964+ /// The tab set for the active backend — `Screen::ALL` minus Videos
965965+ /// when the server is Subsonic.
966966+ fn visible_screens(&self) -> Vec<Screen> {
967967+ Screen::ALL
968968+ .iter()
969969+ .copied()
970970+ .filter(|s| *s != Screen::Videos || self.videos_available())
971971+ .collect()
972972+ }
973973+974974+ /// Neighbor of the current screen within the visible tab set (`dir`
975975+ /// is +1 / −1), wrapping. Falls back to the first tab if the current
976976+ /// screen isn't in the set.
977977+ fn adjacent_screen(&self, dir: isize) -> Screen {
978978+ let vis = self.visible_screens();
979979+ let n = vis.len() as isize;
980980+ match vis.iter().position(|s| *s == self.screen) {
981981+ Some(i) => vis[((i as isize + dir + n) % n) as usize],
982982+ None => vis.first().copied().unwrap_or(Screen::Music),
983983+ }
984984+ }
985985+986986+ /// Bounce off screens the new backend doesn't offer — e.g. a server
987987+ /// switch lands on Subsonic while the Videos tab is open. Call after
988988+ /// any server switch, before reloading the screen.
989989+ fn ensure_screen_supported(&mut self) {
990990+ if self.screen == Screen::Videos && !self.videos_available() {
991991+ self.screen = Screen::Music;
992992+ *self.open_series.lock() = None;
993993+ self.list_state.select(Some(0));
994994+ }
995995+ }
996996+958997 async fn switch_to_mpv(&self) {
959998 // Local playback: audio → symphonia, video → mpv. Persistence is
960999 // wired in so switching to local while there's a saved queue picks
···13031342 app.should_quit = true;
13041343 }
13051344 (KeyCode::Tab, _) | (KeyCode::Right, KeyModifiers::CONTROL) => {
13061306- app.screen = app.screen.next();
13451345+ app.screen = app.adjacent_screen(1);
13071346 app.list_state.select(Some(0));
13081347 app.load_screen().await;
13091348 }
13101349 (KeyCode::BackTab, _) | (KeyCode::Left, KeyModifiers::CONTROL) => {
13111311- app.screen = app.screen.prev();
13501350+ app.screen = app.adjacent_screen(-1);
13121351 app.list_state.select(Some(0));
13131352 app.load_screen().await;
13141353 }
···13191358 app.load_screen().await;
13201359 }
13211360 (KeyCode::Char('2'), _) => {
13221322- app.screen = Screen::Videos;
13231323- *app.open_series.lock() = None;
13241324- app.list_state.select(Some(0));
13251325- app.load_screen().await;
13611361+ // Videos is a Jellyfin-only tab — Subsonic has no video API.
13621362+ if app.videos_available() {
13631363+ app.screen = Screen::Videos;
13641364+ *app.open_series.lock() = None;
13651365+ app.list_state.select(Some(0));
13661366+ app.load_screen().await;
13671367+ } else {
13681368+ app.set_status("Videos are Jellyfin-only — this server is Subsonic.");
13691369+ }
13261370 }
13271371 (KeyCode::Char('3'), _) => {
13281372 app.screen = Screen::Playlists;
···14301474 if let Err(e) = app.switch_server(&name) {
14311475 app.set_status(format!("switch: {}", e));
14321476 } else {
14771477+ app.ensure_screen_supported();
14331478 app.load_screen().await;
14341479 }
14351480 }
···14831528 }
14841529 (KeyCode::Char('t'), _) => {
14851530 app.cycle_server();
15311531+ app.ensure_screen_supported();
14861532 app.load_screen().await;
14871533 }
14881534 (KeyCode::Char('z'), _) => {
···17551801 let block = neon_block("", false);
17561802 let inner = block.inner(area);
17571803 f.render_widget(block, area);
17581758- let labels: Vec<(&str, &str)> = Screen::ALL.iter().map(|s| (s.icon(), s.label())).collect();
17591759- let selected = Screen::ALL
17601760- .iter()
17611761- .position(|s| *s == app.screen)
17621762- .unwrap_or(0);
18041804+ // Tab set depends on the backend — Subsonic has no Videos tab. The
18051805+ // number keys stay stable (2 = Videos on Jellyfin, a no-op hint on
18061806+ // Subsonic) since the bar doesn't display digits.
18071807+ let screens = app.visible_screens();
18081808+ let labels: Vec<(&str, &str)> = screens.iter().map(|s| (s.icon(), s.label())).collect();
18091809+ let selected = screens.iter().position(|s| *s == app.screen).unwrap_or(0);
17631810 f.render_widget(NeonTabs::new(&labels, selected), inner);
17641811}
17651812