From 750292a1371ef65de98a07295b988c756646df95 Mon Sep 17 00:00:00 2001 From: Nathan McWilliams Date: Sun, 26 Jan 2025 17:41:06 -0500 Subject: [PATCH 1/4] support elements --- src/ProgressBar.tsx | 6 ++- src/index.tsx | 20 ++++++++-- stories/edge-cases.stories.js | 13 ++++++- stories/playlist.stories.js | 47 ++++++++++++++++++++-- stories/playlist.tsx | 73 +++++++++++++++++++++++++++-------- stories/utils.js | 12 +++++- 6 files changed, 143 insertions(+), 28 deletions(-) diff --git a/src/ProgressBar.tsx b/src/ProgressBar.tsx index e2cc2a5..b73a61d 100644 --- a/src/ProgressBar.tsx +++ b/src/ProgressBar.tsx @@ -82,13 +82,15 @@ class ProgressBar extends Component { // Get time info while dragging indicator by mouse or touch getCurrentProgress = (event: MouseEvent | TouchEvent): TimePosInfo => { const { audio, progressBar } = this.props + const audioSrc = audio.src || audio.currentSrc + // A single-file progressive download (non-blob) can have transient states // where currentTime is not yet finite. In those cases return zeros to avoid // NaN propagation. const isSingleFileProgressiveDownload = - audio.src.indexOf('blob:') !== 0 && typeof this.props.srcDuration === 'undefined' + audioSrc.indexOf('blob:') !== 0 && typeof this.props.srcDuration === 'undefined' - if (isSingleFileProgressiveDownload && (!audio.src || !isFinite(audio.currentTime) || !progressBar.current)) { + if (isSingleFileProgressiveDownload && (!audioSrc || !isFinite(audio.currentTime) || !progressBar.current)) { return { currentTime: 0, currentTimePos: '0%' } } diff --git a/src/index.tsx b/src/index.tsx index ab82cf4..710e627 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -35,6 +35,10 @@ interface PlayerProps { * Whether to play audio after src prop is changed */ autoPlayAfterSrcChange?: boolean + /** + * Whether to force a load of the audio after src or srcKey prop is changed + */ + autoLoadAfterSrcChange?: boolean /** * custom classNames */ @@ -90,6 +94,11 @@ interface PlayerProps { * HTML5 Audio tag src property */ src?: string + /** + * A unique key for the current audio source, for use along with child + * elements + */ + srcKey?: string defaultCurrentTime?: ReactNode defaultDuration?: ReactNode volume?: number @@ -183,7 +192,7 @@ class H5AudioPlayer extends Component { togglePlay = (e: React.SyntheticEvent): void => { e.stopPropagation() const audio = this.audio.current - if ((audio.paused || audio.ended) && audio.src) { + if ((audio.paused || audio.ended) && (audio.src || audio.currentSrc)) { this.playAudioPromise() } else if (!audio.paused) { audio.pause() @@ -663,8 +672,13 @@ class H5AudioPlayer extends Component { } componentDidUpdate(prevProps: PlayerProps): void { - const { src, autoPlayAfterSrcChange } = this.props - if (prevProps.src !== src) { + const { src, srcKey, autoPlayAfterSrcChange, autoLoadAfterSrcChange } = this.props + + if (prevProps.src !== src || prevProps.srcKey !== srcKey) { + if (autoLoadAfterSrcChange && this.audio.current) { + this.audio.current.load() + } + if (autoPlayAfterSrcChange) { this.playAudioPromise() } else { diff --git a/stories/edge-cases.stories.js b/stories/edge-cases.stories.js index afb73bb..404fada 100644 --- a/stories/edge-cases.stories.js +++ b/stories/edge-cases.stories.js @@ -1,5 +1,5 @@ import { action } from "storybook/actions"; -import { SAMPLE_MP3_URL } from "./utils"; +import { SAMPLE_URL_A_MP3, SAMPLE_URL_A_FLAC } from "./utils"; import AudioPlayer from "../src/index.tsx"; import React from "react"; @@ -40,3 +40,14 @@ export const InvalidSrc = { name: "Invalid src", }; + +export const ChildSourceElements = { + render: () => ( + + + + + ), + + name: "Child elements", +}; diff --git a/stories/playlist.stories.js b/stories/playlist.stories.js index a245826..e72aa5e 100644 --- a/stories/playlist.stories.js +++ b/stories/playlist.stories.js @@ -1,14 +1,53 @@ -import { SAMPLE_MP3_URL } from "./utils"; -import AudioPlayer from "../src/index.tsx"; +import { + SAMPLE_MP3_URL, + SAMPLE_MP3_URL_B, + SAMPLE_MP3_URL_C, + SAMPLE_URL_A_OGG, + SAMPLE_URL_A_MP3, + SAMPLE_URL_A_FLAC, + SAMPLE_URL_B_OGG, + SAMPLE_URL_B_MP3, + SAMPLE_URL_B_FLAC, +} from './utils.js' import PlayList from "./playlist.tsx"; import React from "react"; +const mp3Playlist = [ + { src: SAMPLE_MP3_URL, type: 'audio/mpeg' }, + { src: SAMPLE_MP3_URL_B, type: 'audio/mpeg' }, + { src: SAMPLE_MP3_URL_C, type: 'audio/mpeg'}, +] + +const multiSourcePlaylist = [ + { + src: SAMPLE_URL_A_OGG, + type: 'audio/ogg', + additionalSrcs: [ + { src: SAMPLE_URL_A_MP3, type: 'audio/mpeg' }, + { src: SAMPLE_URL_A_FLAC, type: 'audio/flac' }, + ] + }, + { + src: SAMPLE_URL_B_OGG, + type: 'audio/ogg', + additionalSrcs: [ + { src: SAMPLE_URL_B_MP3, type: 'audio/mpeg' }, + { src: SAMPLE_URL_B_FLAC,type: 'audio/flac' }, + ] + } +] + export default { title: "Play List", component: PlayList, }; export const Playlist = { - render: () => , - name: "playlist", + render: () => , + name: "Playlist", +}; + +export const PlaylistWithSourceElements = { + render: () => , + name: "Playlist with elements", }; diff --git a/stories/playlist.tsx b/stories/playlist.tsx index c170758..9414a06 100644 --- a/stories/playlist.tsx +++ b/stories/playlist.tsx @@ -1,47 +1,86 @@ import React, { Component } from 'react' import AudioPlayer from '../src/index' -const playlist = [ - { name: 'SoundHelix-Song-9', src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3' }, - { name: 'SoundHelix-Song-4', src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3' }, - { name: 'SoundHelix-Song-8', src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3' }, -] +interface Track { + src: string + type: string + additionalSrcs: [ + { + src: string + type: string + } + ] +} + +interface PlaylistProps { + playlist: Track[] + useSourceElements?: boolean +} interface PlayListState { currentMusicIndex: number } -class PlayList extends Component { +class PlayList extends Component { + playlist: Track[] + useSourceElements?: boolean = false + state = { currentMusicIndex: 0, } + constructor(props: PlaylistProps) { + super(props) + this.playlist = props.playlist + this.useSourceElements = props.useSourceElements + } + handleClickPrevious = (): void => { this.setState((prevState) => ({ - currentMusicIndex: prevState.currentMusicIndex === 0 ? playlist.length - 1 : prevState.currentMusicIndex - 1, + currentMusicIndex: prevState.currentMusicIndex === 0 ? this.playlist.length - 1 : prevState.currentMusicIndex - 1, })) } handleClickNext = (): void => { this.setState((prevState) => ({ - currentMusicIndex: prevState.currentMusicIndex < playlist.length - 1 ? prevState.currentMusicIndex + 1 : 0, + currentMusicIndex: prevState.currentMusicIndex < this.playlist.length - 1 ? prevState.currentMusicIndex + 1 : 0, })) } render(): React.ReactNode { const { currentMusicIndex } = this.state + const track = this.playlist[currentMusicIndex] + return (

currentMusicIndex: {currentMusicIndex}

- + + {this.useSourceElements ? ( + + + {track.additionalSrcs && + track.additionalSrcs.map(({ src, type }) => )} + + ) : ( + + )}
) } diff --git a/stories/utils.js b/stories/utils.js index 9479420..7ec39eb 100644 --- a/stories/utils.js +++ b/stories/utils.js @@ -1 +1,11 @@ -export const SAMPLE_MP3_URL = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3' +export const SAMPLE_MP3_URL = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3' +export const SAMPLE_MP3_URL_B = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3' +export const SAMPLE_MP3_URL_C = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3' + +export const SAMPLE_URL_A_OGG = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.ogg' +export const SAMPLE_URL_A_MP3 = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.mp3' +export const SAMPLE_URL_A_FLAC = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.flac' + +export const SAMPLE_URL_B_OGG = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.ogg' +export const SAMPLE_URL_B_MP3 = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.mp3' +export const SAMPLE_URL_B_FLAC = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.flac' From 3afe09133ed10483afaee8497ba585d069e1dbb8 Mon Sep 17 00:00:00 2001 From: Nathan McWilliams Date: Tue, 28 Jan 2025 14:57:59 -0500 Subject: [PATCH 2/4] update docs --- README.md | 33 ++++++++++++++++++++++++++++++++- src/index.tsx | 9 +++++---- stories/playlist.tsx | 7 +++---- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 154ad08..32fba2a 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,8 @@ The `controls` attribute defaults to `false` and should never be changed to `tru | showFilledProgress | boolean | true | Show filled (already played) area on progress bar | | showFilledVolume | boolean | false | Show filled volume area on volume bar | | hasDefaultKeyBindings | boolean | true | Whether has default keyboard shortcuts | -| autoPlayAfterSrcChange | boolean | true | Play audio after `src` is changed, no matter `autoPlay` is `true` or `false` | +| autoPlayAfterSrcChange | boolean | true | Play audio after `src` or `srcKey` is changed, no matter `autoPlay` is `true` or `false` | +| autoLoadAfterSrcChange | boolean | true | Load audio after `src` or `srcKey` is changed, necessary if using child `` elements | | volumeJumpStep | number | 0.1 | Indicates the volume jump step when pressing up/down arrow key, volume range is `0` to `1` | | progressJumpStep | number | 5000 | **Deprecated, use progressJumpSteps.** Indicates the progress jump step (ms) when clicking rewind/forward button or left/right arrow key | | progressJumpSteps | object | `{ backward: 5000, forward: 5000 }` | Indicates the progress jump step (ms) when clicking rewind/forward button or left/right arrow key| @@ -126,6 +127,12 @@ The `controls` attribute defaults to `false` and should never be changed to `tru | mse.onSeek | Function (Event) | - | The callback to be used when seek happens (this is a key of the _mse_ prop) | | mse.srcDuration | number | - | The callback to be used when encrypted audio is detected and needs to be decrypted (this is a key of the _mse_ prop) | +### Other Props + +| Props | Type | Default | Note | +| ------------------------ | ----------------- | ------- | ---- | +| srcKey | string | '' | A unique key for the current audio source when not using the `src` prop, i.e., when using child `` elements with a playlist. | + ### Event Props Supported media events: `onPlay`, `onPause`, `onEnded`, `onSeeking`, `onSeeked`, `onAbort`, `onCanPlay`, `onCanPlayThrough`, `onEmptied`, `onError`, `onLoadStart`, `onLoadedMetaData`, `onLoadedData`, `onPlaying`, `onSuspend`, `onWaiting`, `onVolumeChange` @@ -198,6 +205,30 @@ Then you can access the audio element like this: You can use [Media Source Extensions](https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API) and [Encrypted Media Extensions](https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API) with this player. You need to provide the complete duration, and also a onSeek and onEncrypted callbacks. The logic for feeding the audio buffer and providing the decryption keys (if using encryption) must be set in the consumer side. The player does not provide that logic. Check the [StoryBook example](https://github.com/lhz516/react-h5-audio-player/blob/master/stories/mse-eme-player.tsx) to understand better how to use. +### Using `` Elements + +You can use child `` elements instead of the `src` prop, for example [to provide different file types or codecs based on browser support](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio#usage_notes). + +```jsx + + + + + + +``` + +When using `` elements in playlists, use the `srcKey` prop to specify a unique identifier for the current src. + +```jsx + + + + + + +``` + ## Release Notes https://github.com/lhz516/react-h5-audio-player/releases diff --git a/src/index.tsx b/src/index.tsx index 710e627..6c75be8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -36,7 +36,8 @@ interface PlayerProps { */ autoPlayAfterSrcChange?: boolean /** - * Whether to force a load of the audio after src or srcKey prop is changed + * Whether to load the audio after changing the src. This is necessary when + * using child `` elements */ autoLoadAfterSrcChange?: boolean /** @@ -95,8 +96,8 @@ interface PlayerProps { */ src?: string /** - * A unique key for the current audio source, for use along with child - * elements + * A unique key for the current audio source when not using the `src` prop, + * i.e., when using child elements */ srcKey?: string defaultCurrentTime?: ReactNode @@ -672,7 +673,7 @@ class H5AudioPlayer extends Component { } componentDidUpdate(prevProps: PlayerProps): void { - const { src, srcKey, autoPlayAfterSrcChange, autoLoadAfterSrcChange } = this.props + const { src, srcKey, autoPlayAfterSrcChange, autoLoadAfterSrcChange = true } = this.props if (prevProps.src !== src || prevProps.srcKey !== srcKey) { if (autoLoadAfterSrcChange && this.audio.current) { diff --git a/stories/playlist.tsx b/stories/playlist.tsx index 9414a06..3d74965 100644 --- a/stories/playlist.tsx +++ b/stories/playlist.tsx @@ -4,7 +4,7 @@ import AudioPlayer from '../src/index' interface Track { src: string type: string - additionalSrcs: [ + additionalSrcs?: [ { src: string type: string @@ -57,14 +57,13 @@ class PlayList extends Component { {this.useSourceElements ? ( {track.additionalSrcs && @@ -72,11 +71,11 @@ class PlayList extends Component { ) : ( From e5dbc35723698c0c7d4d473489bda6b429dafb1b Mon Sep 17 00:00:00 2001 From: Nathan McWilliams Date: Wed, 3 Sep 2025 19:59:46 -0400 Subject: [PATCH 3/4] add jest test --- src/ProgressBar.tsx | 2 +- src/index.test.js | 18 ++++++++++++++++++ stories/edge-cases.stories.js | 6 +++--- stories/playlist.stories.js | 24 ++++++++++++------------ stories/utils.js | 12 ++++++------ 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/ProgressBar.tsx b/src/ProgressBar.tsx index b73a61d..80ff747 100644 --- a/src/ProgressBar.tsx +++ b/src/ProgressBar.tsx @@ -83,7 +83,7 @@ class ProgressBar extends Component { getCurrentProgress = (event: MouseEvent | TouchEvent): TimePosInfo => { const { audio, progressBar } = this.props const audioSrc = audio.src || audio.currentSrc - + // A single-file progressive download (non-blob) can have transient states // where currentTime is not yet finite. In those cases return zeros to avoid // NaN propagation. diff --git a/src/index.test.js b/src/index.test.js index e5bf20f..efc1c40 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -702,4 +702,22 @@ describe('H5AudioPlayer', () => { }) }) }) + + describe('Multiple Source Elements', () => { + it('renders child elements', () => { + const srcAac = 'test-audio.aac' + const srcMp3 = 'test-audio.mp3' + + const { container } = render( + + + + + ) + + expect(screen.getByTestId('aac')).toBeInTheDocument() + expect(screen.getByTestId('mp3')).toBeInTheDocument() + expect(container.querySelector('audio')).not.toHaveAttribute('src') + }) + }) }) diff --git a/stories/edge-cases.stories.js b/stories/edge-cases.stories.js index 404fada..525e195 100644 --- a/stories/edge-cases.stories.js +++ b/stories/edge-cases.stories.js @@ -1,5 +1,5 @@ import { action } from "storybook/actions"; -import { SAMPLE_URL_A_MP3, SAMPLE_URL_A_FLAC } from "./utils"; +import { BRAHMS_FLAC_URL, BRAHMS_MP3_URL } from "./utils"; import AudioPlayer from "../src/index.tsx"; import React from "react"; @@ -44,8 +44,8 @@ export const InvalidSrc = { export const ChildSourceElements = { render: () => ( - - + + ), diff --git a/stories/playlist.stories.js b/stories/playlist.stories.js index e72aa5e..d8a596f 100644 --- a/stories/playlist.stories.js +++ b/stories/playlist.stories.js @@ -2,12 +2,12 @@ import { SAMPLE_MP3_URL, SAMPLE_MP3_URL_B, SAMPLE_MP3_URL_C, - SAMPLE_URL_A_OGG, - SAMPLE_URL_A_MP3, - SAMPLE_URL_A_FLAC, - SAMPLE_URL_B_OGG, - SAMPLE_URL_B_MP3, - SAMPLE_URL_B_FLAC, + BRAHMS_OGG_URL, + BRAHMS_MP3_URL, + BRAHMS_FLAC_URL, + MOZART_OGG_URL, + MOZART_MP3_URL, + MOZART_FLAC_URL, } from './utils.js' import PlayList from "./playlist.tsx"; import React from "react"; @@ -20,19 +20,19 @@ const mp3Playlist = [ const multiSourcePlaylist = [ { - src: SAMPLE_URL_A_OGG, + src: BRAHMS_OGG_URL, type: 'audio/ogg', additionalSrcs: [ - { src: SAMPLE_URL_A_MP3, type: 'audio/mpeg' }, - { src: SAMPLE_URL_A_FLAC, type: 'audio/flac' }, + { src: BRAHMS_MP3_URL, type: 'audio/mpeg' }, + { src: BRAHMS_FLAC_URL, type: 'audio/flac' }, ] }, { - src: SAMPLE_URL_B_OGG, + src: MOZART_OGG_URL, type: 'audio/ogg', additionalSrcs: [ - { src: SAMPLE_URL_B_MP3, type: 'audio/mpeg' }, - { src: SAMPLE_URL_B_FLAC,type: 'audio/flac' }, + { src: MOZART_MP3_URL, type: 'audio/mpeg' }, + { src: MOZART_FLAC_URL, type: 'audio/flac' }, ] } ] diff --git a/stories/utils.js b/stories/utils.js index 7ec39eb..f4b8f46 100644 --- a/stories/utils.js +++ b/stories/utils.js @@ -2,10 +2,10 @@ export const SAMPLE_MP3_URL = 'https://www.soundhelix.com/examples/mp3/SoundHeli export const SAMPLE_MP3_URL_B = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3' export const SAMPLE_MP3_URL_C = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3' -export const SAMPLE_URL_A_OGG = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.ogg' -export const SAMPLE_URL_A_MP3 = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.mp3' -export const SAMPLE_URL_A_FLAC = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.flac' +export const BRAHMS_OGG_URL = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.ogg' +export const BRAHMS_MP3_URL = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.mp3' +export const BRAHMS_FLAC_URL = 'https://archive.org/download/MusopenCollectionAsFlac/Brahms_SymphonyNo.3inFMajor/JohannesBrahms-SymphonyNo.3InFMajorOp.90-03-PocoAllegretto.flac' -export const SAMPLE_URL_B_OGG = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.ogg' -export const SAMPLE_URL_B_MP3 = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.mp3' -export const SAMPLE_URL_B_FLAC = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.flac' +export const MOZART_OGG_URL = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.ogg' +export const MOZART_MP3_URL = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.mp3' +export const MOZART_FLAC_URL = 'https://archive.org/download/MusopenCollectionAsFlac/Mozart_StringQuartetNo.15inDMinorK421/WolfgangAmadeusMozart-StringQuartetNo.15InDMinorK421-03-Minuetto.flac' From 175096ddfa11b3a8c32ea0ee4a313f9ff54294d8 Mon Sep 17 00:00:00 2001 From: Nathan McWilliams Date: Fri, 19 Sep 2025 13:30:46 -0400 Subject: [PATCH 4/4] remove srcKey property --- .gitignore | 1 + README.md | 36 ++++++++++++++--------- src/index.tsx | 34 ++++++++++++---------- stories/edge-cases.stories.js | 2 +- stories/playlist.stories.js | 34 +++++++++------------- stories/playlist.tsx | 55 +++++++++++------------------------ 6 files changed, 74 insertions(+), 88 deletions(-) diff --git a/.gitignore b/.gitignore index f929c2f..da65407 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ lib/ es/ coverage/ .vscode/ +.DS_Store # yarn .yarn/ diff --git a/README.md b/README.md index 32fba2a..10724f7 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,7 @@ The `controls` attribute defaults to `false` and should never be changed to `tru | showFilledProgress | boolean | true | Show filled (already played) area on progress bar | | showFilledVolume | boolean | false | Show filled volume area on volume bar | | hasDefaultKeyBindings | boolean | true | Whether has default keyboard shortcuts | -| autoPlayAfterSrcChange | boolean | true | Play audio after `src` or `srcKey` is changed, no matter `autoPlay` is `true` or `false` | -| autoLoadAfterSrcChange | boolean | true | Load audio after `src` or `srcKey` is changed, necessary if using child `` elements | +| autoPlayAfterSrcChange | boolean | true | Play audio after `src` is changed, no matter `autoPlay` is `true` or `false` | | volumeJumpStep | number | 0.1 | Indicates the volume jump step when pressing up/down arrow key, volume range is `0` to `1` | | progressJumpStep | number | 5000 | **Deprecated, use progressJumpSteps.** Indicates the progress jump step (ms) when clicking rewind/forward button or left/right arrow key | | progressJumpSteps | object | `{ backward: 5000, forward: 5000 }` | Indicates the progress jump step (ms) when clicking rewind/forward button or left/right arrow key| @@ -127,12 +126,6 @@ The `controls` attribute defaults to `false` and should never be changed to `tru | mse.onSeek | Function (Event) | - | The callback to be used when seek happens (this is a key of the _mse_ prop) | | mse.srcDuration | number | - | The callback to be used when encrypted audio is detected and needs to be decrypted (this is a key of the _mse_ prop) | -### Other Props - -| Props | Type | Default | Note | -| ------------------------ | ----------------- | ------- | ---- | -| srcKey | string | '' | A unique key for the current audio source when not using the `src` prop, i.e., when using child `` elements with a playlist. | - ### Event Props Supported media events: `onPlay`, `onPause`, `onEnded`, `onSeeking`, `onSeeked`, `onAbort`, `onCanPlay`, `onCanPlayThrough`, `onEmptied`, `onError`, `onLoadStart`, `onLoadedMetaData`, `onLoadedData`, `onPlaying`, `onSuspend`, `onWaiting`, `onVolumeChange` @@ -218,14 +211,29 @@ You can use child `` elements instead of the `src` prop, for example [to ``` -When using `` elements in playlists, use the `srcKey` prop to specify a unique identifier for the current src. +When using `` elements in playlists, be sure to set a unique `key` +property for each element. ```jsx - - - - - +const srcs = [ + [ + { src: 'https://example.com/audio1.aac', type: 'audio/aac' }, + { src: 'https://example.com/audio1.mp3', type: 'audio/mpeg' }, + { src: 'https://example.com/audio1.wav', type: 'audio/wav' }, + ], + [ + { src: 'https://example.com/audio2.aac', type: 'audio/aac' }, + { src: 'https://example.com/audio2.mp3', type: 'audio/mpeg' }, + { src: 'https://example.com/audio2.wav', type: 'audio/wav' }, + ], +] + +const currentIndex = 0 + + + {srcs[currentIndex].map(({ src, type }) => ( + + ))} ``` diff --git a/src/index.tsx b/src/index.tsx index 6c75be8..92d7824 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import React, { CSSProperties, ReactElement, Key, + Children, } from 'react' import { Icon } from '@iconify/react' import ProgressBar from './ProgressBar' @@ -35,11 +36,6 @@ interface PlayerProps { * Whether to play audio after src prop is changed */ autoPlayAfterSrcChange?: boolean - /** - * Whether to load the audio after changing the src. This is necessary when - * using child `` elements - */ - autoLoadAfterSrcChange?: boolean /** * custom classNames */ @@ -95,11 +91,6 @@ interface PlayerProps { * HTML5 Audio tag src property */ src?: string - /** - * A unique key for the current audio source when not using the `src` prop, - * i.e., when using child elements - */ - srcKey?: string defaultCurrentTime?: ReactNode defaultDuration?: ReactNode volume?: number @@ -673,13 +664,26 @@ class H5AudioPlayer extends Component { } componentDidUpdate(prevProps: PlayerProps): void { - const { src, srcKey, autoPlayAfterSrcChange, autoLoadAfterSrcChange = true } = this.props + const { src, autoPlayAfterSrcChange, children } = this.props - if (prevProps.src !== src || prevProps.srcKey !== srcKey) { - if (autoLoadAfterSrcChange && this.audio.current) { - this.audio.current.load() - } + let isAudioSrcChanged = false + + if (prevProps.src !== src) { + isAudioSrcChanged = true + } else if (children && Children.count(children) > 0) { + const prevSrcs: string[] = [] + Children.toArray(prevProps.children).forEach((c) => { + if (isValidElement(c) && c.type === 'source') { + prevSrcs.push(c.key) + } + }) + + isAudioSrcChanged = Children.toArray(children).some( + (c) => isValidElement(c) && c.type === 'source' && !prevSrcs.includes(c.key) + ) + } + if (isAudioSrcChanged) { if (autoPlayAfterSrcChange) { this.playAudioPromise() } else { diff --git a/stories/edge-cases.stories.js b/stories/edge-cases.stories.js index 525e195..c4c5aac 100644 --- a/stories/edge-cases.stories.js +++ b/stories/edge-cases.stories.js @@ -49,5 +49,5 @@ export const ChildSourceElements = { ), - name: "Child elements", + name: "Source elements", }; diff --git a/stories/playlist.stories.js b/stories/playlist.stories.js index d8a596f..55dc3d2 100644 --- a/stories/playlist.stories.js +++ b/stories/playlist.stories.js @@ -12,29 +12,23 @@ import { import PlayList from "./playlist.tsx"; import React from "react"; -const mp3Playlist = [ - { src: SAMPLE_MP3_URL, type: 'audio/mpeg' }, - { src: SAMPLE_MP3_URL_B, type: 'audio/mpeg' }, - { src: SAMPLE_MP3_URL_C, type: 'audio/mpeg'}, +const singleSourcePlaylist = [ + SAMPLE_MP3_URL, + SAMPLE_MP3_URL_B, + SAMPLE_MP3_URL_C, ] const multiSourcePlaylist = [ - { - src: BRAHMS_OGG_URL, - type: 'audio/ogg', - additionalSrcs: [ + [ + { src: BRAHMS_OGG_URL, type: 'audio/ogg' }, { src: BRAHMS_MP3_URL, type: 'audio/mpeg' }, { src: BRAHMS_FLAC_URL, type: 'audio/flac' }, - ] - }, - { - src: MOZART_OGG_URL, - type: 'audio/ogg', - additionalSrcs: [ - { src: MOZART_MP3_URL, type: 'audio/mpeg' }, - { src: MOZART_FLAC_URL, type: 'audio/flac' }, - ] - } + ], + [ + { src: MOZART_OGG_URL, type: 'audio/ogg' }, + { src: MOZART_MP3_URL, type: 'audio/mpeg' }, + { src: MOZART_FLAC_URL, type: 'audio/flac' }, + ] ] export default { @@ -43,11 +37,11 @@ export default { }; export const Playlist = { - render: () => , + render: () => , name: "Playlist", }; export const PlaylistWithSourceElements = { - render: () => , + render: () => , name: "Playlist with elements", }; diff --git a/stories/playlist.tsx b/stories/playlist.tsx index 3d74965..cbcc754 100644 --- a/stories/playlist.tsx +++ b/stories/playlist.tsx @@ -1,20 +1,13 @@ import React, { Component } from 'react' import AudioPlayer from '../src/index' -interface Track { +interface AudioSource { src: string - type: string - additionalSrcs?: [ - { - src: string - type: string - } - ] + type?: string } interface PlaylistProps { - playlist: Track[] - useSourceElements?: boolean + playlist: string[] | AudioSource[][] } interface PlayListState { @@ -22,8 +15,7 @@ interface PlayListState { } class PlayList extends Component { - playlist: Track[] - useSourceElements?: boolean = false + playlist: string[] | AudioSource[][] state = { currentMusicIndex: 0, @@ -32,7 +24,6 @@ class PlayList extends Component { constructor(props: PlaylistProps) { super(props) this.playlist = props.playlist - this.useSourceElements = props.useSourceElements } handleClickPrevious = (): void => { @@ -50,36 +41,24 @@ class PlayList extends Component { render(): React.ReactNode { const { currentMusicIndex } = this.state const track = this.playlist[currentMusicIndex] + const singleStringSrc = typeof track === 'string' ? track : null + const multipleSrcs: AudioSource[] | null = Array.isArray(track) ? track : null return (

currentMusicIndex: {currentMusicIndex}

- {this.useSourceElements ? ( - - - {track.additionalSrcs && - track.additionalSrcs.map(({ src, type }) => )} - - ) : ( - - )} + + {multipleSrcs && multipleSrcs.map(({ src, type }) => )} +
) }