From fc51d7394b981892bb15013a88596fc17213ae24 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 20:49:55 +0300 Subject: [PATCH 01/26] App: Move index.html to root folder --- app/{src => }/index.html | 2 +- scripts/rollup.config.app.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/{src => }/index.html (88%) diff --git a/app/src/index.html b/app/index.html similarity index 88% rename from app/src/index.html rename to app/index.html index 0a12e85..e113bd8 100644 --- a/app/src/index.html +++ b/app/index.html @@ -10,6 +10,6 @@ You need to enable JavaScript to run this app.
- + diff --git a/scripts/rollup.config.app.mjs b/scripts/rollup.config.app.mjs index 5f745e8..3a71287 100644 --- a/scripts/rollup.config.app.mjs +++ b/scripts/rollup.config.app.mjs @@ -49,7 +49,7 @@ const options = [ open: true, verbose: true, contentBase: ["", "app", "app/src"], - host: "0.0.0.0", + host: "localhost", port: "3000", }), livereload({ From b89ad116a122e2e3d33eabf8233fcca0cd31aa42 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 20:50:35 +0300 Subject: [PATCH 02/26] App: Add mux.js to support HLS playback --- app/index.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/index.html b/app/index.html index e113bd8..5d17cb8 100644 --- a/app/index.html +++ b/app/index.html @@ -3,13 +3,14 @@ + PRESTOplay React Components - -
- + +
+ From beb08e5b61e1a4a2cf9c84ba23570bf3157064eb Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 20:51:23 +0300 Subject: [PATCH 03/26] Add prepack script This way it is easy to pack TGZ NPM packages for local use. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6950647..d900286 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "test:watch": "jest --watch", "lint": "npx eslint .", "storybook": "storybook dev --port 6006", - "build-storybook": "storybook build --output-dir ./dist/storybook" + "build-storybook": "storybook build --output-dir ./dist/storybook", + "prepack": "npm run build" }, "license": "Apache-2.0", "devDependencies": { From 6cec87c75d871781ea7222eb6aa3e133b356e466 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 20:54:48 +0300 Subject: [PATCH 04/26] ADD `controlsVisibility` to base theme In order to configure visibility of UI controls. --- src/components/BaseThemeOverlay.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/BaseThemeOverlay.tsx b/src/components/BaseThemeOverlay.tsx index e1265cf..a46403d 100644 --- a/src/components/BaseThemeOverlay.tsx +++ b/src/components/BaseThemeOverlay.tsx @@ -1,5 +1,7 @@ import React from 'react' +import { ControlsVisibilityMode } from '../services/controls' + import { BufferingIndicator } from './BufferingIndicator' import { CurrentTime } from './CurrentTime' import { Duration } from './Duration' @@ -63,6 +65,10 @@ export interface BaseThemeOverlayProps extends BaseComponentProps { * seekbar, 'none' hides the seekbar. */ seekBar?: 'enabled' | 'disabled' | 'none' + /** + * Visibility mode of UI controls. + */ + controlsVisibility?: ControlsVisibilityMode } /** @@ -106,7 +112,7 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { return (
- + {/* Top bar */} From c33eddcde7d1790d8c522f40cf3ffeef8b6ce4fe Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 20:55:29 +0300 Subject: [PATCH 05/26] ADD the option to hide UI controls Option: 'never' --- src/components/PlayerControls.tsx | 2 +- src/services/controls.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PlayerControls.tsx b/src/components/PlayerControls.tsx index adb23d3..7479f27 100644 --- a/src/components/PlayerControls.tsx +++ b/src/components/PlayerControls.tsx @@ -45,7 +45,7 @@ export interface PlayerControlsProps extends BaseComponentProps { export const PlayerControls = (props: PlayerControlsProps) => { const [lastFocusIndex, setLastFocusIndex] = useState(-1) const { player } = useContext(PrestoContext) - const controlsVisible = useControlsVisible() + const controlsVisible = useControlsVisible() && props.mode !== 'never' useEffect(() => { if (props.hideDelay) { diff --git a/src/services/controls.ts b/src/services/controls.ts index dbdbd61..ba9a511 100644 --- a/src/services/controls.ts +++ b/src/services/controls.ts @@ -1,4 +1,4 @@ -export type ControlsVisibilityMode = 'auto' | 'always-visible' +export type ControlsVisibilityMode = 'auto' | 'always-visible' | 'never' type Callback = (visible: boolean) => void From 611b41a5b50dfae81bd1a16c5cde4699dbceb8fa Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:02:18 +0300 Subject: [PATCH 06/26] ESlint: disable a miss-reported rule `@typescript-eslint/no-unsafe-assignment` it is getting reported in situations where it there is no reason for it. --- .eslintrc.js | 1 + src/services/fullscreen.ts | 1 - story/stories/components/VuMeter.stories.tsx | 1 - story/stories/prep.tsx | 1 - tests/testUtils.tsx | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d15cd9a..5285fee 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,6 +24,7 @@ module.exports = { //
) => event.altKey}}/> "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-assignment": "off", }, overrides: [ { diff --git a/src/services/fullscreen.ts b/src/services/fullscreen.ts index 4c2643a..825b0fb 100644 --- a/src/services/fullscreen.ts +++ b/src/services/fullscreen.ts @@ -1,5 +1,4 @@ /* eslint-disable - @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/ban-ts-comment */ diff --git a/story/stories/components/VuMeter.stories.tsx b/story/stories/components/VuMeter.stories.tsx index 761ec57..22609af 100644 --- a/story/stories/components/VuMeter.stories.tsx +++ b/story/stories/components/VuMeter.stories.tsx @@ -30,7 +30,6 @@ const Component = () => { }, []) const playerConfig = { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment source: TEST_ASSETS[0].config.source ?? '', } diff --git a/story/stories/prep.tsx b/story/stories/prep.tsx index 04fb819..3ce945b 100644 --- a/story/stories/prep.tsx +++ b/story/stories/prep.tsx @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { clpp } from '@castlabs/prestoplay' import '@castlabs/prestoplay/cl.dash' import '@castlabs/prestoplay/cl.hls' diff --git a/tests/testUtils.tsx b/tests/testUtils.tsx index 759b2f4..e62ebd9 100644 --- a/tests/testUtils.tsx +++ b/tests/testUtils.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { render, act } from '@testing-library/react' import React from 'react' From 79d2919212fe8b30be3b3cdb45583e556b2a6a60 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:09:17 +0300 Subject: [PATCH 07/26] FIX rounding of duration Now all values with remainder 0.8 and higher are rounded UP instead of down, to produce a more accurate value. --- src/components/Duration.tsx | 2 +- src/utils.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Duration.tsx b/src/components/Duration.tsx index 8cc2aa2..c61b12f 100644 --- a/src/components/Duration.tsx +++ b/src/components/Duration.tsx @@ -15,7 +15,7 @@ const toString = (duration: number) => { if (duration === Infinity) { return 'Live' } - return timeToString(duration, getMinimalFormat(duration)) + return timeToString(duration, getMinimalFormat(duration), 0.2) } export interface DurationProps extends BaseComponentProps { diff --git a/src/utils.ts b/src/utils.ts index 1266835..880e3e9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -35,16 +35,22 @@ export function classNames(classes: Record, classNames?: string * * @param {number} timeInSeconds The duration in seconds * @param {string} [opt_format] The output format + * @param {number} [roundingMargin] this margin allows for smarter rounding. + * For example, if we use precision of seconds and the `timeInSeconds` value + * is 6.99, it would be rounded to 6. This can be ameliorated by setting a + * `roundingMargin` which is added to `timeInSeconds` before rounding in order + * to produce a more accurate result of 7. * @returns {string} The formatted duration as a string * @export */ -export function timeToString(timeInSeconds: number, opt_format = '%hh:%mm:%ss') { +export function timeToString(timeInSeconds: number, opt_format = '%hh:%mm:%ss', roundingMargin = 0.0) { if (timeInSeconds === null || timeInSeconds === undefined) { timeInSeconds = 0 } if (opt_format === null || opt_format === undefined) { opt_format = '%h:%mm:%ss' } + timeInSeconds = timeInSeconds + roundingMargin const hours = Math.floor(timeInSeconds / 3600) const minutes = Math.floor((timeInSeconds - (hours * 3600)) / 60) const seconds = Math.floor(timeInSeconds - (hours * 3600) - (minutes * 60)) From 664bb58d24f8475486f21d846e5b511c10c88b87 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:11:24 +0300 Subject: [PATCH 08/26] Extract disposer type --- src/services/volumeMeterService.ts | 4 +++- src/types.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/types.ts diff --git a/src/services/volumeMeterService.ts b/src/services/volumeMeterService.ts index 7333899..12297ad 100644 --- a/src/services/volumeMeterService.ts +++ b/src/services/volumeMeterService.ts @@ -1,5 +1,7 @@ import { clpp } from '@castlabs/prestoplay' +import type { Disposer } from '../types' + type GradientColorStop = { stop: number color: string @@ -64,7 +66,7 @@ export class VolumeMeterService { private audioSource: AudioNode | null = null private canvas: HTMLCanvasElement | null = null private config: ConfigInternal = DEFAULT_CONFIG - private disposers: (() => void)[] = [] + private disposers: Disposer[] = [] private enabled = false private mediaElementToSourceNodeMap: Map = new Map() private log = new clpp.log.Logger('clpp.services.VuMeter') diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..68f3193 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,4 @@ +/** + * A function that disposes of a resource. + */ +export type Disposer = () => void From b600625efd32598c47c179f0f1b2894918d09c2a Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:16:28 +0300 Subject: [PATCH 09/26] Base theme make configurable: audio controls, full screen button, track controls --- src/components/BaseThemeOverlay.tsx | 44 ++++++++++++++++++++--------- src/themes/pp-ui-base-theme.css | 4 +++ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/components/BaseThemeOverlay.tsx b/src/components/BaseThemeOverlay.tsx index a46403d..bafabb6 100644 --- a/src/components/BaseThemeOverlay.tsx +++ b/src/components/BaseThemeOverlay.tsx @@ -69,6 +69,22 @@ export interface BaseThemeOverlayProps extends BaseComponentProps { * Visibility mode of UI controls. */ controlsVisibility?: ControlsVisibilityMode + /** + * If true, audio controls are displayed. Defaults to true. + */ + hasAudioControls?: boolean + /** + * If true, a fullscreen button is displayed. Defaults to true. + */ + hasFullScreenButton?: boolean + /** + * If true, track controls are displayed. Defaults to true. + */ + hasTrackControls?: boolean + /** + * If true, the top controls bar is displayed. Defaults to true. + */ + hasTopControlsBar?: boolean } /** @@ -76,6 +92,10 @@ export interface BaseThemeOverlayProps extends BaseComponentProps { */ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { const selectionOptions = props.menuSelectionOptions ?? DEFAULT_SELECTION_OPTIONS + const hasAudioControls = props.hasAudioControls ?? true + const hasTrackControls = props.hasTrackControls ?? true + const hasFullScreenButton = props.hasFullScreenButton ?? true + const hasTopControlsBar = props.hasTopControlsBar ?? true const renderOptionsMenu = () => { if (selectionOptions.length === 0) {return} @@ -83,23 +103,19 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { } const renderTopBar = () => { - if (selectionOptions.length === 0) {return} + if (!hasTopControlsBar || selectionOptions.length === 0) {return} return ( - + -
- -
+ {hasTrackControls ? ( +
+ +
+ ) : null}
) } - // Some component rendering depends on configuration, and - // we wrap the rendering code into helpers - const renderFullscreenButton = () => { - return - } - const renderPosterImage = () => { if (!props.posterUrl) {return} return @@ -149,11 +165,11 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { - + {hasAudioControls ? : null} - - {renderFullscreenButton()} + {hasAudioControls ? : null} + {hasFullScreenButton ? : null}
diff --git a/src/themes/pp-ui-base-theme.css b/src/themes/pp-ui-base-theme.css index 69425cc..b8e29bc 100644 --- a/src/themes/pp-ui-base-theme.css +++ b/src/themes/pp-ui-base-theme.css @@ -750,6 +750,10 @@ max-width: 200px; } +.pp-ui-basic-theme .pp-ui-top-bar { + height: 40px; +} + .pp-ui-spacer { flex-grow: 1; flex-shrink: 1; From 62615b491f5836393b8c7042fc6d98fda7f08892 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:28:10 +0300 Subject: [PATCH 10/26] Refactor --- src/components/Slider.tsx | 10 +++++----- src/components/StartButton.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Slider.tsx b/src/components/Slider.tsx index 468387b..0ff9ec5 100644 --- a/src/components/Slider.tsx +++ b/src/components/Slider.tsx @@ -79,8 +79,6 @@ const getPositionFromMouseEvent = (e: PressEvent, container: HTMLDivElement | nu const width = rect.width let x: number if (e.type === 'touchmove' || e.type === 'touchstart' || e.type === 'touchend') { - // Eslint false alarm - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const touchEvent = e as React.TouchEvent x = touchEvent.changedTouches[touchEvent.changedTouches.length - 1].pageX - rect.left } else { @@ -310,11 +308,13 @@ export const Slider = forwardRef((props: SliderProps, ref) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + const className = `pp-ui-slider ${interacting ? 'pp-ui-slider-interacting' : ''} ` + + `${props.disabled ? 'pp-ui-disabled' : 'pp-ui-enabled'} ${props.className || ''}` + return (
{ >
+ className="pp-ui-slider-range">
Promise + onClick?: () => any | Promise } const isVisibleState = (state: State) => { From 8547906ef0370ddeb9b8c655a15d45f5a8a01658 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:33:19 +0300 Subject: [PATCH 11/26] Refactor: Get rid of unnecessary private annotations --- src/Player.ts | 55 +++------------------------------------- src/services/controls.ts | 3 +++ 2 files changed, 6 insertions(+), 52 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index 02e7d72..1b9a4a3 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -201,152 +201,103 @@ const isEnabledState = (state: State): boolean => { export class Player { /** * The player instance - * @private */ private pp_: clpp.Player | null = null /** * We maintain a queue of actions that will be posted towards the player * instance once it is initialized - * - * @private */ private _actionQueue_: Action[] = [] /** * Function that resolves the player initialization - * - * @private */ private _actionQueueResolved?: () => void /** * A promise that resolves once the player is initialized - * @private */ private readonly _actionQueuePromise: Promise /** * The player initializer - * @private */ private readonly _initializer?: PlayerInitializer - /** * Internal state that indicates that the "controls" are visible - * - * @private */ private _controlsVisible = false /** * Internal controls that indicate that the slide in menu is visible - * @private */ private _slideInMenuVisible = false /** * The currently playing video track - * - * @private */ private _playingVideoTrack: Track | undefined - /** * The currently selected video track - * - * @private */ private _videoTrack: Track = getUnavailableTrack('video') /** * The currently selected audio track - * @private */ private _audioTrack: Track = getUnavailableTrack('audio') /** * The currently selected text track - * - * @private */ private _textTrack: Track = getUnavailableTrack('text') - /** * All available video tracks - * - * @private */ private _videoTracks: Track[] = [] /** * All available audio tracks - * - * @private */ private _audioTracks: Track[] = [] - /** * All available text tracks - * - * @private */ private _textTracks: Track[] = [] - /** * The track sorter - * - * @private */ private _trackSorter: TrackSorter = defaultTrackSorter - /** * The track labeler - * - * @private */ private _trackLabeler: TrackLabeler = defaultTrackLabel - /** * The track labeler options - * @private */ private _trackLabelerOptions?: TrackLabelerOptions - /** * The event emitter for UI related events - * - * @private */ private readonly _eventEmitter = new EventEmitter() - /** * This is true while we are waiting for a user initiated seek even to * complete - * - * @private */ private _isUserSeeking = false - /** * The target of the last user initiated seek event. We use this in case * there were more seek events while we were waiting for the last event * to complete - * - * @private */ private _userSeekingTarget = -1 - /** * Proxy the playback rate - * @private */ private _rate = 1 - /** * The current player configuration - * - * @private */ private _config: clpp.PlayerConfiguration | null = null - /** * Indicate that the config was loaded - * @private */ private _configLoaded = false - + /** + * UI control visibility manager + */ private _controls = new Controls() constructor(initializer?: PlayerInitializer) { diff --git a/src/services/controls.ts b/src/services/controls.ts index ba9a511..252fe71 100644 --- a/src/services/controls.ts +++ b/src/services/controls.ts @@ -4,6 +4,9 @@ type Callback = (visible: boolean) => void const AUTO_HIDE_DELAY_MS = 3_000 +/** + * A helper for hiding and showing UI controls. + */ export class Controls { public onChange: Callback = () => {} public hideDelayMs = AUTO_HIDE_DELAY_MS From 359a8e2a69d22e41ee1827488cf9fc9b8cd877ef Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 21:53:39 +0300 Subject: [PATCH 12/26] Make `presto` context attribute nullable --- src/components/MuteButton.tsx | 2 +- src/components/Thumbnail.tsx | 1 + src/components/VuMeter.tsx | 38 +++++++++++++++++++++++++++++------ src/context/PrestoContext.ts | 5 ++--- src/react.ts | 4 +++- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/components/MuteButton.tsx b/src/components/MuteButton.tsx index 9d5a463..3746154 100644 --- a/src/components/MuteButton.tsx +++ b/src/components/MuteButton.tsx @@ -36,7 +36,7 @@ export const MuteButton = (props: MuteButtonProps) => { }) function toggle(e: React.MouseEvent) { - if (e.defaultPrevented) {return} + if (e.defaultPrevented || !presto) {return} presto.setMuted(!presto.isMuted()) } diff --git a/src/components/Thumbnail.tsx b/src/components/Thumbnail.tsx index 503804a..23a234d 100644 --- a/src/components/Thumbnail.tsx +++ b/src/components/Thumbnail.tsx @@ -28,6 +28,7 @@ export interface ThumbnailProps extends BaseComponentProps { */ const usePlugin = () => { const { presto } = useContext(PrestoContext) + if (!presto) {return null} return presto.getPlugin(clpp.thumbnails.ThumbnailsPlugin.Id) as clpp.thumbnails.ThumbnailsPlugin | null } diff --git a/src/components/VuMeter.tsx b/src/components/VuMeter.tsx index ebeb777..883ee1c 100644 --- a/src/components/VuMeter.tsx +++ b/src/components/VuMeter.tsx @@ -11,23 +11,49 @@ export type Props = BaseComponentProps & { height: number } + +const mount = (service: VolumeMeterService, canvas: HTMLCanvasElement, config: VuMeterConfig) => { + service.configure(canvas, config) + service.mount() +} + /** * Volume Unit Meter */ export const VuMeter = (props: Props) => { - const ctx = useContext(PrestoContext) - const serviceRef = useRef(new VolumeMeterService(ctx.presto)) + const { presto } = useContext(PrestoContext) + const canvasRef = useRef(null) + const serviceRef = useRef(null) + const config = props.config ?? {} + + useEffect(() => { + if (presto) { + serviceRef.current = new VolumeMeterService(presto) + if (canvasRef.current) { + mount(serviceRef.current, canvasRef.current, config) + } + } + }, [presto]) + const onRef = useCallback((canvas: HTMLCanvasElement) => { - serviceRef.current.configure(canvas, props.config ?? {}) - serviceRef.current.mount() + canvasRef.current = canvas + if (serviceRef.current) { + mount(serviceRef.current, canvas, config) + } }, []) useEffect(() => { return () => { - serviceRef.current.unmount() + if (serviceRef.current) { + serviceRef.current.unmount() + serviceRef.current = null + canvasRef.current = null + } } }, []) - return + return } diff --git a/src/context/PrestoContext.ts b/src/context/PrestoContext.ts index 68ef55f..a60b0d7 100644 --- a/src/context/PrestoContext.ts +++ b/src/context/PrestoContext.ts @@ -7,18 +7,17 @@ import { Player } from '../Player' export type PrestoContextType = { playerSurface: HTMLDivElement player: Player - presto: clpp.Player + presto: clpp.Player | null } /** * Main context for our React components. */ export const PrestoContext = createContext({ - // @ts-ignore All the values here will be defined when the context is instantiated. + // @ts-ignore Some of the values here will be defined when the context is instantiated. // But I have to specify some default values here anyway, so I'm setting all to null. playerSurface: null, // @ts-ignore player: null, - // @ts-ignore presto: null, }) diff --git a/src/react.ts b/src/react.ts index 7e32519..ee558f7 100644 --- a/src/react.ts +++ b/src/react.ts @@ -28,6 +28,8 @@ export function usePrestoCoreEvent( const presto = useContext(PrestoContext).presto ?? presto_ useEffect(() => { + if (!presto) {return} + const handleEvent = (event: Record) => { handler(event, presto) } @@ -37,7 +39,7 @@ export function usePrestoCoreEvent( presto.off(eventName, handleEvent) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [eventName, ...dependencies]) + }, [presto, eventName, ...dependencies]) } /** From 8cbecbef29fb243b47b3745032c797723684872c Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 22:41:37 +0300 Subject: [PATCH 13/26] Refactor listeners to PRESTOplay instance --- src/Player.ts | 112 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 35 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index 1b9a4a3..ac1e457 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -13,6 +13,7 @@ import { TrackType, } from './Track' import { defaultTrackLabel, defaultTrackSorter, TrackLabeler, TrackLabelerOptions, TrackSorter } from './TrackLabeler' +import { Disposer } from './types' /** * The player initializer is a function that receives the presto play instance @@ -299,6 +300,10 @@ export class Player { * UI control visibility manager */ private _controls = new Controls() + /** + * Disposers of listeners on the PRESTOplay player instance + */ + private _prestoDisposers: Disposer[] = [] constructor(initializer?: PlayerInitializer) { this._initializer = initializer @@ -326,7 +331,25 @@ export class Player { this.pp_ = new clpp.Player(element, baseConfig) - const handlePlayerTracksChanged = (type?: TrackType) => { + this.attachListeners_(this.pp_) + + if (this._initializer) { + this._initializer(this.pp_) + } + for (let i = 0; i < this._actionQueue_.length; i++) { + await this._actionQueue_[i]() + } + this._actionQueue_ = [] + if (this._actionQueueResolved) { + this._actionQueueResolved() + } + } + + /** + * Attach listeners to PRESTOplay events + */ + private attachListeners_(player: clpp.Player) { + const createTrackChangeHandler = (type?: TrackType) => { return () => { const trackManager = this.trackManager if (!trackManager) {return} @@ -346,12 +369,16 @@ export class Player { } } - this.pp_.on(clpp.events.TRACKS_ADDED, handlePlayerTracksChanged()) - this.pp_.on(clpp.events.AUDIO_TRACK_CHANGED, handlePlayerTracksChanged('audio')) - this.pp_.on(clpp.events.VIDEO_TRACK_CHANGED, handlePlayerTracksChanged('video')) - this.pp_.on(clpp.events.TEXT_TRACK_CHANGED, handlePlayerTracksChanged('text')) + const onTracksAdded = createTrackChangeHandler() + const onAudioTrackChanged = createTrackChangeHandler('audio') + const onVideoTrackChanged = createTrackChangeHandler('video') + const onTextTrackChanged = createTrackChangeHandler('text') + player.on(clpp.events.TRACKS_ADDED, onTracksAdded) + player.on(clpp.events.AUDIO_TRACK_CHANGED, onAudioTrackChanged) + player.on(clpp.events.VIDEO_TRACK_CHANGED, onVideoTrackChanged) + player.on(clpp.events.TEXT_TRACK_CHANGED, onTextTrackChanged) - this.pp_.on(clpp.events.STATE_CHANGED, (event: any) => { + const onStateChanged = (event: any) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const e = event // eslint-disable-next-line @typescript-eslint/no-unsafe-argument @@ -377,39 +404,45 @@ export class Player { } else { this._controls.unpin() } - }) + } + player.on(clpp.events.STATE_CHANGED, onStateChanged) - this.pp_.on('timeupdate', () => { - const position = this.pp_?.getPosition() + const onTimeupdate = () => { + const position = player.getPosition() if (position != null) { this.emitUIEvent('position', position) } - }) + } + player.on('timeupdate', onTimeupdate) - this.pp_.on('ratechange', () => { - const ppRate = this.pp_?.getPlaybackRate() + this._rate = player.getPlaybackRate() + const onRateChange = () => { + const ppRate = player.getPlaybackRate() if (ppRate != null && this.state !== State.Buffering) { this._rate = ppRate this.emitUIEvent('ratechange', this.rate) } - }) + } + player.on('ratechange', onRateChange) - this.pp_.on('durationchange', () => { - const duration = this.pp_?.getDuration() + const onDurationChange = () => { + const duration = player.getDuration() if (duration != null) { this.emitUIEvent('durationchange', duration) } - }) + } + player.on('durationchange', onDurationChange) - this.pp_.on('volumechange', () => { + const onVolumeChange = () => { this.emitUIEvent('volumechange', { volume: this.volume, muted: this.muted, }) - }) + } + player.on('volumechange', onVolumeChange) - this.pp_.on(clpp.events.BITRATE_CHANGED, (e: any) => { + const onBitrateChange = (e: any) => { const tm = this.trackManager if (tm) { @@ -417,21 +450,29 @@ export class Player { } else { this.playingVideoTrack = undefined } - handlePlayerTracksChanged('video') + } + player.on(clpp.events.BITRATE_CHANGED, onBitrateChange) + + this._prestoDisposers.push(() => { + player.on(clpp.events.TRACKS_ADDED, onTracksAdded) + player.on(clpp.events.AUDIO_TRACK_CHANGED, onAudioTrackChanged) + player.on(clpp.events.VIDEO_TRACK_CHANGED, onVideoTrackChanged) + player.on(clpp.events.TEXT_TRACK_CHANGED, onTextTrackChanged) + player.on(clpp.events.STATE_CHANGED, onStateChanged) + player.on('timeupdate', onTimeupdate) + player.on('ratechange', onRateChange) + player.on('durationchange', onDurationChange) + player.on('volumechange', onVolumeChange) + player.on(clpp.events.BITRATE_CHANGED, onBitrateChange) }) + } - this._rate = this.pp_.getPlaybackRate() - - if (this._initializer) { - this._initializer(this.pp_) - } - for (let i = 0; i < this._actionQueue_.length; i++) { - await this._actionQueue_[i]() - } - this._actionQueue_ = [] - if (this._actionQueueResolved) { - this._actionQueueResolved() - } + /** + * Remove listeners to PRESTOplay events + */ + private removeListeners_ () { + this._prestoDisposers.forEach(dispose => dispose()) + this._prestoDisposers = [] } get trackManager() { @@ -589,6 +630,7 @@ export class Player { } private async reset_() { + this.removeListeners_() if (this.pp_) { await this.pp_.release() } @@ -840,9 +882,9 @@ export class Player { const isSameTrack = (a?: Track, b?: Track): boolean => { if (a && b) { return a.type === b.type && - a.ppTrack === b.ppTrack && - a.selected === b.selected && - a.id === b.id + a.ppTrack === b.ppTrack && + a.selected === b.selected && + a.id === b.id } return !a && !b From e4649f3f7ddd6d82f141602493773b9b15f51962 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Sun, 7 Apr 2024 23:05:40 +0300 Subject: [PATCH 14/26] lint --- src/Player.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index ac1e457..3359c16 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -389,9 +389,7 @@ export class Player { this.emitUIEvent('statechanged', { currentState, previousState, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment reason: e.detail.reason, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment timeSinceLastStateChangeMS: e.detail.timeSinceLastStateChangeMS, }) From 798efd8c3e3428eed3d5d19faef2434f67c988d0 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 07:53:02 +0300 Subject: [PATCH 15/26] Fix context filter --- src/components/PlayerSurface.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/PlayerSurface.tsx b/src/components/PlayerSurface.tsx index 6ce87e3..f5de044 100644 --- a/src/components/PlayerSurface.tsx +++ b/src/components/PlayerSurface.tsx @@ -51,8 +51,10 @@ export interface PlayerProps extends BaseComponentProps { } const getContext = (nullableContext: Partial) => { - return Object.values(nullableContext) - .every(value => value != null) ? nullableContext as PrestoContextType : null + if (!nullableContext.playerSurface || !nullableContext.player) { + return null + } + return nullableContext as PrestoContextType } /** From b485716da64d919c7585c87059078f02418ffb07 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 08:18:24 +0300 Subject: [PATCH 16/26] ADD support for UI cues (so far empty) --- src/components/SeekBar.tsx | 42 +++++++++++++++++++++++---------- src/components/SeekBarCues.tsx | 30 +++++++++++++++++++++++ src/hooks/hooks.ts | 8 +++++++ src/themes/pp-ui-base-theme.css | 30 +++++++++++++++++++++++ src/types.ts | 15 ++++++++++++ 5 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 src/components/SeekBarCues.tsx diff --git a/src/components/SeekBar.tsx b/src/components/SeekBar.tsx index a72336f..55e498c 100644 --- a/src/components/SeekBar.tsx +++ b/src/components/SeekBar.tsx @@ -1,8 +1,10 @@ import React, { ForwardedRef, forwardRef, useContext, useState } from 'react' import { PrestoContext } from '../context/PrestoContext' +import { useCues } from '../hooks/hooks' import { usePrestoEnabledState, usePrestoUiEvent } from '../react' +import { SeekBarCues } from './SeekBarCues' import { Slider } from './Slider' import { Thumbnail } from './Thumbnail' @@ -16,6 +18,10 @@ export interface SeekBarProps extends BaseComponentProps { keyboardSeekBackward?: number notFocusable?: boolean enabled?: boolean + /** + * If cues should be shown on the timeline. Default: true. + */ + showCues?: boolean } const useEnabled = (enabled: boolean) => { @@ -32,14 +38,18 @@ const useEnabled = (enabled: boolean) => { export const SeekBar = forwardRef((props: SeekBarProps, ref: ForwardedRef) => { const { player } = useContext(PrestoContext) const [progress, setProgress] = useState(0) + const [duration, setDuration] = useState(0) const [hoverPosition, setHoverPosition] = useState(-1) const [hoverValue, setHoverValue] = useState(0) const [thumbWidth, setThumbWidth] = useState(0) const enabled = useEnabled(props.enabled ?? true) + const cues = useCues() + const showCues = props.showCues ?? true function updateFromPlayer(position?: number): number { const range = player.seekRange const rangeDuration = range.end - range.start + setDuration(rangeDuration) position = position || player.position const positionInRange = position - range.start const progress = Math.min(100, Math.max(0, 100.0 * (positionInRange / rangeDuration))) @@ -113,26 +123,32 @@ export const SeekBar = forwardRef((props: SeekBarProps, ref: ForwardedRef - +
+ +
{renderThumbnailSlider()} + {showCues && cues.length > 0 ? ( +
+ +
+ ): null}
) }) diff --git a/src/components/SeekBarCues.tsx b/src/components/SeekBarCues.tsx new file mode 100644 index 0000000..84cd3c5 --- /dev/null +++ b/src/components/SeekBarCues.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import { Cue } from '../types' + +type Props = { + cues: Cue[] + duration: number +} + +/** + * Timeline cues + */ +export const SeekBarCues = (props: Props) => { + const cues = props.cues + return ( +
+
+ {cues.map((cue) => { + const left = (cue.startTime / props.duration) * 100 + return ( +
+ ) + })} +
+
+ ) +} diff --git a/src/hooks/hooks.ts b/src/hooks/hooks.ts index e81db94..2b3590c 100644 --- a/src/hooks/hooks.ts +++ b/src/hooks/hooks.ts @@ -1,6 +1,7 @@ import { useState } from 'react' import { usePrestoUiEvent } from '../react' +import { Cue } from '../types' /** * @returns The current hover position as a percentage @@ -15,3 +16,10 @@ export const useHoverPercent = () => { return percent < 0 ? null : percent } + +/** + * @returns Timeline / seekBar cues + */ +export const useCues = (): Cue[] => { + return [] +} diff --git a/src/themes/pp-ui-base-theme.css b/src/themes/pp-ui-base-theme.css index b8e29bc..53a74d0 100644 --- a/src/themes/pp-ui-base-theme.css +++ b/src/themes/pp-ui-base-theme.css @@ -514,8 +514,38 @@ .pp-ui-seekbar { position: relative; width: 100%; + height: 30px; } +.pp-ui-seekbar-layer { + position: absolute; + width: 100%; + height: 100%; +} + +.pp-ui-seekbar-layer.cues { + pointer-events: none; +} + +.pp-ui-seekbar-cues-margin { + margin: var(--pp-ui-slider-margin); + height: var(--pp-ui-slider-height); +} + +.pp-ui-seekbar-cues { + position: relative; + width: 100%; + height: 100%; +} + +.pp-ui-seekbar-cue { + background-color: rgba(7, 7, 7, 0.3); + height: 130%; + position: absolute; + top: 16px; +} + + .pp-ui-seekbar .pp-ui-thumbnail { display: none; position: absolute; diff --git a/src/types.ts b/src/types.ts index 68f3193..7f8a707 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,3 +2,18 @@ * A function that disposes of a resource. */ export type Disposer = () => void + +export type Cue = { + /** + * ID of the cue. + */ + id: string + /** + * End time of the cue in seconds. + */ + endTime: number + /** + * Start time of the cue in seconds. + */ + startTime: number +} From bd006a1a1cd63e3ea1ad3a16f0e766c6f5de1d29 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 08:27:11 +0300 Subject: [PATCH 17/26] Support gold seek bar color --- src/components/SeekBar.tsx | 5 +++++ src/themes/pp-ui-base-theme.css | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/components/SeekBar.tsx b/src/components/SeekBar.tsx index 55e498c..872d82f 100644 --- a/src/components/SeekBar.tsx +++ b/src/components/SeekBar.tsx @@ -22,6 +22,10 @@ export interface SeekBarProps extends BaseComponentProps { * If cues should be shown on the timeline. Default: true. */ showCues?: boolean + /** + * Class name passed to sub-component. + */ + sliderClassName?: string } const useEnabled = (enabled: boolean) => { @@ -131,6 +135,7 @@ export const SeekBar = forwardRef((props: SeekBarProps, ref: ForwardedRef
Date: Mon, 8 Apr 2024 08:46:21 +0300 Subject: [PATCH 18/26] ADD renderBottomCompanion, showInterstitialMarkers, showSeekBarCues to base theme --- src/components/BaseThemeOverlay.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/BaseThemeOverlay.tsx b/src/components/BaseThemeOverlay.tsx index bafabb6..0894ab7 100644 --- a/src/components/BaseThemeOverlay.tsx +++ b/src/components/BaseThemeOverlay.tsx @@ -85,6 +85,18 @@ export interface BaseThemeOverlayProps extends BaseComponentProps { * If true, the top controls bar is displayed. Defaults to true. */ hasTopControlsBar?: boolean + /** + * Render a custom bottom companion component. + */ + renderBottomCompanion?: () => (JSX.Element | null) + /** + * If true, seek bar cues are shown. Default: true. + */ + showSeekBarCues?: boolean + /** + * Class name for the seek bar slider component. + */ + seekBarSliderClassName?: string } /** @@ -96,6 +108,7 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { const hasTrackControls = props.hasTrackControls ?? true const hasFullScreenButton = props.hasFullScreenButton ?? true const hasTopControlsBar = props.hasTopControlsBar ?? true + const showSeekBarCues = props.showSeekBarCues ?? true const renderOptionsMenu = () => { if (selectionOptions.length === 0) {return} @@ -141,6 +154,8 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { + {props.renderBottomCompanion?.()} + {/* Bottom bar */}
@@ -156,6 +171,8 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => { adjustWithKeyboard={true} enableThumbnailSlider={false} enabled={(props.seekBar ?? 'enabled') === 'enabled'} + showCues={showSeekBarCues} + sliderClassName={props.seekBarSliderClassName} />}
From 89fa42f93eaf3849fc725be257c986c18e02353e Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 09:42:06 +0300 Subject: [PATCH 19/26] Add last playback state to player --- src/Player.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Player.ts b/src/Player.ts index 3359c16..1e4db9a 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -304,6 +304,10 @@ export class Player { * Disposers of listeners on the PRESTOplay player instance */ private _prestoDisposers: Disposer[] = [] + /** + * The last playback state + */ + private _lastPlaybackState: State = State.Unset constructor(initializer?: PlayerInitializer) { this._initializer = initializer @@ -383,6 +387,7 @@ export class Player { const e = event // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const currentState = toState(e.detail.currentState) + this._lastPlaybackState = currentState // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const previousState = toState(e.detail.previousState) From 561371c37101a18679666e098711dc36a13d36b7 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 09:48:04 +0300 Subject: [PATCH 20/26] Player: store and check last playback state --- src/Player.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index 1e4db9a..abc2fd0 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -387,7 +387,6 @@ export class Player { const e = event // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const currentState = toState(e.detail.currentState) - this._lastPlaybackState = currentState // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const previousState = toState(e.detail.previousState) @@ -398,7 +397,10 @@ export class Player { timeSinceLastStateChangeMS: e.detail.timeSinceLastStateChangeMS, }) - if (isEnabledState(currentState) !== isEnabledState(previousState)) { + if ( + isEnabledState(currentState) !== isEnabledState(previousState) + || isEnabledState(currentState) !== isEnabledState(this._lastPlaybackState) + ) { this.emitUIEvent('enabled', isEnabledState(currentState)) } @@ -407,6 +409,8 @@ export class Player { } else { this._controls.unpin() } + + this._lastPlaybackState = currentState } player.on(clpp.events.STATE_CHANGED, onStateChanged) From db41ad9227464d7bafb1883f314a07bc046ccce6 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 09:55:54 +0300 Subject: [PATCH 21/26] Player: support ignore of state 'ended' --- src/Player.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Player.ts b/src/Player.ts index abc2fd0..33a9bcb 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -308,6 +308,10 @@ export class Player { * The last playback state */ private _lastPlaybackState: State = State.Unset + /** + * If true state changes to "ended" state should be ignored + */ + private _ignoreStateEnded = false constructor(initializer?: PlayerInitializer) { this._initializer = initializer @@ -390,6 +394,10 @@ export class Player { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const previousState = toState(e.detail.previousState) + if (this._ignoreStateEnded && currentState === State.Ended) { + return + } + this.emitUIEvent('statechanged', { currentState, previousState, @@ -486,6 +494,18 @@ export class Player { return this.pp_?.getTrackManager() ?? null } + /** + * Configure the player to ignore the "ended" state change. This is useful + * for situations where RESTOplay goes to the "ended" state prematurely, which + * sometimes happens (e.g. state changes to ended, but the video still plays + * another 800 ms or so and timeupdates are triggered). + * Not sure if this is a bug in PRESTOplay or a problem with the asset, but + * it happens. + */ + set ignoreStateEnded(value: boolean) { + this._ignoreStateEnded = value + } + /** * Seek to the given position. Calling this function has no effect unless the * presto instance is already initialized. From ee9074da20d2e76cfa59136174959f0ebd45698c Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 10:29:44 +0300 Subject: [PATCH 22/26] Player protected methods --- src/Player.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index 33a9bcb..36a961b 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -203,7 +203,7 @@ export class Player { /** * The player instance */ - private pp_: clpp.Player | null = null + protected pp_: clpp.Player | null = null /** * We maintain a queue of actions that will be posted towards the player * instance once it is initialized @@ -356,7 +356,7 @@ export class Player { /** * Attach listeners to PRESTOplay events */ - private attachListeners_(player: clpp.Player) { + protected attachListeners_(player: clpp.Player) { const createTrackChangeHandler = (type?: TrackType) => { return () => { const trackManager = this.trackManager @@ -482,6 +482,33 @@ export class Player { }) } + protected refreshPlayerState_ (player: clpp.Player) { + const state = toState(player.getState()) + + if (isEnabledState(state)) { + this.emitUIEvent('enabled', true) + } + + this.emitUIEvent('statechanged', { + currentState: state, + previousState: this._lastPlaybackState, + timeSinceLastStateChangeMS: 10, + }) + + const position = player.getPosition() ?? 0 + this.emitUIEvent('position', position) + + const duration = player.getDuration() + if (duration != null) { + this.emitUIEvent('durationchange', duration) + } + + this.emitUIEvent('volumechange', { + volume: this.volume, + muted: this.muted, + }) + } + /** * Remove listeners to PRESTOplay events */ From f68c983bd0fc2c1f3e1720a3c826b82673376baa Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 10:39:32 +0300 Subject: [PATCH 23/26] Add safe z-index to ui-overlay --- src/themes/pp-ui-base-theme.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/themes/pp-ui-base-theme.css b/src/themes/pp-ui-base-theme.css index 63cabc3..716adde 100644 --- a/src/themes/pp-ui-base-theme.css +++ b/src/themes/pp-ui-base-theme.css @@ -613,6 +613,7 @@ bottom: 0; left: 0; right: 0; + z-index: 1; } :-webkit-full-screen .pp-ui-overlay.pp-ui-ipad { From d3310e34b16ddcc299b6a6c5c8558809799359a9 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 10:49:45 +0300 Subject: [PATCH 24/26] Support cues --- src/Player.ts | 25 ++++++++++++++++++++++++- src/hooks/hooks.ts | 8 ++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index 36a961b..830c29e 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -13,7 +13,7 @@ import { TrackType, } from './Track' import { defaultTrackLabel, defaultTrackSorter, TrackLabeler, TrackLabelerOptions, TrackSorter } from './TrackLabeler' -import { Disposer } from './types' +import { Cue, Disposer } from './types' /** * The player initializer is a function that receives the presto play instance @@ -183,6 +183,10 @@ export interface UIEvents { * Unset, Idle, and Error states. */ enabled: boolean + /** + * Event triggered when timeline/seek bar cues change. + */ + cuesChanged: Cue[] } /** @@ -312,6 +316,10 @@ export class Player { * If true state changes to "ended" state should be ignored */ private _ignoreStateEnded = false + /** + * Timeline cues + */ + private _cues: Cue[] = [] constructor(initializer?: PlayerInitializer) { this._initializer = initializer @@ -353,6 +361,21 @@ export class Player { } } + /** + * Set timeline cues + */ + setCues(cues: Cue[]) { + this._cues = cues + this.emitUIEvent('cuesChanged', cues) + } + + /** + * Get timeline cues + */ + getCues(): Cue[] { + return this._cues + } + /** * Attach listeners to PRESTOplay events */ diff --git a/src/hooks/hooks.ts b/src/hooks/hooks.ts index 2b3590c..a9d64f6 100644 --- a/src/hooks/hooks.ts +++ b/src/hooks/hooks.ts @@ -1,5 +1,6 @@ -import { useState } from 'react' +import { useContext, useState } from 'react' +import { PrestoContext } from '../context/PrestoContext' import { usePrestoUiEvent } from '../react' import { Cue } from '../types' @@ -21,5 +22,8 @@ export const useHoverPercent = () => { * @returns Timeline / seekBar cues */ export const useCues = (): Cue[] => { - return [] + const { player } = useContext(PrestoContext) + const [cues, setCues] = useState(player.getCues()) + usePrestoUiEvent('cuesChanged', setCues) + return cues } From 055976d74dfcfdac43c5433d80f288118935db90 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 11:11:10 +0300 Subject: [PATCH 25/26] Fix presto listeners --- src/Player.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Player.ts b/src/Player.ts index 830c29e..d445a2b 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -347,7 +347,7 @@ export class Player { this.pp_ = new clpp.Player(element, baseConfig) - this.attachListeners_(this.pp_) + this.attachPrestoListeners_(this.pp_) if (this._initializer) { this._initializer(this.pp_) @@ -379,7 +379,7 @@ export class Player { /** * Attach listeners to PRESTOplay events */ - protected attachListeners_(player: clpp.Player) { + protected attachPrestoListeners_(player: clpp.Player) { const createTrackChangeHandler = (type?: TrackType) => { return () => { const trackManager = this.trackManager @@ -492,20 +492,20 @@ export class Player { player.on(clpp.events.BITRATE_CHANGED, onBitrateChange) this._prestoDisposers.push(() => { - player.on(clpp.events.TRACKS_ADDED, onTracksAdded) - player.on(clpp.events.AUDIO_TRACK_CHANGED, onAudioTrackChanged) - player.on(clpp.events.VIDEO_TRACK_CHANGED, onVideoTrackChanged) - player.on(clpp.events.TEXT_TRACK_CHANGED, onTextTrackChanged) - player.on(clpp.events.STATE_CHANGED, onStateChanged) - player.on('timeupdate', onTimeupdate) - player.on('ratechange', onRateChange) - player.on('durationchange', onDurationChange) - player.on('volumechange', onVolumeChange) - player.on(clpp.events.BITRATE_CHANGED, onBitrateChange) + player.off(clpp.events.TRACKS_ADDED, onTracksAdded) + player.off(clpp.events.AUDIO_TRACK_CHANGED, onAudioTrackChanged) + player.off(clpp.events.VIDEO_TRACK_CHANGED, onVideoTrackChanged) + player.off(clpp.events.TEXT_TRACK_CHANGED, onTextTrackChanged) + player.off(clpp.events.STATE_CHANGED, onStateChanged) + player.off('timeupdate', onTimeupdate) + player.off('ratechange', onRateChange) + player.off('durationchange', onDurationChange) + player.off('volumechange', onVolumeChange) + player.off(clpp.events.BITRATE_CHANGED, onBitrateChange) }) } - protected refreshPlayerState_ (player: clpp.Player) { + protected refreshPrestoState_ (player: clpp.Player) { const state = toState(player.getState()) if (isEnabledState(state)) { @@ -535,7 +535,7 @@ export class Player { /** * Remove listeners to PRESTOplay events */ - private removeListeners_ () { + protected removePrestoListeners_ () { this._prestoDisposers.forEach(dispose => dispose()) this._prestoDisposers = [] } @@ -707,7 +707,7 @@ export class Player { } private async reset_() { - this.removeListeners_() + this.removePrestoListeners_() if (this.pp_) { await this.pp_.release() } From f0be44f8485f64d818a8381fbddede15c3a8b882 Mon Sep 17 00:00:00 2001 From: Artur Finger Date: Mon, 8 Apr 2024 12:28:23 +0300 Subject: [PATCH 26/26] changelog --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 447fd05..341f627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 0.8.3 (Beta) + +## New Features + +* Add option to keep player controls hidden. +* Make `BaseThemeOverlay` more configurable: + * Add option to hide buttons (audio, full screen, track options) + * Add option to hide the top bar of player controls + * Add option to render a companion component above the bottom bar + of player controls +* Add option to display cues on the seek bar + +## Fixes + +* Fix rounding of duration. + # 0.7.3 (Beta) ## Fixes