Skip to content
Snippets Groups Projects
Verified Commit d6a8748a authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Cleanup content loading

parent fe2716f2
No related branches found
No related tags found
No related merge requests found
Showing
with 324 additions and 211 deletions
...@@ -9327,6 +9327,15 @@ ...@@ -9327,6 +9327,15 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"match-sorter": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.1.0.tgz",
"integrity": "sha512-sKPMf4kbF7Dm5Crx0bbfLpokK68PUJ/0STUIOPa1ZmTZEA3lCaPK3gapQR573oLmvdkTfGojzySkIwuq6Z6xRQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"remove-accents": "0.4.2"
}
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
...@@ -12022,6 +12031,15 @@ ...@@ -12022,6 +12031,15 @@
"tiny-warning": "^1.0.2" "tiny-warning": "^1.0.2"
} }
}, },
"react-query": {
"version": "3.5.9",
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.5.9.tgz",
"integrity": "sha512-thlxrnl7cDg6qmk+N2ADjDVDJkoU3c7ZFJivYph0XoBDgkRIpb3A+tpqH7o6gu7JXZum9lfX1o294UfYfTiwvg==",
"requires": {
"@babel/runtime": "^7.5.5",
"match-sorter": "^6.0.2"
}
},
"react-refresh": { "react-refresh": {
"version": "0.8.3", "version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
...@@ -12363,6 +12381,11 @@ ...@@ -12363,6 +12381,11 @@
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
}, },
"remove-accents": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
"integrity": "sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U="
},
"remove-trailing-separator": { "remove-trailing-separator": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-jss": "^10.5.0", "react-jss": "^10.5.0",
"react-query": "^3.5.9",
"react-router": "^5.2.0", "react-router": "^5.2.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.1", "react-scripts": "4.0.1",
......
...@@ -2,9 +2,13 @@ import {BrowserRouter, Route} from 'react-router-dom'; ...@@ -2,9 +2,13 @@ import {BrowserRouter, Route} from 'react-router-dom';
import {MainPage} from "./routes/main/MainPage"; import {MainPage} from "./routes/main/MainPage";
import {PlayerPage} from "./routes/player/PlayerPage"; import {PlayerPage} from "./routes/player/PlayerPage";
import {ContentRoute} from "./routes/ContentRoute"; import {ContentRoute} from "./routes/ContentRoute";
import {QueryClient, QueryClientProvider} from "react-query";
const queryClient = new QueryClient();
export function App() { export function App() {
return ( return (
<QueryClientProvider client={queryClient}>
<BrowserRouter> <BrowserRouter>
<Route path="/player/:contentId" exact> <Route path="/player/:contentId" exact>
<ContentRoute> <ContentRoute>
...@@ -15,5 +19,6 @@ export function App() { ...@@ -15,5 +19,6 @@ export function App() {
<MainPage/> <MainPage/>
</Route> </Route>
</BrowserRouter> </BrowserRouter>
</QueryClientProvider>
) )
} }
import {RequestClient} from "../util/request/RequestClient";
import {GenreWithContent} from "./models/dto/GenreWithContent";
import {ContentMeta} from "./models/dto/ContentMeta";
import {Instalment} from "./models/Instalment";
import {Genre} from "./models/Genre";
import {Content} from "./models/Content";
export class ApiClient extends RequestClient {
public async listGenres(): Promise<Genre[]> {
return await this.request(`api/v1/genres`, {
method: "GET"
});
}
public async getGenre(genreId: string): Promise<GenreWithContent> {
return await this.request(`api/v1/genres/${genreId}`, {
method: "GET"
});
}
public async listContent(): Promise<Content[]> {
return await this.request(`api/v1/content`, {
method: "GET"
});
}
public async getContent(contentId: string): Promise<ContentMeta> {
return await this.request(`api/v1/content/${contentId}`, {
method: "GET"
});
}
public async listEpisodes(contentId: string): Promise<Instalment[]> {
return await this.request(`api/v1/content/${contentId}/episodes`, {
method: "GET"
});
}
}
import {createContext, useContext} from "react";
import {ApiClient} from "./ApiClient";
const ApiClientContext = createContext<ApiClient>(
new ApiClient(
localStorage.getItem("API_ENDPOINT") ||
new URL("/", window.location.href).toString()
)
);
export const ApiClientProvider = ApiClientContext.Provider;
export const ApiClientConsumer = ApiClientContext.Consumer;
export const useApiClient = () => useContext<ApiClient>(ApiClientContext);
import {createContext, useContext} from "react";
import {parseUrl} from "../util/parseUrl";
const ApiEndpointContext = createContext<URL>(
parseUrl(localStorage.getItem("API_ENDPOINT"))
|| new URL("/", window.location.href)
);
export const ApiEndpointProvider = ApiEndpointContext.Provider;
export const ApiEndpointConsumer = ApiEndpointContext.Consumer;
export const useApiEndpoint = () => useContext<URL>(ApiEndpointContext);
import {Content} from "./models/Content";
import {useQuery} from "react-query";
import {getContent, listEpisodes} from "./queries";
import {Instalment} from "./models/Instalment";
import {useApiEndpoint} from "./ApiEndpointContext";
import {ContentMeta} from "./models/dto/ContentMeta";
export function useEpisodes(item: Content | undefined): [Instalment[], boolean, unknown] {
const apiEndpoint = useApiEndpoint();
const {data, isLoading, error} = useQuery(
["episodes", item?.ids?.uuid],
() => item?.kind === "show"
? listEpisodes(apiEndpoint, item.ids.uuid)
: []
)
return [data || [], isLoading, error];
}
export function useContent(id: string): [ContentMeta | null, boolean, unknown] {
const apiEndpoint = useApiEndpoint();
const {data, isLoading, error} = useQuery(
["media", id],
() => getContent(apiEndpoint, id)
)
return [data || null, isLoading, error];
}
import {Genre} from "./models/Genre";
import {GenreWithContent} from "./models/dto/GenreWithContent";
import {Content} from "./models/Content";
import {ContentMeta} from "./models/dto/ContentMeta";
import {Instalment} from "./models/Instalment";
import {RequestErrorKind} from "../util/request/RequestErrorKind";
import {RequestError} from "../util/request/RequestError";
async function request<T>(
baseUrl: URL,
path: string,
headers?: RequestInit
): Promise<T> {
const url = new URL(path, baseUrl).toString();
const response = await fetch(url, headers);
if (response.ok) {
return await response.json();
} else if (response.status in RequestErrorKind) {
throw new RequestError(response.statusText, response.status)
} else {
throw new RequestError(response.statusText, RequestErrorKind.UnknownError)
}
}
export async function listGenres(baseUrl: URL): Promise<Genre[]> {
return await request(baseUrl, `api/v1/genres`, {
method: "GET"
});
}
export async function getGenre(baseUrl: URL, genreId: string): Promise<GenreWithContent> {
return await request(baseUrl, `api/v1/genres/${genreId}`, {
method: "GET"
});
}
export async function listContent(baseUrl: URL): Promise<Content[]> {
return await request(baseUrl, `api/v1/content`, {
method: "GET"
});
}
export async function getContent(baseUrl: URL, contentId: string): Promise<ContentMeta> {
return await request(baseUrl, `api/v1/content/${contentId}`, {
method: "GET"
});
}
export async function listEpisodes(baseUrl: URL, contentId: string): Promise<Instalment[]> {
return await request(baseUrl, `api/v1/content/${contentId}/episodes`, {
method: "GET"
});
}
import { PropsWithChildren, useEffect, useState } from "react"; import {PropsWithChildren} from "react";
import {useParams} from "react-router"; import {useParams} from "react-router";
import {useApiClient} from "../api/ApiClientContext";
import {ContentMeta} from "../api/models/dto/ContentMeta";
import {CurrentContentProvider} from "../util/CurrentContentContext"; import {CurrentContentProvider} from "../util/CurrentContentContext";
import {useContent} from "../api/hooks";
export function ContentRoute(props: PropsWithChildren<{}>) { export function ContentRoute(props: PropsWithChildren<{}>) {
const {children} = props; const {children} = props;
const {contentId} = useParams<{ contentId: string }>(); const {contentId} = useParams<{ contentId: string }>();
const apiClient = useApiClient(); const [meta/*, isLoading, error*/] = useContent(contentId);
const [meta, setMeta] = useState<ContentMeta | null>(null);
useEffect(() => {
apiClient.getContent(contentId).then(setMeta);
}, [apiClient, contentId, setMeta]);
return ( return (
<CurrentContentProvider value={meta}> <CurrentContentProvider value={meta}>
......
import { useEffect, useState } from "react"; import {useApiEndpoint} from "../../api/ApiEndpointContext";
import {createUseStyles} from "react-jss"; import {Content, getLocalizedName} from "../../api/models/Content";
import {Link} from "react-router-dom";
import {useApiClient} from "../../api/ApiClientContext";
import {Instalment} from "../../api/models/Instalment";
import {getLocalizedDescription, getLocalizedName, getLocalizedRating, Content} from "../../api/models/Content";
import {Locale} from "../../util/locale/Locale";
import {sortLexicallyAsc} from "../../util/sort/sortLexically"; import {sortLexicallyAsc} from "../../util/sort/sortLexically";
import {useQuery} from "react-query";
import {listContent} from "../../api/queries";
import {MediaEntry} from "./MediaEntry";
import {useLocale} from "../../util/locale/LocalizedContext";
interface Props { export function MainPage() {
const apiEndpoint = useApiEndpoint();
} const locale = useLocale();
const {data/*, isLoading, isError*/} = useQuery(
export function MainPage(props: Props) { "media",
const apiClient = useApiClient(); () => listContent(apiEndpoint)
const [data, setData] = useState<Content[]>(); );
useEffect(() => { const media = data || [];
apiClient.listContent().then(setData);
}, [apiClient]);
const locale: Locale = {
language: "en",
region: "DE",
};
const [episodes, setEpisodes] = useState<{ [key: string]: Instalment[] }>({});
const contentToLoad = data?.filter(it => it.kind === "show" && episodes[it.ids.uuid] === undefined);
useEffect(() => {
if (contentToLoad?.length !== undefined && contentToLoad.length > 0) {
const contentBeingLoaded = Object.fromEntries(contentToLoad?.map(it => [it.ids.uuid, []]) || []);
setEpisodes({...contentBeingLoaded, ...episodes});
Promise.all(contentToLoad?.map(it => apiClient.listEpisodes(it.ids.uuid).then(eps => [it.ids.uuid, eps])) || [])
.then(it => setEpisodes({...Object.fromEntries(it), ...episodes}));
}
}, [apiClient, episodes, contentToLoad]);
const classes = useStyles();
return ( return (
<div> <div>
{data?.sort(sortLexicallyAsc(item => getLocalizedName(item, locale)?.name || "")) {media
.map((item: Content) => { .sort(sortLexicallyAsc(item => getLocalizedName(item, locale)?.name || ""))
const title = getLocalizedName(item, locale); .map((item: Content) => (<MediaEntry item={item}/>))}
const description = getLocalizedDescription(item, locale);
const rating = getLocalizedRating(item, locale);
const poster = item.images.find(it => it.kind === "poster");
const backdrop = item.images.find(it => it.kind === "backdrop");
return (
<div key={item.ids.uuid} className={classes.movie}>
<h1>{title?.name}</h1>
<p><strong>{rating?.certification}</strong></p>
<p><strong>{description?.tagline}</strong></p>
<p>{description?.overview}</p>
{item.kind === "movie" && (
<p>
<Link to={"/player/" + item.ids.uuid}>Play</Link>
</p>
)}
{item.kind === "show" && (
<ul>
{episodes[item.ids.uuid]?.map(episode => {
const episodeTitle = getLocalizedName(episode.content, locale);
const episodeDescription = getLocalizedDescription(episode.content, locale);
return (
<li key={episode.content.ids.uuid}>
<p>
<strong>S{episode.season}E{episode.episode}</strong>{episode.airDate}
</p>
<p><strong>{episodeTitle?.name}</strong></p>
<p><strong>{episodeDescription?.tagline}</strong></p>
<p>{episodeDescription?.overview}</p>
<p>
<Link to={"/player/" + episode.content.ids.uuid}>Play</Link>
</p>
</li>
)
})}
</ul>
)}
{poster && (
<img
className={classes.poster}
alt={`Movie poster for ${title?.name}`}
src={poster.src}
/>
)}
{backdrop && (
<img
className={classes.poster}
alt={`Movie backdrop for ${title?.name}`}
src={backdrop.src}
/>
)}
</div>
)
})}
</div> </div>
); );
} }
const useStyles = createUseStyles({
movie: {
maxWidth: "40rem",
margin: {
left: "auto",
right: "auto",
}
},
poster: {
maxWidth: "20rem",
maxHeight: "20rem",
}
});
import {Content, getLocalizedDescription, getLocalizedName, getLocalizedRating} from "../../api/models/Content";
import {Fragment} from "react";
import {Link} from "react-router-dom";
import {createUseStyles} from "react-jss";
import {useLocale} from "../../util/locale/LocalizedContext";
import {useEpisodes} from "../../api/hooks";
import {MediaEpisode} from "./MediaEpisode";
export interface Props {
item: Content
}
export function MediaEntry(
{item}: Props
) {
const locale = useLocale();
const classes = useStyles();
const [episodes/*, episodesLoading*/, episodesError] = useEpisodes(item);
const title = getLocalizedName(item, locale);
const description = getLocalizedDescription(item, locale);
const rating = getLocalizedRating(item, locale);
const poster = item.images.find(it => it.kind === "poster");
const backdrop = item.images.find(it => it.kind === "backdrop");
return (
<div key={item.ids.uuid} className={classes.movie}>
<h1>{title?.name}</h1>
<p><strong>{rating?.certification}</strong></p>
<p><strong>{description?.tagline}</strong></p>
<p>{description?.overview}</p>
{item.kind === "movie" && (
<p>
<Link to={"/player/" + item.ids.uuid}>Play</Link>
</p>
)}
{item.kind === "show" && (
<Fragment>
{episodesError ? (
<p>{""+episodesError}</p>
) : (
<ul>
{episodes.map(episode =>
<MediaEpisode item={episode} />)}
</ul>
)}
</Fragment>
)}
{poster && (
<img
className={classes.poster}
alt={`Movie poster for ${title?.name}`}
src={poster.src}
/>
)}
{backdrop && (
<img
className={classes.poster}
alt={`Movie backdrop for ${title?.name}`}
src={backdrop.src}
/>
)}
</div>
)
}
const useStyles = createUseStyles({
movie: {
maxWidth: "40rem",
margin: {
left: "auto",
right: "auto",
}
},
poster: {
maxWidth: "20rem",
maxHeight: "20rem",
}
});
import {Instalment} from "../../api/models/Instalment";
import {getLocalizedDescription, getLocalizedName} from "../../api/models/Content";
import {Link} from "react-router-dom";
import {useLocale} from "../../util/locale/LocalizedContext";
export interface Props {
item: Instalment,
disabled?: boolean
}
export function MediaEpisode({item, disabled}: Props) {
const locale = useLocale();
const episodeTitle = getLocalizedName(item.content, locale);
const episodeDescription = getLocalizedDescription(item.content, locale);
return (
<li key={item.content.ids.uuid}>
<p>
<strong>S{item.season}E{item.episode}</strong>{item.airDate}
</p>
<p><strong>{episodeTitle?.name}</strong></p>
<p><strong>{episodeDescription?.tagline}</strong></p>
<p>{episodeDescription?.overview}</p>
{disabled ? (
<p><u>Play</u></p>
) : (
<p><Link to={"/player/" + item.content.ids.uuid}>Play</Link></p>
)}
</li>
)
}
import {Fragment, useEffect, useState} from "react"; import {Fragment, useState} from "react";
import {createUseStyles} from "react-jss"; import {createUseStyles} from "react-jss";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {ContentMeta} from "../../api/models/dto/ContentMeta"; import {ContentMeta} from "../../api/models/dto/ContentMeta";
...@@ -17,8 +17,9 @@ import {Subtitle} from "../../api/models/Subtitle"; ...@@ -17,8 +17,9 @@ import {Subtitle} from "../../api/models/Subtitle";
import {MousePosition} from "../../util/mouse/MousePosition"; import {MousePosition} from "../../util/mouse/MousePosition";
import {VideoProvider} from "./video/VideoContext"; import {VideoProvider} from "./video/VideoContext";
import {SubtitleRenderer} from "./subtitles/SubtitleRenderer"; import {SubtitleRenderer} from "./subtitles/SubtitleRenderer";
import {useApiClient} from "../../api/ApiClientContext"; import {useEpisodes} from "../../api/hooks";
import {Instalment, sortInstalments} from "../../api/models/Instalment"; import {MediaEpisode} from "../main/MediaEpisode";
import {usePaused} from "../../util/media/usePaused";
interface Props { interface Props {
meta: ContentMeta, meta: ContentMeta,
...@@ -28,19 +29,11 @@ interface Props { ...@@ -28,19 +29,11 @@ interface Props {
export function Player( export function Player(
{meta, media}: Props {meta, media}: Props
) { ) {
const apiClient = useApiClient();
const {content, instalment} = meta; const {content, instalment} = meta;
const classes = useStyles(); const classes = useStyles();
const locale = useLocale(); const locale = useLocale();
const [relatedInstalments, setRelatedInstalments] = useState<Instalment[]>([]); const [relatedEpisodes/*, relatedEpisodesLoading*/, relatedEpisodesError] = useEpisodes(instalment?.content);
useEffect(() => {
setRelatedInstalments([]);
if (instalment?.content.ids.uuid !== undefined) {
apiClient.listEpisodes(instalment.content.ids.uuid)
.then(result => setRelatedInstalments(sortInstalments(result)));
}
}, [apiClient, instalment]);
const name = getLocalizedName(content, locale); const name = getLocalizedName(content, locale);
const description = getLocalizedDescription(content, locale); const description = getLocalizedDescription(content, locale);
...@@ -51,6 +44,7 @@ export function Player( ...@@ -51,6 +44,7 @@ export function Player(
const [playerApi, setPlayerApi] = useState<PlayerApi | null>(null); const [playerApi, setPlayerApi] = useState<PlayerApi | null>(null);
const [mousePosition, setMousePosition] = useState<MousePosition | null>(null); const [mousePosition, setMousePosition] = useState<MousePosition | null>(null);
const paused = usePaused(playerApi);
const position = usePosition(playerApi); const position = usePosition(playerApi);
const duration = useDuration(playerApi); const duration = useDuration(playerApi);
const [audioTracks, currentTrack, setCurrentTrack] = useAudioTracks(playerApi); const [audioTracks, currentTrack, setCurrentTrack] = useAudioTracks(playerApi);
...@@ -73,29 +67,18 @@ export function Player( ...@@ -73,29 +67,18 @@ export function Player(
{instalment?.content && ( {instalment?.content && (
<Fragment> <Fragment>
<h3>{getLocalizedName(instalment.content, locale)?.name}</h3> <h3>{getLocalizedName(instalment.content, locale)?.name}</h3>
<ol> {relatedEpisodesError ? (
{relatedInstalments.map(episode => { <p>{"" + relatedEpisodesError}</p>
const episodeTitle = getLocalizedName(episode.content, locale);
const episodeDescription = getLocalizedDescription(episode.content, locale);
return (
<li key={episode.content.ids.uuid}>
<p>
<strong>S{episode.season}E{episode.episode}</strong>{episode.airDate}
</p>
<p><strong>{episodeTitle?.name}</strong></p>
<p><strong>{episodeDescription?.tagline}</strong></p>
<p>{episodeDescription?.overview}</p>
{episode.content.ids.uuid !== meta.content.ids.uuid ? (
<p>
<Link to={"/player/" + episode.content.ids.uuid}>Play</Link>
</p>
) : ( ) : (
<p><u>Playing</u></p> <ul>
{relatedEpisodes.map(episode =>
<MediaEpisode
item={episode}
disabled={episode.content.ids.uuid === content.ids.uuid}
/>
)}
</ul>
)} )}
</li>
)
})}
</ol>
</Fragment> </Fragment>
)} )}
<div className={classes.player}> <div className={classes.player}>
...@@ -119,13 +102,13 @@ export function Player( ...@@ -119,13 +102,13 @@ export function Player(
</p> </p>
<button <button
onClick={playerApi?.play} onClick={playerApi?.play}
disabled={playerApi?.isPaused() === false} disabled={!paused}
> >
Play Play
</button> </button>
<button <button
onClick={playerApi?.pause} onClick={playerApi?.pause}
disabled={playerApi?.isPaused() === true} disabled={paused}
> >
Pause Pause
</button> </button>
......
...@@ -19,6 +19,8 @@ export const DashVideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>( ...@@ -19,6 +19,8 @@ export const DashVideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>(
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
METADATA_EVENT: dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED, METADATA_EVENT: dashjs.MediaPlayer.events.PLAYBACK_METADATA_LOADED,
TIMECHANGE_EVENT: dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, TIMECHANGE_EVENT: dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED,
PLAY_EVENT: dashjs.MediaPlayer.events.PLAYBACK_PLAYING,
PAUSE_EVENT: dashjs.MediaPlayer.events.PLAYBACK_PAUSED,
play() { play() {
if (!player.isReady()) { if (!player.isReady()) {
return; return;
......
...@@ -3,6 +3,8 @@ import {MediaInfo} from "dashjs"; ...@@ -3,6 +3,8 @@ import {MediaInfo} from "dashjs";
export interface PlayerApi { export interface PlayerApi {
METADATA_EVENT: string METADATA_EVENT: string
TIMECHANGE_EVENT: string TIMECHANGE_EVENT: string
PLAY_EVENT: string
PAUSE_EVENT: string
play(): void play(): void
......
...@@ -18,6 +18,8 @@ export const RawVideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>(f ...@@ -18,6 +18,8 @@ export const RawVideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>(f
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
METADATA_EVENT: "loadedmetadata", METADATA_EVENT: "loadedmetadata",
TIMECHANGE_EVENT: "timeupdate", TIMECHANGE_EVENT: "timeupdate",
PLAY_EVENT: "play",
PAUSE_EVENT: "pause",
play() { play() {
if (!videoElement) { if (!videoElement) {
return; return;
......
import {useEffect, useState} from "react";
import {PlayerApi} from "../../routes/player/video/PlayerApi";
export const usePaused = (video: PlayerApi | null) => {
const [paused, setPaused] = useState<boolean>(false);
useEffect(() => {
if (video !== null) {
const listener = () => {
window.requestAnimationFrame(() => {
setPaused(video.isPaused());
})
};
video.addEventListener(video.PLAY_EVENT, listener)
video.addEventListener(video.PAUSE_EVENT, listener)
return () => {
video.removeEventListener(video.PLAY_EVENT, listener)
video.removeEventListener(video.PAUSE_EVENT, listener)
}
}
}, [video]);
return paused;
}
export function parseUrl(url: string | null | undefined): URL | null {
if (url === null || url === undefined || url === "") {
return null;
}
try {
return new URL(url);
} catch (_) {
return null;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment