diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 25005c5c6..7e82f5309 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -14,8 +14,5 @@ jobs: node-version: 18 - name: Install dependencies run: npm ci --unsafe-perm - - name: Publish to Chromatic - uses: chromaui/action@v1 - with: - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - buildScriptName: storybook:build + - name: Run tests + run: npm run test diff --git a/.storybook/stories/0-MediaPlayer.stories.tsx b/.storybook/stories/0-MediaPlayer.stories.tsx index 798ed7c71..e0a499249 100644 --- a/.storybook/stories/0-MediaPlayer.stories.tsx +++ b/.storybook/stories/0-MediaPlayer.stories.tsx @@ -1,6 +1,5 @@ -import { Paper } from '@mui/material'; import { Meta, StoryFn } from '@storybook/react'; -import * as React from 'react'; +import { useToggle } from 'react-use'; import { makeStyles } from 'tss-react/mui'; import { @@ -8,6 +7,7 @@ import { MediaPlayerProps, } from '../../src/components/media-player/MediaPlayer'; import { withDemoCard, withIntl, withPlayerTheme } from '../decorators'; + const useStyles = makeStyles()(theme => ({ wrapper: { height: theme.spacing(500), @@ -33,31 +33,19 @@ const useStyles = makeStyles()(theme => ({ export const Basic: StoryFn = args => { const { classes } = useStyles(); - - return ( -
- -
- ); -}; - -export const PIPModifiers: StoryFn = args => { - const pipContainer = React.useRef(null); - const { classes } = useStyles(); + const [collapse, toggleCollapse] = useToggle(true); return (
- - PIP can be dragged only here -
); }; + export default { title: 'Media Player', component: MediaPlayer, @@ -87,33 +75,6 @@ export default { defaultValue: { summary: undefined }, }, }, - isPipEnabled: { - name: 'props.isPipEnabled', - description: - 'Enables/disables all pip features(scrolling, entering/leaving PIP mode)', - table: { - type: { summary: 'boolean' }, - defaultValue: { summary: true }, - }, - }, - yAxisDistance: { - name: 'props.yAxisDistance', - description: - 'Distance from window border bottom, on Y axis in `pixels`, for PIP player position initialization ', - table: { - type: { summary: 'number' }, - defaultValue: { summary: 16 }, - }, - }, - xAxisDistance: { - name: 'props.xAxisDistance', - description: - 'Distance from window border right, on X axis in `pixels`, for PIP player position initialization', - table: { - type: { summary: 'number' }, - defaultValue: { summary: 16 }, - }, - }, }, parameters: { controls: { expanded: true }, diff --git a/.storybook/stories/11-KaraokeMode.stories.tsx b/.storybook/stories/11-KaraokeMode.stories.tsx index 788fcc9f1..6128e10f3 100644 --- a/.storybook/stories/11-KaraokeMode.stories.tsx +++ b/.storybook/stories/11-KaraokeMode.stories.tsx @@ -142,7 +142,6 @@ export const KaraokeMode: React.FC = args => { url={args.url} onStoreUpdate={setMediaContext} alarms={alarmRef.current} - isPipEnabled={args.isPipEnabled} />
{timeStampsMemo}
{createActiveSpan()} @@ -176,15 +175,5 @@ export default { defaultValue: { summary: 2 }, }, }, - isPipEnabled: { - name: 'props.isPipEnabled', - description: - 'Enables/disables all pip features(scrolling, entering/leaving PIP mode)', - - table: { - type: { summary: 'boolean' }, - defaultValue: { summary: true }, - }, - }, }, }; diff --git a/package-lock.json b/package-lock.json index 45fd0cdf7..a2dde51bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "zustand": "^4.5.2" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@collaborne/github-badges": "^0.0.3", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", @@ -796,10 +797,17 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -2129,6 +2137,18 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", @@ -32409,11 +32429,16 @@ } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", "dev": true, - "requires": {} + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -33293,6 +33318,13 @@ "resolve": "^1.14.2" } }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "requires": {} + }, "babel-plugin-polyfill-corejs2": { "version": "0.4.10", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", diff --git a/package.json b/package.json index 1b168db4f..67abd14d6 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "uuidv4": "^6.2.13" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@collaborne/github-badges": "^0.0.3", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", diff --git a/src/components/bottom-control-buttons/components/CollapseIconButton.tsx b/src/components/bottom-control-buttons/components/CollapseIconButton.tsx new file mode 100644 index 000000000..ed99e7b99 --- /dev/null +++ b/src/components/bottom-control-buttons/components/CollapseIconButton.tsx @@ -0,0 +1,30 @@ +import { IconButton } from '@mui/material'; +import { PiArrowsInLineVertical, PiArrowsVertical } from 'react-icons/pi'; + +import { useOnHoveredControlElement } from '../../../hooks'; + +export interface CollapseIconButtonProps { + isCollapsed?: boolean; + onToggleCollapse: VoidFunction; +} + +/** + * @category React Component + * @category UI Controls + */ +export function CollapseIconButton(props: CollapseIconButtonProps) { + const { onMouseEnter, onMouseLeave } = useOnHoveredControlElement(); + + const Icon = !props.isCollapsed ? PiArrowsInLineVertical : PiArrowsVertical; + + return ( + + + + ); +} diff --git a/src/components/bottom-controls/useBottomControlsStyles.ts b/src/components/bottom-controls/useBottomControlsStyles.ts index faef2a315..ad7fe2d86 100644 --- a/src/components/bottom-controls/useBottomControlsStyles.ts +++ b/src/components/bottom-controls/useBottomControlsStyles.ts @@ -2,7 +2,7 @@ import { makeStyles } from 'tss-react/mui'; export const useBottomControlsStyles = makeStyles<{ isAudio: boolean }>()( (theme, { isAudio }) => { - const bottomPadding = !isAudio ? theme.spacing(2) : 0; + const bottomPadding = !isAudio ? theme.spacing(1) : 0; return { bottomControls: { display: 'flex', diff --git a/src/components/controls/Controls.tsx b/src/components/controls/Controls.tsx index 17617c0ce..6b9b98f9b 100644 --- a/src/components/controls/Controls.tsx +++ b/src/components/controls/Controls.tsx @@ -1,6 +1,5 @@ import { FC, ReactNode } from 'react'; -import { useMediaStore } from '../../context'; import { CONTROLS } from '../../utils'; import { useControlsStyles } from './useControlsStyles'; @@ -21,18 +20,10 @@ export const Controls: FC = ({ className, 'data-testid': dataTestId = CONTROLS, }) => { - const showControls = useMediaStore(state => state.showControls); - const isAudio = useMediaStore(state => state.isAudio); - // Controls styles const { classes, cx } = useControlsStyles(); const classNameControls = cx(classes.controls, className); - // Only should be present if Controls components are not shown - if (!showControls && !isAudio) { - return null; - } - return (
{children} diff --git a/src/components/controls/useControlsStyles.ts b/src/components/controls/useControlsStyles.ts index b9c5d51a7..c2e18e7b6 100644 --- a/src/components/controls/useControlsStyles.ts +++ b/src/components/controls/useControlsStyles.ts @@ -1,10 +1,16 @@ import { makeStyles } from 'tss-react/mui'; -export const useControlsStyles = makeStyles()({ +export const useControlsStyles = makeStyles<{ + isCollapsed?: boolean; +} | void>()((theme, { isCollapsed = false } = {}) => ({ controls: { width: '100%', height: '100%', position: 'absolute', pointerEvents: 'none', + ...(isCollapsed && { + position: 'relative', + backgroundColor: theme.palette.common.black, + }), }, -}); +})); diff --git a/src/components/media-container/MediaContainer.tsx b/src/components/media-container/MediaContainer.tsx index 41ae0ad01..cc26cf3e6 100644 --- a/src/components/media-container/MediaContainer.tsx +++ b/src/components/media-container/MediaContainer.tsx @@ -16,7 +16,10 @@ import { MediaPoster } from '../media-poster/MediaPoster'; import { Player } from '../player/Player'; import { useIsPlayerReadyHook } from './useIsPlayerReadyHook'; -import { useMediaContainerStyles } from './useMediaContainerStyles'; +import { + useFilePlayerStyles, + useMediaContainerStyles, +} from './useMediaContainerStyles'; import { useMouseActivityHook } from './useMouseActivityHook'; import { UsePipHook } from './UsePipHook'; import { useReactPlayerProps } from './useReactPlayerProps'; @@ -77,11 +80,16 @@ export const MediaContainer: FC = memo( } = useMediaContainerStyles({ isAudio, }); + const { wrapper: playerWrapper } = useFilePlayerStyles({ isAudio }).classes; const { isPlayerReady } = useIsPlayerReadyHook({ url }); const { onMouseEnter, onMouseLeave, onMouseMove } = useMouseActivityHook(); const { reactPlayerProps } = useReactPlayerProps(); - const reactClassNames = cx(reactPlayer, reactPlayerClassName); + const reactClassNames = cx( + reactPlayer, + playerWrapper, + reactPlayerClassName, + ); const playerProps = { url, diff --git a/src/components/media-container/useMediaContainerStyles.ts b/src/components/media-container/useMediaContainerStyles.ts index 46df61623..72321f4c9 100644 --- a/src/components/media-container/useMediaContainerStyles.ts +++ b/src/components/media-container/useMediaContainerStyles.ts @@ -9,7 +9,7 @@ export const useMediaContainerStyles = makeStyles<{ isAudio: boolean }>()( justifyContent: 'center', backgroundSize: 'cover', overflow: 'hidden', - height: isAudio ? theme.spacing(6.25) : 'unset', + height: isAudio ? theme.spacing(7.65) : 'unset', borderRadius: isAudio ? theme.spacing(0.5, 0.5, 0, 0) : 'unset', }, pipText: { diff --git a/src/components/media-player/MediaPlayer.tsx b/src/components/media-player/MediaPlayer.tsx index 3f60b447c..b0000dfd9 100644 --- a/src/components/media-player/MediaPlayer.tsx +++ b/src/components/media-player/MediaPlayer.tsx @@ -1,37 +1,21 @@ -import Grid from '@mui/material/Grid'; import { FC, memo, ReactNode } from 'react'; -import { BottomControlButtons } from '../bottom-control-buttons/BottomControlButtons'; -import { - TimeDisplay, - PlaybackRateButton, - PictureInPictureButton, - FullscreenButton, -} from '../bottom-control-buttons/components'; -import { FwdButton } from '../bottom-control-buttons/components/FwdButton'; -import { PlayPauseReplay } from '../bottom-control-buttons/components/PlayPauseReplay'; -import { RwdButton } from '../bottom-control-buttons/components/RwdButton'; -import { VolumeButton } from '../bottom-control-buttons/components/VolumeButton'; -import { VolumeSlider } from '../bottom-control-buttons/components/VolumeSlider'; -import { BottomControls } from '../bottom-controls/BottomControls'; -import { CenteredPlayButton } from '../centered-play-button/CenteredPlayButton'; -import { CenteredReplayButton } from '../centered-replay-button/CenteredReplayButton'; -import { Controls } from '../controls/Controls'; -import { useControlsStyles } from '../controls/useControlsStyles'; import { CorePlayer, CorePlayerProps } from '../core-player/CorePlayer'; -import { PlayerFrame } from '../frame'; -import { PauseAnimation } from '../play-pause-animation/PauseAnimation'; -import { PlayAnimation } from '../play-pause-animation/PlayAnimation'; -import { ProgressBar } from '../progress-bar/ProgressBar'; -import { AdditionalControls } from './components/AdditionalControls'; +import { MediaPlayerControls } from './components/MediaPlayerControls'; import { useMediaPlayerStyles } from './useMediaPlayerStyles'; -export interface MediaPlayerProps extends Omit { +export interface MediaPlayerProps + extends Omit< + CorePlayerProps, + 'children' | 'isPipEnabled' | 'pipPortalClassName' | 'pipContainer' + > { classes?: { - playerFrame?: string; + collapsedClassName?: string; }; children?: ReactNode; + collapse?: boolean; + onToggleCollapse?: VoidFunction; } /** @@ -40,57 +24,34 @@ export interface MediaPlayerProps extends Omit { * @category Player */ export const MediaPlayer: FC = memo( - ({ children, isPipEnabled = true, classes, ...corePlayerProps }) => { - const { classes: playerClasses } = useMediaPlayerStyles(); - const { controls } = useControlsStyles().classes; + ({ + children, + className, + classes, + collapse, + onToggleCollapse, + ...corePlayerProps + }) => { + const isCollapsed = Boolean(collapse); + + const { classes: mediaClasses, cx } = useMediaPlayerStyles(); + + const collapsedClassName = cx({ + [classes?.collapsedClassName ?? mediaClasses.collapsedContainer]: + isCollapsed, + }); + return ( - - - - - - - - {children} - - - - - - - - - - - - - - - - - - - {isPipEnabled && } - - - - - + + + {children} ); }, diff --git a/src/components/media-player/__test__/MediaPlayer.spec.tsx b/src/components/media-player/__test__/MediaPlayer.spec.tsx index d94edb48a..3d8dda5a5 100644 --- a/src/components/media-player/__test__/MediaPlayer.spec.tsx +++ b/src/components/media-player/__test__/MediaPlayer.spec.tsx @@ -9,12 +9,10 @@ import { CENTERED_PLAY_BUTTON, CONTROLS, DEFAULT_EVENT_ANIMATION_DURATION, - DRAGGABLE_POPOVER, FULLSCREEN_BUTTON, MEDIA_CONTAINER, OVERLAY_HIDE_DELAY, PAUSE_ANIMATION, - PIP_BUTTON, PLAY_ANIMATION, PLAY_PAUSE_REPLAY, PROGRESS_BAR, @@ -236,21 +234,6 @@ describe('', () => { await act(async () => await sleep(OVERLAY_HIDE_DELAY + 1000)); expect(queryByTestId(BOTTOM_CONTROL_BUTTONS)).toBeInTheDocument(); }); - it(`always display when PIP mode is on`, async () => { - const { getByTestId, queryByTestId, mediaStore } = setupMediaPlayer(); - // wait 1 ms to mount state and load initial data - await act(async () => await sleep(1)); - - const startBtn = getByTestId(CENTERED_PLAY_BUTTON); - - await userEvent.click(startBtn); - - await userEvent.click(getByTestId(PIP_BUTTON)); - expect(mediaStore.isPlaying).toBeTruthy(); - expect(mediaStore.isPip).toBeTruthy(); - - expect(queryByTestId(BOTTOM_CONTROL_BUTTONS)).toBeInTheDocument(); - }); }); describe('', () => { it('do not display before first time play', async () => { @@ -282,50 +265,6 @@ describe('', () => { expect(queryByTestId(PROGRESS_BAR)).toBeInTheDocument(); }); }); - describe('PIP mode and ', () => { - it('show PIP on clicking ', async () => { - const { getByTestId, mediaStore } = setupMediaPlayer(); - // wait 1 ms to mount state and load initial data - await act(async () => await sleep(1)); - - const startBtn = getByTestId(CENTERED_PLAY_BUTTON); - await userEvent.click(startBtn); - expect(mediaStore.isPip).toBeFalsy(); - - const draggablePopover = getByTestId(DRAGGABLE_POPOVER); - const mediaContainer = getByTestId(MEDIA_CONTAINER); - expect(mediaContainer.contains(draggablePopover)).toBeTruthy(); - - // start PIP mode = wont be a child for - await userEvent.click(getByTestId(PIP_BUTTON)); - expect(mediaContainer.contains(draggablePopover)).toBeFalsy(); - expect(mediaStore.isPip).toBeTruthy(); - }); - it('close PIP on clicking ', async () => { - const { getByTestId, mediaStore } = setupMediaPlayer(); - // wait 1 ms to mount state and load initial data - await act(async () => await sleep(1)); - - const startBtn = getByTestId(CENTERED_PLAY_BUTTON); - await userEvent.click(startBtn); - - const draggablePopover = getByTestId(DRAGGABLE_POPOVER); - const mediaContainer = getByTestId(MEDIA_CONTAINER); - expect(mediaContainer.contains(draggablePopover)).toBeTruthy(); - - // start PIP mode = wont be a child for - await userEvent.click(getByTestId(PIP_BUTTON)); - expect(mediaContainer.contains(draggablePopover)).toBeFalsy(); - expect(mediaStore.isPip).toBeTruthy(); - - // close pip = is a child for - await userEvent.click(getByTestId(PIP_BUTTON)); - expect(mediaStore.isPip).toBeFalsy(); - expect( - getByTestId(MEDIA_CONTAINER).contains(getByTestId(DRAGGABLE_POPOVER)), - ).toBeTruthy(); - }); - }); describe('Fullscreen API', () => { it('FullscreenButton is not present for audio files', async () => { const { getByTestId, queryByTestId } = setupMediaPlayer(AUDIO_URL); diff --git a/src/components/media-player/components/MediaPlayerControlButtons.tsx b/src/components/media-player/components/MediaPlayerControlButtons.tsx new file mode 100644 index 000000000..fbe62243a --- /dev/null +++ b/src/components/media-player/components/MediaPlayerControlButtons.tsx @@ -0,0 +1,55 @@ +import Grid from '@mui/material/Grid'; +import { FC, memo, ReactNode } from 'react'; + +import { useIsAudio, usePlayPauseReplayHook } from '../../../hooks'; +import { BOTTOM_CONTROL_BUTTONS } from '../../../utils'; +import { useBottomControlButtonsHook } from '../../bottom-control-buttons/useBottomControlButtonsHook'; +import { useBottomControlButtonsStyles } from '../../bottom-control-buttons/useBottomControlButtonsStyles'; + +export interface MediaPlayerControlButtonsProps { + className?: string; + children: ReactNode; + isCollapsed: boolean; + 'data-testid'?: string; +} + +/** + * Wrapper that includes media player bottom controls buttons + * @category React Component + * @category UI Controls + */ +export const MediaPlayerControlButtons: FC = + memo( + ({ + className, + children, + isCollapsed, + 'data-testid': dataTestId = BOTTOM_CONTROL_BUTTONS, + }) => { + const isAudio = useIsAudio(); + const { classes, cx } = useBottomControlButtonsStyles(); + const { hasStarted, showControls } = useBottomControlButtonsHook(); + const { isFinished } = usePlayPauseReplayHook(); + + const hide = isCollapsed + ? !hasStarted || isFinished + : (!showControls || !hasStarted) && !isAudio; + + if (hide) { + return null; + } + + return ( + + {children} + + ); + }, + ); diff --git a/src/components/media-player/components/MediaPlayerControls.tsx b/src/components/media-player/components/MediaPlayerControls.tsx new file mode 100644 index 000000000..306cc9c10 --- /dev/null +++ b/src/components/media-player/components/MediaPlayerControls.tsx @@ -0,0 +1,106 @@ +import Grid, { GridProps } from '@mui/material/Grid'; +import { FC, memo } from 'react'; + +import { useMediaStore } from '../../../context'; +import { useIsAudio } from '../../../hooks'; +import { + VolumeButton, + VolumeSlider, + RwdButton, + PlayPauseReplay, + FwdButton, + PlaybackRateButton, + FullscreenButton, +} from '../../bottom-control-buttons'; +import { CollapseIconButton } from '../../bottom-control-buttons/components/CollapseIconButton'; +import { BottomControls } from '../../bottom-controls/BottomControls'; +import { CenteredPlayButton } from '../../centered-play-button/CenteredPlayButton'; +import { CenteredReplayButton } from '../../centered-replay-button/CenteredReplayButton'; +import { useControlsStyles, Controls } from '../../controls'; +import { PauseAnimation } from '../../play-pause-animation/PauseAnimation'; +import { PlayAnimation } from '../../play-pause-animation/PlayAnimation'; +import { ProgressTimerDisplay } from '../../progress-bar/ProgressTimerDisplay'; +import { useMediaPlayerStyles } from '../useMediaPlayerStyles'; + +import { AdditionalControls } from './AdditionalControls'; +import { MediaPlayerControlButtons } from './MediaPlayerControlButtons'; + +export interface MediaPlayerControlsProps { + isCollapsed: boolean; + toggleCollapse?: VoidFunction; + 'data-testid'?: string; +} + +function ControlGridGroup({ + children, + justifyContent, +}: Pick) { + const { classes: playerClasses } = useMediaPlayerStyles(); + return ( + + {children} + + ); +} + +/** + * Wrapper that includes media controls + * @category React Component + * @category UI Controls + */ +export const MediaPlayerControls: FC = memo( + ({ isCollapsed, toggleCollapse }) => { + const isAudio = useIsAudio(); + const { controls } = useControlsStyles({ isCollapsed }).classes; + const isFullscreen = useMediaStore(state => state.isFullscreen); + + const showCollapseButton = toggleCollapse && !isAudio && !isFullscreen; + + return ( + + {!isCollapsed && ( + <> + + + + + )} + + + + + + + + + + + + + + + + + + + + {showCollapseButton && ( + + )} + {!isCollapsed && } + + + + + + ); + }, +); diff --git a/src/components/media-player/useMediaPlayerStyles.ts b/src/components/media-player/useMediaPlayerStyles.ts index eb4c6ebd0..ab945b551 100644 --- a/src/components/media-player/useMediaPlayerStyles.ts +++ b/src/components/media-player/useMediaPlayerStyles.ts @@ -10,4 +10,7 @@ export const useMediaPlayerStyles = makeStyles()(theme => ({ progressBar: { marginBottom: theme.spacing(0.5), }, + collapsedContainer: { + height: theme.spacing(12), + }, })); diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index 2f55fd7ac..922a29bc3 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -18,15 +18,15 @@ export interface PlayerProps { * @category React Component */ export const Player: FC = memo( - ({ url, className, isFullscreen, reactPlayerProps }) => { + ({ url, className, reactPlayerProps }) => { usePlayerHook({ url }); return ( = ({ + className, + 'data-testid': dataTestId = PROGRESS_BAR, + ...props +}) => { + const isAudio = useIsAudio(); + const { + classes: { timeStampText }, + } = useTimeDisplayStyles(); + + const [hasStarted, duration, setCurrentTime, isPip, currentTime] = + useMediaStore( + state => [ + state.hasPlayedOrSeeked, + state.duration, + state.setCurrentTime, + state.isPip, + state.currentTime, + ], + shallow, + ); + + const onCurrentTimeUpdate = (e: Event, newValue: number | number[]) => { + e.preventDefault(); + if (Array.isArray(newValue)) { + return; + } + // Get new time according to played time from the total media duration + const seekTime = (newValue / PROGRESS_BAR_DIVIDER) * duration; + setCurrentTime(seekTime); + }; + + const { + classes: { progressBar }, + cx, + } = useProgressBarStyles({ isAudio, isPip }); + + if (!hasStarted && !isAudio) { + return null; + } + + return ( + + + {toTimestamp(currentTime * SECONDS_MULTIPLIER)} + + + + + {toTimestamp(duration * SECONDS_MULTIPLIER)} + + + ); +}; diff --git a/src/components/progress-bar/useProgressBarStyles.ts b/src/components/progress-bar/useProgressBarStyles.ts index 0c523c295..98cbb4eee 100644 --- a/src/components/progress-bar/useProgressBarStyles.ts +++ b/src/components/progress-bar/useProgressBarStyles.ts @@ -12,7 +12,6 @@ export const useProgressBarStyles = makeStyles<{ ...(isAudio && { width: '100%', margin: '0 auto', - marginBottom: theme.spacing(1.25), ...(isPip && { top: theme.spacing(0.5), }),