{"id":26475399,"url":"https://github.com/dcollien/react-media-hooks","last_synced_at":"2026-04-07T23:31:10.949Z","repository":{"id":279341932,"uuid":"938498109","full_name":"dcollien/react-media-hooks","owner":"dcollien","description":"Using MediaDevice, WebAudio, etc. in React hooks","archived":false,"fork":false,"pushed_at":"2025-05-20T00:38:06.000Z","size":265,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-20T01:30:45.769Z","etag":null,"topics":["audio","audio-processing","audiocontext","getusermedia","getusermedia-access","getusermedia-components","react","react-hooks","reactjs","streaming","video","video-processing","webaudio","webrtc"],"latest_commit_sha":null,"homepage":"https://dcollien.github.io/react-media-hooks/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dcollien.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-25T03:45:48.000Z","updated_at":"2025-05-20T00:38:09.000Z","dependencies_parsed_at":"2025-05-20T01:26:11.197Z","dependency_job_id":"cb499ac9-e553-4f7d-a215-78d863b0ec34","html_url":"https://github.com/dcollien/react-media-hooks","commit_stats":null,"previous_names":["dcollien/react-media-hooks"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dcollien/react-media-hooks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcollien%2Freact-media-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcollien%2Freact-media-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcollien%2Freact-media-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcollien%2Freact-media-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dcollien","download_url":"https://codeload.github.com/dcollien/react-media-hooks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcollien%2Freact-media-hooks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31533823,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio","audio-processing","audiocontext","getusermedia","getusermedia-access","getusermedia-components","react","react-hooks","reactjs","streaming","video","video-processing","webaudio","webrtc"],"created_at":"2025-03-19T23:17:00.542Z","updated_at":"2026-04-07T23:31:10.925Z","avatar_url":"https://github.com/dcollien.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Hooks for media and audio\n\nA React hook library for media and audio utilities\n\n```\nnpm install react-media-hooks\n```\n\n```typescript\nimport { useMediaRecorder, useAudioContext } from \"react-media-hooks\";\n\n// or\nimport { useMediaRecorder } from \"react-media-hooks/use-media\";\nimport { useAudioContext } from \"react-media-hooks/use-audio\";\n```\n\n## Example Usage\n\n```tsx\nimport { useCallback, useState } from \"react\";\n\nimport {\n  useAudioContext,\n  useAudioLevel,\n  useAudioStreamSource,\n} from \"react-media-hooks/use-audio\";\n\nimport {\n  useMediaBlobRecorder,\n  useElapsedTime,\n  useMediaStream,\n} from \"react-media-hooks/use-media\";\n\nimport { useBlobUrls } from \"react-media-hooks/use-blob\";\n\nexport function AudioRecorder({\n  audioDeviceId,\n  videoDeviceId,\n}: {\n  audioDeviceId: string | null;\n  videoDeviceId: string | null;\n}) {\n  const [isRecording, setIsRecording] = useState(false);\n\n  const constraints =\n    audioDeviceId \u0026\u0026 videoDeviceId\n      ? {\n          audio: { deviceId: { exact: audioDeviceId } },\n          video: { deviceId: { exact: videoDeviceId } },\n        }\n      : null;\n\n  // Create the media stream from the constraints\n  const stream = useMediaStream(constraints);\n\n  // Create the media recorder from the stream\n  const result = useMediaBlobRecorder(stream, isRecording);\n\n  // Calculate the time elapsed from the result\n  const timeElapsed = useElapsedTime(result, isRecording);\n\n  // Create the blob urls from the blobs in the result\n  const blobUrls = useBlobUrls(result.blobs);\n\n  // Create the audio source on the audio context, from the stream\n  const audioSource = useAudioStreamSource(stream);\n\n  // Update the audio level (between 0 and 1) from the audio source\n  // 60 times per second\n  const { level, timestamp } = useAudioLevel(\n    audioSource,\n    1000 / 60 // update 60 times per second\n  );\n\n  // Create the video ref to attach the stream to the video element\n  const videoRef = useCallback(\n    (node: HTMLVideoElement) =\u003e {\n      if (node) node.srcObject = stream;\n    },\n    [stream]\n  );\n\n  const minutes = timeElapsed.minutes.toFixed(0).padStart(2, \"0\");\n  const seconds = timeElapsed.seconds.toFixed(0).padStart(2, \"0\");\n  const milliseconds = timeElapsed.millis.toFixed(0).padStart(3, \"0\");\n\n  return (\n    \u003cdiv\u003e\n      \u003cvideo ref={videoRef} autoPlay playsInline muted\u003e\u003c/video\u003e\n      \u003cbutton onClick={() =\u003e setIsRecording(true)}\u003eRecord\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e setIsRecording(false)}\u003eStop\u003c/button\u003e\n      \u003chr /\u003e\n      \u003cp\u003eAudio level:\u003c/p\u003e\n      \u003cdiv\u003e\n        \u003cdiv\n          style={{\n            width: `${200 * level}px`,\n            height: \"10px\",\n            background: \"black\",\n          }}\n        \u003e\u003c/div\u003e\n      \u003c/div\u003e\n      \u003cpre\u003e\n        {minutes}:{seconds}.{milliseconds}\n      \u003c/pre\u003e\n\n      \u003cp\u003e\n        Changing the audioDeviceId or videoDeviceId will create a new file\n        below. Stopping/Starting the recording will clear all files and start a\n        new recording.\n      \u003c/p\u003e\n      {blobUrls.map((url) =\u003e (\n        \u003cdiv key={url}\u003e\n          \u003caudio controls src={url}\u003e\u003c/audio\u003e\n        \u003c/div\u003e\n      ))}\n    \u003c/div\u003e\n  );\n}\n```\n\n## Demo App\n\n```\npnpm run dev\n```\n\n## Hooks\n\n### Audio\n\nHooks for WebAudio\n\n```typescript\nimport {...} from 'react-media-hooks/use-audio`\n```\n\n#### useAudioContext\n\n```typescript\nuseAudioContext(): AudioContext | null\n```\n\nA global audio context is used by default. As per MDN:\n\n\u003e It's recommended to create one AudioContext and reuse it instead of initializing a new one each time, and it's OK to use a single AudioContext for several different audio sources and pipeline concurrently.\n\nBrowser vendors decided that Web Audio contexts should not be allowed to automatically play audio; they should instead be started by a user. As such, the `useAudioContext` hook waits for any user interaction before initializing the `AudioContext` so that the context does not get created in suspended mode.\n\nThe context is stored as component state so component renders will be triggered when the context initializes and changes from `null` to the initialized `AudioContext` object.\n\ne.g.\n\n```tsx\nimport { useAudioContext } from \"react-media-hooks/use-audio\";\n\nfunction Component() {\n  const context = useAudioContext(); // will use the global audio context\n\n  return \u003c\u003e\u003c/\u003e;\n}\n```\n\nOptionally, a React Context Provider can be used to create separate audio contexts. e.g.\n\n```tsx\nimport { useAudioContext } from \"react-media-hooks/use-audio\";\nimport { AudioContextProvider } from \"react-media-hooks/audio-context\";\n\nfunction SubComponent() {\n  const context = useAudioContext(); // will use the context provider's AudioContext\n  const deviceId = ...;\n\n  useEffect(() =\u003e {\n    // Change only the context provider's sink\n    context.setSinkId(deviceId);\n  }, [deviceId]);\n\n  return \u003c\u003e\u003c/\u003e;\n}\n\nfunction Component() {\n  // Create a new AudioContext and initialize it on user interaction\n  return (\n    \u003cAudioContextProvider\u003e\n      \u003cSubComponent /\u003e\n    \u003c/AudioContextProvider\u003e\n  );\n}\n```\n\nOptionally, your own audio context can be given:\n\n```tsx\nfunction Component() {\n  // Create a new AudioContext and initialize it on user interaction\n  return (\n    \u003cAudioContextProvider audioContext={myAudioContext}\u003e\n      \u003cSubComponent /\u003e\n    \u003c/AudioContextProvider\u003e\n  );\n}\n```\n\nIf the given context is suspended, it will be automatically woken up on next user interaction.\n\n#### useAudioStreamSource\n\n```typescript\nuseAudioStreamSource(\n    stream: MediaStream | null\n): MediaStreamAudioSourceNode`\n```\n\nGiven a MediaStream, creates a MediaStreamSourceNode using `audioContext.createMediaStreamSource(stream)` when both `audioContext` and `stream` become initialized.\n\nThe returned source node is stored as component state so component renders will be triggered when the audio source node becomes available.\n\n#### useAudioAnalyser\n\n```typescript\nuseAudioAnalyser(\n    source: MediaStreamAudioSourceNode | null,\n    fftSize = 256\n): AnalyserNode | null\n```\n\nGiven a MediaStreamAudioSourceNode, returns an AnalyserNode. When the `source` changes, the old source will disconnect the analyser (if it exists), and the new source will be connected to the analyser.\n\n#### useAudioLevel\n\n```typescript\nuseAudioLevel(\n    source: MediaStreamAudioSourceNode | null,\n    updateInterval: number | null = 1000 / 60\n): {\n    level: number,\n    timestamp: number\n}\n```\n\nGiven a MediaStreamAudioSourceNode, returns the current volume level and the timestamp that the volume was last sampled (uses `Date.now()`).\n\nThe `updateInterval` controls how frequently the volume level is sampled, and can be set to `null` to pause updates.\n\n#### useAudioDataSource\n\n```typescript\nuseAudioDataSource(\n    data: ArrayBuffer | AudioBuffer | Blob | null,\n    detune?: number,\n    loop?: boolean,\n    loopStart?: number,\n    loopEnd?: number,\n    playbackRate?: number\n): AudioBufferSourceNode | null\n```\n\nReturns a AudioBufferSourceNode (when one has initialized) from given audio data: either ArrayBuffer, AudioBuffer, or Blob.\n\n#### useAudioDataPlayback\n\n```typescript\nuseAudioDataPlayback(\n    data: ArrayBuffer | AudioBuffer | Blob | null,\n    detune?: number,\n    loop?: boolean,\n    loopStart?: number,\n    loopEnd?: number,\n    playbackRate?: number\n): (when?: number, offset?: number, duration?: number) =\u003e Promise\u003cvoid\u003e\n```\n\nGiven audio data (ArrayBuffer, AudioBuffer, or Blob), returns a `start` function. When called this function will start playing the audio.\n\n### Media\n\nHooks for dealing with MediaDevices\n\n```typescript\nimport {...} from 'react-media-hooks/use-media`\n```\n\n#### useMediaStreamInputDevices\n\n```typescript\nuseMediaStreamInputDevices(\n    constraints: MediaStreamConstraints | null.\n    onAddTrack?: (track: MediaStreamTrack) =\u003e void,\n    onRemoveTrack?: (track: MediaStreamTrack) =\u003e void,\n    onEnded?: () =\u003e void,\n    onAudioEnded?: () =\u003e void,\n    onVideoEnded?: () =\u003e void,\n    onTrackMuted?: (\n      track: MediaStreamTrack,\n      mutedTracks: MediaStreamTrack[]\n    ) =\u003e void,\n    onTrackUnmuted?: (\n      track: MediaStreamTrack,\n      mutedTracks: MediaStreamTrack[]\n    ) =\u003e void\n): {\n    stream: MediaStream | null;\n    audioDevices: MediaDeviceInfo[];\n    videoDevices: MediaDeviceInfo[];\n    reload: () =\u003e void;\n}\n```\n\nGiven MediaStreamConstraints, returns a MediaStream and two lists of device info for audio and video respectively. When the constraints change (either the object reference, the value of its `audio` or `video` properties, or their respective `deviceId` properties), then the stream will be replaced with a new MediaStream for that set of constraints.\n\n`useMediaStreamInputDevices` will request permission to use the available devices and update the stream and device lists when permission is granted. If a new device is connected, the lists will update.\n\nSetting `constraints` to `null` will stop the stream.\n\nCalling `reload` will re-initialize the stream and device lists (same as setting constraints to `null` and back to its original value).\n\nThe same event handlers can be given as `useMediaStreamWithEvents`.\n\n#### useMediaInputDevicesRequest\n\n```typescript\nuseMediaInputDevicesRequest(constraints?: {\n    audio: boolean;\n    video: boolean;\n}): {\n    audioDevices: MediaDeviceInfo[];\n    videoDevices: MediaDeviceInfo[];\n}\n```\n\nChecks whether permissions have already been given, and if not it opens a stream only momentarily, enough to trigger a permissions request and enumerate audio and video devices. The stream is then closed.\n\nChanging the constraints will re-request the devices. If a new device is connected, the lists will update.\n\n#### useMediaPermissionsQuery\n\n```typescript\nuseMediaPermissionsQuery(): {\n    microphone: PermissionState | \"unsupported\" | null;\n    camera: PermissionState | \"unsupported\" | null;\n}\n```\n\nQueries the permissions available.\n\n`microphone` and `camera` will switch from `null` to one of:\n\n- \"granted\": Permission has already been given\n- \"denied\": Permission was explicitly denied\n- \"prompt\": Permission has not been requested\n- \"unsupported\": The browser doesn't support querying for permissions\n\n#### useMediaDevices\n\n```typescript\nuseMediaDevices(isPermissionGranted?: boolean): {\n    readonly reload: () =\u003e void;\n    readonly audioInput: MediaDeviceInfo[];\n    readonly videoInput: MediaDeviceInfo[];\n    readonly audioOutput: MediaDeviceInfo[];\n}\n```\n\nGiven a flag for if permission has been granted, returns three lists of media devices: audio inputs, video inputs, and audio outputs. Does not request permissions itself.\n\nChanging the `isPermissionGranted` flag will re-initialize the lists, as will calling `reload()`;\n\nNew lists will be automatically set when a new device is made available.\n\n#### useMediaInputDevices\n\n```typescript\nuseMediaInputDevices(\n    isPermissionGranted: boolean\n): readonly [MediaDeviceInfo[], MediaDeviceInfo[]]\n```\n\nGiven a flag for if permission has been granted, returns two lists of media input devices: audio and video (in order). Does not request permissions itself.\n\nChanging the `isPermissionGranted` flag will re-initialize the lists.\n\nNew lists will be automatically set when a new device is made available.\n\n#### useMediaStream\n\n```typescript\nuseMediaStream(\n    constraints: MediaStreamConstraints | null\n): MediaStream\n```\n\n`useMediaStream` can be used to initialize the media stream (or request permissions) for a set of constraints, separately.\n\nSetting `constraints` to `null` will stop the stream.\n\n#### useMediaStreamWithEvents\n\n```typescript\nuseMediaStreamWithEvents(\n    constraints: MediaStreamConstraints | null,\n    onAddTrack?: (track: MediaStreamTrack) =\u003e void,\n    onRemoveTrack?: (track: MediaStreamTrack) =\u003e void,\n    onEnded?: () =\u003e void,\n    onAudioEnded?: () =\u003e void,\n    onVideoEnded?: () =\u003e void,\n    onTrackMuted?: (\n      track: MediaStreamTrack,\n      mutedTracks: MediaStreamTrack[]\n    ) =\u003e void,\n    onTrackUnmuted?: (\n      track: MediaStreamTrack,\n      mutedTracks: MediaStreamTrack[]\n    ) =\u003e void\n): {\n  stream: MediaStream | null;\n  reload: () =\u003e void;\n  stopAudio: () =\u003e void;\n  stopVideo: () =\u003e void;\n  stopTrack: (trackId: string) =\u003e void;\n}\n```\n\nSimilar to `useMediaStream` but with more options. Calling `reload` will re-initialize the stream.\nCalling `stopAudio` will stop all audio tracks, `stopVideo` will stop all video tracks, `stopTrack` to specify a track ID to stop.\n\nEvent handlers:\n\n- onAddTrack: when a track is added to the stream\n- onRemoveTrack: when a track is removed from the stram\n- onEnded: when all tracks are in `readyState = \"ended\"`\n- onAudioEnded: when all audio tracks are in `readyState = \"ended\"`\n- onVideoEnded: when all video tracks are in `readyState = \"ended\"`\n- onTrackMuted: when a track becomes muted\n- onTrackUnmuted: when a track becomes unmuted\n\n#### useMediaBlobRecorder\n\n```typescript\nuseMediaBlobRecorder(\n    stream: MediaStream | null,\n    isRecording: boolean,\n    options?: MediaRecorderOptions\n): {\n    startTime: number | null;\n    blobs: Blob[];\n} as RecordedMediaResult\n```\n\nStart recording on a stream. Toggle `isRecording` to start/stop recording. Starting a new recording will re-initialize the `blobs` array. Stopping recording will populate the `blobs` array with new data.\n\nIf a stream changes during recording, a new audio file Blob will be added to the `blobs` output array.\n\nThe `startTime` is the ms since the `timeOrigin`, i.e. compare against `performance.now()`.\n\n```typescript\nuseElapsedTime(\n    result: RecordedMediaResult,\n    isRecording: boolean,\n    updateInterval?: number | null\n): {\n    elapsed: number;\n    minutes: number;\n    seconds: number;\n    millis: number;\n}\n```\n\nCan be used as a shortcut to retrieve the elapsed time since a result was created, updating every `updateInterval`.\n\n### useMediaRecorder\n\n```typescript\nuseMediaRecorder(\n    stream: MediaStream | null,\n    isRecording: boolean,\n    callbacks?: {\n      onDataAvailable?: (event: BlobEvent) =\u003e void,\n      onStart?: (event: MediaRecorderEvent, isResuming: boolean) =\u003e void,\n      onStop?: (event: MediaRecorderEvent) =\u003e void,\n    },\n    options?: UseMediaRecorderOptions\n): void\n```\n\nStart recording on a stream. Toggle `isRecording` to start/stop recording. \n\nStream changes: if the stream changes mid-recording, `onStop` will be called, then `onStart` will be called with `isResuming = true` when the new stream starts.\n\n`onDataAvailable` sends `event.data` that can be combined into an audio file `onStop` using:\n\n```typescript\nconst blob = new Blob([data0, data1, ...], {\n    type: event.recorder.mimeType,\n});\n```\n\n`UseMediaRecorderOptions` has a `timeSlice` property which controls how many ms are recorded until `onDataAvailable` is called.\n\n### Utility Hooks used in the Demo App\n\n#### useInterval\n\n```typescript\nimport { useInterval } from \"react-media-hooks/use-interval\";\n```\n\n```typescript\nuseInterval(callback: () =\u003e void, delay: number | null): void\n```\n\nCalls `callback` every `delay` ms.\n\nSetting `delay` to `null` will stop it.\n\n#### useBlobUrls\n\n```typescript\nimport { useBlobUrls, downloadBlobs } from \"react-media-hooks/use-blob\";\n```\n\n```typescript\nuseBlobUrls(blobs: Blob[]): string[]\n```\n\nCreate blob urls for each blob whenever the `blobs` array changes. Revokes old URLs.\n\nAlso utility function:\n\n```typescript\ndownloadBlobs(blobs: Blob[], filePrefix?: string): void\n```\n\nWhich triggers downloads of an array of blobs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcollien%2Freact-media-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcollien%2Freact-media-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcollien%2Freact-media-hooks/lists"}