npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@siteed/audio-ui

v1.1.2

Published

UI experiments for @siteed/audio-studio

Readme

@siteed/expo-audio-ui

Version License GitHub stars

Give it a GitHub star, if you found this repo useful.

Audio visualization and control components for React Native, built with Skia and Reanimated. Designed to work with @siteed/audio-studio.

Components

  • AudioVisualizer — interactive waveform with navigation, amplitude scaling, and theming
  • DecibelGauge — gauge display for audio levels in dB
  • DecibelMeter — linear meter with customizable thresholds
  • RecordButton — recording button with visual feedback and animations
  • Waveform — lightweight waveform renderer
  • AudioTimeRangeSelector — interactive time range selection with drag handles and optional selected-range dragging
  • MelSpectrogramVisualizer — real-time mel spectrogram display

Audio time range selection

AudioTimeRangeSelector is controlled by committed startTime and endTime values. Drag the handles to resize the range, or drag the selected range body to move it without changing its duration.

<AudioTimeRangeSelector
    durationMs={durationMs}
    startTime={startTime}
    endTime={endTime}
    onRangeChanging={(start, end) => updatePreview(start, end)}
    onRangeChange={(start, end) => setRange({ start, end })}
    rangeDragEnabled={false} // optional: disable selected-range body dragging
/>

Install

yarn add @siteed/expo-audio-ui

Peer dependencies:

yarn add @shopify/react-native-skia react-native-gesture-handler react-native-reanimated

Development

cd packages/audio-ui
yarn storybook
# Opens at http://localhost:6068

Docs

License

MIT — see LICENSE.


Created by Arthur Breton

Waveform/player/recorder contracts

@siteed/audio-ui is a controlled rendering package. It does not read files, play audio, request permissions, start recorders, or call native extraction modules from its main entrypoint. Apps pass waveform bars and state in through props.

Existing-file preview

Use an app/domain extraction layer such as @siteed/audio-studio to create compact bars, then render them with WaveformPreview or AudioPlayerWidget:

import { AudioPlayerWidget } from '@siteed/audio-ui'
import { extractPreviewBars } from '@siteed/audio-studio'

const preview = await extractPreviewBars({ fileUri, numberOfBars: 96 })

<AudioPlayerWidget
  dataPoints={preview.bars}
  width={280}
  density="chat"
  currentTimeMs={currentTimeMs}
  durationMs={preview.durationMs}
  isPlaying={isPlaying}
  onPlayPause={togglePlayback}
  onSeek={seekTo}
/>

Controlled chat recorder

ChatRecordWidget displays the recorder state and live waveform bars supplied by the app. The app still owns useAudioRecorder, permissions, final URI, duration, and send/cancel behavior.

<ChatRecordWidget
    state={isRecording ? 'recording' : 'idle'}
    dataPoints={liveBars}
    elapsedMs={elapsedMs}
    width={320}
    onRecordPress={startRecording}
    onStopPress={stopRecording}
    sendSlot={<SendButton />}
    cancelSlot={<CancelButton />}
/>

File-driven preview (AudioFilePlayerWidget)

Skip the manual extract+state dance for already-recorded audio. Pass a fileUri and an extract impl; the widget extracts once per (fileUri, numberOfBars) pair, caches the result with an LRU (default 16 entries, configurable via maxCachedFiles), and aborts in-flight work when the URI changes mid-extract.

import { AudioFilePlayerWidget } from '@siteed/audio-ui'
import { extractPreviewBars } from '@siteed/audio-studio'
import { useCallback } from 'react'

// IMPORTANT: useCallback the extractor — its identity is part of the
// effect's dep array. An inline arrow re-runs extraction every render
// and bypasses the cache.
const extract = useCallback(extractPreviewBars, [])

<AudioFilePlayerWidget
    fileUri={uri}
    extract={extract}
    width={320}
    currentTimeMs={currentTimeMs}
    isPlaying={isPlaying}
    onPlayPause={togglePlayback}
    onSeek={seekTo}
    // For long-form audio: 1 bar per N ms.
    // Requires a `durationMs` hint since the file length is unknown
    // until extraction completes.
    durationMs={knownDurationMs}
    barDurationMs={100}
/>

Headless hooks

The widgets share their decimation, playhead, gesture, and tap-vs-hold logic through reusable hooks. Reach for these when you need a fully custom render on top of the same math.

import {
    useAudioPlayerWidgetState,
    useChatRecordWidgetState,
} from '@siteed/audio-ui'

// Headless seek/playhead/decimation state for a custom waveform UI.
const {
    onLayout,        // attach to the view that wraps your bars
    measuredWidth,   // 0 until first layout pass
    renderPoints,    // decimated bars matching measured width
    renderVoiceMask, // voice mask aligned with renderPoints, or undefined
    playheadX,       // pixel offset within measuredWidth
    handleCanvasPress, // maps locationX → seek call
    formatTime,      // MM:SS formatter (override via prop)
} = useAudioPlayerWidgetState({
    dataPoints,
    voiceMask,
    currentTimeMs,
    durationMs,
    onSeek,
})

// Headless tap/hold press wiring for a custom mic button.
const {
    isDisabled,
    primaryIcon,
    onPress,     // tap-mode handler (interaction='tap')
    onPressIn,   // hold-mode start (interaction='hold')
    onPressOut,  // hold-mode stop  (interaction='hold')
} = useChatRecordWidgetState({
    state,
    interaction: 'hold',
    onRecordPress,
    onStopPress,
    onRetryPress,
})

Locking the amplitude range

Without a fixed range, the bar visualizer rescales every time a louder sample arrives — identical dB looks different at different points in the same clip. Pin one of the shipped presets via the amplitudeRange prop:

import { SPEECH_AMPLITUDE_RANGE, MUSIC_AMPLITUDE_RANGE, FULL_AMPLITUDE_RANGE } from '@siteed/audio-ui'

<AudioPlayerWidget
    dataPoints={liveBars}
    amplitudeRange={SPEECH_AMPLITUDE_RANGE}  // {min:0, max:0.2} — chat / voice memos
    /* ... */
/>

Migrating from DataPoint[]

The UI components accept structural waveform points: amplitude, optional rms, optional silent, and optional time fields. Existing AudioAnalysis and DataPoint[] objects can be passed directly when they have those fields, or normalized with waveformAnalysisFromAudioStudioAnalysis / waveformBarsFromAudioStudioDataPoints.