mirror of
https://github.com/Nezumi-2711/onedrive-vercel-index.git
synced 2026-09-22 13:38:45 +00:00
render audio album thumbnails and playback controls
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import type { OdFileObject } from '../../types'
|
||||
import { FC, useState } from 'react'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import ReactAudioPlayer from 'react-audio-player'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import DownloadButtonGroup from '../DownloadBtnGtoup'
|
||||
import { DownloadBtnContainer, PreviewContainer } from './Containers'
|
||||
@@ -18,40 +19,83 @@ enum PlayerState {
|
||||
}
|
||||
|
||||
const AudioPreview: FC<{ file: OdFileObject }> = ({ file }) => {
|
||||
const { t } = useTranslation()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const rapRef = useRef<ReactAudioPlayer>(null)
|
||||
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 (
|
||||
<>
|
||||
<PreviewContainer>
|
||||
<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">
|
||||
{playerStatus === PlayerState.Loading ? (
|
||||
<LoadingIcon className="inline-block h-5 w-5 animate-spin" />
|
||||
<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">
|
||||
<div
|
||||
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
|
||||
className={`h-5 w-5 ${playerStatus === PlayerState.Playing ? 'animate-spin' : ''}`}
|
||||
className={`z-10 h-5 w-5 ${playerStatus === PlayerState.Playing ? 'animate-spin' : ''}`}
|
||||
icon="music"
|
||||
size="2x"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full flex-col space-y-2">
|
||||
<div>{file.name}</div>
|
||||
<div className="pb-4 text-sm text-gray-500">
|
||||
{t('Last modified:') + ' ' + formatModifiedDateTime(file.lastModifiedDateTime)}
|
||||
|
||||
<div className="flex w-full flex-col justify-between">
|
||||
<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)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ReactAudioPlayer
|
||||
className="h-11 w-full"
|
||||
src={file['@microsoft.graph.downloadUrl']}
|
||||
ref={rapRef}
|
||||
controls
|
||||
onCanPlay={() => setPlayerStatus(PlayerState.Ready)}
|
||||
onPlay={() => setPlayerStatus(PlayerState.Playing)}
|
||||
onPause={() => setPlayerStatus(PlayerState.Paused)}
|
||||
onError={() => setPlayerStatus(PlayerState.Paused)}
|
||||
onEnded={() => setPlayerStatus(PlayerState.Paused)}
|
||||
preload="auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,9 @@ module.exports = {
|
||||
gray: {
|
||||
850: '#222226'
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'spin-slow': 'spin 5s linear infinite',
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user