diff --git a/packages/web-kitchensink-reactnext/package.json b/packages/web-kitchensink-reactnext/package.json index 637825a..6aca52a 100644 --- a/packages/web-kitchensink-reactnext/package.json +++ b/packages/web-kitchensink-reactnext/package.json @@ -26,10 +26,11 @@ "react-dom": "18.2.0", "tailwindcss": "3.3.2", "typescript": "5.1.3", - "wavesurfer.js": "7.0.0-beta.6" + "wavesurfer.js": "7.0.0-beta.11" }, "devDependencies": { "@types/mime-types": "^2.1.1", - "@types/prismjs": "^1.26.0" + "@types/prismjs": "^1.26.0", + "@types/wavesurfer.js": "^6.0.6" } } diff --git a/packages/web-kitchensink-reactnext/pnpm-lock.yaml b/packages/web-kitchensink-reactnext/pnpm-lock.yaml index 7ce31e4..b998bdd 100644 --- a/packages/web-kitchensink-reactnext/pnpm-lock.yaml +++ b/packages/web-kitchensink-reactnext/pnpm-lock.yaml @@ -57,8 +57,8 @@ dependencies: specifier: 5.1.3 version: 5.1.3 wavesurfer.js: - specifier: 7.0.0-beta.6 - version: 7.0.0-beta.6 + specifier: 7.0.0-beta.11 + version: 7.0.0-beta.11 devDependencies: '@types/mime-types': @@ -67,6 +67,9 @@ devDependencies: '@types/prismjs': specifier: ^1.26.0 version: 1.26.0 + '@types/wavesurfer.js': + specifier: ^6.0.6 + version: 6.0.6 packages: @@ -349,6 +352,10 @@ packages: tslib: 2.5.3 dev: false + /@types/debounce@1.2.1: + resolution: {integrity: sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==} + dev: true + /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: false @@ -387,6 +394,12 @@ packages: resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} dev: false + /@types/wavesurfer.js@6.0.6: + resolution: {integrity: sha512-fD54o0RXZXxkOb+69Rt6rGViaHpIc1Mmde2aOX9qPhlQhrCPepybGnsekiG407+7scPlaK+hmuPez5AnnmlzGg==} + dependencies: + '@types/debounce': 1.2.1 + dev: true + /@typescript-eslint/parser@5.60.0(eslint@8.43.0)(typescript@5.1.3): resolution: {integrity: sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2702,8 +2715,8 @@ packages: graceful-fs: 4.2.11 dev: false - /wavesurfer.js@7.0.0-beta.6: - resolution: {integrity: sha512-vB8J1ppZ58vozmBDmqADDdKBYY6bebSYKUgIaDXB56Qo/CpPdExSlg91tN1FAubN5swZ1IUyB8Z9xOY/TsYRoA==} + /wavesurfer.js@7.0.0-beta.11: + resolution: {integrity: sha512-PwcnEIcV3x8Zi0XMWFkzRy0NsJyTaFITIXByoQn/y6OqtJn9W5jzryTvt/mxv+FcKAWA7yGqkxRGX336D0iWTQ==} dev: false /which-boxed-primitive@1.0.2: diff --git a/packages/web-kitchensink-reactnext/public/audio.wav b/packages/web-kitchensink-reactnext/public/audio.wav new file mode 100644 index 0000000..3c06331 Binary files /dev/null and b/packages/web-kitchensink-reactnext/public/audio.wav differ diff --git a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioFilePreview/index.tsx b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioFilePreview/index.tsx index 3c33cae..1e0987e 100644 --- a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioFilePreview/index.tsx +++ b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioFilePreview/index.tsx @@ -1,87 +1,253 @@ import * as React from 'react'; -import {AudioFile, getMimeTypeDescription} from '@/utils/blob'; -import {formatFileSize, formatNumeral, formatSecondsDurationPrecise} from '@/utils/numeral'; -import {useAudioControls} from '../../hooks/media'; +import {augmentAudioFile, getMimeTypeDescription} from '@/utils/blob'; +import { + formatFileSize, + formatNumeral, + formatSecondsDurationConcise, + formatSecondsDurationPrecise, +} from '@/utils/numeral'; +import {useMediaControls} from '../../hooks/media'; +import {useAugmentedFile} from '@/categories/blob/react'; -export interface AudioFilePreviewProps { - file: AudioFile; +import clsx from 'clsx'; +import {WaveSurferCanvas} from '@/packages/react-wavesurfer'; + +type AudioFilePreviewDerivedComponent = HTMLAudioElement; + +export interface AudioFilePreviewProps extends Omit, 'controls'> { + file?: File; + disabled?: boolean; + enhanced?: boolean; } -export const AudioFilePreview: React.FC = ({ - file: f, -}) => { +export const AudioFilePreview = React.forwardRef(({ + file, + style, + className, + enhanced = false, + disabled = false, +}, forwardedRef) => { + const { augmentedFile, error } = useAugmentedFile({ + file, + augmentFunction: augmentAudioFile, + }); const { - mediaContainerRef, - playMedia, + mediaControllerRef, + refreshControls, + reset, + updateSeekFromPlayback, isPlaying, - } = useAudioControls({ file: f }); + isSeeking, + currentTimeDisplay = 0, + seekTimeDisplay = 0, + durationDisplay = 0, + isSeekTimeCountingDown, + adjustVolume, + volumeRef, + handleAction, + filenameRef, + seekRef, + startSeek, + endSeek, + setSeek, + } = useMediaControls({ + controllerRef: forwardedRef, + }); + const formId = React.useId(); + + if (!augmentedFile) { + return null; + } + + const finalSeekTimeDisplay = isSeekTimeCountingDown ? (durationDisplay - seekTimeDisplay) : seekTimeDisplay; + const finalCurrentTimeDisplay = isSeekTimeCountingDown ? (durationDisplay - currentTimeDisplay) : currentTimeDisplay; return ( -
-
-
-
- -
-
-
-
-
- Name -
-
- {f.name} -
-
-
-
- Type -
-
- {getMimeTypeDescription(f.type, f.name)} -
-
-
-
- Size -
-
- {formatFileSize(f.size)} -
-
+
+
{ - typeof f.metadata?.duration === 'number' + typeof augmentedFile.metadata?.previewUrl === 'string' && ( -
-
- Duration -
-
- {formatSecondsDurationPrecise(f.metadata.duration)} -
+
+
+ + + +
+ {enhanced && ( +
+ +
+ + + + + + {formatSecondsDurationConcise(durationDisplay)} + +
+ +
+ )}
) } -
+
+
+
+
+
+ Name +
+
+ {augmentedFile.name} +
+
+
+
+ Type +
+
+ {getMimeTypeDescription(augmentedFile.type, augmentedFile.name)} +
+
+
+
+ Size +
+
+ {formatFileSize(augmentedFile.size)} +
+
+ { + typeof augmentedFile.metadata?.duration === 'number' + && ( +
+
+ Duration +
+
+ {formatSecondsDurationPrecise(augmentedFile.metadata.duration)} +
+
+ ) + } +
+
+
+ + Controls + + +
+
+
); -}; +}); + +AudioFilePreview.displayName = 'AudioFilePreview'; diff --git a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioMiniFilePreview/index.tsx b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioMiniFilePreview/index.tsx index 13b29c0..ce765bf 100644 --- a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioMiniFilePreview/index.tsx +++ b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/AudioMiniFilePreview/index.tsx @@ -11,7 +11,7 @@ export const AudioMiniFilePreview: React.FC = ({ file: f, }) => { const { - mediaContainerRef, + mountRef, playMedia, isPlaying, } = useAudioControls({ file: f }); @@ -19,7 +19,7 @@ export const AudioMiniFilePreview: React.FC = ({ return (
); diff --git a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/ImageFilePreview/index.tsx b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/ImageFilePreview/index.tsx index 17c6956..102aa52 100644 --- a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/ImageFilePreview/index.tsx +++ b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/ImageFilePreview/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {augmentImageFile, getMimeTypeDescription, ImageFile} from '@/utils/blob'; +import {augmentImageFile, getMimeTypeDescription} from '@/utils/blob'; import {formatFileSize, formatNumeral} from '@/utils/numeral'; import clsx from 'clsx'; import {useAugmentedFile, useImageControls} from '@/categories/blob/react'; @@ -18,7 +18,7 @@ export const ImageFilePreview = React.forwardRef { - const { augmentedFile, error } = useAugmentedFile({ + const { augmentedFile, error } = useAugmentedFile({ file, augmentFunction: augmentImageFile, }); diff --git a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/VideoFilePreview/index.tsx b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/VideoFilePreview/index.tsx index 1b0c2b5..42d4e5d 100644 --- a/packages/web-kitchensink-reactnext/src/categories/blob/react/components/VideoFilePreview/index.tsx +++ b/packages/web-kitchensink-reactnext/src/categories/blob/react/components/VideoFilePreview/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import {augmentVideoFile, getMimeTypeDescription, VideoFile} from '@/utils/blob'; +import {augmentVideoFile, getMimeTypeDescription} from '@/utils/blob'; import {formatFileSize, formatNumeral, formatSecondsDurationConcise} from '@/utils/numeral'; -import {useAugmentedFile, useVideoControls} from '@tesseract-design/web-blob-react'; +import {useAugmentedFile, useMediaControls} from '@tesseract-design/web-blob-react'; import clsx from 'clsx'; type VideoFilePreviewDerivedComponent = HTMLVideoElement; @@ -12,7 +12,7 @@ export interface VideoFilePreviewProps extends Omit(({ +export const VideoFilePreview = React.forwardRef(({ file, className, style, @@ -20,7 +20,7 @@ export const VideoFilePreview = React.forwardRef { - const { augmentedFile, error } = useAugmentedFile({ + const { augmentedFile, error } = useAugmentedFile({ file, augmentFunction: augmentVideoFile, }); @@ -29,7 +29,7 @@ export const VideoFilePreview = React.forwardRef({ + controllerRef: forwardedRef, }); const formId = React.useId(); @@ -78,7 +78,7 @@ export const VideoFilePreview = React.forwardRef {isPlaying ? '⏸' : '▶'} -
+
+ + )} +
+ ); +}); + +WaveSurferCanvas.displayName = 'WavesurferCanvas'; diff --git a/packages/web-kitchensink-reactnext/src/packages/react-wavesurfer/index.ts b/packages/web-kitchensink-reactnext/src/packages/react-wavesurfer/index.ts new file mode 100644 index 0000000..104f28d --- /dev/null +++ b/packages/web-kitchensink-reactnext/src/packages/react-wavesurfer/index.ts @@ -0,0 +1,10 @@ +export * from './WaveSurferCanvas'; + +export interface WaveSurfer extends Omit { + play: () => Promise; + pause: () => void; + on: (event: string, callback: () => void) => void; + seekTo: (time: number) => void; + load: (url: string) => void; + destroy: () => void; +} diff --git a/packages/web-kitchensink-reactnext/src/pages/categories/blob/index.tsx b/packages/web-kitchensink-reactnext/src/pages/categories/blob/index.tsx index 1378699..6a77f8a 100644 --- a/packages/web-kitchensink-reactnext/src/pages/categories/blob/index.tsx +++ b/packages/web-kitchensink-reactnext/src/pages/categories/blob/index.tsx @@ -27,6 +27,17 @@ const BlobPage: NextPage = () => { }); }, []); + const [audioFile, setAudioFile] = React.useState(); + React.useEffect(() => { + fetch('/audio.wav').then((response) => { + response.blob().then((blob) => { + setAudioFile(new File([blob], 'audio.wav', { + type: 'audio/wav', + })); + }); + }); + }, []); + return (
@@ -46,6 +57,15 @@ const BlobPage: NextPage = () => { />
+
+ + + +
{/* { metadata?: AudioFileMetadata; } -const augmentAudioFile = async (f: File): Promise => { +export const augmentAudioFile = async (f: File): Promise => { const previewUrl = await readAsDataURL(f); const audioExtensions = await getAudioMetadata(previewUrl, f.type) as AudioFileMetadata; return {