diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx
new file mode 100644
index 0000000000..ddda27b058
--- /dev/null
+++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx
@@ -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();
+ });
+ it('AudioPlayerWithDownload', () => {
+ render();
+ });
+ });
+});
+
+it('should display the audio player', () => {
+ const { container } = render();
+ expect(container.querySelector('audio')).toBeInTheDocument();
+});
+
+it('should display the download button when download prop is true', () => {
+ const { container } = render();
+ expect(container.querySelector('a[download]')).toBeInTheDocument();
+});
+
+it('should display the loading state when the audio is loading', () => {
+ const { container } = render();
+ 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();
+ 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();
+ const audio = container.querySelector('audio');
+
+ if (audio) {
+ fireEvent(audio, new Event('play'));
+ }
+
+ expect(
+ container.querySelector('button[aria-label="Pause"]'),
+ ).toBeInTheDocument();
+});
diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx
index aa063feafe..baf0031520 100644
--- a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx
+++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx
@@ -1,4 +1,4 @@
-import type { Meta } from '@storybook/react-webpack5';
+import type { Meta, StoryFn } from '@storybook/react-webpack5';
import AudioPlayer from './AudioPlayer';
@@ -10,4 +10,17 @@ export default {
const AUDIO_URL =
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-17.mp3';
-export const AudioPlayerDefault = () => ;
+const Template: StoryFn = (args) => (
+
+);
+
+export const AudioPlayerDefault = Template.bind({});
+AudioPlayerDefault.args = {
+ src: AUDIO_URL,
+};
+
+export const AudioPlayerWithDownload = Template.bind({});
+AudioPlayerWithDownload.args = {
+ src: AUDIO_URL,
+ download: true,
+};
diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.styles.scss b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.styles.scss
new file mode 100644
index 0000000000..436ba3c8df
--- /dev/null
+++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.styles.scss
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx
index 61b44839f8..974059f105 100644
--- a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx
+++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx
@@ -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)
@@ -73,6 +74,16 @@ export type AudioPlayerProps = {
trackProps?: TrackHTMLAttributes;
};
+const getIcon = (isLoading: boolean, isPlaying: boolean) => {
+ if (isLoading) {
+ return 'loading';
+ }
+ if (isPlaying) {
+ return 'pause-shape-filled';
+ }
+ return 'play-shape-filled';
+};
+
const AudioPlayer = forwardRef(
(
{
@@ -93,6 +104,8 @@ const AudioPlayer = forwardRef(
) => {
const audioRef = useRef(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);
@@ -128,6 +141,8 @@ const AudioPlayer = forwardRef(
return (
(
medium
onClick={handlePlay}
aria-label={isPlaying ? pauseLabel : playLabel}
- icon={isPlaying ? 'pause-shape-filled' : 'play-shape-filled'}
+ icon={getIcon(isLoading, isPlaying)}
+ disabled={isLoading}
/>
@@ -160,6 +176,7 @@ const AudioPlayer = forwardRef(
showOutput={false}
value={currentTime}
maxValue={durationTime}
+ disabled={!isReady}
onChange={(value) => {
if (audioRef.current) {
audioRef.current.currentTime = value;
@@ -171,6 +188,7 @@ const AudioPlayer = forwardRef(