Skip to content
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

Add MSE/EME support #66

Merged
merged 8 commits into from
Sep 2, 2020
Prev Previous commit
Next Next commit
Use object as props for MSE / Add documentation to MSE&EME
  • Loading branch information
SergioCrisostomo committed Aug 29, 2020
commit 359d2c762fb57a2b1e96b1c5ab1b97020bf6f532
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ The `controls` attribute defaults to `false` and should never be changed to `tru
| customControlsSection | [Array<string \|<br>ReactElement>](https://github.com/lhz516/react-h5-audio-player/blob/fa1a61eb7f77146e1ce4547a14181279be68ecfd/src/index.tsx#L92) | [ADDITIONAL_CONTROLS,<br>MAIN_CONTROLS,<br>VOLUME_CONTROLS] | [Custom layout](https://static.hanzluo.com/react-h5-audio-player-storybook/index.html?path=/docs/layouts-advanced) of controls section |
| customAdditionalControls | [Array<string \|<br>ReactElement>](https://github.com/lhz516/react-h5-audio-player/blob/fa1a61eb7f77146e1ce4547a14181279be68ecfd/src/index.tsx#L93) | [LOOP] | [Custom layout](https://static.hanzluo.com/react-h5-audio-player-storybook/index.html?path=/docs/layouts-advanced) of additional controls |
| customVolumeControls | [Array<string \|<br>ReactElement>](https://github.com/lhz516/react-h5-audio-player/blob/fa1a61eb7f77146e1ce4547a14181279be68ecfd/src/index.tsx#L94) | [VOLUME] | [Custom layout](https://static.hanzluo.com/react-h5-audio-player-storybook/index.html?path=/docs/layouts-advanced) of volume controls |
| useMSE | Object | null | A configuration object so the player can play audio chunks, MSE streams and encrypted audio (See [section about Media Source Extensions](#media-source-extensions-and-encrypted-media-extensions) in this Readme) |
| useMSE > srcDuration | Number | - | The complete duration of the MSE audio chunks together (this is a key of the _useMSE_ prop) |
| useMSE > onSeek | Function | - | The callback to be used when seek happens (this is a key of the _useMSE_ prop) |
| useMSE > srcDuration | Number | - | The callback to be used when encrypted audio is detected and needs to be decrypted (this is a key of the _useMSE_ prop) |
lhz516 marked this conversation as resolved.
Show resolved Hide resolved

### Event Props

Expand Down Expand Up @@ -164,6 +168,10 @@ Then you can access the audio element like this:

`this.player.current.audio.current`

### Media Source Extensions and Encrypted Media Extensions

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.

## Release Notes

https://github.com/lhz516/react-h5-audio-player/releases
Expand Down
31 changes: 23 additions & 8 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ type CustomUIModule = RHAP_UI | ReactElement
type CustomUIModules = Array<CustomUIModule>
type OnSeek = (audio: HTMLAudioElement, time: number) => Promise<void>

interface AudioChunk {
url: string
duration: number
data?: AudioBuffer
}

interface MSEPropsObject {
onSeek?: OnSeek
onEcrypted?: (e: unknown) => void
srcDuration?: number
playlist?: AudioChunk[]
lhz516 marked this conversation as resolved.
Show resolved Hide resolved
}

interface PlayerProps {
/**
* HTML5 Audio tag autoPlay property
Expand Down Expand Up @@ -69,8 +82,7 @@ interface PlayerProps {
onClickPrevious?: (e: React.SyntheticEvent) => void
onClickNext?: (e: React.SyntheticEvent) => void
onPlayError?: (err: Error) => void
onSeek?: OnSeek
onEcrypted?: (e: any) => void
useMSE?: MSEPropsObject
/**
* HTML5 Audio tag preload property
*/
Expand All @@ -85,7 +97,6 @@ interface PlayerProps {
src?: string
defaultCurrentTime?: ReactNode
defaultDuration?: ReactNode
srcDuration?: number
volume?: number
showJumpControls?: boolean
showSkipControls?: boolean
Expand Down Expand Up @@ -345,18 +356,22 @@ class H5AudioPlayer extends Component<PlayerProps> {
key={key}
ref={this.progressBar}
audio={this.audio.current}
srcDuration={this.props.srcDuration}
srcDuration={this.props.useMSE.srcDuration}
progressUpdateInterval={progressUpdateInterval}
showDownloadProgress={showDownloadProgress}
showFilledProgress={showFilledProgress}
onSeek={this.props.onSeek}
onSeek={this.props.useMSE.onSeek}
/>
)
case RHAP_UI.DURATION:
return (
<div key={key} className="rhap_time rhap_total-time">
{this.props.srcDuration ? (
getDisplayTimeBySeconds(this.props.srcDuration, this.props.srcDuration, this.props.timeFormat)
{this.props.useMSE && this.props.useMSE.srcDuration ? (
getDisplayTimeBySeconds(
this.props.useMSE.srcDuration,
this.props.useMSE.srcDuration,
this.props.timeFormat
)
) : (
<Duration audio={this.audio.current} defaultDuration={defaultDuration} timeFormat={timeFormat} />
)}
Expand Down Expand Up @@ -539,7 +554,7 @@ class H5AudioPlayer extends Component<PlayerProps> {
})

audio.addEventListener('encrypted', (e) => {
this.props.onEcrypted && this.props.onEcrypted(e)
this.props.useMSE.onEcrypted && this.props.useMSE.onEcrypted(e)
})
}

Expand Down
17 changes: 12 additions & 5 deletions stories/mse-eme-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class MediaSourcePlayer extends PureComponent<MediaSourcePlayer> {
return new Promise((resolve) => {
if (bufferIsCompleteUpToNewCurrentTime) {
audio.currentTime = time
this.checkBufferLoad()
resolve()
} else {
if (!audio.paused) audio.pause()
Expand All @@ -152,8 +153,11 @@ class MediaSourcePlayer extends PureComponent<MediaSourcePlayer> {

this.whenBufferUpdateEndCallbacks.push(() => {
audio.currentTime = time
if (audio.paused) audio.play()
resolve()
this.checkBufferLoad()

if (audio.paused) {
audio.play().then(resolve)
} else resolve()
})

// use or fetch buffers
Expand Down Expand Up @@ -200,6 +204,7 @@ class MediaSourcePlayer extends PureComponent<MediaSourcePlayer> {
}

onTimeUpdate(): void {
if (this.state.audioSrc === SAMPLE_MP3_URL) return
this.checkBufferLoad()
}

Expand Down Expand Up @@ -259,9 +264,11 @@ class MediaSourcePlayer extends PureComponent<MediaSourcePlayer> {
autoPlayAfterSrcChange={false}
ref={this.player}
src={this.state.audioSrc}
srcDuration={this.state.srcDuration}
onEcrypted={(e) => this.onEncrypted(e)}
onSeek={this.state.srcDuration && ((audio, time) => this.onSeek(audio, time))}
useMSE={{
srcDuration: this.state.srcDuration,
onEcrypted: (e) => this.onEncrypted(e),
onSeek: (audio, time) => this.onSeek(audio, time),
}}
onListen={() => this.onTimeUpdate()}
listenInterval={250}
/>
Expand Down