From 699b95221e474bc01db8ec57fd4fa958f92ee0b1 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Fri, 27 Nov 2020 19:05:30 +0100 Subject: [PATCH] Cleanup subtitle and preview handling with portals --- src/App.test.tsx | 2 +- src/api/ApiClientContext.ts | 12 +- src/routes/ContentRoute.tsx | 2 +- src/routes/main/MainPage.tsx | 2 +- src/routes/player/Player.tsx | 169 ++++++++---------- src/routes/player/PreviewBar.tsx | 18 +- src/routes/player/SeekBar.tsx | 25 +-- .../player/subtitles/SubtitleRenderer.tsx | 22 +++ src/routes/player/subtitles/TtmlRenderer.tsx | 32 ++-- src/routes/player/video/DashVideoElement.tsx | 9 +- .../video/{VideoApi.ts => PlayerApi.ts} | 4 +- src/routes/player/video/RawVideoElement.tsx | 7 +- src/routes/player/video/Track.tsx | 17 ++ src/routes/player/video/VideoContext.tsx | 5 + src/routes/player/video/VideoElement.tsx | 4 +- src/util/media/useAudioTracks.ts | 4 +- src/util/media/useDuration.ts | 4 +- src/util/media/usePosition.ts | 4 +- src/util/ttml/html.js | 14 +- src/util/ttml/ismc.ts | 3 + 20 files changed, 209 insertions(+), 150 deletions(-) create mode 100644 src/routes/player/subtitles/SubtitleRenderer.tsx rename src/routes/player/video/{VideoApi.ts => PlayerApi.ts} (89%) create mode 100644 src/routes/player/video/Track.tsx create mode 100644 src/routes/player/video/VideoContext.tsx diff --git a/src/App.test.tsx b/src/App.test.tsx index 9a9be2e..13224db 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react'; -import App from './App'; +import {App} from './App'; test('renders learn react link', () => { const { getByText } = render(<App />); diff --git a/src/api/ApiClientContext.ts b/src/api/ApiClientContext.ts index f8f2618..44218d5 100644 --- a/src/api/ApiClientContext.ts +++ b/src/api/ApiClientContext.ts @@ -1,17 +1,13 @@ import {createContext, useContext} from "react"; import {ApiClient} from "./ApiClient"; -interface ApiClientContext { - apiClient: ApiClient -} - -const ApiClientContext = createContext<ApiClientContext>({ - apiClient: new 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<ApiClientContext>(ApiClientContext); +export const useApiClient = () => useContext<ApiClient>(ApiClientContext); diff --git a/src/routes/ContentRoute.tsx b/src/routes/ContentRoute.tsx index 5d5b36a..805a29c 100644 --- a/src/routes/ContentRoute.tsx +++ b/src/routes/ContentRoute.tsx @@ -7,7 +7,7 @@ import {CurrentContentProvider} from "../util/CurrentContentContext"; export function ContentRoute(props: PropsWithChildren<{}>) { const {children} = props; const {contentId} = useParams<{ contentId: string }>(); - const {apiClient} = useApiClient(); + const apiClient = useApiClient(); const [meta, setMeta] = useState<ContentMeta | null>(null); useEffect(() => { apiClient.getContent(contentId).then(setMeta); diff --git a/src/routes/main/MainPage.tsx b/src/routes/main/MainPage.tsx index 03d69bc..ddd6cb5 100644 --- a/src/routes/main/MainPage.tsx +++ b/src/routes/main/MainPage.tsx @@ -12,7 +12,7 @@ interface Props { } export function MainPage(props: Props) { - const {apiClient} = useApiClient(); + const apiClient = useApiClient(); const [data, setData] = useState<Content[]>(); useEffect(() => { apiClient.listContent().then(setData); diff --git a/src/routes/player/Player.tsx b/src/routes/player/Player.tsx index a497aaa..7a5b1f5 100644 --- a/src/routes/player/Player.tsx +++ b/src/routes/player/Player.tsx @@ -10,11 +10,13 @@ import {useDuration} from "../../util/media/useDuration"; import {usePosition} from "../../util/media/usePosition"; import {SeekBar} from "./SeekBar"; import {VideoElement} from "./video/VideoElement"; -import {VideoApi} from "./video/VideoApi"; +import {PlayerApi} from "./video/PlayerApi"; import {useAudioTracks} from "../../util/media/useAudioTracks"; import {useDebugInfo} from "../../util/media/useDebugInfo"; import {Subtitle} from "../../api/models/Subtitle"; import {TtmlRenderer} from "./subtitles/TtmlRenderer"; +import {MousePosition} from "../../util/mouse/MousePosition"; +import {VideoProvider} from "./video/VideoContext"; interface Props { meta: ContentMeta, @@ -34,17 +36,14 @@ export function Player( const [subtitle, setSubtitle] = useState<Subtitle | null>(null); - const [videoElement, setVideoElement] = useState<VideoApi | null>(null); - const [previewTrack, setPreviewTrack] = useState<HTMLTrackElement | null>(null); - const [subtitleTrack, setSubtitleTrack] = useState<HTMLTrackElement | null>(null); + const [playerApi, setPlayerApi] = useState<PlayerApi | null>(null); + const [mousePosition, setMousePosition] = useState<MousePosition | null>(null); - const position = usePosition(videoElement); - const duration = useDuration(videoElement); - const [audioTracks, currentTrack, setCurrentTrack] = useAudioTracks(videoElement); + const position = usePosition(playerApi); + const duration = useDuration(playerApi); + const [audioTracks, currentTrack, setCurrentTrack] = useAudioTracks(playerApi); - useDebugInfo("playerEl", videoElement); - useDebugInfo("previewTrackEl", previewTrack); - useDebugInfo("subtitleTrackEl", subtitleTrack); + useDebugInfo("player", playerApi); useDebugInfo("content", content); useDebugInfo("subtitle", subtitle); useDebugInfo("position", position); @@ -52,88 +51,78 @@ export function Player( useDebugInfo("audioTracks", audioTracks); return ( - <div> - <Link to="/">Back</Link> - <h2>{name?.name}</h2> - <p>{rating?.certification}</p> - <strong>{description?.tagline}</strong> - <p>{description?.overview}</p> - <p>{instalment?.content && getLocalizedName(instalment?.content, locale)?.name}</p> - <div className={classes.player}> - <div className={classes.playerCanvas}> - <VideoElement - className={classes.video} - media={media} - autoPlay={true} - ref={setVideoElement} - > - {content.preview && ( - <track - ref={setPreviewTrack} - kind="metadata" - label="previews" - src={content.preview} - /> - )} - {subtitle && ( - <track - ref={setSubtitleTrack} - kind="captions" - label={`${subtitle.language} (${subtitle.specifier})`} - src={subtitle.src} - /> - )} - </VideoElement> - <TtmlRenderer - className={classes.subtitleCanvas} - trackElement={subtitleTrack} - subtitle={subtitle} - duration={duration} - /> + <VideoProvider value={playerApi?.getVideoElement() || null}> + <div> + <Link to="/">Back</Link> + <h2>{name?.name}</h2> + <p>{rating?.certification}</p> + <strong>{description?.tagline}</strong> + <p>{description?.overview}</p> + <p>{instalment?.content && getLocalizedName(instalment?.content, locale)?.name}</p> + <div className={classes.player}> + <div className={classes.playerCanvas}> + <VideoElement + className={classes.video} + media={media} + autoPlay={true} + ref={setPlayerApi} + /> + <TtmlRenderer + className={classes.subtitleCanvas} + videoElement={playerApi?.getVideoElement() || null} + subtitle={subtitle} + duration={duration} + /> + </div> </div> + <p style={{fontVariant: "tabular-nums"}}> + {formatDuration(mousePosition ? (mousePosition.relative * duration) : position)} / {formatDuration(duration)} + </p> + <button onClick={playerApi?.play}>Play</button> + <button onClick={playerApi?.pause}>Pause</button> + <h3>Audio</h3> + <ul> + {audioTracks.map(track => ( + <li key={track.index}> + <strong>{track.lang}</strong> + {track.labels} + + <button + disabled={currentTrack === track} + onClick={() => setCurrentTrack(track)} + > + Choose + </button> + </li> + ))} + </ul> + <h3>Subtitles</h3> + <ul> + {[null, ...content.subtitles].map(track => ( + <li key={track?.src || "none"}> + <strong>{track?.language || "none"}</strong> + {track?.specifier} + {track?.format} + + <button + disabled={subtitle === track} + onClick={() => setSubtitle(track)} + > + Choose + </button> + </li> + ))} + </ul> + <SeekBar + videoApi={playerApi} + previewSrc={content.preview} + mousePosition={mousePosition} + setMousePosition={setMousePosition} + duration={duration} + position={position} + /> </div> - <p style={{fontVariant: "tabular-nums"}}>{formatDuration(position)} / {formatDuration(duration)}</p> - <button onClick={videoElement?.play}>Play</button> - <button onClick={videoElement?.pause}>Pause</button> - <h3>Audio</h3> - <ul> - {audioTracks.map(track => ( - <li key={track.index}> - <strong>{track.lang}</strong> - {track.labels} - - <button - disabled={currentTrack === track} - onClick={() => setCurrentTrack(track)} - > - Choose - </button> - </li> - ))} - </ul> - <h3>Subtitles</h3> - <ul> - {[null, ...content.subtitles].map(track => ( - <li key={track?.src || "none"}> - <strong>{track?.language || "none"}</strong> - {track?.specifier} - - <button - disabled={subtitle === track} - onClick={() => setSubtitle(track)} - > - Choose - </button> - </li> - ))} - </ul> - <SeekBar - video={videoElement} - previewTrack={previewTrack} - duration={duration} - position={position} - /> - </div> + </VideoProvider> ); } diff --git a/src/routes/player/PreviewBar.tsx b/src/routes/player/PreviewBar.tsx index 9c5a7fe..a3dee23 100644 --- a/src/routes/player/PreviewBar.tsx +++ b/src/routes/player/PreviewBar.tsx @@ -1,22 +1,24 @@ -import { useMemo } from "react"; +import {useState} from "react"; import {createUseStyles} from "react-jss"; import {MousePosition} from "../../util/mouse/MousePosition"; import {useOffsetAbsoluteRef} from "../../util/offset/useOffsetAbsoluteRef"; import {PreviewViewer} from "./PreviewViewer"; +import {Track} from "./video/Track"; interface Props { - previewTrack: HTMLTrackElement | null, + previewSrc: string | null, duration: number, position: MousePosition | null, hidden: boolean, } -export function PreviewBar({previewTrack, duration, position, hidden}: Props) { +export function PreviewBar({previewSrc, duration, position, hidden}: Props) { const classes = useStyles(); + const [previewTrack, setPreviewTrack] = useState<HTMLTrackElement | null>(null); const [previewBarRef, previewHeadRef, offset] = useOffsetAbsoluteRef(position?.absolute || 0); - return useMemo(() => ( + return ( <div ref={previewBarRef} className={classes.previewBar} @@ -29,13 +31,19 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) { opacity: hidden ? 0 : 1, }} > + <Track + ref={setPreviewTrack} + kind="metadata" + label="previews" + src={previewSrc || undefined} + /> <PreviewViewer previewTrack={previewTrack} position={(position?.relative || 0) * duration} /> </div> </div> - ), [previewBarRef, classes.previewBar, classes.previewHead, previewHeadRef, offset, hidden, previewTrack, position, duration]); + ); } const useStyles = createUseStyles({ diff --git a/src/routes/player/SeekBar.tsx b/src/routes/player/SeekBar.tsx index b77d4de..2a2de12 100644 --- a/src/routes/player/SeekBar.tsx +++ b/src/routes/player/SeekBar.tsx @@ -1,23 +1,24 @@ -import { Fragment, MouseEvent, useCallback, useMemo, useState } from "react"; +import {Fragment, MouseEvent, useCallback, useMemo, useState} from "react"; import {createUseStyles} from "react-jss"; import {getMousePosition} from "../../util/mouse/getMousePosition"; import {MousePosition} from "../../util/mouse/MousePosition"; import {useOffsetAbsolute} from "../../util/offset/useOffsetAbsolute"; import {useOffsetRelative} from "../../util/offset/useOffsetRelative"; import {PreviewBar} from "./PreviewBar"; -import {VideoApi} from "./video/VideoApi"; +import {PlayerApi} from "./video/PlayerApi"; interface Props { - previewTrack: HTMLTrackElement | null, - video: VideoApi | null, + videoApi: PlayerApi | null, + previewSrc: string | null, + mousePosition: MousePosition | null, + setMousePosition: (position: MousePosition | null) => void, duration: number, position: number, } -export function SeekBar({video, previewTrack, duration, position}: Props) { +export function SeekBar({videoApi, previewSrc, mousePosition, setMousePosition, duration, position}: Props) { const classes = useStyles(); - const [mousePosition, setMousePosition] = useState<MousePosition | null>(null); const isVisible = mousePosition !== null; const [seekBarRef, setSeekBarRef] = useState<HTMLDivElement | null>(null); @@ -29,20 +30,20 @@ export function SeekBar({video, previewTrack, duration, position}: Props) { const onMouseLeave = useCallback(() => { setMousePosition(null) - }, []); + }, [setMousePosition]); const onClick = useCallback((event: MouseEvent<HTMLDivElement>) => { const position = getMousePosition(event); setMousePosition(position); - if (video && position) { - video.setCurrentTime(position.relative * duration); + if (videoApi && position) { + videoApi.setCurrentTime(position.relative * duration); } - }, [duration, video]); + }, [duration, setMousePosition, videoApi]); const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => { const position = getMousePosition(event); setMousePosition(position); - }, []); + }, [setMousePosition]); const seekHead = useMemo(() => ( <div @@ -81,7 +82,7 @@ export function SeekBar({video, previewTrack, duration, position}: Props) { return ( <Fragment> <PreviewBar - previewTrack={previewTrack} + previewSrc={previewSrc} duration={duration} position={mousePosition} hidden={mousePosition === null} diff --git a/src/routes/player/subtitles/SubtitleRenderer.tsx b/src/routes/player/subtitles/SubtitleRenderer.tsx new file mode 100644 index 0000000..dc33205 --- /dev/null +++ b/src/routes/player/subtitles/SubtitleRenderer.tsx @@ -0,0 +1,22 @@ +import {Subtitle} from "../../../api/models/Subtitle"; +import {Fragment} from "react"; +import {TtmlRenderer} from "./TtmlRenderer"; + +interface Props { + videoElement: HTMLVideoElement | null, + subtitle: Subtitle | null, + duration: number, + className?: string, +} + +export function SubtitleRenderer( + props: Props +) { + switch (props.subtitle?.format) { + case "ttml": + return (<TtmlRenderer {...props} />); + //case "ass": return (<AssRendered {...props} />); + default: + return (<Fragment/>); + } +} diff --git a/src/routes/player/subtitles/TtmlRenderer.tsx b/src/routes/player/subtitles/TtmlRenderer.tsx index 06c2112..5e94fd8 100644 --- a/src/routes/player/subtitles/TtmlRenderer.tsx +++ b/src/routes/player/subtitles/TtmlRenderer.tsx @@ -1,18 +1,20 @@ -import {useEffect, useState} from "react"; +import {Fragment, useEffect, useState} from "react"; import TtmlHelper from "./TtmlHelper"; import {Subtitle} from "../../../api/models/Subtitle"; +import {Track} from "../video/Track"; interface Props { - trackElement: HTMLTrackElement | null, + videoElement: HTMLVideoElement | null, subtitle: Subtitle | null, duration: number, className?: string, } export function TtmlRenderer( - {trackElement, subtitle, duration, className}: Props -) { + {subtitle, duration, className}: Props +): JSX.Element { const [subtitleCanvas, setSubtitleCanvas] = useState<HTMLElement | null>(null); + const [trackElement, setTrackElement] = useState<HTMLTrackElement | null>(null); useEffect(() => { if (subtitleCanvas && trackElement && subtitle) { @@ -21,10 +23,20 @@ export function TtmlRenderer( }, [subtitleCanvas, subtitle, trackElement, duration]); return ( - <div - ref={setSubtitleCanvas} - lang={subtitle?.language || undefined} - className={className} - /> - ) + <Fragment> + {subtitle && ( + <Track + ref={setTrackElement} + kind="captions" + label={`${subtitle.language} (${subtitle.specifier})`} + src={subtitle.src} + /> + )} + <div + ref={setSubtitleCanvas} + lang={subtitle?.language || undefined} + className={className} + /> + </Fragment> + ); } diff --git a/src/routes/player/video/DashVideoElement.tsx b/src/routes/player/video/DashVideoElement.tsx index aa7dce5..afa2f81 100644 --- a/src/routes/player/video/DashVideoElement.tsx +++ b/src/routes/player/video/DashVideoElement.tsx @@ -1,7 +1,7 @@ import {forwardRef, PropsWithChildren, useEffect, useImperativeHandle, useMemo, useState,} from "react"; import {Media} from "../../../api/models/Media"; import dashjs, {MediaInfo} from "dashjs"; -import {VideoApi} from "./VideoApi"; +import {PlayerApi} from "./PlayerApi"; interface Props { media: Media, @@ -9,7 +9,7 @@ interface Props { className?: string, } -export const DashVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(function ( +export const DashVideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>(function ( {media, autoPlay, className, children}, ref ) { @@ -105,8 +105,11 @@ export const DashVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(f }, debug(): any { return player; + }, + getVideoElement(): HTMLVideoElement | null { + return videoElement; } - }), [player]); + }), [player, videoElement]); useEffect(() => { player.initialize(); diff --git a/src/routes/player/video/VideoApi.ts b/src/routes/player/video/PlayerApi.ts similarity index 89% rename from src/routes/player/video/VideoApi.ts rename to src/routes/player/video/PlayerApi.ts index 24c7fb1..bf11227 100644 --- a/src/routes/player/video/VideoApi.ts +++ b/src/routes/player/video/PlayerApi.ts @@ -1,6 +1,6 @@ import {MediaInfo} from "dashjs"; -export interface VideoApi { +export interface PlayerApi { METADATA_EVENT: string TIMECHANGE_EVENT: string @@ -34,4 +34,6 @@ export interface VideoApi { debug(): any + getVideoElement(): HTMLVideoElement | null + } diff --git a/src/routes/player/video/RawVideoElement.tsx b/src/routes/player/video/RawVideoElement.tsx index 0d1c630..b378ebd 100644 --- a/src/routes/player/video/RawVideoElement.tsx +++ b/src/routes/player/video/RawVideoElement.tsx @@ -1,6 +1,6 @@ import {forwardRef, PropsWithChildren, useImperativeHandle, useState} from "react"; import {Media} from "../../../api/models/Media"; -import {VideoApi} from "./VideoApi"; +import {PlayerApi} from "./PlayerApi"; import {MediaInfo} from "dashjs"; interface Props { @@ -9,7 +9,7 @@ interface Props { className?: string, } -export const RawVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(function ( +export const RawVideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>(function ( {media, autoPlay, className, children}, ref ) { @@ -105,6 +105,9 @@ export const RawVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(fu }, debug(): any { return videoElement; + }, + getVideoElement(): HTMLVideoElement | null { + return videoElement; } }), [videoElement]); diff --git a/src/routes/player/video/Track.tsx b/src/routes/player/video/Track.tsx new file mode 100644 index 0000000..c3a922f --- /dev/null +++ b/src/routes/player/video/Track.tsx @@ -0,0 +1,17 @@ +import {forwardRef, Fragment, HTMLProps} from "react"; +import {createPortal} from "react-dom"; +import {useVideo} from "./VideoContext"; + +export const Track = forwardRef<HTMLTrackElement, HTMLProps<HTMLTrackElement>>(function ( + props, ref +) { + const video = useVideo(); + if (video) { + return createPortal( + <track ref={ref} {...props} />, + video + ); + } else { + return <Fragment/>; + } +}); diff --git a/src/routes/player/video/VideoContext.tsx b/src/routes/player/video/VideoContext.tsx new file mode 100644 index 0000000..af8de15 --- /dev/null +++ b/src/routes/player/video/VideoContext.tsx @@ -0,0 +1,5 @@ +import {createContext, useContext} from "react"; + +const videoContext = createContext<HTMLVideoElement | null>(null); +export const VideoProvider = videoContext.Provider; +export const useVideo = () => useContext<HTMLVideoElement | null>(videoContext); diff --git a/src/routes/player/video/VideoElement.tsx b/src/routes/player/video/VideoElement.tsx index 526b40a..1de8562 100644 --- a/src/routes/player/video/VideoElement.tsx +++ b/src/routes/player/video/VideoElement.tsx @@ -2,7 +2,7 @@ import { forwardRef, PropsWithChildren } from "react"; import {Media} from "../../../api/models/Media"; import {DashVideoElement} from "./DashVideoElement"; import {RawVideoElement} from "./RawVideoElement"; -import {VideoApi} from "./VideoApi"; +import {PlayerApi} from "./PlayerApi"; interface Props { media: Media, @@ -10,7 +10,7 @@ interface Props { className?: string, } -export const VideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(function ( +export const VideoElement = forwardRef<PlayerApi, PropsWithChildren<Props>>(function ( props: Props, ref ) { switch (props.media.mime) { diff --git a/src/util/media/useAudioTracks.ts b/src/util/media/useAudioTracks.ts index d22fb0e..ca9ee8f 100644 --- a/src/util/media/useAudioTracks.ts +++ b/src/util/media/useAudioTracks.ts @@ -1,8 +1,8 @@ import {useCallback, useEffect, useState} from "react"; -import {VideoApi} from "../../routes/player/video/VideoApi"; +import {PlayerApi} from "../../routes/player/video/PlayerApi"; import {MediaInfo} from "dashjs"; -export function useAudioTracks(video: VideoApi | null): +export function useAudioTracks(video: PlayerApi | null): [MediaInfo[], MediaInfo | null, (track: MediaInfo) => void] { const [audioTracks, setAudioTracks] = useState<MediaInfo[]>([]); const [currentTrack, setCurrentTrack] = useState<MediaInfo | null>(null); diff --git a/src/util/media/useDuration.ts b/src/util/media/useDuration.ts index 8b2d42b..c0259a3 100644 --- a/src/util/media/useDuration.ts +++ b/src/util/media/useDuration.ts @@ -1,7 +1,7 @@ import {useEffect, useState} from "react"; -import {VideoApi} from "../../routes/player/video/VideoApi"; +import {PlayerApi} from "../../routes/player/video/PlayerApi"; -export const useDuration = (video: VideoApi | null) => { +export const useDuration = (video: PlayerApi | null) => { const [duration, setDuration] = useState<number>(0); useEffect(() => { if (video !== null) { diff --git a/src/util/media/usePosition.ts b/src/util/media/usePosition.ts index 28db74d..d8a3b8a 100644 --- a/src/util/media/usePosition.ts +++ b/src/util/media/usePosition.ts @@ -1,7 +1,7 @@ import {useEffect, useState} from "react"; -import {VideoApi} from "../../routes/player/video/VideoApi"; +import {PlayerApi} from "../../routes/player/video/PlayerApi"; -export const usePosition = (video: VideoApi | null) => { +export const usePosition = (video: PlayerApi | null) => { const [position, setPosition] = useState<number>(0); useEffect(() => { if (video !== null) { diff --git a/src/util/ttml/html.js b/src/util/ttml/html.js index fa42a24..a76ee4a 100644 --- a/src/util/ttml/html.js +++ b/src/util/ttml/html.js @@ -705,15 +705,13 @@ let STYLING_MAP_DEFS = [ "http://www.w3.org/ns/ttml#styling fontFamily", function (context, dom_element, isd_element, attr) { /* per IMSC1 */ + const styleClasses = ["monospaceSerif", "proportionalSerif", "monospace", "sansSerif", "serif", "monospaceSansSerif", "proportionalSansSerif"]; for (let attribute of attr) { - // monospaceSerif - // proportionalSansSerif - // monospace - // sansSerif - // serif - // monospaceSansSerif - // proportionalSerif - dom_element.classList.add("ttml-" + attribute); + if (styleClasses.includes(attribute)) { + dom_element.classList.add("ttml-" + attribute); + } else { + dom_element.style.fontFamily = attribute; + } } } ), diff --git a/src/util/ttml/ismc.ts b/src/util/ttml/ismc.ts index fae12ca..8a33d89 100644 --- a/src/util/ttml/ismc.ts +++ b/src/util/ttml/ismc.ts @@ -144,6 +144,9 @@ type renderHTML = ( enableRollUp?: boolean ) => ISDState; +// eslint-disable-next-line @typescript-eslint/no-redeclare export const generateISD: generateISD = isd.generateISD; +// eslint-disable-next-line @typescript-eslint/no-redeclare export const fromXML: fromXML = doc.fromXML; +// eslint-disable-next-line @typescript-eslint/no-redeclare export const renderHTML: renderHTML = html.render; -- GitLab