- import * as React from 'react';
- import {getFormValues} from '@theoryofnekomata/formxtra';
-
- export interface UseMediaControlsOptions<T extends HTMLMediaElement> {
- controllerRef: React.Ref<T>;
- actionFormKey?: string;
- visualizationMode?: string;
- }
-
- export const useMediaControls = <T extends HTMLMediaElement>({
- controllerRef: forwardedRef,
- actionFormKey = 'action' as const,
- }: UseMediaControlsOptions<T>) => {
- const defaultRef = React.useRef<T>(null);
- const ref = forwardedRef ?? defaultRef;
- const seekRef = React.useRef<HTMLInputElement>(null);
- const volumeRef = React.useRef<HTMLInputElement>(null);
- const filenameRef = React.useRef<HTMLElement>(null);
- const visualizationId = React.useId();
- const formId = React.useId();
- const [isPlaying, setIsPlaying] = React.useState(false);
- const [isSeeking, setIsSeeking] = React.useState(false);
- const [currentTimeDisplay, setCurrentTimeDisplay] = React.useState<number>();
- const [seekTimeDisplay, setSeekTimeDisplay] = React.useState<number>();
- const [durationDisplay, setDurationDisplay] = React.useState<number>();
- const [isSeekTimeCountingDown, setIsSeekTimeCountingDown] = React.useState(false);
-
- const refreshControls: React.ReactEventHandler<T> = React.useCallback((e) => {
- const { currentTarget: mediaController } = e;
-
- setCurrentTimeDisplay(mediaController.currentTime);
- setDurationDisplay(mediaController.duration);
- }, []);
-
- const togglePlayback = React.useCallback(() => {
- setIsPlaying((p) => !p);
- }, []);
-
- const startSeek: React.MouseEventHandler<HTMLInputElement> = React.useCallback(() => {
- setIsSeeking(true);
- }, []);
-
- const doSetSeek = React.useCallback((thisElement: HTMLInputElement, mediaController: HTMLMediaElement) => {
- mediaController.currentTime = thisElement.valueAsNumber;
- }, []);
-
- const setSeek: React.ChangeEventHandler<HTMLInputElement> = React.useCallback((e) => {
- if (!(typeof ref === 'object' && ref)) {
- return;
- }
- const { current: mediaController } = ref;
- if (!mediaController) {
- return;
- }
-
- const { currentTarget: thisElement } = e;
- setSeekTimeDisplay(thisElement.valueAsNumber);
-
- if (isSeeking) {
- return;
- }
-
- doSetSeek(thisElement, mediaController);
- }, [ref, isSeeking, doSetSeek]);
-
- const endSeek: React.MouseEventHandler<HTMLInputElement> = React.useCallback((e) => {
- if (!(typeof ref === 'object' && ref)) {
- return;
- }
- const { current: mediaController } = ref;
- if (!mediaController) {
- return;
- }
-
- const { currentTarget: thisElement } = e;
- setIsSeeking(false);
- setCurrentTimeDisplay(thisElement.valueAsNumber);
- doSetSeek(thisElement, mediaController);
- }, [ref, doSetSeek]);
-
- const reset: React.ReactEventHandler<T> = React.useCallback((e) => {
- const videoElement = e.currentTarget;
- setIsPlaying(false);
- videoElement.currentTime = 0;
- }, []);
-
- const updateSeekFromPlayback: React.ReactEventHandler<T> = React.useCallback((e) => {
- if (isSeeking) {
- return;
- }
-
- const videoElement = e.currentTarget;
- const currentTime = videoElement.currentTime;
- setCurrentTimeDisplay(currentTime);
-
- if (!(typeof seekRef === 'object' && seekRef)) {
- return;
- }
- const { current: seek } = seekRef;
- if (!seek) {
- return;
- }
- seek.value = String(currentTime);
- }, [isSeeking, seekRef]);
-
- const toggleSeekTimeCountMode = React.useCallback(() => {
- setIsSeekTimeCountingDown((b) => !b);
- }, []);
-
- const download = React.useCallback(() => {
- if (!(typeof ref === 'object' && ref)) {
- return;
- }
- const { current: mediaController } = ref;
- if (!mediaController) {
- return;
- }
-
- if (!(typeof filenameRef === 'object' && filenameRef)) {
- return;
- }
- const { current: filename } = filenameRef;
- if (!filename) {
- return;
- }
-
- const downloadLink = window.document.createElement('a');
- downloadLink.download = filename.textContent ?? 'media';
- downloadLink.href = mediaController.currentSrc;
- downloadLink.addEventListener('click', () => {
- downloadLink.remove();
- });
- downloadLink.click();
- }, [ref, filenameRef]);
-
-
- const actions = React.useMemo(() => ({
- togglePlayback,
- toggleSeekTimeCountMode,
- download,
- }), [togglePlayback, toggleSeekTimeCountMode, download]);
-
- const handleAction: React.FormEventHandler<HTMLFormElement> = React.useCallback((e) => {
- e.preventDefault();
- const nativeEvent = e.nativeEvent as unknown as { submitter: HTMLElement };
- const formData = getFormValues(
- e.currentTarget,
- {
- submitter: nativeEvent.submitter,
- }
- );
- const actionName = formData[actionFormKey] as keyof typeof actions;
- const { [actionName]: actionFunction } = actions;
- actionFunction?.();
- }, [actions, actionFormKey]);
-
- const adjustVolume: React.ChangeEventHandler<HTMLInputElement> = React.useCallback((e) => {
- if (!(typeof ref === 'object' && ref)) {
- return;
- }
- const { current: mediaController } = ref;
- if (!mediaController) {
- return;
- }
-
- const { value } = e.currentTarget;
- mediaController.volume = Number(value);
- }, [ref]);
-
- React.useEffect(() => {
- if (!(typeof ref === 'object' && ref)) {
- return;
- }
- const { current: mediaController } = ref;
- if (!mediaController) {
- return;
- }
-
- if (isPlaying) {
- void mediaController.play();
- return
- }
-
- mediaController.pause();
- }, [isPlaying, ref]);
-
- React.useEffect(() => {
- if (!(typeof seekRef === 'object' && seekRef)) {
- return;
- }
- const { current: seek } = seekRef;
- if (!seek) {
- return;
- }
-
- seek.value = String(currentTimeDisplay);
- }, [currentTimeDisplay, seekRef]);
-
- React.useEffect(() => {
- if (!(typeof ref === 'object' && ref)) {
- return;
- }
- const { current: mediaController } = ref;
- if (!mediaController) {
- return;
- }
-
- if (!(typeof volumeRef === 'object' && volumeRef)) {
- return;
- }
- const { current: volume } = volumeRef;
- if (!volume) {
- return;
- }
-
- volume.value = String(mediaController.volume);
- }, [ref, volumeRef]);
-
- return React.useMemo(() => ({
- seekRef,
- volumeRef,
- isPlaying,
- refreshControls,
- adjustVolume,
- reset,
- startSeek,
- endSeek,
- setSeek,
- updateSeekFromPlayback,
- durationDisplay,
- currentTimeDisplay,
- seekTimeDisplay,
- isSeeking,
- isSeekTimeCountingDown,
- mediaControllerRef: ref,
- handleAction,
- filenameRef,
- visualizationId,
- formId,
- }), [
- refreshControls,
- isPlaying,
- isSeeking,
- adjustVolume,
- reset,
- startSeek,
- endSeek,
- setSeek,
- updateSeekFromPlayback,
- durationDisplay,
- currentTimeDisplay,
- seekTimeDisplay,
- isSeekTimeCountingDown,
- ref,
- handleAction,
- filenameRef,
- visualizationId,
- formId,
- ]);
- };
|