feat: Replace react-h5-audio-player with @arraypress/waveform-player and update AudioPlayer component to support waveform visualization
This commit is contained in:
50
qwen3-tts-frontend/package-lock.json
generated
50
qwen3-tts-frontend/package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "qwen3-tts-frontend",
|
"name": "qwen3-tts-frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@arraypress/waveform-player": "^1.0.0",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
@@ -34,7 +35,6 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2",
|
"react": "^19.2",
|
||||||
"react-dom": "^19.2",
|
"react-dom": "^19.2",
|
||||||
"react-h5-audio-player": "^3.10.1",
|
|
||||||
"react-hook-form": "^7.71.1",
|
"react-hook-form": "^7.71.1",
|
||||||
"react-i18next": "^16.5.4",
|
"react-i18next": "^16.5.4",
|
||||||
"react-router-dom": "^7.13.0",
|
"react-router-dom": "^7.13.0",
|
||||||
@@ -73,6 +73,19 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@arraypress/waveform-player": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@arraypress/waveform-player/-/waveform-player-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZZq8s3DAe0rD1P6pEdTkc6TFmT/dyTyJbajqfGYNuIcNe+OtFiY3FlHfuo/GK1+bIL5TLnJTK99887RBzyaVfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/arraypress"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
|
||||||
@@ -1065,27 +1078,6 @@
|
|||||||
"url": "https://github.com/sponsors/nzakas"
|
"url": "https://github.com/sponsors/nzakas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@iconify/react": {
|
|
||||||
"version": "5.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@iconify/react/-/react-5.2.1.tgz",
|
|
||||||
"integrity": "sha512-37GDR3fYDZmnmUn9RagyaX+zca24jfVOMY8E1IXTqJuE8pxNtN51KWPQe3VODOWvuUurq7q9uUu3CFrpqj5Iqg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@iconify/types": "^2.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/cyberalien"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@iconify/types": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -5506,20 +5498,6 @@
|
|||||||
"react": "^19.2.4"
|
"react": "^19.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-h5-audio-player": {
|
|
||||||
"version": "3.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-h5-audio-player/-/react-h5-audio-player-3.10.1.tgz",
|
|
||||||
"integrity": "sha512-r6fSj9WXR6af1kxH5qQ/tawwDK4KrMfayiVCUettLYGX/KZ3BH8OGuaZP4O5KD0AxwsKAXtBv4kVQCWFzaIrUA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.10.2",
|
|
||||||
"@iconify/react": "^5"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
||||||
"react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.71.1",
|
"version": "7.71.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2",
|
"react": "^19.2",
|
||||||
"react-dom": "^19.2",
|
"react-dom": "^19.2",
|
||||||
"react-h5-audio-player": "^3.10.1",
|
"@arraypress/waveform-player": "^1.0.0",
|
||||||
"react-hook-form": "^7.71.1",
|
"react-hook-form": "^7.71.1",
|
||||||
"react-i18next": "^16.5.4",
|
"react-i18next": "^16.5.4",
|
||||||
"react-router-dom": "^7.13.0",
|
"react-router-dom": "^7.13.0",
|
||||||
|
|||||||
@@ -8,92 +8,58 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_container) {
|
.waveformContainer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: transparent;
|
min-width: 0;
|
||||||
box-shadow: none;
|
}
|
||||||
|
|
||||||
|
.audioPlayerWrapper :global(.waveform-player) {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_main) {
|
.audioPlayerWrapper :global(.waveform-btn) {
|
||||||
--rhap_theme-color: hsl(var(--primary));
|
color: hsl(var(--foreground));
|
||||||
--rhap_background-color: transparent;
|
border-color: hsl(var(--border));
|
||||||
--rhap_bar-color: hsl(var(--secondary));
|
background: transparent;
|
||||||
--rhap_time-color: hsl(var(--muted-foreground));
|
transition: all 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-indicator),
|
.audioPlayerWrapper :global(.waveform-btn:hover) {
|
||||||
.audioPlayerWrapper :global(.rhap_volume-indicator) {
|
color: hsl(var(--primary));
|
||||||
background: hsl(var(--primary));
|
border-color: hsl(var(--primary));
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-filled),
|
.audioPlayerWrapper :global(.waveform-canvas) {
|
||||||
.audioPlayerWrapper :global(.rhap_volume-bar) {
|
|
||||||
background-color: hsl(var(--primary));
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-bar),
|
|
||||||
.audioPlayerWrapper :global(.rhap_volume-container) {
|
|
||||||
background-color: hsl(var(--secondary));
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-bar) {
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: height 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-bar):hover {
|
|
||||||
height: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-filled) {
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-indicator) {
|
.audioPlayerWrapper :global(.waveform-info) {
|
||||||
width: 14px;
|
position: relative;
|
||||||
height: 14px;
|
justify-content: center;
|
||||||
top: -4px;
|
|
||||||
margin-left: -7px;
|
|
||||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-indicator):hover {
|
.audioPlayerWrapper :global(.waveform-text) {
|
||||||
transform: scale(1.1);
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_progress-container) {
|
.audioPlayerWrapper :global(.waveform-title) {
|
||||||
margin: 0 0.5rem;
|
text-align: center;
|
||||||
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_horizontal .rhap_controls-section) {
|
.audioPlayerWrapper :global(.waveform-time) {
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_time) {
|
|
||||||
color: hsl(var(--muted-foreground));
|
color: hsl(var(--muted-foreground));
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
.audioPlayerWrapper :global(.rhap_button-clear) {
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_button-clear):hover {
|
|
||||||
color: hsl(var(--primary));
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_main-controls-button) {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audioPlayerWrapper :global(.rhap_main-controls-button svg) {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloadButton {
|
.downloadButton {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useRef, useState, useEffect, useCallback, memo } from 'react'
|
import { useRef, useState, useEffect, useCallback, memo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AudioPlayerLib from 'react-h5-audio-player'
|
import WaveformPlayer from '@arraypress/waveform-player'
|
||||||
import 'react-h5-audio-player/lib/styles.css'
|
import '@arraypress/waveform-player/dist/waveform-player.css'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Download } from 'lucide-react'
|
import { Download } from 'lucide-react'
|
||||||
import apiClient from '@/lib/api'
|
import apiClient from '@/lib/api'
|
||||||
@@ -10,14 +10,17 @@ import styles from './AudioPlayer.module.css'
|
|||||||
interface AudioPlayerProps {
|
interface AudioPlayerProps {
|
||||||
audioUrl: string
|
audioUrl: string
|
||||||
jobId: number
|
jobId: number
|
||||||
|
text?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => {
|
const AudioPlayer = memo(({ audioUrl, jobId, text }: AudioPlayerProps) => {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const [blobUrl, setBlobUrl] = useState<string>('')
|
const [blobUrl, setBlobUrl] = useState<string>('')
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadError, setLoadError] = useState<string | null>(null)
|
const [loadError, setLoadError] = useState<string | null>(null)
|
||||||
const previousAudioUrlRef = useRef<string>('')
|
const previousAudioUrlRef = useRef<string>('')
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const playerInstanceRef = useRef<WaveformPlayer | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!audioUrl || audioUrl === previousAudioUrlRef.current) return
|
if (!audioUrl || audioUrl === previousAudioUrlRef.current) return
|
||||||
@@ -65,6 +68,49 @@ const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current || !blobUrl) return
|
||||||
|
|
||||||
|
const truncateText = (str: string, maxLength: number = 30) => {
|
||||||
|
if (!str) return ''
|
||||||
|
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = new WaveformPlayer(containerRef.current, {
|
||||||
|
url: blobUrl,
|
||||||
|
waveformStyle: 'mirror',
|
||||||
|
height: 60,
|
||||||
|
barWidth: 3,
|
||||||
|
barSpacing: 1,
|
||||||
|
samples: 200,
|
||||||
|
showTime: true,
|
||||||
|
showPlaybackSpeed: false,
|
||||||
|
autoplay: false,
|
||||||
|
enableMediaSession: true,
|
||||||
|
title: text ? truncateText(text) : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
playerInstanceRef.current = player
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
const buttons = containerRef.current.querySelectorAll('button')
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
if (!btn.hasAttribute('type')) {
|
||||||
|
btn.setAttribute('type', 'button')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (playerInstanceRef.current) {
|
||||||
|
playerInstanceRef.current.destroy()
|
||||||
|
playerInstanceRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [blobUrl, text])
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.href = blobUrl || audioUrl
|
link.href = blobUrl || audioUrl
|
||||||
@@ -94,27 +140,16 @@ const AudioPlayer = memo(({ audioUrl, jobId }: AudioPlayerProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.audioPlayerWrapper}>
|
<div className={styles.audioPlayerWrapper}>
|
||||||
<AudioPlayerLib
|
<div ref={containerRef} className={styles.waveformContainer} />
|
||||||
src={blobUrl}
|
<Button
|
||||||
layout="horizontal"
|
type="button"
|
||||||
customAdditionalControls={[
|
variant="ghost"
|
||||||
<Button
|
size="icon"
|
||||||
key="download"
|
onClick={handleDownload}
|
||||||
type="button"
|
className={styles.downloadButton}
|
||||||
variant="ghost"
|
>
|
||||||
size="icon"
|
<Download className="h-4 w-4" />
|
||||||
onClick={handleDownload}
|
</Button>
|
||||||
className={styles.downloadButton}
|
|
||||||
>
|
|
||||||
<Download className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
]}
|
|
||||||
customVolumeControls={[]}
|
|
||||||
showJumpControls={false}
|
|
||||||
volume={1}
|
|
||||||
preload="metadata"
|
|
||||||
autoPlayAfterSrcChange={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ const JobDetailDialog = memo(({ job, open, onOpenChange }: JobDetailDialogProps)
|
|||||||
<Separator />
|
<Separator />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="font-semibold text-sm">{t('job:audioPlayback')}</h3>
|
<h3 className="font-semibold text-sm">{t('job:audioPlayback')}</h3>
|
||||||
<AudioPlayer audioUrl={audioUrl} jobId={job.id} />
|
<AudioPlayer audioUrl={audioUrl} jobId={job.id} text={job.parameters?.text} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ const CustomVoiceForm = forwardRef<CustomVoiceFormHandle>((_props, ref) => {
|
|||||||
<AudioPlayer
|
<AudioPlayer
|
||||||
audioUrl={memoizedAudioUrl}
|
audioUrl={memoizedAudioUrl}
|
||||||
jobId={currentJob.id}
|
jobId={currentJob.id}
|
||||||
|
text={currentJob.parameters?.text}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -422,6 +422,7 @@ function VoiceCloneForm() {
|
|||||||
<AudioPlayer
|
<AudioPlayer
|
||||||
audioUrl={memoizedAudioUrl}
|
audioUrl={memoizedAudioUrl}
|
||||||
jobId={currentJob.id}
|
jobId={currentJob.id}
|
||||||
|
text={currentJob.parameters?.text}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ const VoiceDesignForm = forwardRef<VoiceDesignFormHandle>((_props, ref) => {
|
|||||||
<AudioPlayer
|
<AudioPlayer
|
||||||
audioUrl={memoizedAudioUrl}
|
audioUrl={memoizedAudioUrl}
|
||||||
jobId={currentJob.id}
|
jobId={currentJob.id}
|
||||||
|
text={currentJob.parameters?.text}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
35
qwen3-tts-frontend/src/types/waveform-player.d.ts
vendored
Normal file
35
qwen3-tts-frontend/src/types/waveform-player.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
declare module '@arraypress/waveform-player' {
|
||||||
|
export interface WaveformPlayerOptions {
|
||||||
|
url?: string
|
||||||
|
waveformStyle?: 'bars' | 'mirror' | 'line' | 'blocks' | 'dots' | 'seekbar'
|
||||||
|
height?: number
|
||||||
|
barWidth?: number
|
||||||
|
barSpacing?: number
|
||||||
|
samples?: number
|
||||||
|
waveformColor?: string
|
||||||
|
progressColor?: string
|
||||||
|
buttonColor?: string
|
||||||
|
showTime?: boolean
|
||||||
|
showPlaybackSpeed?: boolean
|
||||||
|
playbackRate?: number
|
||||||
|
autoplay?: boolean
|
||||||
|
enableMediaSession?: boolean
|
||||||
|
title?: string
|
||||||
|
subtitle?: string
|
||||||
|
onLoad?: (player: WaveformPlayer) => void
|
||||||
|
onPlay?: (player: WaveformPlayer) => void
|
||||||
|
onPause?: (player: WaveformPlayer) => void
|
||||||
|
onEnd?: (player: WaveformPlayer) => void
|
||||||
|
onTimeUpdate?: (current: number, total: number, player: WaveformPlayer) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class WaveformPlayer {
|
||||||
|
constructor(container: HTMLElement, options?: WaveformPlayerOptions)
|
||||||
|
play(): void
|
||||||
|
pause(): void
|
||||||
|
togglePlay(): void
|
||||||
|
seekTo(seconds: number): void
|
||||||
|
setVolume(level: number): void
|
||||||
|
destroy(): void
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,9 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['@arraypress/waveform-player']
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
@@ -29,7 +32,7 @@ export default defineConfig({
|
|||||||
if (id.includes('i18next')) {
|
if (id.includes('i18next')) {
|
||||||
return 'i18n';
|
return 'i18n';
|
||||||
}
|
}
|
||||||
if (id.includes('react-h5-audio-player') || id.includes('sonner')) {
|
if (id.includes('@arraypress/waveform-player') || id.includes('sonner')) {
|
||||||
return 'audio';
|
return 'audio';
|
||||||
}
|
}
|
||||||
if (id.includes('axios') || id.includes('clsx') || id.includes('tailwind-merge') ||
|
if (id.includes('axios') || id.includes('clsx') || id.includes('tailwind-merge') ||
|
||||||
|
|||||||
Reference in New Issue
Block a user