From c84dfdd2ab91aea7f992984f90f3d285e37426e5 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Tue, 15 Oct 2019 17:30:31 +0700 Subject: [PATCH] better audio player --- src/components/bars/PlayerBar/index.tsx | 12 ++++++++---- src/components/bars/PlayerBar/styles.scss | 1 + src/components/media/AudioPlayer/index.tsx | 8 ++++---- src/redux/player/actions.ts | 13 +++++++++++++ src/redux/player/constants.ts | 3 +++ src/redux/player/sagas.ts | 22 +++++++++++++++++----- src/utils/player.ts | 9 ++++++++- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/components/bars/PlayerBar/index.tsx b/src/components/bars/PlayerBar/index.tsx index 166a4724..3770ebd3 100644 --- a/src/components/bars/PlayerBar/index.tsx +++ b/src/components/bars/PlayerBar/index.tsx @@ -8,12 +8,15 @@ import pick from 'ramda/es/pick'; import { selectPlayer } from '~/redux/player/selectors'; import * as PLAYER_ACTIONS from '~/redux/player/actions'; import { IPlayerProgress, Player } from '~/utils/player'; +import path from 'ramda/es/path'; +import { IFile } from '~/redux/types'; const mapStateToProps = state => pick(['status', 'file'], selectPlayer(state)); const mapDispatchToProps = { playerPlay: PLAYER_ACTIONS.playerPlay, playerPause: PLAYER_ACTIONS.playerPause, playerSeek: PLAYER_ACTIONS.playerSeek, + playerStop: PLAYER_ACTIONS.playerStop, }; type IProps = ReturnType & typeof mapDispatchToProps & {}; @@ -23,6 +26,7 @@ const PlayerBarUnconnected: FC = ({ playerPlay, playerPause, playerSeek, + playerStop, file, }) => { const [progress, setProgress] = useState({ progress: 0, current: 0, total: 0 }); @@ -60,10 +64,10 @@ const PlayerBarUnconnected: FC = ({ if (status === PLAYER_STATES.UNSET) return null; + const metadata: IFile['metadata'] = path(['metadata'], file); const title = - file.metadata && - (file.metadata.title || - [file.metadata.id3artist, file.metadata.id3title].filter(el => !!el).join(' - ')); + metadata && + (metadata.title || [metadata.id3artist, metadata.id3title].filter(el => !!el).join(' - ')); return (
@@ -81,7 +85,7 @@ const PlayerBarUnconnected: FC = ({
-
+
diff --git a/src/components/bars/PlayerBar/styles.scss b/src/components/bars/PlayerBar/styles.scss index 5f8d460f..9d9356f7 100644 --- a/src/components/bars/PlayerBar/styles.scss +++ b/src/components/bars/PlayerBar/styles.scss @@ -63,6 +63,7 @@ } .info { + flex: 1; display: flex; min-width: 0; align-items: center; diff --git a/src/components/media/AudioPlayer/index.tsx b/src/components/media/AudioPlayer/index.tsx index 8494333e..5dcbf462 100644 --- a/src/components/media/AudioPlayer/index.tsx +++ b/src/components/media/AudioPlayer/index.tsx @@ -14,7 +14,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = { - playerSetFile: PLAYER_ACTIONS.playerSetFile, + playerSetFileAndPlay: PLAYER_ACTIONS.playerSetFileAndPlay, playerPlay: PLAYER_ACTIONS.playerPlay, playerPause: PLAYER_ACTIONS.playerPause, playerSeek: PLAYER_ACTIONS.playerSeek, @@ -29,7 +29,7 @@ const AudioPlayerUnconnected = ({ file, player: { file: current, status }, - playerSetFile, + playerSetFileAndPlay, playerPlay, playerPause, playerSeek, @@ -43,8 +43,8 @@ const AudioPlayerUnconnected = ({ return playerPlay(); } - playerSetFile(file); - }, [file, current, status, playerPlay, playerPause, playerSetFile]); + playerSetFileAndPlay(file); + }, [file, current, status, playerPlay, playerPause, playerSetFileAndPlay]); const onProgress = useCallback( ({ detail }: { detail: IPlayerProgress }) => { diff --git a/src/redux/player/actions.ts b/src/redux/player/actions.ts index b2232d3d..fc242e2b 100644 --- a/src/redux/player/actions.ts +++ b/src/redux/player/actions.ts @@ -6,6 +6,11 @@ export const playerSetFile = (file: IPlayerState['file']) => ({ file, }); +export const playerSetFileAndPlay = (file: IPlayerState['file']) => ({ + type: PLAYER_ACTIONS.SET_FILE_AND_PLAY, + file, +}); + export const playerSetStatus = (status: IPlayerState['status']) => ({ type: PLAYER_ACTIONS.SET_STATUS, status, @@ -19,6 +24,14 @@ export const playerPause = () => ({ type: PLAYER_ACTIONS.PAUSE, }); +export const playerStop = () => ({ + type: PLAYER_ACTIONS.STOP, +}); + +export const playerStopped = () => ({ + type: PLAYER_ACTIONS.STOPPED, +}); + export const playerSeek = (seek: number) => ({ type: PLAYER_ACTIONS.SEEK, seek, diff --git a/src/redux/player/constants.ts b/src/redux/player/constants.ts index 83909cfe..ab36ee61 100644 --- a/src/redux/player/constants.ts +++ b/src/redux/player/constants.ts @@ -2,11 +2,14 @@ const prefix = 'PLAYER.'; export const PLAYER_ACTIONS = { SET_FILE: `${prefix}SET_FILE`, + SET_FILE_AND_PLAY: `${prefix}SET_FILE_AND_PLAY`, SET_STATUS: `${prefix}SET_STATUS`, PLAY: `${prefix}PLAY`, PAUSE: `${prefix}PAUSE`, SEEK: `${prefix}SEEK`, + STOP: `${prefix}STOP`, + STOPPED: `${prefix}STOPPED`, }; export const PLAYER_STATES = { diff --git a/src/redux/player/sagas.ts b/src/redux/player/sagas.ts index fd3ba811..28b24354 100644 --- a/src/redux/player/sagas.ts +++ b/src/redux/player/sagas.ts @@ -1,10 +1,11 @@ -import { takeLatest } from 'redux-saga/effects'; -import { PLAYER_ACTIONS } from './constants'; -import { playerSetFile, playerSeek } from './actions'; +import { takeLatest, put } from 'redux-saga/effects'; +import { PLAYER_ACTIONS, PLAYER_STATES } from './constants'; +import { playerSetFile, playerSeek, playerSetStatus } from './actions'; import { Player } from '~/utils/player'; import { getURL } from '~/utils/dom'; -function setFileSaga({ file }: ReturnType) { +function* setFileAndPlaySaga({ file }: ReturnType) { + yield put(playerSetFile(file)); Player.set(getURL(file)); Player.play(); } @@ -17,13 +18,24 @@ function pauseSaga() { Player.pause(); } +function stopSaga() { + Player.stop(); +} + function seekSaga({ seek }: ReturnType) { Player.jumpToPercent(seek * 100); } +function* stoppedSaga() { + yield put(playerSetStatus(PLAYER_STATES.UNSET)); + yield put(playerSetFile(null)); +} + export default function* playerSaga() { - yield takeLatest(PLAYER_ACTIONS.SET_FILE, setFileSaga); + yield takeLatest(PLAYER_ACTIONS.SET_FILE_AND_PLAY, setFileAndPlaySaga); yield takeLatest(PLAYER_ACTIONS.PAUSE, pauseSaga); yield takeLatest(PLAYER_ACTIONS.PLAY, playSaga); yield takeLatest(PLAYER_ACTIONS.SEEK, seekSaga); + yield takeLatest(PLAYER_ACTIONS.STOP, stopSaga); + yield takeLatest(PLAYER_ACTIONS.STOPPED, stoppedSaga); } diff --git a/src/utils/player.ts b/src/utils/player.ts index f467bcf5..23e5d501 100644 --- a/src/utils/player.ts +++ b/src/utils/player.ts @@ -1,5 +1,5 @@ import { store } from '~/redux/store'; -import { playerSetStatus } from '~/redux/player/actions'; +import { playerSetStatus, playerStopped } from '~/redux/player/actions'; import { PLAYER_STATES } from '~/redux/player/constants'; type PlayerEventType = keyof HTMLMediaElementEventMap; @@ -64,6 +64,11 @@ export class PlayerClass { this.element.pause(); }; + public stop = () => { + this.element.src = ''; + this.element.dispatchEvent(new CustomEvent('stop')); + }; + public getDuration = () => { return this.element.currentTime; }; @@ -83,5 +88,7 @@ const Player = new PlayerClass(); Player.on('play', () => store.dispatch(playerSetStatus(PLAYER_STATES.PLAYING))); Player.on('pause', () => store.dispatch(playerSetStatus(PLAYER_STATES.PAUSED))); +Player.on('stop', () => store.dispatch(playerStopped())); +Player.on('error', () => store.dispatch(playerStopped())); export { Player };