Design system.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

203 rader
4.6 KiB

  1. import * as React from 'react';
  2. import {WaveSurferOptions} from 'wavesurfer.js';
  3. import clsx from 'clsx';
  4. import {getFormValues} from '@theoryofnekomata/formxtra';
  5. export type WaveformCanvasDerivedElement = HTMLDivElement;
  6. export interface WaveformCanvasProps
  7. extends React.HTMLProps<WaveformCanvasDerivedElement>,
  8. Omit<WaveSurferOptions, 'plugins' | 'height' | 'media' | 'container' | 'fillParent' | 'url' | 'autoplay' | 'renderFunction'> {
  9. audioRef?: React.Ref<HTMLAudioElement>;
  10. }
  11. export const WaveformCanvas = React.forwardRef<WaveformCanvasDerivedElement, WaveformCanvasProps>(({
  12. className,
  13. children,
  14. controls,
  15. waveColor,
  16. progressColor,
  17. cursorColor,
  18. cursorWidth,
  19. barWidth,
  20. barGap,
  21. barRadius,
  22. barHeight,
  23. barAlign,
  24. minPxPerSec,
  25. peaks,
  26. duration,
  27. autoPlay,
  28. interact,
  29. hideScrollbar,
  30. audioRate,
  31. autoScroll,
  32. autoCenter,
  33. sampleRate,
  34. splitChannels,
  35. normalize,
  36. audioRef,
  37. ...etcProps
  38. }, forwardedRef) => {
  39. const [isPlaying, setIsPlaying] = React.useState(false);
  40. const defaultRef = React.useRef<WaveformCanvasDerivedElement>(null);
  41. const containerRef = forwardedRef ?? defaultRef;
  42. const waveSurferRef = React.useRef<any>(null);
  43. const handleAction: React.FormEventHandler<HTMLFormElement> = (e) => {
  44. e.preventDefault();
  45. const nativeEvent = e.nativeEvent as unknown as { submitter: HTMLElement };
  46. const formData = getFormValues(
  47. e.currentTarget,
  48. {
  49. submitter: nativeEvent.submitter,
  50. }
  51. );
  52. const actionName = formData['action'] as string;
  53. switch (actionName) {
  54. case 'togglePlayback':
  55. setIsPlaying((prev) => !prev);
  56. break;
  57. default:
  58. break;
  59. }
  60. };
  61. React.useEffect(() => {
  62. if (!(typeof audioRef === 'object' && audioRef)) {
  63. return;
  64. }
  65. const { current: media } = audioRef;
  66. if (!media) {
  67. return;
  68. }
  69. if (!(typeof containerRef === 'object' && containerRef)) {
  70. return;
  71. }
  72. const { current: container } = containerRef;
  73. if (!container) {
  74. return;
  75. }
  76. const load = async (media: HTMLAudioElement, container: HTMLElement) => {
  77. const {default: WaveSurfer} = await import('wavesurfer.js');
  78. const waveSurferInstance = WaveSurfer.create({
  79. container,
  80. height: 'auto',
  81. autoplay: autoPlay,
  82. fillParent: true,
  83. waveColor,
  84. progressColor,
  85. cursorColor,
  86. barWidth,
  87. barGap,
  88. barRadius,
  89. barHeight,
  90. barAlign,
  91. minPxPerSec,
  92. peaks,
  93. duration,
  94. interact,
  95. hideScrollbar,
  96. audioRate,
  97. autoScroll,
  98. autoCenter,
  99. sampleRate,
  100. splitChannels,
  101. normalize,
  102. cursorWidth,
  103. plugins: [],
  104. media,
  105. });
  106. waveSurferInstance.on('ready', () => {
  107. if (!container) {
  108. return;
  109. }
  110. while (container.children.length > 1) {
  111. container.removeChild(container.children[0]);
  112. }
  113. });
  114. await waveSurferInstance.load(media.currentSrc);
  115. waveSurferInstance.setTime(media.currentTime);
  116. return waveSurferInstance;
  117. };
  118. const { current: waveSurferCurrent } = waveSurferRef;
  119. load(media, container)
  120. .then((i) => {
  121. waveSurferRef.current = i;
  122. })
  123. .catch((error) => {
  124. console.log(error);
  125. });
  126. return () => {
  127. if (waveSurferCurrent) {
  128. (waveSurferCurrent as unknown as Record<string, Function>).destroy();
  129. }
  130. if (container) {
  131. container.innerHTML = '';
  132. }
  133. };
  134. }, [
  135. audioRef,
  136. autoPlay,
  137. waveColor,
  138. progressColor,
  139. cursorColor,
  140. barWidth,
  141. barGap,
  142. barRadius,
  143. barHeight,
  144. barAlign,
  145. minPxPerSec,
  146. peaks,
  147. duration,
  148. interact,
  149. hideScrollbar,
  150. audioRate,
  151. autoScroll,
  152. autoCenter,
  153. sampleRate,
  154. splitChannels,
  155. normalize,
  156. cursorWidth,
  157. containerRef,
  158. ]);
  159. return (
  160. <div
  161. className={clsx(
  162. 'flex flex-col',
  163. className,
  164. )}
  165. >
  166. <div
  167. className="flex-auto relative aspect-video sm:aspect-auto"
  168. >
  169. <div className="absolute top-0 left-0 w-full h-full">
  170. <div className="w-full h-full relative"
  171. ref={containerRef}
  172. />
  173. </div>
  174. </div>
  175. {controls && (
  176. <form
  177. onSubmit={handleAction}
  178. >
  179. <button
  180. type="submit"
  181. name="action"
  182. value="togglePlayback"
  183. >
  184. {isPlaying ? '⏸' : '▶'}
  185. </button>
  186. </form>
  187. )}
  188. </div>
  189. );
  190. });
  191. WaveformCanvas.displayName = 'WavesurferCanvas';