Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

231 Commits
231 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pulse app icon

Pulse

Short-form institutional video — record and edit on-device, upload to your own server.

React Native Expo TypeScript Whisper VisionCamera

Capture knowledge before it walks out the door — training walkthroughs, process demos, handoffs — without sending a single frame to a third-party cloud.


Pulse is a React Native (Expo) app for capturing institutional knowledge as short-form video. Everything happens on the device: segmented recording, trimming, merging, and even speech-to-text captioning run locally. When you're ready to publish, Pulse uploads to a PulseVault server you run — your organization keeps the content on its own infrastructure and owns auth, retention, and quota decisions. No central Pulse service exists.

Anatomy of a Pulse

Two words show up throughout Pulse — here's what they mean.

What is a Pulse?

A Pulse is a single short-form video, assembled on-device from an ordered set of clips. Record a few, import a few, reorder them — on export they merge (losslessly where formats match) into one .mp4. Publishing a Pulse sends a small bundle: the merged video, its captions (WebVTT), a beat manifest, and a thumbnail. On-device each Pulse is a draft in your library; drafts move between devices as .pulse bundles.

flowchart LR
    subgraph pulse["🫀 One Pulse — an ordered set of clips"]
        direction LR
        c0["Clip 0"] --> c1["Clip 1"] --> c2["Clip 2"]
    end
    pulse ==>|"merge on export"| merged["Merged .mp4"]
    merged --> vtt["Captions · .vtt"]
    merged --> man["Beat manifest"]
    merged --> thumb["Thumbnail"]
Loading

What is a Beat?

A Beat is one clip's exact slot on the merged timeline — { segmentId, order, startMs, endMs }. The beat manifest records every clip's start/end so any moment in the merged video can be traced back to the segment it came from (and later deep-linked — e.g. HLS chapter jumps). Beats are contiguous — each beat's endMs equals the next beat's startMs — and sum exactly to the Pulse's true duration.

flowchart TB
    subgraph clips["Recorded clips, in order"]
        direction LR
        s0["Clip 0"] --> s1["Clip 1"] --> s2["Clip 2"]
    end
    subgraph tl["One merged timeline · 0 → durationMs"]
        direction LR
        b0["Beat 0<br/>0 – 4200 ms"] --- b1["Beat 1<br/>4200 – 9100 ms"] --- b2["Beat 2<br/>9100 – 15000 ms"]
    end
    s0 -.-> b0
    s1 -.-> b1
    s2 -.-> b2
Loading

Highlights

  • 🎬 Segmented recording — build a walkthrough from multiple clips, reorder them, re-record the ones you fumbled
  • ✂️ Non-compounding edits — trims re-encode from the pristine original every time, so re-editing never stacks generation loss
  • 🗣️ On-device captions — Whisper (whisper.cpp) transcription with word-level timing, no audio ever leaves the phone
  • 📡 Resumable uploads — TUS v1 protocol; a two-minute capture survives signal drops, app kills, and relaunches
  • 🔐 Self-hosted by design — pair with your server via QR / deep link; capability tokens live in the device keychain
  • 📦 Local-first drafts — SQLite-backed library that works fully offline, shareable device-to-device as .pulse bundles

Features

Recording & camera

  • Multi-clip segmented recording with VisionCamera v5 — segments append to one draft with a live segment bar and drag-to-reorder
  • Front/back flip, torch, mic mute, tap-to-focus, pinch-to-zoom, lens selection, and stabilization modes (off / standard / cinematic / auto), all persisted across sessions
  • Call-aware capture: a custom native module (CallKit on iOS) gates the microphone during phone/VoIP calls so recordings don't silently freeze
  • Import existing videos from Photos instead of recording

Editing & export

  • Per-clip trim and transform via FFmpeg — destructive but safe: re-editing always reopens the untouched original
  • Smart merge on export: clips with matching formats are concatenated losslessly (passthrough); mixed resolution/fps/codec/orientation drafts conform only the outlier clips, with a full re-encode fallback
  • Save to Photos or Files, share anywhere, or upload — with a live progress ring

Captions & transcription

  • Fully on-device speech-to-text with whisper.rn — choose Base (en), Small (en), Small multilingual, or Large-v3-turbo, downloaded on demand
  • Silero VAD pre-gate skips silent clips and suppresses Whisper's hallucinated filler on empty audio
  • Word-level timestamps reflowed into broadcast-style cues (line-length, duration, and sentence-boundary aware)
  • Optimistic caption editor with autosave and undoable edit history — hand-edited cues are protected from being overwritten by re-transcription or model switches
  • Captions upload as WebVTT with word-level cue timestamps (karaoke-ready) alongside the video

