mirror of
https://github.com/myronblair/kino-app
synced 2026-07-30 14:32:40 -05:00
Add filesystem Library Scan (movies/tv/music) and a full Music library
- New admin Library Scan page walks the NAS mounts directly for movies, TV, and music not already in Radarr/Sonarr/local storage, and imports them with storage_type=scan (fully integrated: streamable, transcodable, listed in Admin) — Radarr/Sonarr import flows are untouched. - New Music section (nav tab, album-grid browse, per-album track list, persistent bottom player with queue/seek) backed by a Track model and range-request audio streaming endpoint. - MusicBrainz + Cover Art Archive integration (free, no API key) to fill in album art for scanned tracks, paced to their 1 req/sec rate limit. - Mounted the NAS music share into CT104 (new mp2 LXC mountpoint, CIFS read-only) alongside the existing video mount. - Optional Bluetooth/output-device picker on the player bar via the Audio Output Devices API (setSinkId) for already-paired speakers — Chrome/Edge only, since browsers can't pair new BT devices directly.
This commit is contained in:
+38
-27
@@ -2,9 +2,11 @@ import { BrowserRouter, Routes, Route, Navigate, useLocation } from "react-route
|
||||
import { Toaster } from "sonner";
|
||||
import { AuthProvider, useAuth } from "./lib/auth";
|
||||
import { ProfileProvider, useProfile } from "./lib/profile";
|
||||
import { MusicPlayerProvider } from "./lib/musicPlayer";
|
||||
import Navbar from "./components/Navbar";
|
||||
import GrainOverlay from "./components/GrainOverlay";
|
||||
import ServiceStatus from "./components/ServiceStatus";
|
||||
import MusicPlayerBar from "./components/MusicPlayerBar";
|
||||
import Login from "./pages/Login";
|
||||
import Register from "./pages/Register";
|
||||
import Browse from "./pages/Browse";
|
||||
@@ -23,6 +25,9 @@ import EpisodePlayer from "./pages/EpisodePlayer";
|
||||
import SonarrImport from "./pages/SonarrImport";
|
||||
import AdminShowEpisodes from "./pages/AdminShowEpisodes";
|
||||
import Users from "./pages/Users";
|
||||
import Music from "./pages/Music";
|
||||
import AlbumDetail from "./pages/AlbumDetail";
|
||||
import LibraryScan from "./pages/LibraryScan";
|
||||
|
||||
const ProfileGate = ({ children }) => {
|
||||
const { user, loading: authLoading } = useAuth();
|
||||
@@ -53,6 +58,7 @@ const Shell = ({ children }) => {
|
||||
{!hideChrome && user && active && <Navbar />}
|
||||
{!hideChrome && user?.is_admin && active && <ServiceStatus />}
|
||||
{children}
|
||||
{!hideChrome && user && active && <MusicPlayerBar />}
|
||||
<GrainOverlay />
|
||||
</>
|
||||
);
|
||||
@@ -72,33 +78,38 @@ function App() {
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
<ProfileProvider>
|
||||
<Shell>
|
||||
<Routes>
|
||||
<Route path="/" element={<RootRedirect />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/browse" element={<ProfileGate><Browse /></ProfileGate>} />
|
||||
<Route path="/shows" element={<ProfileGate><Shows /></ProfileGate>} />
|
||||
<Route path="/show/:id" element={<ProfileGate><ShowDetail /></ProfileGate>} />
|
||||
<Route path="/watch/episode/:id" element={<ProfileGate><EpisodePlayer /></ProfileGate>} />
|
||||
<Route path="/my-list" element={<ProfileGate><MyList /></ProfileGate>} />
|
||||
<Route path="/search" element={<ProfileGate><Search /></ProfileGate>} />
|
||||
<Route path="/watch/:id" element={<ProfileGate><Player /></ProfileGate>} />
|
||||
<Route path="/requests" element={<ProfileGate><Requests /></ProfileGate>} />
|
||||
<Route path="/settings" element={<ProfileGate><Settings /></ProfileGate>} />
|
||||
<Route path="/admin" element={<AdminGate><Admin /></AdminGate>} />
|
||||
<Route path="/admin/users" element={<AdminGate><Users /></AdminGate>} />
|
||||
<Route path="/admin/upload" element={<AdminGate><AdminUpload /></AdminGate>} />
|
||||
<Route path="/admin/radarr" element={<AdminGate><RadarrImport /></AdminGate>} />
|
||||
<Route path="/admin/sonarr" element={<AdminGate><SonarrImport /></AdminGate>} />
|
||||
<Route path="/admin/shows/:id/episodes" element={<AdminGate><AdminShowEpisodes /></AdminGate>} />
|
||||
<Route path="/admin/queue" element={<AdminGate><TranscodeQueue /></AdminGate>} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</Shell>
|
||||
<Toaster position="bottom-right" theme="dark" toastOptions={{
|
||||
style: { background: "#0F0F0F", border: "1px solid #222", color: "#F2F2F2" },
|
||||
}} />
|
||||
<MusicPlayerProvider>
|
||||
<Shell>
|
||||
<Routes>
|
||||
<Route path="/" element={<RootRedirect />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/browse" element={<ProfileGate><Browse /></ProfileGate>} />
|
||||
<Route path="/shows" element={<ProfileGate><Shows /></ProfileGate>} />
|
||||
<Route path="/show/:id" element={<ProfileGate><ShowDetail /></ProfileGate>} />
|
||||
<Route path="/watch/episode/:id" element={<ProfileGate><EpisodePlayer /></ProfileGate>} />
|
||||
<Route path="/my-list" element={<ProfileGate><MyList /></ProfileGate>} />
|
||||
<Route path="/search" element={<ProfileGate><Search /></ProfileGate>} />
|
||||
<Route path="/watch/:id" element={<ProfileGate><Player /></ProfileGate>} />
|
||||
<Route path="/requests" element={<ProfileGate><Requests /></ProfileGate>} />
|
||||
<Route path="/settings" element={<ProfileGate><Settings /></ProfileGate>} />
|
||||
<Route path="/music" element={<ProfileGate><Music /></ProfileGate>} />
|
||||
<Route path="/music/album/:artist/:album" element={<ProfileGate><AlbumDetail /></ProfileGate>} />
|
||||
<Route path="/admin" element={<AdminGate><Admin /></AdminGate>} />
|
||||
<Route path="/admin/users" element={<AdminGate><Users /></AdminGate>} />
|
||||
<Route path="/admin/upload" element={<AdminGate><AdminUpload /></AdminGate>} />
|
||||
<Route path="/admin/radarr" element={<AdminGate><RadarrImport /></AdminGate>} />
|
||||
<Route path="/admin/sonarr" element={<AdminGate><SonarrImport /></AdminGate>} />
|
||||
<Route path="/admin/scan" element={<AdminGate><LibraryScan /></AdminGate>} />
|
||||
<Route path="/admin/shows/:id/episodes" element={<AdminGate><AdminShowEpisodes /></AdminGate>} />
|
||||
<Route path="/admin/queue" element={<AdminGate><TranscodeQueue /></AdminGate>} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</Shell>
|
||||
<Toaster position="bottom-right" theme="dark" toastOptions={{
|
||||
style: { background: "#0F0F0F", border: "1px solid #222", color: "#F2F2F2" },
|
||||
}} />
|
||||
</MusicPlayerProvider>
|
||||
</ProfileProvider>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
|
||||
Reference in New Issue
Block a user