Skip to content

Commit

Permalink
fix: volume fade-in and fade-out issues #256
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffreyCA committed Jan 10, 2021
1 parent cf8b674 commit 9ab8b07
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 35 deletions.
57 changes: 53 additions & 4 deletions __tests__/tests/player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1593,11 +1593,12 @@ describe('<ReactJkMusicPlayer/>', () => {
)

wrapper.find('.play-btn').simulate('click')
await sleep(300)
wrapper.find('.play-btn').simulate('click')
expect(wrapper.state().soundValue).toEqual(1)
expect(wrapper.state().currentAudioVolume).toEqual(1)

await sleep(200)
await sleep(300)

expect(onAudioVolumeChange).not.toHaveBeenCalled()
expect(wrapper.state().soundValue).toEqual(1)
Expand All @@ -1609,8 +1610,16 @@ describe('<ReactJkMusicPlayer/>', () => {
volume: 1,
}
const fn = jest.fn()
adjustVolume(audio, 0, { duration: 0 }).then(fn)
const { fadeInterval, updateIntervalEndVolume } = adjustVolume(
audio,
1,
0,
{ duration: 0 },
fn,
)
await sleep(100)
expect(fadeInterval).toBeUndefined()
expect(updateIntervalEndVolume).toBeUndefined()
expect(audio.volume).toStrictEqual(0)
expect(fn).toHaveBeenCalledTimes(1)
})
Expand All @@ -1620,8 +1629,16 @@ describe('<ReactJkMusicPlayer/>', () => {
volume: 1,
}
const fn = jest.fn()
adjustVolume(audio, 1, { duration: 100 }).then(fn)
const { fadeInterval, updateIntervalEndVolume } = adjustVolume(
audio,
1,
1,
{ duration: 100 },
fn,
)
await sleep(100)
expect(fadeInterval).toBeUndefined()
expect(updateIntervalEndVolume).toBeUndefined()
expect(audio.volume).toStrictEqual(1)
expect(fn).toHaveBeenCalledTimes(1)
})
Expand All @@ -1631,7 +1648,15 @@ describe('<ReactJkMusicPlayer/>', () => {
volume: 1,
}
const fn = jest.fn()
adjustVolume(audio, 0, { duration: 200 }).then(fn)
const { fadeInterval, updateIntervalEndVolume } = adjustVolume(
audio,
audio.volume,
0,
{ duration: 200 },
fn,
)
expect(fadeInterval).not.toBeUndefined()
expect(updateIntervalEndVolume).not.toBeUndefined()
expect(audio.volume).toStrictEqual(1)
expect(fn).not.toHaveBeenCalled()

Expand All @@ -1640,4 +1665,28 @@ describe('<ReactJkMusicPlayer/>', () => {
expect(Math.floor(audio.volume)).toStrictEqual(0)
expect(fn).toHaveBeenCalledTimes(1)
})

it('should respond to end volume changes for fade-ins', async () => {
const audio = {
volume: 0,
}
const fn = jest.fn()
const { fadeInterval, updateIntervalEndVolume } = adjustVolume(
audio,
audio.volume,
1,
{ duration: 200 },
fn,
)
expect(fadeInterval).not.toBeUndefined()
expect(updateIntervalEndVolume).not.toBeUndefined()
updateIntervalEndVolume(0.5)
expect(audio.volume).toStrictEqual(0)
expect(fn).not.toHaveBeenCalled()

await sleep(500)

expect(audio.volume).toStrictEqual(0.5)
expect(fn).toHaveBeenCalledTimes(1)
})
})
5 changes: 5 additions & 0 deletions src/config/volumeFade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const VOLUME_FADE = {
IN: 'in',
OUT: 'out',
NONE: 'none',
}
127 changes: 110 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import PLAY_MODE from './config/playMode'
import PROP_TYPES from './config/propTypes'
import { sliderBaseOptions } from './config/slider'
import { THEME } from './config/theme'
import { VOLUME_FADE } from './config/volumeFade'
import LOCALE_CONFIG from './locale'
import Lyric from './lyric'
import {
Expand Down Expand Up @@ -123,7 +124,9 @@ export default class ReactJkMusicPlayer extends PureComponent {
isAutoPlayWhenUserClicked: false,
playIndex: this.props.playIndex || this.props.defaultPlayIndex || 0,
canPlay: false,
isVolumeFadeChang: false,
currentVolumeFade: VOLUME_FADE.NONE,
currentVolumeFadeInterval: undefined,
updateIntervalEndVolume: undefined,
}

static defaultProps = {
Expand Down Expand Up @@ -449,6 +452,20 @@ export default class ReactJkMusicPlayer extends PureComponent {
return null
}

let actionButtonIcon

if (playing) {
if (this.state.currentVolumeFade === VOLUME_FADE.OUT) {
actionButtonIcon = this.iconMap.play
} else {
actionButtonIcon = this.iconMap.pause
}
} else if (this.state.currentVolumeFade === VOLUME_FADE.IN) {
actionButtonIcon = this.iconMap.pause
} else {
actionButtonIcon = this.iconMap.play
}

return createPortal(
<div
className={cls(
Expand Down Expand Up @@ -571,7 +588,7 @@ export default class ReactJkMusicPlayer extends PureComponent {
: locale.clickToPlayText
}
>
{playing ? this.iconMap.pause : this.iconMap.play}
{actionButtonIcon}
</span>
)}
<span
Expand Down Expand Up @@ -1030,8 +1047,13 @@ export default class ReactJkMusicPlayer extends PureComponent {
this.setState({
currentAudioVolume: value,
soundValue: value,
isVolumeFadeChang: false,
})

// Update fade-in interval to transition to new volume
if (this.state.currentVolumeFade === VOLUME_FADE.IN) {
this.state.updateIntervalEndVolume &&
this.state.updateIntervalEndVolume(value)
}
}

stopAll = (target) => {
Expand Down Expand Up @@ -1183,21 +1205,92 @@ export default class ReactJkMusicPlayer extends PureComponent {
onTogglePlay = () => {
if (this.state.audioLists.length >= 1) {
const { fadeIn, fadeOut } = this.props.volumeFade || {}
this.setState({ isVolumeFadeChang: fadeIn || fadeOut })

if (this.state.playing) {
adjustVolume(this.audio, 0, {
duration: fadeOut,
}).then(() => {
this.audio.pause()
const { currentVolumeFade, currentVolumeFadeInterval } = this.state
const isCurrentlyFading =
currentVolumeFade === VOLUME_FADE.IN ||
currentVolumeFade === VOLUME_FADE.OUT

/**
* Currently in middle of fading in/out, so need to cancel the current interval and do the opposite action.
* E.g. if currently fading out, then we need to cancel the fade-out and do a fade-in starting at current volume.
*/
if (isCurrentlyFading) {
// Clear current fade-in/out
clearInterval(currentVolumeFadeInterval)
this.setState({
currentVolumeFadeInterval: undefined,
updateIntervalEndVolume: undefined,
})
return
}

adjustVolume(this.audio, this.state.currentAudioVolume, {
duration: fadeIn,
})
this.setState({ isAutoPlayWhenUserClicked: true }, this.loadAndPlayAudio)
// Currently playing track or in the middle of fading in
if (
(!isCurrentlyFading && this.state.playing) ||
currentVolumeFade === VOLUME_FADE.IN
) {
this.setState({ currentVolumeFade: VOLUME_FADE.OUT })
// Fade in from current volume to 0
const {
fadeInterval: fadeOutInterval,
updateIntervalEndVolume,
} = adjustVolume(
this.audio,
this.audio.volume,
0,
{
duration: fadeOut,
},
() => {
this.audio.pause()
this.setState({
currentVolumeFade: VOLUME_FADE.NONE,
currentVolumeFadeInterval: undefined,
updateIntervalEndVolume: undefined,
})
// It's possible that the volume level in the UI has changed since beginning of fade
this.audio.volume = this.state.soundValue
},
)

this.setState({
currentVolumeFadeInterval: fadeOutInterval,
updateIntervalEndVolume,
})
} else {
this.setState({ currentVolumeFade: VOLUME_FADE.IN })
const startVolume = isCurrentlyFading ? this.audio.volume : 0
const endVolume = this.state.soundValue
// Always fade in from 0 to current volume
const {
fadeInterval: fadeInInterval,
updateIntervalEndVolume,
} = adjustVolume(
this.audio,
startVolume,
endVolume,
{
// If starting track from beginning, start immediately without fade-in
duration: fadeIn,
},
() => {
this.setState({
currentVolumeFade: VOLUME_FADE.NONE,
currentVolumeFadeInterval: undefined,
updateIntervalEndVolume: undefined,
})
this.audio.volume = this.state.soundValue
},
)

this.setState(
{
currentVolumeFadeInterval: fadeInInterval,
updateIntervalEndVolume,
isAutoPlayWhenUserClicked: true,
},
this.loadAndPlayAudio,
)
}
}
}

Expand Down Expand Up @@ -1436,8 +1529,8 @@ export default class ReactJkMusicPlayer extends PureComponent {

onAudioVolumeChange = () => {
const { volume } = this.audio
const { isVolumeFadeChang } = this.state
if (isVolumeFadeChang) {
const { currentVolumeFade } = this.state
if (currentVolumeFade !== VOLUME_FADE.NONE) {
return
}
this.setState({
Expand Down
46 changes: 32 additions & 14 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,42 @@ export function swing(p) {

export function adjustVolume(
element,
newVolume,
startVolume,
endVolume,
{ duration = 1000, easing = swing, interval = 13 } = {},
callback,
) {
const originalVolume = element.volume
const delta = newVolume - originalVolume
let delta = endVolume - startVolume
if (!delta || !duration || !easing || !interval) {
element.volume = newVolume
return Promise.resolve()
element.volume = endVolume
callback()
return { fadeInterval: undefined, updateIntervalEndVolume: undefined }
}

const ticks = Math.floor(duration / interval)
let tick = 1
return new Promise((resolve) => {
const timer = setInterval(() => {
element.volume = originalVolume + easing(tick / ticks) * delta
if (++tick === ticks) {
clearInterval(timer)
resolve()
}
}, interval)
})

const updateIntervalEndVolume = (newVolume) => {
endVolume = newVolume
}

const timer = setInterval(() => {
// End volume may have changed in middle of fading
const newDelta = endVolume - startVolume
if (newDelta !== delta) {
delta = newDelta
}

element.volume = startVolume + easing(tick / ticks) * delta
if (++tick >= ticks) {
element.volume = endVolume
clearInterval(timer)
callback()
}
}, interval)

return {
fadeInterval: timer,
updateIntervalEndVolume,
}
}

0 comments on commit 9ab8b07

Please sign in to comment.