@@ -253,6 +253,7 @@ const AudioWrapper = () => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
minWidth: '600px',
|
||||||
minHeight: '200px',
|
minHeight: '200px',
|
||||||
border: '2px dashed #ccc',
|
border: '2px dashed #ccc',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
@@ -305,7 +306,7 @@ const AudioWrapper = () => {
|
|||||||
playbackState={playbackState}
|
playbackState={playbackState}
|
||||||
seekTime={seekTime}
|
seekTime={seekTime}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
loading={loading}
|
loading={loading || waveform === null}
|
||||||
onPlay={handlePlay}
|
onPlay={handlePlay}
|
||||||
onPause={handlePause}
|
onPause={handlePause}
|
||||||
onStop={handleStop}
|
onStop={handleStop}
|
||||||
|
|||||||
@@ -82,12 +82,6 @@ export const AudioPlayer = ({
|
|||||||
|
|
||||||
// Calculate progress percentage
|
// Calculate progress percentage
|
||||||
const progressPercentage = duration > 0 ? seekTime / duration : 0;
|
const progressPercentage = duration > 0 ? seekTime / duration : 0;
|
||||||
const iconState = loading
|
|
||||||
? 'loading'
|
|
||||||
: playbackState === 'playing'
|
|
||||||
? 'pause'
|
|
||||||
: 'play';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root} onClick={onClick}>
|
<div className={styles.root} onClick={onClick}>
|
||||||
<div className={styles.upper}>
|
<div className={styles.upper}>
|
||||||
@@ -107,7 +101,7 @@ export const AudioPlayer = ({
|
|||||||
<AnimatedPlayIcon
|
<AnimatedPlayIcon
|
||||||
onClick={handlePlayToggle}
|
onClick={handlePlayToggle}
|
||||||
className={styles.controlButton}
|
className={styles.controlButton}
|
||||||
state={iconState}
|
state={playbackState === 'playing' ? 'pause' : 'play'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,6 +111,7 @@ export const AudioPlayer = ({
|
|||||||
waveform={waveform || []}
|
waveform={waveform || []}
|
||||||
progress={progressPercentage}
|
progress={progressPercentage}
|
||||||
onManualSeek={handleProgressClick}
|
onManualSeek={handleProgressClick}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<div className={styles.timeDisplay}>{formatTime(duration)}</div>
|
<div className={styles.timeDisplay}>{formatTime(duration)}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,12 +178,6 @@ export const MiniAudioPlayer = ({
|
|||||||
|
|
||||||
// Calculate progress percentage
|
// Calculate progress percentage
|
||||||
const progressPercentage = duration > 0 ? seekTime / duration : 0;
|
const progressPercentage = duration > 0 ? seekTime / duration : 0;
|
||||||
const iconState =
|
|
||||||
playbackState === 'playing'
|
|
||||||
? 'pause'
|
|
||||||
: playbackState === 'paused'
|
|
||||||
? 'play'
|
|
||||||
: 'loading';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.miniRoot} onClick={onClick}>
|
<div className={styles.miniRoot} onClick={onClick}>
|
||||||
@@ -203,7 +192,7 @@ export const MiniAudioPlayer = ({
|
|||||||
<AnimatedPlayIcon
|
<AnimatedPlayIcon
|
||||||
onClick={handlePlayToggle}
|
onClick={handlePlayToggle}
|
||||||
className={styles.controlButton}
|
className={styles.controlButton}
|
||||||
state={iconState}
|
state={playbackState === 'playing' ? 'pause' : 'play'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { type AffineThemeKeyV2, cssVarV2 } from '@toeverything/theme/v2';
|
|||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { Skeleton } from '../skeleton';
|
||||||
import * as styles from './audio-waveform.css';
|
import * as styles from './audio-waveform.css';
|
||||||
|
|
||||||
// Helper function to get computed CSS variable value
|
// Helper function to get computed CSS variable value
|
||||||
@@ -113,11 +114,13 @@ export const AudioWaveform = ({
|
|||||||
progress,
|
progress,
|
||||||
onManualSeek,
|
onManualSeek,
|
||||||
mini = false, // the bar will be 0.5px instead. by default, the bar is 1px
|
mini = false, // the bar will be 0.5px instead. by default, the bar is 1px
|
||||||
|
loading = false,
|
||||||
}: {
|
}: {
|
||||||
waveform: number[];
|
waveform: number[];
|
||||||
progress: number;
|
progress: number;
|
||||||
onManualSeek: (progress: number) => void;
|
onManualSeek: (progress: number) => void;
|
||||||
mini?: boolean;
|
mini?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
@@ -176,7 +179,11 @@ export const AudioWaveform = ({
|
|||||||
aria-valuemax={100}
|
aria-valuemax={100}
|
||||||
aria-valuenow={progress * 100}
|
aria-valuenow={progress * 100}
|
||||||
>
|
>
|
||||||
<canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />
|
{loading ? (
|
||||||
|
<Skeleton />
|
||||||
|
) : (
|
||||||
|
<canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,13 +30,8 @@ Pause.args = {
|
|||||||
state: 'pause',
|
state: 'pause',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Loading = Template.bind({});
|
|
||||||
Loading.args = {
|
|
||||||
state: 'loading',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithStateToggle: StoryFn<typeof AnimatedPlayIcon> = () => {
|
export const WithStateToggle: StoryFn<typeof AnimatedPlayIcon> = () => {
|
||||||
const [state, setState] = useState<'play' | 'pause' | 'loading'>('play');
|
const [state, setState] = useState<'play' | 'pause'>('play');
|
||||||
|
|
||||||
const cycleState = () => {
|
const cycleState = () => {
|
||||||
setState(current => {
|
setState(current => {
|
||||||
@@ -45,8 +40,6 @@ export const WithStateToggle: StoryFn<typeof AnimatedPlayIcon> = () => {
|
|||||||
return 'pause';
|
return 'pause';
|
||||||
case 'pause':
|
case 'pause':
|
||||||
return 'play';
|
return 'play';
|
||||||
case 'loading':
|
|
||||||
return 'play';
|
|
||||||
default:
|
default:
|
||||||
return 'play';
|
return 'play';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import type { LottieRef } from 'lottie-react';
|
|||||||
import Lottie from 'lottie-react';
|
import Lottie from 'lottie-react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { Loading } from '../loading';
|
|
||||||
import playandpause from './playandpause.json';
|
import playandpause from './playandpause.json';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export interface AnimatedPlayIconProps {
|
export interface AnimatedPlayIconProps {
|
||||||
state: 'play' | 'pause' | 'loading';
|
state: 'play' | 'pause';
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: (e: React.MouseEvent) => void;
|
onClick?: (e: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
@@ -62,9 +61,6 @@ export const AnimatedPlayIcon = ({
|
|||||||
onClick,
|
onClick,
|
||||||
}: AnimatedPlayIconProps) => {
|
}: AnimatedPlayIconProps) => {
|
||||||
const state = useDebouncedValue(_state, 25);
|
const state = useDebouncedValue(_state, 25);
|
||||||
if (state === 'loading') {
|
|
||||||
return <Loading size={40} />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<PlayAndPauseIcon state={state} onClick={onClick} className={className} />
|
<PlayAndPauseIcon state={state} onClick={onClick} className={className} />
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user