MediaControl

Example

Wait (false)
Buffers (0)
Duration (0)
Time (0)
Progress (0%)
Volume (100%)
Speed (1x)

Example Source Code

Code

import { useEffect, useRef, useState } from "react";

export function useMediaControl<TElement extends HTMLMediaElement>() {
	const refTargetElement = useRef<TElement>(null);
	const [metadata, setMetadata] = useState(false);
	const [paused, setPaused] = useState(true);
	const [muted, setMuted] = useState(false);
	const [wait, setWait] = useState(false);
	const [pitch, setPitch] = useState(true);

	const [duration, setDuration] = useState(0);
	const [time, setTime] = useState(0);
	const [progress, setProgress] = useState(0);
	const [volume, setVolume] = useState(100);
	const [speed, setSpeed] = useState(1);
	const [buffers, setBuffers] = useState<[number, number][]>([]);

	useEffect(() => {
		const onLoadedMetadata = () => {
			setMetadata(true);
			onTimeUpdate();
		};
		const onDurationChange = () => {
			setDuration(refTargetElement.current!.duration);
		};
		onDurationChange();
		const onTimeUpdate = () => {
			const target = refTargetElement.current!;
			const { currentTime, duration } = target;
			setTime(currentTime);
			onDurationChange();
			setProgress((currentTime / duration) * 100);
		};
		const onPlay = () => {
			setPaused(false);
			setWait(false);
		};
		const onPause = () => {
			setPaused(true);
		};
		const onVolumeChange = () => {
			setMuted(refTargetElement.current?.muted ?? true);
			setVolume((refTargetElement.current?.volume ?? 1) * 100);
		};
		onVolumeChange();
		const onWaiting = () => {
			setWait(true);
		};
		const onRateChange = () => {
			setSpeed(refTargetElement.current?.playbackRate ?? 1);
		};
		onRateChange();
		const onProgress = () => {
			const bufferTotal = refTargetElement.current?.buffered.length ?? 0;
			const d = refTargetElement.current?.duration ?? 0;
			const result: typeof buffers = [];

			for (let i = 0; i < bufferTotal; i++) {
				result.push([
					(refTargetElement.current!.buffered.start(i) / d) * 100,
					(refTargetElement.current!.buffered.end(i) / d) * 100,
				]);
			}

			setBuffers(result);
		};

		refTargetElement.current?.addEventListener(
			"loadedmetadata",
			onLoadedMetadata,
		);
		refTargetElement.current?.addEventListener(
			"durationchange",
			onDurationChange,
		);
		refTargetElement.current?.addEventListener("timeupdate", onTimeUpdate);
		refTargetElement.current?.addEventListener("play", onPlay);
		refTargetElement.current?.addEventListener("playing", onPlay);
		refTargetElement.current?.addEventListener("pause", onPause);
		refTargetElement.current?.addEventListener(
			"volumechange",
			onVolumeChange,
		);
		refTargetElement.current?.addEventListener("waiting", onWaiting);
		refTargetElement.current?.addEventListener("ratechange", onRateChange);
		refTargetElement.current?.addEventListener("progress", onProgress);

		return () => {
			refTargetElement.current?.removeEventListener(
				"loadedmetadata",
				onLoadedMetadata,
			);
			refTargetElement.current?.removeEventListener(
				"durationchange",
				onDurationChange,
			);
			refTargetElement.current?.removeEventListener(
				"timeupdate",
				onTimeUpdate,
			);
			refTargetElement.current?.removeEventListener("play", onPlay);
			refTargetElement.current?.removeEventListener("playing", onPlay);
			refTargetElement.current?.removeEventListener("pause", onPause);
			refTargetElement.current?.removeEventListener(
				"volumechange",
				onVolumeChange,
			);
			refTargetElement.current?.removeEventListener("waiting", onWaiting);
			refTargetElement.current?.removeEventListener(
				"ratechange",
				onRateChange,
			);
			refTargetElement.current?.removeEventListener(
				"progress",
				onProgress,
			);
		};
	}, []);

	const play = () => {
		refTargetElement.current?.play();
	};

	const pause = () => {
		refTargetElement.current?.pause();
	};

	const changeTime = (seconds: number) => {
		if (refTargetElement.current) {
			refTargetElement.current.currentTime = Math.max(
				0,
				Math.min(seconds, duration),
			);
		}
	};

	const mute = () => {
		refTargetElement.current!.muted = true;
	};

	const unmute = () => {
		refTargetElement.current!.muted = false;
	};

	const changeVolume = (value: number) => {
		if (refTargetElement.current) {
			refTargetElement.current.volume = Math.max(
				0,
				Math.min(value / 100, 1),
			);
		}
	};

	const changeSpeed = (value: number) => {
		refTargetElement.current!.playbackRate = value;
	};

	const changePitch = (value: boolean) => {
		refTargetElement.current!.preservesPitch = value;
		setPitch(value);
	};

	return {
		refTargetElement,

		metadata,
		paused,
		muted,
		wait,
		pitch,

		duration,
		time,
		progress,
		volume,
		speed,
		buffers,

		play,
		pause,
		changeTime,
		mute,
		unmute,
		changeVolume,
		changeSpeed,
		changePitch,
	};
}

Usage

import { useMediaControl } from "@hooks/ReactMediaControl";

export function MediaControl() {
	const {
		refTargetElement,
		paused,

		play,
		pause,
	} = useMediaControl<HTMLVideoElement>();

	return (
		<div>
			<div>
				<video
					ref={refTargetElement}
					src="https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v"
					controls
				></video>
			</div>
			<div>
				<button
					onClick={() => {
						if (paused) {
							play();
						} else {
							pause();
						}
					}}
				>
					{paused ? <>Play</> : <>Pause</>}
				</button>
			</div>
		</div>
	);
}