-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/arc 3904 video player #150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
46774cb
ARC-3904: start reworking the flowplayer for custom controls
reunefe 0a00702
ARC-3904: tweak flowplayer and add tests
reunefe 6286c7b
ARC-3904: tweak flowplayer and add tests
reunefe 9f8ee18
ARC-3904: Clean up
reunefe 6400a33
ARC-3904: Fix jumping play/pause overlay
reunefe 8f4626c
ARC-3904: Remove volume flyout and replace to sound on/off
reunefe d154c94
ARC-3904: tweak flowplayer
reunefe 49f651f
ARC-3904: use accent color for icon backgrounds
reunefe 9daf3b7
ARC-3904: Ensure dropdown is not being cut off
reunefe 80ab9a8
ARC-3904: Style option controls like subtitels and speed
reunefe 2d26dac
ARC-3904: Remove subtitle persistence
reunefe 3e96bef
ARC-3904: Make colors a bit more tweakable
reunefe 391925a
ARC-3904: PR remarks audio wave form
reunefe 6bd12c5
ARC-3904: PR remarks css
reunefe 22a0826
ARC-3904: PR remarks flyoutId to enum
reunefe d033044
ARC-3904: PR remarks tweak color fallbacks and configs
reunefe 5fbdde6
ARC-3904: PR remarks fix translations
reunefe 6a44ea7
ARC-3904: Export AudioWaveFormDisplaySize
reunefe 4edd920
ARC-3904: Ran biome
reunefe a6175b7
ARC-3904: PR remarks
reunefe 21c11d6
ARC-3904: PR remarks wcag
reunefe 90dadd4
ARC-3904: PR remarks ensure buttons are being clicked
reunefe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
src/components/AudioWaveFormDisplay/AudioWaveFormDisplay.helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| export enum AudioWaveFormDisplaySize { | ||
| Small = 'small', | ||
| Large = 'large', | ||
| } | ||
|
|
||
| export interface WaveFormBar { | ||
| /** Horizontal position of the bar (viewBox units). Shared by both endpoints since the bar is a vertical line. */ | ||
| x: number; | ||
| /** Y-coordinate of the bar's top endpoint (viewBox units). */ | ||
| yTop: number; | ||
| /** Y-coordinate of the bar's bottom endpoint (viewBox units). */ | ||
| yBottom: number; | ||
| } | ||
|
|
||
| // Bar geometry traced from the original design asset. | ||
| const WAVE_FORM_VIEW_BOX_WIDTH = 92; | ||
| const WAVE_FORM_VIEW_BOX_HEIGHT = 44; | ||
| const WAVE_FORM_CENTER_Y = 21.6; | ||
| const WAVE_FORM_FIRST_BAR_X = 0.9; | ||
| const WAVE_FORM_BAR_SPACING = 3; | ||
| export const WAVE_FORM_STROKE_WIDTH = 1.8; | ||
|
|
||
| // Padding around the bars, baked into the viewBox rather than CSS padding so it can't collapse | ||
| // to zero on a short/narrow container. | ||
| const WAVE_FORM_PADDING_X_RATIO = 0.15; | ||
| const WAVE_FORM_PADDING_Y_RATIO = 0.3; | ||
|
|
||
| // Exported so PeakDisplay.tsx can account for this padding in its own clip-path math - otherwise | ||
| // the played/unplayed reveal "plays through" blank space before any bar is visible. | ||
| export const WAVE_FORM_PADDING_X_PERCENT = WAVE_FORM_PADDING_X_RATIO * 100; | ||
|
|
||
| // Half the height of each bar (viewBox units), left to right, traced from the original asset. | ||
| const WAVE_FORM_BAR_HALF_HEIGHTS: readonly number[] = [ | ||
| 0.3, 3.3, 3.3, 6.9, 3.3, 6.9, 13.5, 20.7, 10.5, 6.9, 17.1, 13.5, 10.5, 3.3, 6.9, 3.3, 3.3, 6.9, | ||
| 10.5, 13.5, 6.9, 3.3, 3.3, 6.9, 3.3, 3.3, 6.9, 3.3, 3.3, 1.5, | ||
| ]; | ||
|
|
||
| const WAVE_FORM_BAR_COUNT = WAVE_FORM_BAR_HALF_HEIGHTS.length; | ||
|
|
||
| // Right margin the reference asset leaves after its last bar, reused to size the large viewBox. | ||
| const WAVE_FORM_RIGHT_MARGIN = | ||
| WAVE_FORM_VIEW_BOX_WIDTH - | ||
| (WAVE_FORM_FIRST_BAR_X + (WAVE_FORM_BAR_COUNT - 1) * WAVE_FORM_BAR_SPACING); | ||
|
|
||
| function buildWaveFormBars( | ||
| barCount: number, | ||
| halfHeightAt: (index: number) => number | ||
| ): WaveFormBar[] { | ||
| return Array.from({ length: barCount }, (_, index) => { | ||
| const x = WAVE_FORM_FIRST_BAR_X + index * WAVE_FORM_BAR_SPACING; | ||
| const halfHeight = halfHeightAt(index); | ||
| return { x, yTop: WAVE_FORM_CENTER_Y - halfHeight, yBottom: WAVE_FORM_CENTER_Y + halfHeight }; | ||
| }); | ||
| } | ||
|
|
||
| function getWaveFormViewBoxWidth(barCount: number): number { | ||
| return WAVE_FORM_FIRST_BAR_X + (barCount - 1) * WAVE_FORM_BAR_SPACING + WAVE_FORM_RIGHT_MARGIN; | ||
| } | ||
|
|
||
| const SMALL_WAVE_FORM_BARS: readonly WaveFormBar[] = buildWaveFormBars( | ||
| WAVE_FORM_BAR_COUNT, | ||
| (index) => WAVE_FORM_BAR_HALF_HEIGHTS[index] | ||
| ); | ||
|
|
||
| // Large: the small waveform immediately followed by its own mirror, on one continuous grid. | ||
| const LARGE_WAVE_FORM_BARS: readonly WaveFormBar[] = buildWaveFormBars( | ||
| WAVE_FORM_BAR_COUNT * 2, | ||
| (index) => | ||
| WAVE_FORM_BAR_HALF_HEIGHTS[ | ||
| index < WAVE_FORM_BAR_COUNT ? index : WAVE_FORM_BAR_COUNT * 2 - 1 - index | ||
| ] | ||
| ); | ||
|
|
||
| export function getWaveFormBars(size: AudioWaveFormDisplaySize): readonly WaveFormBar[] { | ||
| return size === AudioWaveFormDisplaySize.Large ? LARGE_WAVE_FORM_BARS : SMALL_WAVE_FORM_BARS; | ||
| } | ||
|
|
||
| // Expands the bars' bounding box to the full display box, per the padding ratios above. | ||
| export function getWaveFormViewBox(size: AudioWaveFormDisplaySize): string { | ||
| const contentWidth = getWaveFormViewBoxWidth(getWaveFormBars(size).length); | ||
| const contentHeight = WAVE_FORM_VIEW_BOX_HEIGHT; | ||
|
|
||
| const width = contentWidth / (1 - 2 * WAVE_FORM_PADDING_X_RATIO); | ||
| const height = contentHeight / (1 - 2 * WAVE_FORM_PADDING_Y_RATIO); | ||
| const minX = -(width - contentWidth) / 2; | ||
| const minY = -(height - contentHeight) / 2; | ||
| return `${minX} ${minY} ${width} ${height}`; | ||
| } |
22 changes: 22 additions & 0 deletions
22
src/components/AudioWaveFormDisplay/AudioWaveFormDisplay.scss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| .c-audio-wave-form-display { | ||
| display: block; | ||
| width: 100%; | ||
| height: 100%; | ||
| background-color: var(--c-audio-wave-form-display-bg, transparent); | ||
|
|
||
| &__scaler { | ||
| display: block; | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
|
|
||
| &__svg { | ||
| display: block; | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
|
|
||
| &__bar { | ||
| stroke: var(--c-audio-wave-form-display-bar-color, var(--c-audio-wave-form-display-wave-color, #fff)); | ||
| } | ||
| } |
50 changes: 50 additions & 0 deletions
50
src/components/AudioWaveFormDisplay/AudioWaveFormDisplay.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||
|
|
||
| import { AudioWaveFormDisplay } from './AudioWaveFormDisplay'; | ||
| import { AudioWaveFormDisplaySize } from './AudioWaveFormDisplay.helpers'; | ||
|
|
||
| const meta: Meta<typeof AudioWaveFormDisplay> = { | ||
| title: 'Components/AudioWaveFormDisplay', | ||
| component: AudioWaveFormDisplay, | ||
| }; | ||
| export default meta; | ||
| type Story = StoryObj<typeof AudioWaveFormDisplay>; | ||
|
|
||
| export const Default: Story = { | ||
| render: () => ( | ||
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| flexDirection: 'column', | ||
| gap: '1rem', | ||
| background: '#111', | ||
| padding: '1rem', | ||
| }} | ||
| > | ||
| <div style={{ height: '4rem' }}> | ||
| <AudioWaveFormDisplay | ||
| ariaLabel="Waveform" | ||
| size={AudioWaveFormDisplaySize.Small} | ||
| waveColor="#fff" | ||
| /> | ||
| </div> | ||
| <div style={{ height: '4rem' }}> | ||
| <AudioWaveFormDisplay | ||
| ariaLabel="Waveform" | ||
| size={AudioWaveFormDisplaySize.Large} | ||
| waveColor="#00c8aa" | ||
| /> | ||
| </div> | ||
| </div> | ||
| ), | ||
| args: {}, | ||
| }; | ||
|
|
||
| export const CustomColors: Story = { | ||
| render: () => ( | ||
| <div style={{ height: '4rem' }}> | ||
| <AudioWaveFormDisplay ariaLabel="Waveform" waveColor="#ff6b6b" backgroundColor="#1d1d1d" /> | ||
| </div> | ||
| ), | ||
| args: {}, | ||
| }; |
51 changes: 51 additions & 0 deletions
51
src/components/AudioWaveFormDisplay/AudioWaveFormDisplay.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { cleanup, render } from '@testing-library/react'; | ||
|
|
||
| import { AudioWaveFormDisplay } from './AudioWaveFormDisplay'; | ||
| import { AudioWaveFormDisplaySize } from './AudioWaveFormDisplay.helpers'; | ||
|
|
||
| afterEach(() => { | ||
| cleanup(); | ||
| }); | ||
|
|
||
| describe('<AudioWaveFormDisplay />', () => { | ||
| it('should be able to render', () => { | ||
| render(<AudioWaveFormDisplay />); | ||
| }); | ||
|
|
||
| it('should render the large size with twice as many bars as the small size', () => { | ||
| const { container: small } = render( | ||
| <AudioWaveFormDisplay size={AudioWaveFormDisplaySize.Small} /> | ||
| ); | ||
| const { container: large } = render( | ||
| <AudioWaveFormDisplay size={AudioWaveFormDisplaySize.Large} /> | ||
| ); | ||
|
|
||
| const smallBarCount = small.querySelectorAll('.c-audio-wave-form-display__bar').length; | ||
| const largeBarCount = large.querySelectorAll('.c-audio-wave-form-display__bar').length; | ||
|
|
||
| expect(largeBarCount).toBe(smallBarCount * 2); | ||
| }); | ||
|
|
||
| it('should leave the wave color CSS variable unset when none is given, falling back to the SCSS default', () => { | ||
| const { container } = render(<AudioWaveFormDisplay />); | ||
| const outer = container.querySelector('.c-audio-wave-form-display') as HTMLElement; | ||
|
|
||
| expect(outer.style.getPropertyValue('--c-audio-wave-form-display-wave-color')).toBe(''); | ||
| }); | ||
|
|
||
| it('should pass the given wave and background colors through as CSS variables', () => { | ||
| const { container } = render( | ||
| <AudioWaveFormDisplay waveColor="#00c8aa" backgroundColor="#1d1d1d" /> | ||
| ); | ||
| const outer = container.querySelector('.c-audio-wave-form-display') as HTMLElement; | ||
|
|
||
| expect(outer.style.getPropertyValue('--c-audio-wave-form-display-wave-color')).toBe('#00c8aa'); | ||
| expect(outer.style.getPropertyValue('--c-audio-wave-form-display-bg')).toBe('#1d1d1d'); | ||
| }); | ||
|
|
||
| it('should pass the given className through', () => { | ||
| const { container } = render(<AudioWaveFormDisplay className="my-extra-class" />); | ||
|
|
||
| expect(container.querySelector('.c-audio-wave-form-display.my-extra-class')).not.toBeNull(); | ||
| }); | ||
| }); |
67 changes: 67 additions & 0 deletions
67
src/components/AudioWaveFormDisplay/AudioWaveFormDisplay.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import clsx from 'clsx'; | ||
| import { type CSSProperties, type FC, memo } from 'react'; | ||
| import { | ||
| AudioWaveFormDisplaySize, | ||
| getWaveFormBars, | ||
| getWaveFormViewBox, | ||
| WAVE_FORM_STROKE_WIDTH, | ||
| } from './AudioWaveFormDisplay.helpers'; | ||
| import type { AudioWaveFormDisplayProps } from './AudioWaveFormDisplay.types'; | ||
|
|
||
| import './AudioWaveFormDisplay.scss'; | ||
|
|
||
| // Memoized so PeakDisplay's static "inactive" waveform layer (unchanging colors/size) skips | ||
| // reconciling its ~30-60 <line> elements on every playback timeupdate tick, which only changes | ||
| // the sibling "active" layer's clip-path, not either layer's own props. | ||
| export const AudioWaveFormDisplay: FC<AudioWaveFormDisplayProps> = memo( | ||
| function AudioWaveFormDisplay({ | ||
| className, | ||
| rootClassName: root = 'c-audio-wave-form-display', | ||
| ariaLabel, | ||
| waveColor, | ||
| backgroundColor, | ||
| size = AudioWaveFormDisplaySize.Small, | ||
| }) { | ||
| const bars = getWaveFormBars(size); | ||
| const viewBox = getWaveFormViewBox(size); | ||
|
|
||
| return ( | ||
| <div | ||
| role="img" | ||
| aria-label={ariaLabel} | ||
| className={clsx(root, `${root}--${size}`, className)} | ||
| style={ | ||
| { | ||
| '--c-audio-wave-form-display-bg': backgroundColor, | ||
| '--c-audio-wave-form-display-wave-color': waveColor, | ||
| } as CSSProperties | ||
| } | ||
| > | ||
| {/* Plain box for consumers to hook a hover-zoom transform onto: transitioning `transform` | ||
| on an <svg> itself doesn't animate smoothly in every browser, unlike an ordinary element. */} | ||
| <div className="c-audio-wave-form-display__scaler"> | ||
| <svg | ||
| className="c-audio-wave-form-display__svg" | ||
| viewBox={viewBox} | ||
| preserveAspectRatio="xMidYMid meet" | ||
| aria-hidden="true" | ||
| > | ||
| {bars.map((bar, index) => ( | ||
| <line | ||
| // biome-ignore lint/suspicious/noArrayIndexKey: decorative, no identity of its own | ||
| key={index} | ||
| className="c-audio-wave-form-display__bar" | ||
| x1={bar.x} | ||
| x2={bar.x} | ||
| y1={bar.yTop} | ||
| y2={bar.yBottom} | ||
| strokeWidth={WAVE_FORM_STROKE_WIDTH} | ||
| strokeLinecap="round" | ||
| /> | ||
| ))} | ||
| </svg> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| ); | ||
9 changes: 9 additions & 0 deletions
9
src/components/AudioWaveFormDisplay/AudioWaveFormDisplay.types.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import type { DefaultComponentProps } from '../../types'; | ||
| import type { AudioWaveFormDisplaySize } from './AudioWaveFormDisplay.helpers'; | ||
|
|
||
| export type AudioWaveFormDisplayProps = DefaultComponentProps & { | ||
| waveColor?: string; | ||
| backgroundColor?: string; | ||
| size?: AudioWaveFormDisplaySize; | ||
| ariaLabel?: string; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export { AudioWaveFormDisplay } from './AudioWaveFormDisplay'; | ||
| export { | ||
| AudioWaveFormDisplaySize, | ||
| WAVE_FORM_PADDING_X_PERCENT, | ||
| } from './AudioWaveFormDisplay.helpers'; | ||
| export * from './AudioWaveFormDisplay.types'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.