Skip to content

Commit 33f4be7

Browse files
[DTRA] henry/dtra-2087/update-video-player (#17653)
* 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]>
1 parent b24a1d1 commit 33f4be7

File tree

13 files changed

+6656
-7586
lines changed

13 files changed

+6656
-7586
lines changed

package-lock.json

Lines changed: 6259 additions & 7462 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/src/components/dropdown/dropdown.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type TDropdown = {
4848
suffix_icon_size?: number;
4949
should_open_on_hover?: boolean;
5050
should_scroll_to_selected?: boolean;
51+
should_auto_close_dropdown_list?: boolean;
5152
should_autohide?: boolean;
5253
test_id?: string;
5354
value?: string | number;
@@ -284,6 +285,7 @@ const Dropdown = ({
284285
suffix_icon_size = 16,
285286
should_open_on_hover = false,
286287
should_scroll_to_selected,
288+
should_auto_close_dropdown_list,
287289
should_autohide,
288290
test_id,
289291
value,
@@ -332,6 +334,12 @@ const Dropdown = ({
332334
});
333335
};
334336

337+
React.useEffect(() => {
338+
if (should_auto_close_dropdown_list) {
339+
setIsListVisible(false);
340+
}
341+
}, [should_auto_close_dropdown_list]);
342+
335343
React.useEffect(() => {
336344
if (is_nativepicker && !is_nativepicker_visible && is_list_visible) {
337345
setIsListVisible(false);
Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,63 @@
11
import React from 'react';
2-
import { render, screen } from '@testing-library/react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
33
import VideoOverlay from '../video-overlay';
44

5-
const mocked_props = {
6-
onClick: jest.fn(),
7-
};
5+
jest.mock('@deriv/quill-icons', () => ({
6+
...jest.requireActual('@deriv/quill-icons'),
7+
StandalonePauseFillIcon: () => 'StandalonePauseFillIcon',
8+
StandalonePlayFillIcon: () => 'StandalonePlayFillIcon',
9+
StandaloneXmarkRegularIcon: () => 'StandaloneXmarkRegularIcon',
10+
}));
11+
12+
describe('VideoOverlay', () => {
13+
const mockProps = {
14+
onClick: jest.fn(),
15+
togglePlay: jest.fn(),
16+
show_controls: false,
17+
is_ended: false,
18+
is_mobile: false,
19+
is_v2: false,
20+
is_playing: false,
21+
onModalClose: jest.fn(),
22+
};
23+
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
28+
it('should handle play/pause state correctly', () => {
29+
render(<VideoOverlay {...mockProps} is_v2={true} is_playing={true} />);
30+
expect(screen.getByText('StandalonePauseFillIcon')).toBeInTheDocument();
831

9-
const icon_testid = 'dt_player_overlay_icon';
32+
render(<VideoOverlay {...mockProps} is_v2={true} is_playing={false} />);
33+
expect(screen.getByText('StandalonePlayFillIcon')).toBeInTheDocument();
34+
});
1035

11-
describe('<VideoOverlay />', () => {
12-
it('should render the component', () => {
13-
const { container } = render(<VideoOverlay {...mocked_props} />);
36+
it('should handle modal close', () => {
37+
render(<VideoOverlay {...mockProps} is_v2={true} />);
38+
fireEvent.click(screen.getByText('StandaloneXmarkRegularIcon'));
39+
expect(mockProps.onModalClose).toHaveBeenCalled();
40+
});
1441

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

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

21-
expect(screen.getByTestId(icon_testid)).toHaveAttribute('height', '128');
52+
fireEvent.click(replayIcon);
53+
expect(mockProps.togglePlay).toHaveBeenCalled();
2254
});
2355

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

27-
expect(screen.getByTestId(icon_testid)).toHaveAttribute('height', '88');
60+
fireEvent.click(replayIcon);
61+
expect(mockProps.togglePlay).toHaveBeenCalled();
2862
});
2963
});

packages/components/src/components/video-player/__tests__/video-player.spec.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,6 @@ describe('<VideoPlayer />', () => {
9999
await userEvent.click(pause_button);
100100
expect(screen.queryByText(icon_pause)).not.toBeInTheDocument();
101101
expect(screen.getByText(icon_play)).toBeInTheDocument();
102-
103-
// a tap upon replay overlay on mobile should not resume playing while the video is not ended:
104-
const replay_button = screen.getByText(icon_replay);
105-
expect(replay_button).toBeInTheDocument();
106-
await userEvent.click(replay_button);
107-
expect(screen.getByText(icon_play)).toBeInTheDocument();
108102
});
109103
it('should render the component on mobile for Safari', () => {
110104
Object.defineProperty(window.navigator, 'userAgent', {

packages/components/src/components/video-player/playback-rate-control.tsx

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
import React from 'react';
22
import Dropdown from '../dropdown';
33
import Icon from '../icon';
4+
import clsx from 'clsx';
5+
import { StandalonePlaybackSpeedFillIcon } from '@deriv/quill-icons';
46
import { localize } from '@deriv/translations';
57

68
type TPlaybackRateControl = {
79
onPlaybackRateChange: (new_value: number) => void;
810
is_mobile?: boolean;
911
playback_rate: number;
12+
is_v2?: boolean;
13+
show_controls?: boolean;
1014
};
1115

12-
const PlaybackRateControl = ({ onPlaybackRateChange, is_mobile, playback_rate }: TPlaybackRateControl) => {
16+
const PlaybackRateControl = ({
17+
onPlaybackRateChange,
18+
is_mobile,
19+
playback_rate,
20+
is_v2 = false,
21+
show_controls = false,
22+
}: TPlaybackRateControl) => {
1323
const playback_rate_list = [
1424
{ text: '0.25x', value: '0.25' },
1525
{ text: '0.5x', value: '0.5' },
1626
{ text: '0.75x', value: '0.75' },
17-
{ text: localize('Normal'), value: '1' },
27+
{ text: is_v2 ? '1x' : localize('Normal'), value: '1' },
1828
{ text: '1.5x', value: '1.5' },
19-
{ text: '2.0x', value: '2' },
29+
{ text: is_v2 ? '2x' : '2.0x', value: '2' },
2030
];
2131

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

2636
return (
2737
<button className='player__controls__button player__playback-rate__wrapper'>
28-
<Icon
29-
icon='IcPlaybackRate'
30-
custom_color='var(--text-colored-background)'
31-
size={20}
32-
className='playback-rate__icon'
33-
/>
38+
{is_v2 ? (
39+
<StandalonePlaybackSpeedFillIcon fill='#ffffff' iconSize='md' className='playback-rate__icon' />
40+
) : (
41+
<Icon
42+
icon='IcPlaybackRate'
43+
custom_color='var(--text-colored-background)'
44+
size={20}
45+
className='playback-rate__icon'
46+
/>
47+
)}
3448
<Dropdown
35-
classNameDisplay='dc-dropdown__display--playback-rate'
49+
classNameDisplay={clsx('', {
50+
'dc-dropdown__display--playback-rate': !is_v2,
51+
'dc-dropdown__display--playback-rate--v2': is_v2,
52+
})}
3653
classNameItems='dc-dropdown__display--playback-rate__item'
3754
id='playback_rate'
3855
is_alignment_top
@@ -44,6 +61,7 @@ const PlaybackRateControl = ({ onPlaybackRateChange, is_mobile, playback_rate }:
4461
should_open_on_hover={!is_mobile}
4562
should_scroll_to_selected
4663
should_autohide={false}
64+
should_auto_close_dropdown_list={!show_controls}
4765
/>
4866
</button>
4967
);

packages/components/src/components/video-player/video-controls.tsx

Lines changed: 84 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Text from '../text';
55
import { formatDurationTime } from '@deriv/shared';
66
import VolumeControl from './volume-control';
77
import PlaybackRateControl from './playback-rate-control';
8+
import clsx from 'clsx';
89

910
type TVideoControls = {
1011
block_controls?: boolean;
@@ -16,10 +17,12 @@ type TVideoControls = {
1617
is_playing?: boolean;
1718
is_mobile?: boolean;
1819
is_muted?: boolean;
20+
is_v2?: boolean;
1921
increased_drag_area?: boolean;
2022
onRewind: (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
2123
onVolumeChange: (new_value: number) => void;
2224
onPlaybackRateChange: (new_value: number) => void;
25+
onUserActivity: () => void;
2326
progress_bar_filled_ref: React.RefObject<HTMLDivElement>;
2427
progress_bar_ref: React.RefObject<HTMLDivElement>;
2528
progress_dot_ref: React.RefObject<HTMLSpanElement>;
@@ -41,6 +44,7 @@ const VideoControls = ({
4144
is_playing,
4245
is_mobile,
4346
is_muted,
47+
is_v2 = false,
4448
increased_drag_area,
4549
onRewind,
4650
onVolumeChange,
@@ -54,18 +58,61 @@ const VideoControls = ({
5458
toggleMute,
5559
video_duration,
5660
volume,
61+
onUserActivity,
5762
}: TVideoControls) => {
5863
const [is_drag_dot_visible, setIsDragDotVisible] = React.useState(false);
5964

65+
const handleUserInteraction = () => {
66+
onUserActivity?.();
67+
};
68+
6069
return (
6170
<div
6271
className={classNames('player__controls__wrapper', {
6372
'player__controls__wrapper--visible': show_controls,
6473
'player__controls__wrapper--interactive': show_controls,
6574
})}
75+
onMouseMove={handleUserInteraction}
76+
onTouchStart={handleUserInteraction}
77+
onTouchMove={handleUserInteraction}
78+
onClick={handleUserInteraction}
6679
>
80+
{is_v2 && (
81+
<div
82+
className={classNames('player__controls__bottom-bar--v2', {
83+
'player__controls__bottom-bar--blocked': block_controls,
84+
})}
85+
>
86+
<div className='controls__right--v2'>
87+
<VolumeControl
88+
onVolumeChange={onVolumeChange}
89+
volume={volume}
90+
is_mobile={is_mobile}
91+
is_muted={is_muted}
92+
toggleMute={toggleMute}
93+
is_v2
94+
/>
95+
<PlaybackRateControl
96+
onPlaybackRateChange={onPlaybackRateChange}
97+
is_mobile={is_mobile}
98+
playback_rate={playback_rate}
99+
is_v2
100+
show_controls={show_controls}
101+
/>
102+
</div>
103+
<div className='controls__left--v2'>
104+
<div className='player__controls__time-wrapper--v2'>
105+
<Text size='xxxs' line_height='s' color='colored-background'>
106+
{formatDurationTime(current_time)}
107+
{' / '}
108+
{formatDurationTime(video_duration)}
109+
</Text>
110+
</div>
111+
</div>
112+
</div>
113+
)}
67114
<div
68-
className='player__controls__progress-bar'
115+
className={clsx('player__controls__progress-bar', { 'player__controls__progress-bar--v2': is_v2 })}
69116
onClick={onRewind}
70117
onKeyDown={onRewind}
71118
onMouseOver={() => setIsDragDotVisible(true)}
@@ -96,43 +143,45 @@ const VideoControls = ({
96143
)}
97144
</div>
98145
</div>
99-
<div
100-
className={classNames('player__controls__bottom-bar', {
101-
'player__controls__bottom-bar--blocked': block_controls,
102-
})}
103-
>
104-
<div className='player__controls__bottom-bar controls__left'>
105-
<button onClick={togglePlay} className='player__controls__button'>
106-
<Icon
107-
icon={is_playing ? 'IcPause' : 'IcPlay'}
108-
custom_color='var(--text-colored-background)'
109-
height={18}
110-
width={15}
146+
{!is_v2 && (
147+
<div
148+
className={classNames('player__controls__bottom-bar', {
149+
'player__controls__bottom-bar--blocked': block_controls,
150+
})}
151+
>
152+
<div className='player__controls__bottom-bar controls__left'>
153+
<button onClick={togglePlay} className='player__controls__button'>
154+
<Icon
155+
icon={is_playing ? 'IcPause' : 'IcPlay'}
156+
custom_color='var(--text-colored-background)'
157+
height={18}
158+
width={15}
159+
/>
160+
</button>
161+
<div className='player__controls__time-wrapper'>
162+
<Text size='xxxs' line_height='s' color='colored-background'>
163+
{formatDurationTime(current_time)}
164+
{' / '}
165+
{formatDurationTime(video_duration)}
166+
</Text>
167+
</div>
168+
</div>
169+
<div className='player__controls__bottom-bar controls__right'>
170+
<VolumeControl
171+
onVolumeChange={onVolumeChange}
172+
volume={volume}
173+
is_mobile={is_mobile}
174+
is_muted={is_muted}
175+
toggleMute={toggleMute}
176+
/>
177+
<PlaybackRateControl
178+
onPlaybackRateChange={onPlaybackRateChange}
179+
is_mobile={is_mobile}
180+
playback_rate={playback_rate}
111181
/>
112-
</button>
113-
<div className='player__controls__time-wrapper'>
114-
<Text size='xxxs' line_height='s' color='colored-background'>
115-
{formatDurationTime(current_time)}
116-
{' / '}
117-
{formatDurationTime(video_duration)}
118-
</Text>
119182
</div>
120183
</div>
121-
<div className='player__controls__bottom-bar controls__right'>
122-
<VolumeControl
123-
onVolumeChange={onVolumeChange}
124-
volume={volume}
125-
is_mobile={is_mobile}
126-
is_muted={is_muted}
127-
toggleMute={toggleMute}
128-
/>
129-
<PlaybackRateControl
130-
onPlaybackRateChange={onPlaybackRateChange}
131-
is_mobile={is_mobile}
132-
playback_rate={playback_rate}
133-
/>
134-
</div>
135-
</div>
184+
)}
136185
</div>
137186
);
138187
};

0 commit comments

Comments
 (0)