Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { composeStories } from '@storybook/react-webpack5';
import { fireEvent } from '@testing-library/dom';

import { render } from '../../testing';

import AudioPlayer from './AudioPlayer';
import * as stories from './AudioPlayer.stories';

const { AudioPlayerDefault, AudioPlayerWithDownload } = composeStories(stories);

describe('[AudioPlayer Component]', () => {
describe('Story renders without crashing', () => {
it('AudioPlayerDefault', () => {
render(<AudioPlayerDefault />);
});
it('AudioPlayerWithDownload', () => {
render(<AudioPlayerWithDownload />);
});
});
});

it('should display the audio player', () => {
const { container } = render(<AudioPlayer src='#' />);
expect(container.querySelector('audio')).toBeInTheDocument();
});

it('should display the download button when download prop is true', () => {
const { container } = render(<AudioPlayer src='#' download />);
expect(container.querySelector('a[download]')).toBeInTheDocument();
});

it('should display the loading state when the audio is loading', () => {
const { container } = render(<AudioPlayer src='#' />);
const audio = container.querySelector('audio');

if (audio) {
fireEvent(audio, new Event('loadstart'));
}

expect(
container.querySelector('.rcx-audio-player--loading'),
).toBeInTheDocument();
});

it('should display the play button when the audio is not playing', () => {
const { container } = render(<AudioPlayer src='#' />);
const audio = container.querySelector('audio');

if (audio) {
fireEvent(audio, new Event('canplay'));
}

expect(
container.querySelector('button[aria-label="Play"]'),
).toBeInTheDocument();
});

it('should display the pause button when the audio is playing', () => {
const { container } = render(<AudioPlayer src='#' />);
const audio = container.querySelector('audio');

if (audio) {
fireEvent(audio, new Event('play'));
}

expect(
container.querySelector('button[aria-label="Pause"]'),
).toBeInTheDocument();
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react-webpack5';
import type { Meta, StoryFn } from '@storybook/react-webpack5';

import AudioPlayer from './AudioPlayer';

Expand All @@ -10,4 +10,17 @@ export default {
const AUDIO_URL =
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-17.mp3';

export const AudioPlayerDefault = () => <AudioPlayer src={AUDIO_URL} />;
const Template: StoryFn<typeof AudioPlayer> = (args) => (
<AudioPlayer {...args} />
);

export const AudioPlayerDefault = Template.bind({});
AudioPlayerDefault.args = {
src: AUDIO_URL,
};

export const AudioPlayerWithDownload = Template.bind({});
AudioPlayerWithDownload.args = {
src: AUDIO_URL,
download: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.rcx-audio-player {
&--loading {
.rcx-icon--name-loading {
animation: spin-animation 0.8s linear infinite;
}
}
}

@keyframes spin-animation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
33 changes: 32 additions & 1 deletion packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState, useRef, forwardRef } from 'react';

import { Box, Button, IconButton, Margins } from '../..';
import { Slider } from '../Slider';
import './AudioPlayer.styles.scss';

const getMaskTime = (durationTime: number) =>
new Date(durationTime * 1000)
Expand Down Expand Up @@ -73,6 +74,16 @@ export type AudioPlayerProps = {
trackProps?: TrackHTMLAttributes<HTMLTrackElement>;
};

const getIcon = (isLoading: boolean, isPlaying: boolean) => {
if (isLoading) {
return 'loading';
}
if (isPlaying) {
return 'pause-shape-filled';
}
return 'play-shape-filled';
};

const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(
(
{
Expand All @@ -93,6 +104,8 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(
) => {
const audioRef = useRef<HTMLAudioElement>(null);
const refs = useMergedRefs(ref, audioRef);
const [isReady, setIsReady] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [durationTime, setDurationTime] = useState(0);
Expand Down Expand Up @@ -128,6 +141,8 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(

return (
<Box
rcx-audio-player
rcx-audio-player--loading={isLoading}
borderWidth='default'
bg='tint'
borderColor='extra-light'
Expand All @@ -146,7 +161,8 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(
medium
onClick={handlePlay}
aria-label={isPlaying ? pauseLabel : playLabel}
icon={isPlaying ? 'pause-shape-filled' : 'play-shape-filled'}
icon={getIcon(isLoading, isPlaying)}
disabled={isLoading}
/>
<Margins inline={8}>
<Box fontScale='p2' color='secondary-info'>
Expand All @@ -160,6 +176,7 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(
showOutput={false}
value={currentTime}
maxValue={durationTime}
disabled={!isReady}
onChange={(value) => {
if (audioRef.current) {
audioRef.current.currentTime = value;
Expand All @@ -171,6 +188,7 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(
<Button
secondary
small
disabled={isLoading}
onClick={handlePlaybackSpeedSingleControl}
aria-label={changePlaybackSpeedLabel}
>
Expand Down Expand Up @@ -200,6 +218,9 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(
onTimeUpdate={(e) => {
setCurrentTime((e.target as HTMLAudioElement).currentTime);
}}
onLoadStart={() => {
setIsLoading(true);
}}
onLoadedMetadata={(e) => {
const { duration } = e.target as HTMLAudioElement;

Expand All @@ -209,6 +230,16 @@ const AudioPlayer = forwardRef<HTMLAudioElement, AudioPlayerProps>(

getDurationForInfinityDurationAudioFile(src, setDurationTime);
}}
onSeeking={() => {
setIsLoading(true);
}}
onSeeked={() => {
setIsLoading(false);
}}
onCanPlay={() => {
setIsReady(true);
setIsLoading(false);
}}
onEnded={() => setIsPlaying(false)}
ref={refs}
preload='metadata'
Expand Down