Case Study
Stream of Worship
- Role
- Product Owner & Engineer
- Team
- Solo
- Stack
- Next.js 16 FastAPI AWS Lambda/SQS Neon Postgres + pgvector Cloudflare R2 PyTorch / Demucs Jetpack Compose (Android)
- Live web app with 445 songs cataloged and over 100 manually verified for lyrics and timing
Problem
I built Stream of Worship because I wanted small group worship to flow without having to manage YouTube between songs. Leading worship also meant dealing with awkward pauses between videos and dropped connections in the middle of a song.
The leader already has enough to think about. Finding the next video and waiting for it to load shouldn’t be part of the job. There weren’t many lyrics-video tools for Mandarin worship, so I started building one.
The workflow I wanted was simple: pick songs, arrange them into a set, render a continuous lyrics video, and cast it to the TV. Download it beforehand so playback doesn’t depend on the network. An audio-only version would also help the group learn unfamiliar songs before meeting.
I wrote about the work in Stream of Worship, a series. The posts follow the project from building the catalog to lyrics alignment, transitions, and offline playback, including the parts that still need work.
Constraints
Three things shaped the project:
- Playback has to work offline. Unreliable networks were part of the reason for building this. A leader needs to download a set before worship and play it without a connection.
- Accurate LRC files are hard to find. An LRC file pairs each lyric line with a timestamp. The words have to match the recording, including every repeated verse and chorus, and each line has to appear at the right time. Having the official lyrics only solves part of that problem.
- ASR still needs human review. Automatic speech recognition (ASR) made plenty of mistakes on the Chinese songs I worked with, even Alibaba’s Qwen3 ASR. Feeding a recording to Whisper or Qwen3 wasn’t enough. I needed to automate the first pass and make it practical for a human to check and correct the result.
Decisions
Make lyrics review part of the product
A video can render successfully and still be unusable for singing. Wrong characters and late lyrics are different problems, so I separated text correction from timing alignment. The pipeline uses available captions or ASR to get a first pass, corrects against the official lyrics, and supports a separate forced-alignment step for timing. Machine-generated files go into review before publication. I explain the tradeoffs in Synced lyrics: generation and alignment.
That made the review workflow worth improving. I added a lyrics editor and a review queue that prioritizes reported problems and songs people use. Listeners can report timing or text problems and leave a note about the affected lines. Feedback points me toward a correction; it doesn’t overwrite the shared lyrics automatically.
Let the leader listen and adjust
The editor lets a leader choose the song order and adjust gaps and crossfades. Gaps are measured in beats so the space follows the tempo.
Transition recommendations compare where the recordings meet, but a compatibility score doesn’t tell me whether a transition sounds good. The shipped renderer handles gaps, fades, and volume normalization; automatic pitch shifting and tempo matching aren’t implemented. The transition engine covers both the design and those limits.
Move rendering out of the web request
Generating a multi-song video takes longer than a normal web request. I moved rendering into a background worker so the app can handle editing and show progress. It produces an MP3 mix and an MP4 with synchronized lyrics and chapter markers.
The output needed iteration too. Long Chinese lines were getting smaller because the renderer shrank each line to fit. I changed the layout to wrap at phrase boundaries and use one lyric font size throughout the video. Keeping the words readable on a TV matters more than fitting every phrase onto one line.
Download everything needed to start playback
Caching the video alone isn’t enough. The player page, lyrics, and chapter data also need to be available without a connection. A downloaded set also needs to support seeking without a connection.
Shared sets also need predictable behavior. A share link points to a frozen copy, so editing my own set later doesn’t change what someone else prepared to play. Offline worship playback goes into the cache and startup problems behind this workflow.
Showcase
The live web app is the working result. After signing in, a leader can browse songs, preview them with lyrics, arrange a set, and adjust the space between songs. The set can then be rendered as audio or a lyrics video, downloaded for offline playback, projected or cast to a larger screen, and shared with others.
The catalog contains 445 songs, with over 100 manually verified for lyrics and timing accuracy. That distinction matters. Generating lyrics for a recording and checking that a group can actually sing along with them are separate steps.
Next steps: help choose the songs
Automated songset construction is an area I’m exploring. The constructor proposes song combinations using worship themes, tempo, and harmonic compatibility. For now, I don’t have enough listening evidence to say that a higher-ranked set makes for better worship flow. I want to evaluate the proposed sets by listening before making the scores a bigger part of the product. The songset constructor post covers the approach in more detail.
Artifacts
- Web app (sign-in required): app.streamofworship.com
- Marketing site: streamofworship.com
- Repo: mhuang74/stream_of_worship
- Blog series: Stream of Worship, a series, with posts on the lyrics pipeline, the transition engine, and offline playback.
Outcome
Stream of Worship is live, with a workflow from song selection to a downloaded worship set. A leader can prepare continuous playback without manually editing audio or lining up lyrics in a video editor.
The catalog work is still ongoing. More than 100 songs have been checked by hand, with 300+ to go. I built an LRC problem detector to identify songs likely to contain lyric or timing problems, and the lyrics feedback system captures problems actual users report.