Skip to content

Commit

Permalink
[DTRA] henry/dtra-2087/update-video-player (#17653)
Browse files Browse the repository at this point in the history
* fix: initial commit

* fix: update quill-icons package

* fix: package lock

* fix: make it work

* fix: refactor onclick

* fix: reset video time and progress bar to zero if clicking on replay

* fix: close dropdown list when auto closing video controls

* fix: prevent video controls from closing when there is user interaction

* fix: sync with package update branch

* chore: fix tests

* fix: merge error

* fix: resolve comments

* fix: build fail

* fix: some bugs

* fix: syntax

* fix: package-lock

* fix: merge conflict

* fix: do not show overlay on hover

* fix: test

* fix: test

* fix: test

* fix: test

* fix: add back

* fix: trying this

* fix: trying new time

* fix: add click event

* fix: revert back to 500

* fix: error

* fix: revert change in i18next file

* fix: revert change in i18next file

---------

Co-authored-by: Nijil Nirmal <[email protected]>
  • Loading branch information
henry-deriv and nijil-deriv authored Jan 14, 2025
1 parent b24a1d1 commit 33f4be7
Show file tree
Hide file tree
Showing 13 changed files with 6,656 additions and 7,586 deletions.
13,721 changes: 6,259 additions & 7,462 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions packages/components/src/components/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type TDropdown = {
suffix_icon_size?: number;
should_open_on_hover?: boolean;
should_scroll_to_selected?: boolean;
should_auto_close_dropdown_list?: boolean;
should_autohide?: boolean;
test_id?: string;
value?: string | number;
Expand Down Expand Up @@ -284,6 +285,7 @@ const Dropdown = ({
suffix_icon_size = 16,
should_open_on_hover = false,
should_scroll_to_selected,
should_auto_close_dropdown_list,
should_autohide,
test_id,
value,
Expand Down Expand Up @@ -332,6 +334,12 @@ const Dropdown = ({
});
};

React.useEffect(() => {
if (should_auto_close_dropdown_list) {
setIsListVisible(false);
}
}, [should_auto_close_dropdown_list]);

React.useEffect(() => {
if (is_nativepicker && !is_nativepicker_visible && is_list_visible) {
setIsListVisible(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,63 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import VideoOverlay from '../video-overlay';

const mocked_props = {
onClick: jest.fn(),
};
jest.mock('@deriv/quill-icons', () => ({
...jest.requireActual('@deriv/quill-icons'),
StandalonePauseFillIcon: () => 'StandalonePauseFillIcon',
StandalonePlayFillIcon: () => 'StandalonePlayFillIcon',
StandaloneXmarkRegularIcon: () => 'StandaloneXmarkRegularIcon',
}));

describe('VideoOverlay', () => {
const mockProps = {
onClick: jest.fn(),
togglePlay: jest.fn(),
show_controls: false,
is_ended: false,
is_mobile: false,
is_v2: false,
is_playing: false,
onModalClose: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should handle play/pause state correctly', () => {
render(<VideoOverlay {...mockProps} is_v2={true} is_playing={true} />);
expect(screen.getByText('StandalonePauseFillIcon')).toBeInTheDocument();

const icon_testid = 'dt_player_overlay_icon';
render(<VideoOverlay {...mockProps} is_v2={true} is_playing={false} />);
expect(screen.getByText('StandalonePlayFillIcon')).toBeInTheDocument();
});

describe('<VideoOverlay />', () => {
it('should render the component', () => {
const { container } = render(<VideoOverlay {...mocked_props} />);
it('should handle modal close', () => {
render(<VideoOverlay {...mockProps} is_v2={true} />);
fireEvent.click(screen.getByText('StandaloneXmarkRegularIcon'));
expect(mockProps.onModalClose).toHaveBeenCalled();
});

expect(container).not.toBeEmptyDOMElement();
it('should not show play/pause icons when video has ended', () => {
render(<VideoOverlay {...mockProps} is_v2={true} is_ended={true} />);
expect(screen.queryByText('StandalonePlayFillIcon')).not.toBeInTheDocument();
expect(screen.queryByText('StandalonePauseFillIcon')).not.toBeInTheDocument();
});

it('should pass correct size to icon for desktop', () => {
render(<VideoOverlay {...mocked_props} />);
it('should handle replay icon click when video has ended', () => {
render(<VideoOverlay {...mockProps} is_ended={true} />);
const replayIcon = screen.getByTestId('dt_player_overlay_icon');

expect(screen.getByTestId(icon_testid)).toHaveAttribute('height', '128');
fireEvent.click(replayIcon);
expect(mockProps.togglePlay).toHaveBeenCalled();
});

it('should pass correct size to icon for mobile', () => {
render(<VideoOverlay {...mocked_props} is_mobile />);
it('should handle replay icon click in mobile view', () => {
render(<VideoOverlay {...mockProps} is_ended={true} is_mobile={true} />);
const replayIcon = screen.getByTestId('dt_player_overlay_icon');

expect(screen.getByTestId(icon_testid)).toHaveAttribute('height', '88');
fireEvent.click(replayIcon);
expect(mockProps.togglePlay).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,6 @@ describe('<VideoPlayer />', () => {
await userEvent.click(pause_button);
expect(screen.queryByText(icon_pause)).not.toBeInTheDocument();
expect(screen.getByText(icon_play)).toBeInTheDocument();

// a tap upon replay overlay on mobile should not resume playing while the video is not ended:
const replay_button = screen.getByText(icon_replay);
expect(replay_button).toBeInTheDocument();
await userEvent.click(replay_button);
expect(screen.getByText(icon_play)).toBeInTheDocument();
});
it('should render the component on mobile for Safari', () => {
Object.defineProperty(window.navigator, 'userAgent', {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import React from 'react';
import Dropdown from '../dropdown';
import Icon from '../icon';
import clsx from 'clsx';
import { StandalonePlaybackSpeedFillIcon } from '@deriv/quill-icons';
import { localize } from '@deriv/translations';

type TPlaybackRateControl = {
onPlaybackRateChange: (new_value: number) => void;
is_mobile?: boolean;
playback_rate: number;
is_v2?: boolean;
show_controls?: boolean;
};

const PlaybackRateControl = ({ onPlaybackRateChange, is_mobile, playback_rate }: TPlaybackRateControl) => {
const PlaybackRateControl = ({
onPlaybackRateChange,
is_mobile,
playback_rate,
is_v2 = false,
show_controls = false,
}: TPlaybackRateControl) => {
const playback_rate_list = [
{ text: '0.25x', value: '0.25' },
{ text: '0.5x', value: '0.5' },
{ text: '0.75x', value: '0.75' },
{ text: localize('Normal'), value: '1' },
{ text: is_v2 ? '1x' : localize('Normal'), value: '1' },
{ text: '1.5x', value: '1.5' },
{ text: '2.0x', value: '2' },
{ text: is_v2 ? '2x' : '2.0x', value: '2' },
];

const changePlaybackRate = (e: { target: { name: string; value: string } }) => {
Expand All @@ -25,14 +35,21 @@ const PlaybackRateControl = ({ onPlaybackRateChange, is_mobile, playback_rate }:

return (
<button className='player__controls__button player__playback-rate__wrapper'>
<Icon
icon='IcPlaybackRate'
custom_color='var(--text-colored-background)'
size={20}
className='playback-rate__icon'
/>
{is_v2 ? (
<StandalonePlaybackSpeedFillIcon fill='#ffffff' iconSize='md' className='playback-rate__icon' />
) : (
<Icon
icon='IcPlaybackRate'
custom_color='var(--text-colored-background)'
size={20}
className='playback-rate__icon'
/>
)}
<Dropdown
classNameDisplay='dc-dropdown__display--playback-rate'
classNameDisplay={clsx('', {
'dc-dropdown__display--playback-rate': !is_v2,
'dc-dropdown__display--playback-rate--v2': is_v2,
})}
classNameItems='dc-dropdown__display--playback-rate__item'
id='playback_rate'
is_alignment_top
Expand All @@ -44,6 +61,7 @@ const PlaybackRateControl = ({ onPlaybackRateChange, is_mobile, playback_rate }:
should_open_on_hover={!is_mobile}
should_scroll_to_selected
should_autohide={false}
should_auto_close_dropdown_list={!show_controls}
/>
</button>
);
Expand Down
119 changes: 84 additions & 35 deletions packages/components/src/components/video-player/video-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Text from '../text';
import { formatDurationTime } from '@deriv/shared';
import VolumeControl from './volume-control';
import PlaybackRateControl from './playback-rate-control';
import clsx from 'clsx';

type TVideoControls = {
block_controls?: boolean;
Expand All @@ -16,10 +17,12 @@ type TVideoControls = {
is_playing?: boolean;
is_mobile?: boolean;
is_muted?: boolean;
is_v2?: boolean;
increased_drag_area?: boolean;
onRewind: (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
onVolumeChange: (new_value: number) => void;
onPlaybackRateChange: (new_value: number) => void;
onUserActivity: () => void;
progress_bar_filled_ref: React.RefObject<HTMLDivElement>;
progress_bar_ref: React.RefObject<HTMLDivElement>;
progress_dot_ref: React.RefObject<HTMLSpanElement>;
Expand All @@ -41,6 +44,7 @@ const VideoControls = ({
is_playing,
is_mobile,
is_muted,
is_v2 = false,
increased_drag_area,
onRewind,
onVolumeChange,
Expand All @@ -54,18 +58,61 @@ const VideoControls = ({
toggleMute,
video_duration,
volume,
onUserActivity,
}: TVideoControls) => {
const [is_drag_dot_visible, setIsDragDotVisible] = React.useState(false);

const handleUserInteraction = () => {
onUserActivity?.();
};

return (
<div
className={classNames('player__controls__wrapper', {
'player__controls__wrapper--visible': show_controls,
'player__controls__wrapper--interactive': show_controls,
})}
onMouseMove={handleUserInteraction}
onTouchStart={handleUserInteraction}
onTouchMove={handleUserInteraction}
onClick={handleUserInteraction}
>
{is_v2 && (
<div
className={classNames('player__controls__bottom-bar--v2', {
'player__controls__bottom-bar--blocked': block_controls,
})}
>
<div className='controls__right--v2'>
<VolumeControl
onVolumeChange={onVolumeChange}
volume={volume}
is_mobile={is_mobile}
is_muted={is_muted}
toggleMute={toggleMute}
is_v2
/>
<PlaybackRateControl
onPlaybackRateChange={onPlaybackRateChange}
is_mobile={is_mobile}
playback_rate={playback_rate}
is_v2
show_controls={show_controls}
/>
</div>
<div className='controls__left--v2'>
<div className='player__controls__time-wrapper--v2'>
<Text size='xxxs' line_height='s' color='colored-background'>
{formatDurationTime(current_time)}
{' / '}
{formatDurationTime(video_duration)}
</Text>
</div>
</div>
</div>
)}
<div
className='player__controls__progress-bar'
className={clsx('player__controls__progress-bar', { 'player__controls__progress-bar--v2': is_v2 })}
onClick={onRewind}
onKeyDown={onRewind}
onMouseOver={() => setIsDragDotVisible(true)}
Expand Down Expand Up @@ -96,43 +143,45 @@ const VideoControls = ({
)}
</div>
</div>
<div
className={classNames('player__controls__bottom-bar', {
'player__controls__bottom-bar--blocked': block_controls,
})}
>
<div className='player__controls__bottom-bar controls__left'>
<button onClick={togglePlay} className='player__controls__button'>
<Icon
icon={is_playing ? 'IcPause' : 'IcPlay'}
custom_color='var(--text-colored-background)'
height={18}
width={15}
{!is_v2 && (
<div
className={classNames('player__controls__bottom-bar', {
'player__controls__bottom-bar--blocked': block_controls,
})}
>
<div className='player__controls__bottom-bar controls__left'>
<button onClick={togglePlay} className='player__controls__button'>
<Icon
icon={is_playing ? 'IcPause' : 'IcPlay'}
custom_color='var(--text-colored-background)'
height={18}
width={15}
/>
</button>
<div className='player__controls__time-wrapper'>
<Text size='xxxs' line_height='s' color='colored-background'>
{formatDurationTime(current_time)}
{' / '}
{formatDurationTime(video_duration)}
</Text>
</div>
</div>
<div className='player__controls__bottom-bar controls__right'>
<VolumeControl
onVolumeChange={onVolumeChange}
volume={volume}
is_mobile={is_mobile}
is_muted={is_muted}
toggleMute={toggleMute}
/>
<PlaybackRateControl
onPlaybackRateChange={onPlaybackRateChange}
is_mobile={is_mobile}
playback_rate={playback_rate}
/>
</button>
<div className='player__controls__time-wrapper'>
<Text size='xxxs' line_height='s' color='colored-background'>
{formatDurationTime(current_time)}
{' / '}
{formatDurationTime(video_duration)}
</Text>
</div>
</div>
<div className='player__controls__bottom-bar controls__right'>
<VolumeControl
onVolumeChange={onVolumeChange}
volume={volume}
is_mobile={is_mobile}
is_muted={is_muted}
toggleMute={toggleMute}
/>
<PlaybackRateControl
onPlaybackRateChange={onPlaybackRateChange}
is_mobile={is_mobile}
playback_rate={playback_rate}
/>
</div>
</div>
)}
</div>
);
};
Expand Down
Loading

0 comments on commit 33f4be7

Please sign in to comment.