Skip to content

Commit a7172a4

Browse files
committed
feat: add useAudio hook, refactor playMusic reducer, add audioContext
1 parent f2cf52f commit a7172a4

13 files changed

Lines changed: 365 additions & 128 deletions

File tree

src/components/AudioTimer/index.tsx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
1-
import React, { useEffect } from 'react'
1+
import React from 'react'
22

3-
import useInterval from 'hooks/useInterval'
3+
import { AudioContext } from 'reducers/playMusic'
44
import { formatTime } from 'helpers/time'
55

6-
const { useState } = React
6+
const { useContext } = React
77

8-
interface IProps {
9-
audio?: HTMLAudioElement
10-
}
11-
12-
const DEFAULT_TIME = '00:00'
13-
14-
const AudioTimer: React.FC<IProps> = ({ audio }) => {
15-
const [time, setTime] = useState(audio?.currentTime || DEFAULT_TIME)
16-
const interval = audio?.currentTime === audio?.duration ? null : 1000
17-
18-
// 每次url变化时,先执行一次,否则会延时1秒才执行(因为setInterval)
19-
useEffect(() => {
20-
const time = formatTime(audio?.currentTime)
21-
setTime(time)
22-
}, [audio?.currentSrc])
8+
const AudioTimer = () => {
9+
const audioInfo = useContext(AudioContext)
2310

24-
useInterval(() => {
25-
const time = formatTime(audio?.currentTime)
26-
setTime(time)
27-
}, interval)
28-
2911
return (
30-
<div>{time} / {formatTime(audio?.duration)}</div>
12+
<div>
13+
{formatTime(audioInfo?.state?.time)} / {formatTime(audioInfo?.state?.duration)}
14+
</div>
3115
)
3216
}
3317

src/components/Layout/Footer/index.tsx

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,29 @@ import { Icon, Tooltip } from '@blueprintjs/core'
44
import ProgressBar from 'components/ProgressBar'
55
import Artists from 'components/Artists'
66
import AudioTimer from 'components/AudioTimer'
7-
import { PlayMusicStateContext, PlayMusicDispatchContext, ACTIONS } from 'reducers/playMusic'
8-
import { getMusicUrl } from 'helpers/business'
7+
import { PlayMusicStateContext, AudioContext } from 'reducers/playMusic'
98
import styles from './style.module.css'
109

11-
const { useEffect, useContext, useRef, useState } = React
10+
const { useContext } = React
1211

1312
const Footer = () => {
14-
const [audio, setAudio] = useState<HTMLAudioElement>()
15-
const dispatch = useContext(PlayMusicDispatchContext)
13+
const audioInfo = useContext(AudioContext)
1614
const state = useContext(PlayMusicStateContext)
17-
const { musicId, music, isPlaying } = state
18-
19-
// useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render.
20-
// 所以添加了一个 audio state 来触发变化
21-
const audioRef = useRef<HTMLAudioElement>()
22-
23-
useEffect(() => {
24-
if (musicId === 0) {
25-
return
26-
}
27-
28-
const musicUrl = getMusicUrl(musicId)
29-
if (!audioRef.current) {
30-
audioRef.current = new Audio(musicUrl)
31-
} else {
32-
audioRef.current.src = musicUrl
33-
audioRef.current.pause()
34-
audioRef.current.currentTime = 0
35-
}
36-
37-
setAudio(audioRef.current)
38-
audioRef.current.play()
39-
}, [musicId])
15+
const { musicId, music } = state
4016

4117
const togglePlayStatus = () => {
42-
if (isPlaying) {
43-
audioRef.current?.pause()
18+
if (audioInfo.state?.paused) {
19+
audioInfo.controls?.play()
4420
} else {
45-
audioRef.current?.play()
21+
audioInfo.controls?.pause()
4622
}
47-
48-
dispatch({ type: ACTIONS.TOGGLE_PLAY_STATUS })
4923
}
5024

5125
return (
5226
<div className={styles.root}>
5327
{musicId ? (
5428
<div className={styles.progressBar}>
55-
<ProgressBar audio={audio} />
29+
<ProgressBar />
5630
</div>
5731
) : null}
5832

@@ -64,7 +38,7 @@ const Footer = () => {
6438
<Artists artists={state?.music?.song?.artists} />
6539
</div>
6640
<div className={styles.time}>
67-
<AudioTimer audio={audio} />
41+
<AudioTimer />
6842
</div>
6943
</div>
7044
</div>
@@ -74,7 +48,7 @@ const Footer = () => {
7448
<Icon icon='step-backward' />
7549
</div>
7650
<div className={styles.pause} onClick={togglePlayStatus}>
77-
<Icon icon={isPlaying ? 'pause' : 'play'} />
51+
<Icon icon={audioInfo.state?.paused ? 'play' : 'pause'} />
7852
</div>
7953
<div className={styles.next}>
8054
<Icon icon='step-forward' />

src/components/ProgressBar/index.tsx

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,44 @@
11
import React from 'react'
22

3-
import useInterval from 'hooks/useInterval'
43
import { formatTime } from 'helpers/time'
4+
import { AudioContext } from 'reducers/playMusic'
55
import styles from './style.module.css'
66

7-
const { useState, useRef } = React
7+
const { useRef, useContext } = React
88

9-
interface IProps {
10-
audio?: HTMLAudioElement
11-
}
12-
13-
const ProgressBar: React.FC<IProps> = ({ audio }) => {
14-
const [label, setLabel] = useState('')
15-
const [donePercent, setDonePercent] = useState(0)
9+
const ProgressBar = () => {
10+
const audioInfo = useContext(AudioContext)
1611
const barRef = useRef<HTMLDivElement | null>()
1712

18-
useInterval(() => {
19-
const duration = audio?.duration
20-
const currentTime = audio?.currentTime || 0
21-
const percent = duration ? (currentTime / duration) : 0
22-
23-
setLabel(formatTime(audio?.currentTime))
24-
setDonePercent(percent)
25-
}, donePercent < 1 ? 500 : null)
26-
2713
const getPercent = (event: React.MouseEvent<HTMLDivElement>) => {
2814
const clickX = event.pageX
29-
const percent = barRef.current ? clickX / barRef.current.offsetWidth : 0
15+
const percent = barRef.current
16+
? clickX / barRef.current.offsetWidth
17+
: 0
18+
3019
return percent
3120
}
3221

3322
const handleBarClick = (event: React.MouseEvent<HTMLDivElement>) => {
3423
const percent = getPercent(event)
35-
36-
// 修改audio的currentTime即可,百分比会在interval中自动重新计算
37-
if (audio) {
38-
audio.currentTime = audio.duration * percent
39-
}
24+
audioInfo.controls?.seek((audioInfo.state?.duration || 0) * percent)
4025
}
4126

4227
// TODO: Fix Drag Interaction
4328
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
4429
event.preventDefault()
4530
event.dataTransfer.dropEffect = 'link'
46-
47-
const percent = getPercent(event)
48-
setDonePercent(percent)
4931
}
5032

5133
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
5234
event.preventDefault()
53-
5435
const percent = getPercent(event)
55-
56-
if (audio) {
57-
audio.currentTime = audio.duration * percent
58-
}
36+
audioInfo.controls?.seek((audioInfo.state?.duration || 0) * percent)
5937
}
6038

61-
if (!audio) {
62-
return null
63-
}
39+
const donePercent = audioInfo.state?.duration
40+
? (audioInfo.state?.time / audioInfo.state.duration)
41+
: 0
6442

6543
return (
6644
<div
@@ -70,10 +48,15 @@ const ProgressBar: React.FC<IProps> = ({ audio }) => {
7048
onDragOver={handleDragOver}
7149
onDrop={handleDrop}
7250
>
73-
<div className={styles.doneWrap} style={{ width: `${donePercent * 100}%` }}>
51+
<div
52+
className={styles.doneWrap}
53+
style={{ width: `${donePercent * 100}%` }}
54+
>
7455
<div className={styles.done}></div>
7556
<div className={styles.controllDot} draggable>
76-
<div className={styles.label}>{label}</div>
57+
<div className={styles.label}>
58+
{formatTime(audioInfo.state?.time)}
59+
</div>
7760
</div>
7861
</div>
7962
</div>

src/components/Table/index.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,22 @@ function Table<RecordType extends object = any>({ columns, data }: IProps<Record
1818
return (
1919
<div className={styles.root}>
2020
<div className={styles.header}>
21-
{columns.map(({ title, key, width }, index) => {
21+
{columns.map(({ title, width }, index) => {
2222
return <div key={index} style={{ width }}>{title}</div>
2323
})}
2424
</div>
2525
<div className={styles.content}>
2626
{data?.map((item, index) => {
2727
return <div className={styles.row} key={index}>
2828
{columns.map(({ key, width, render }, idx) => {
29-
return <div key={idx} style={{ width }}>{render(item[key], item, index)}</div>
29+
return (
30+
<div
31+
key={idx}
32+
style={{ width }}
33+
>
34+
{render(item[key], item, index)}
35+
</div>
36+
)
3037
})}
3138
</div>
3239
})}

src/helpers/business.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export const getMusicUrl = (id: number): string => {
2-
return `https://music.163.com/song/media/outer/url?id=${id}.mp3`
1+
export const getMusicUrl = (id?: number): string => {
2+
return id ? `https://music.163.com/song/media/outer/url?id=${id}.mp3` : ''
33
}

src/hooks/useAudio.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import createHTMLMediaHook from './utils/createHTMLMediaHook'
2+
3+
const useAudio = createHTMLMediaHook('audio')
4+
5+
export default useAudio

src/hooks/useSetState.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useCallback, useState } from 'react'
2+
3+
const useSetState = <T extends object>(
4+
initialState: T = {} as T
5+
): [T, (patch: Partial<T> | ((prevState: T) => Partial<T>)) => void] => {
6+
const [state, set] = useState<T>(initialState)
7+
const setState = useCallback(
8+
patch => {
9+
set(prevState => Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch))
10+
},
11+
[set]
12+
)
13+
14+
return [state, setState]
15+
}
16+
17+
export default useSetState

0 commit comments

Comments
 (0)