render audio album thumbnails and playback controls

This commit is contained in:
spencerwooo
2022-02-10 13:37:03 +08:00
parent cf97983063
commit 7c04ddd6bc
2 changed files with 62 additions and 15 deletions
+58 -14
View File
@@ -1,9 +1,10 @@
import type { OdFileObject } from '../../types' import type { OdFileObject } from '../../types'
import { FC, useState } from 'react' import { FC, useEffect, useRef, useState } from 'react'
import ReactAudioPlayer from 'react-audio-player' import ReactAudioPlayer from 'react-audio-player'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'
import DownloadButtonGroup from '../DownloadBtnGtoup' import DownloadButtonGroup from '../DownloadBtnGtoup'
import { DownloadBtnContainer, PreviewContainer } from './Containers' import { DownloadBtnContainer, PreviewContainer } from './Containers'
@@ -18,40 +19,83 @@ enum PlayerState {
} }
const AudioPreview: FC<{ file: OdFileObject }> = ({ file }) => { const AudioPreview: FC<{ file: OdFileObject }> = ({ file }) => {
const { t } = useTranslation()
const { asPath } = useRouter()
const rapRef = useRef<ReactAudioPlayer>(null)
const [playerStatus, setPlayerStatus] = useState(PlayerState.Loading) const [playerStatus, setPlayerStatus] = useState(PlayerState.Loading)
const { t } = useTranslation() // Render audio thumbnail, and also check for broken thumbnails
const thumbnail = `/api/thumbnail?path=${asPath}&size=medium`
const [brokenThumbnail, setBrokenThumbnail] = useState(false)
useEffect(() => {
// Manually get the HTML audio element and set onplaying event.
// - As the default event callbacks provided by the React component does not guarantee playing state to be set
// - properly when the user seeks through the timeline or the audio is buffered.
const rap = (rapRef.current as ReactAudioPlayer).audioEl.current
if (rap) {
rap.oncanplay = () => setPlayerStatus(PlayerState.Ready)
rap.onended = () => setPlayerStatus(PlayerState.Paused)
rap.onpause = () => setPlayerStatus(PlayerState.Paused)
rap.onplay = () => setPlayerStatus(PlayerState.Playing)
rap.onplaying = () => setPlayerStatus(PlayerState.Playing)
rap.onseeking = () => setPlayerStatus(PlayerState.Loading)
rap.onwaiting = () => setPlayerStatus(PlayerState.Loading)
rap.onerror = () => setPlayerStatus(PlayerState.Paused)
}
}, [])
return ( return (
<> <>
<PreviewContainer> <PreviewContainer>
<div className="flex flex-col space-y-4 md:flex-row md:space-x-4"> <div className="flex flex-col space-y-4 md:flex-row md:space-x-4">
<div className="flex aspect-square w-full items-center justify-center rounded bg-gray-100 transition-all duration-75 dark:bg-gray-700 md:w-40"> <div className="relative flex aspect-square w-full items-center justify-center rounded bg-gray-100 transition-all duration-75 dark:bg-gray-700 md:w-48">
{playerStatus === PlayerState.Loading ? ( <div
<LoadingIcon className="inline-block h-5 w-5 animate-spin" /> className={`absolute z-20 flex h-full w-full items-center justify-center transition-all duration-300 ${
playerStatus === PlayerState.Loading
? 'bg-white opacity-80 dark:bg-gray-800'
: 'bg-transparent opacity-0'
}`}
>
<LoadingIcon className="z-10 inline-block h-5 w-5 animate-spin" />
</div>
{!brokenThumbnail ? (
<div className="absolute m-4 rounded-full shadow-lg">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className={`h-full w-full rounded-full object-cover object-top ${
playerStatus === PlayerState.Playing ? 'animate-spin-slow' : ''
}`}
src={thumbnail}
alt={file.name}
onError={() => setBrokenThumbnail(true)}
/>
</div>
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
className={`h-5 w-5 ${playerStatus === PlayerState.Playing ? 'animate-spin' : ''}`} className={`z-10 h-5 w-5 ${playerStatus === PlayerState.Playing ? 'animate-spin' : ''}`}
icon="music" icon="music"
size="2x" size="2x"
/> />
)} )}
</div> </div>
<div className="flex w-full flex-col space-y-2">
<div>{file.name}</div> <div className="flex w-full flex-col justify-between">
<div className="pb-4 text-sm text-gray-500"> <div>
<div className="mb-2 font-medium">{file.name}</div>
<div className="mb-4 text-sm text-gray-500">
{t('Last modified:') + ' ' + formatModifiedDateTime(file.lastModifiedDateTime)} {t('Last modified:') + ' ' + formatModifiedDateTime(file.lastModifiedDateTime)}
</div> </div>
</div>
<ReactAudioPlayer <ReactAudioPlayer
className="h-11 w-full" className="h-11 w-full"
src={file['@microsoft.graph.downloadUrl']} src={file['@microsoft.graph.downloadUrl']}
ref={rapRef}
controls controls
onCanPlay={() => setPlayerStatus(PlayerState.Ready)} preload="auto"
onPlay={() => setPlayerStatus(PlayerState.Playing)}
onPause={() => setPlayerStatus(PlayerState.Paused)}
onError={() => setPlayerStatus(PlayerState.Paused)}
onEnded={() => setPlayerStatus(PlayerState.Paused)}
/> />
</div> </div>
</div> </div>
+3
View File
@@ -32,6 +32,9 @@ module.exports = {
gray: { gray: {
850: '#222226' 850: '#222226'
} }
},
animation: {
'spin-slow': 'spin 5s linear infinite',
} }
} }
}, },