Drafts & library

  • Local-first draft library on Drizzle ORM + SQLite — create, rename, delete, with live-updating thumbnails
  • Device-to-device draft sharing as .pulse bundles (zip + manifest, with unpack safety limits)
  • First-run onboarding tour

Upload & pairing

  • Pair with a server by scanning a QR / opening a pulsecam:// deep link — trust-on-first-use confirmation, capability negotiation against the server's /capabilities endpoint
  • TUS v1 resumable uploads with exponential backoff; interrupted uploads resume from the server's true byte offset, even after an app relaunch
  • Two upload strategies, negotiated per server: merged (one video + captions + beat-timecode manifest + thumbnail) or segment (per-segment clips + an ordering manifest)
  • Bearer tokens stored in the secure keychain, never in the database

How it works

┌─────────────────────────── on device ───────────────────────────┐
│                                                                  │
│  Record (VisionCamera) ─▶ Trim/Merge (FFmpeg) ─▶ Draft (SQLite)  │
│                                │                                 │
│                    Whisper + VAD ─▶ captions (VTT)               │
│                                                                  │
└──────────────────────────────┬───────────────────────────────────┘
                               │  pair via pulsecam:// QR / deep link
                               ▼
                 TUS v1 resumable upload (video + VTT)
                               │
                               ▼
                ┌──────────────────────────────┐
                │   PulseVault (self-hosted)   │
                │  your auth · your storage    │
                └──────────────────────────────┘

PulseVault — self-hosted uploads

The server side lives in pulsevault-mieweb/: a Fastify plugin (with a framework-agnostic core for Express, Meteor, or plain Node http) that receives Pulse uploads into filesystem-first storage, with authorize / validatePayload / onUploadComplete hooks for wiring in your SSO, audit trail, transcoding, or AI pipeline. The wire contract is documented in PROTOCOL.md — an implementation-independent spec (capability discovery, artifact kinds, tokens, TUS) so anyone can build a compatible server.

Getting started

Git LFS required. The dev-seed video fixtures in assets/dev/*.mp4 are stored in Git LFS. Install it before cloning (or pull after), otherwise those files arrive as tiny pointer stubs and the dev + seed button breaks:

brew install git-lfs && git lfs install   # one-time
git lfs pull                              # if you cloned before installing

The ~400 MB fixture-regen master (fixtures/bbb_master.mov) is intentionally excluded from normal clones (see .lfsconfig); fetch it only when regenerating fixtures: git lfs pull --include "fixtures/*.mov".

# 1. Install dependencies
npm install

# 2. Run a development build (native modules — Expo Go won't work)
npm run ios       # or: npm run android

Pulse uses native modules (VisionCamera, Whisper, FFmpeg), so it needs a dev build, not Expo Go.

No camera? No problem. In a dev build, the Home screen has + seed / clear buttons that create a "Dev sample" draft from the bundled assets/dev/ clips — including deliberately mismatched resolution/fps/codec/orientation clips that exercise the export-normalization path — so the full editor is drivable on a simulator. See assets/dev/README.md.

Development

Commands

Command What it does
npm start Start the Metro dev server
npm run ios / npm run android Build & run the dev client
npm test Run the Jest unit-test suite
npm run lint ESLint via expo lint
npm run format Prettier

Project structure

src/
├── app/            # expo-router screens: home, recorder, subtitles, export, onboarding
├── features/       # recorder, transcription, export, upload, draft-transfer, home, …
├── db/             # Drizzle ORM schema + queries (expo-sqlite)
├── components/     # shared themed UI
└── dev/            # __DEV__-only seed tooling (dead-code-eliminated in release)
modules/            # custom native modules (expo-call-detector)
plugins/            # Expo config plugins
pulsevault-mieweb/  # self-hosted upload server + protocol spec
fixtures/           # fixture-regen sources (Big Buck Bunny, CC-BY)

Testing

Unit tests are co-located with the code and scoped to pure logic — cue reflow, upload/TUS state machines, deep-link parsing, edit history, autosave gating — so they run in a plain Node environment with no native rendering. PulseVault ships its own suite covering the HTTP adapters, capability tokens, and checksum handling.

npm test

License

See LICENSE.


Made with ❤️ at MIE — built on React Native, Expo, whisper.cpp, and FFmpeg.

Releases

Packages

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.