diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4467759e..749a53c1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -16,6 +16,7 @@ import DVR from './pages/DVR'; import Settings from './pages/Settings'; import Users from './pages/Users'; import LogosPage from './pages/Logos'; +import VODsPage from './pages/VODs'; import useAuthStore from './store/auth'; import FloatingVideo from './components/FloatingVideo'; import { WebsocketProvider } from './WebSocket'; @@ -135,6 +136,7 @@ const App = () => { } /> } /> } /> + } /> ) : ( } /> diff --git a/frontend/src/api.js b/frontend/src/api.js index ddaccbc7..cfdf1a90 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1689,4 +1689,76 @@ export default class API { errorNotification('Failed to retrieve streams by IDs', e); } } + + // VOD Methods + static async getVODs(params = {}) { + try { + const searchParams = new URLSearchParams(params); + const response = await request(`${host}/api/vod/vods/?${searchParams.toString()}`); + return response; + } catch (e) { + errorNotification('Failed to retrieve VODs', e); + } + } + + static async getVODCategories() { + try { + const response = await request(`${host}/api/vod/categories/`); + return response; + } catch (e) { + errorNotification('Failed to retrieve VOD categories', e); + } + } + + static async getSeries(params = {}) { + try { + const searchParams = new URLSearchParams(params); + const response = await request(`${host}/api/vod/series/?${searchParams.toString()}`); + return response; + } catch (e) { + errorNotification('Failed to retrieve series', e); + } + } + + static async getSeriesEpisodes(seriesId, params = {}) { + try { + const searchParams = new URLSearchParams(params); + const response = await request(`${host}/api/vod/series/${seriesId}/episodes/?${searchParams.toString()}`); + return response; + } catch (e) { + errorNotification('Failed to retrieve series episodes', e); + } + } + + static async getVODConnections() { + try { + const response = await request(`${host}/api/vod/connections/`); + return response; + } catch (e) { + errorNotification('Failed to retrieve VOD connections', e); + } + } + + static async refreshVODContent(accountId) { + try { + const response = await request(`${host}/api/m3u/accounts/${accountId}/refresh-vod/`, { + method: 'POST' + }); + return response; + } catch (e) { + errorNotification('Failed to refresh VOD content', e); + } + } + + static async updateVODPosition(vodUuid, clientId, position) { + try { + const response = await request(`${host}/proxy/vod/stream/${vodUuid}/position/`, { + method: 'POST', + body: { client_id: clientId, position } + }); + return response; + } catch (e) { + errorNotification('Failed to update playback position', e); + } + } } diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 03ad831e..998bc768 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -99,13 +99,18 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => { path: '/channels', badge: `(${Object.keys(channels).length})`, }, + { + label: 'VODs', + path: '/vods', + icon: