Skip to content

haroldalan/spotify-karaoke

Repository files navigation

Spotify Karaoke

Automatically fetches synced lyrics for songs that don't have it, and provides additional functionality like romanization for all scripts and translation into 132 languages — right inside Spotify

Chrome Web Store Firefox Add-ons Microsoft Edge Add-ons

License GitHub release Chrome users Chrome rating


Key Features



Smart Lyrics Fetch
When Spotify can't find lyrics, the extension fetches them automatically.

Lyrics not availableSynced lyrics found



Phonetic Romanization
Universal support for any script, with optimized local engines for 16 major writing systems.

안녕하세요an-nyeong-ha-se-yo



Real-time Translation
Read lyrics in 132 languages to understand the meaning behind every line.

君を愛してるI love you



Native Restoration
Bypass low-quality fallbacks and restore original, high-fidelity native scripts automatically.

Anyonhasyo안녕하세요


Coming Soon



Ultimate Triple View
Original, Romanized, and Translated text all in a single, perfectly synced frame.

Stacked lyrics (君を愛してる / Kimi o aishiteru / I love you)

Demo reel

demo.mp4

What it does

Spotify's lyrics panel has two problems: it sometimes can't find lyrics at all, and when it does, it often can't help you understand them. Spotify Karaoke fixes both.

Smart lyrics fetching: When Spotify shows "Lyrics not available," the extension quietly searches YouTube Music and LRCLIB in the background. If synced lyrics exist anywhere, they get injected directly into the Spotify lyrics panel — perfectly timed, visually integrated with Spotify's native UI. No user action required.


Smart Lyrics Fetching HUD


Romanization & Translation: Once lyrics are on screen — whether fetched or native — you can switch between three display modes:

  • Original — lyrics as Spotify shows them, unchanged.
  • Romanized — any non-Latin script (Japanese, Korean, Arabic, Indic, Thai, etc.) rendered phonetically in the Latin alphabet for instant sing-along.
  • Translated — lyrics translated into any of 132 languages.

Switch between modes using the floating pill controls injected directly into the lyrics panel, the popup, or keyboard shortcuts. No page reload, no flicker.

Native script restoration: For global non-Latin scripts (Hindi, Thai, Arabic, CJK, etc.), Spotify often serves low-quality romanized fallback lyrics even when the original native-script version exists on Musixmatch. This extension automatically intercepts and restores the original, high-fidelity native script — before Spotify even renders the page. Romanize and Translate modes then operate on the correct native source, producing significantly more accurate results.

Dual Lyrics mode — in Romanized or Translated mode, the processed text becomes the primary karaoke highlight line, with the original script shown below in a smaller font for reference (suppressed when identical to the primary line).

Genetic Lock Shield — keeps the lyrics panel permanently accessible, even for tracks where Spotify deliberately disables the lyrics button (e.g., instrumentals, podcasts).

Ad Intermission HUD — includes a custom 'Intermission' screen during audio ads to maintain a seamless visual experience without breaking the UI.

Cross-device Sync — preferences like your target language and display mode automatically sync across all your devices via storage.sync.

Dual Lyrics On Dual Lyrics Off
Dual Lyrics enabled Dual Lyrics disabled
Original (Korean) Romanized
Original lyrics Romanized lyrics
Translated
Translated lyrics

Controls

There are three ways to switch between Original, Romanized, and Translated:

Method How
Floating pill The [Original] [Romanized] [Translated] pill injected at the top of the Spotify lyrics panel.
Extension popup The same pill is replicated inside the popup - acts as a remote control and always reflects the current mode, even if the floating pill is hidden.
Keyboard shortcuts While the lyrics panel is open, press O (Original), R (Romanized), or T (Translated). Safe to use - shortcuts are ignored when focus is in a text input or search bar.

Note

Floating controls and keyboard shortcuts only function when Spotify's lyrics panel is open. Click the microphone icon in the player to open it.

Power users can toggle off the floating pill entirely via Show Floating Controls in the popup, then use keyboard shortcuts or the popup pill for a completely unobstructed lyrics view.

Floating Controls On Floating Controls Off
Floating controls visible Floating controls hidden

Installation

🌐 Official Browser Stores (Recommended)

🛠️ Manual Installation (Developer Mode)

If you wish to test the latest features before they hit the stores, you can install the extension manually.

Google Chrome / Brave / Opera

  1. Download the latest release .zip from the Releases page and extract it.
  2. Open your browser and navigate to chrome://extensions/.
  3. Enable Developer mode (usually a toggle in the top-right corner).
  4. Click Load unpacked and select the folder where you extracted the extension.

Mozilla Firefox

Important

Firefox Limitation: Temporary add-ons are removed when the browser restarts. For a permanent install, please use the Firefox Add-ons store.

  1. Download and extract the latest release .zip.
  2. Open Firefox and navigate to about:debugging#/runtime/this-firefox.
  3. Click Load Temporary Add-on....
  4. Select the manifest.json file inside the extracted folder.

Microsoft Edge

  1. Download and extract the latest release .zip.
  2. Navigate to edge://extensions/.
  3. Enable Developer mode (toggle in the bottom-left sidebar).
  4. Click Load unpacked and select the folder where you extracted the extension.

Native Script Restoration for Non-Latin Scripts

Spotify often serves romanized fallback lyrics for non-Latin songs (e.g. Thai, Arabic, or Indian languages) even when the original native-script version exists on Musixmatch.

Spotify Karaoke fixes this automatically. When you play a supported song, the extension intercepts Spotify's lyrics API response, detects the romanized fallback, fetches the native-script subtitles from Musixmatch, and replaces the response before Spotify renders it. The original script appears natively in the lyrics panel — no user action required.

Romanize and Translate modes then operate on the correct native source, producing significantly more accurate results.

Supported coverage: Deep restoration and optimization for all major non-Latin scripts globally (Tamil, CJK, Hindi, Arabic, Thai, Cyrillic, Hebrew, etc.).

Forensic Verification: Before injecting any Musixmatch payload, the extension runs a character-level audit — counting native script characters (Hangul, Devanagari, CJK, etc.) against Latin characters in the response. If Musixmatch itself returned a romanized fallback (a known edge case for some tracks), the injection is silently aborted, preventing the extension from overwriting Spotify's romanized lyrics with a different romanized version from a different source.


Romanization Coverage

Script Library Engine
Universal (fallback) transliteration Local
Japanese (Kanji + Kana) @sglkc/kuroshiro + Kuromoji Local
Korean (Hangul) @romanize/korean Local
Chinese (Hanzi) pinyin-pro Local
Tamil tamil-romanizer Local
Indic (Devanagari, Telugu, Gujarati, Gurmukhi, Kannada, Odia) @indic-transliteration/sanscript Local
Cyrillic (Russian, Ukrainian, Bulgarian, Serbian, Belarusian) cyrillic-to-translit-js Local
Thai @dehoist/romanize-thai Local
Malayalam, Bengali, Arabic, Hebrew Google Translate (dt=rm) API
Translation (All 132 languages) Google Translate → MyMemory fallback API

Local — zero latency, runs entirely in your browser.   🌐 API — requires a network call.


Under the Hood

Component Detail
Smart lyrics fetch YouTube Music synced → LRCLIB synced → YouTube Music plain → LRCLIB plain
Pre-fetch racing On every track change, the extension races Spotify to fetch lyrics and stores the result in a 10-minute sliding-window registry — so the hijack decision is already made before Spotify finishes rendering
Local romanization 10 bundled libraries covering 16+ writing systems
Interception point document_start, MAIN world — before React first paint
Cache L1: 10-song RAM · L2: 200-song persistent (browser.storage.local, LRU-evicted) · L3: in-flight deduplication · L4: network
Cache upgrades Background worker silently re-queries YTM weekly for previously unsynced songs; upgrades to synced automatically if found
Forensic Verifier Before injecting a Musixmatch payload, counts non-Latin Unicode characters to confirm a true native script was returned — aborts injection if Musixmatch also returned a romanized fallback, preventing loops
React Fiber bridge slyBridge.ts scans Spotify's Fiber tree at 500ms intervals. Utilizes a target-optimized Crawler Cache Gate to completely stand down heavy signature scans once resolved, maintaining a virtually 0% CPU footprint during active lyrics display
Genetic Lock Object.defineProperty override on Spotify's internal lyrics button disabled prop + extraction of toggleLyrics() directly from memoized Fiber props — keeps the panel permanently accessible and programmatically controllable
Playback sync Wall-clock extrapolation via performance.now() for 60fps sub-second accuracy between Spotify's ~500ms UI ticks
Stale-cancel guards Generation Map + processGen parity counter (2 independent mechanisms)
Translation fallback Google Translate → MyMemory → original preserved
Browser support Chrome MV3 · Firefox MV2 (≥ 142.0)

Developer Setup

Requirements: Node.js 18+

git clone https://github.com/haroldalan/spotify-karaoke.git
cd spotify-karaoke
npm install

npm run dev          # Chrome (live reload)
npm run dev:firefox  # Firefox (live reload)
npm run dev:edge     # Edge (live reload)

npm run build          # Production build - Chrome
npm run build:firefox  # Production build - Firefox
npm run build:edge     # Production build - Edge

npm run zip            # Package for Chrome Web Store submission
npm run zip:firefox    # Package for Firefox Add-ons submission
npm run zip:edge       # Package for Edge Add-ons submission

npm run test           # Run unit and component test suite

Project Structure

entrypoints/
  background.ts              # Service worker: lyrics fetching, romanization + translation orchestration, 4-layer cache
  fetchInterceptor.ts        # MAIN world unlisted script: fetch interceptor, native script restoration
  slyBridge.ts               # MAIN world unlisted script: React Fiber scanner, Genetic Lock Shield, state broadcaster
  spotify-lyrics.content/
    index.ts                 # Content script: bootstrapper, MutationObserver, mode switching, slyBridge wiring
    style.css
  popup/                     # Preact popup: mode pill, language selector, dual lyrics + visibility toggles

lib/
  core/                      # Pipeline B: lifecycle, mode controller, lyrics fetching, state store
  dom/                       # Pipeline B: DOM surgery, lyrics observer, controls pill, toast
  slyCore/                   # Pipeline A: custom DOM engine, playback sync, scavenger, status HUD, ad manager
  lyricsProviders/           # YTM scraper, LRCLIB client, 4-layer cache
  lyrics/                    # Script detector, local romanizer, lyrics processor
  translate/                 # Google Translate API, MyMemory fallback
  mxmClient.ts               # Musixmatch client for native script restoration

How it works

Spotify Karaoke runs two parallel pipelines depending on what Spotify provides.

Technical deep-dive

Pipeline B — Native lyrics exist: A MutationObserver watches document.body for song key updates (aria-label) and newly rendered lyric lines. The observer processes mutations in two passes: Pass 1 handles song key updates to ensure state coherence, and Pass 2 handles DOM structure changes to detect lyric injection. This prevents race conditions where lyrics might be processed against the previous song's key.

When lyrics are detected, the engine reads the current mode (Original / Romanized / Translated), fetches processed lyrics from cache or sends a PROCESS message to the background worker, and writes the result back into the existing DOM elements — specifically overwriting Spotify's React Fiber nodes with sly-main-line and sly-dual-line <span> elements for Dual Lyrics rendering. Spotify's own React state is never touched.

Pipeline A — Lyrics missing or broken: When Spotify reports "Lyrics not available," the slyCore engine takes over. It hides Spotify's native container entirely and injects a custom #lyrics-root-sync container in its place. Lyrics are fetched from YouTube Music or LRCLIB via the background service worker, then rendered with a requestAnimationFrame loop using wall-clock extrapolation for 60fps sync accuracy — completely decoupled from Spotify's own UI refresh cycle.

Pre-fetch racing: The moment a track change is detected, the extension races Spotify to fetch external lyrics and stores the result in a 10-minute sliding-window registry (preFetch). By the time Spotify finishes rendering its UI, the extension already knows whether a fallback hijack is needed — making the transition feel instant.

Romanization & translation: The background service worker receives an array of lyric strings, detects the script using Unicode range scoring, routes to the appropriate local library or Google Translate batch API, and returns both a translated array and a romanized array in a single response.

Native script restoration: entrypoints/fetchInterceptor.ts is compiled as an unlisted script and registered in the extension manifest to run in the MAIN world at document_start. This ensures the interceptor is active before Spotify's application bundle even begins to execute. It monkey-patches window.fetch to intercept color-lyrics/v2/track/* responses, fetches the native-script version from Musixmatch, and runs a Forensic Verifier — counting non-Latin Unicode characters to confirm Musixmatch returned a true native script and not another romanized fallback — before swapping out Spotify's response. The swap happens before React renders, so Spotify's app is entirely unaware it received injected data.

React Fiber bridge: entrypoints/slyBridge.ts runs in the MAIN world and traverses Spotify's React Fiber tree to extract live track metadata, access tokens, and the user's queue. It also maintains a Genetic Lock — an Object.defineProperty override on the internal disabled prop of Spotify's lyrics button that keeps it permanently enabled, even for tracks where Spotify would otherwise disable it. It additionally extracts Spotify's internal toggleLyrics() function directly from memoized Fiber props so the extension can open the lyrics panel programmatically.

Automatic cache upgrades: If a song was previously cached with only unsynced lyrics, the background worker silently re-queries YouTube Music once a week to check whether a synced version has since become available, and automatically upgrades the local cache if so — with no user action required.


Troubleshooting

Why aren't lyrics showing? Ensure the native Spotify lyrics panel is open. The extension injects into the native UI — click the microphone icon in the player bar to open it.

The lyrics panel is stuck or glitching. If Spotify changes its internal class names or state becomes corrupt, open the extension popup and click Reset Data. This safely clears the local cache and resets your preferences.

A song is missing lyrics entirely. When the extension cannot find lyrics on YouTube Music or LRCLIB, it will show a "Lyrics Not Found" screen. You can help the community by clicking the Contribute to LRCLIB link on that screen to upload the missing lyrics yourself.

I just uploaded lyrics, but they still aren't showing up! To keep things fast, the extension caches the "missing" state for 7 days. If you've just contributed lyrics and want to see them immediately, open the extension popup and click Reset Data, then refresh Spotify to force a fresh search.


Contributing

Issues, ideas, and PRs are welcome — especially for romanization accuracy improvements or new script support.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Commit your changes: git commit -m 'feat: describe what you added'
  4. Push and open a Pull Request.

Please keep PRs focused. One feature or fix per PR makes review much faster.


Privacy & Disclaimer

Note

No personal data is collected. Settings are stored in browser.storage.sync. Processed lyrics are cached locally in browser.storage.local and never leave your device. Lyric text is sent to Google Translate or MyMemory only when using Translated or API-based Romanized modes. See Google's Privacy Policy and MyMemory's Terms.

Warning

Spotify Karaoke is not affiliated with or endorsed by Spotify AB. It is an independent open-source project that modifies the Spotify web player UI for personal and accessibility use.


Support & Feedback

Discord

If you encounter any bugs, have suggestions for new features, or just want to chat about the project, feel free to reach out!


MIT License Ko-fi Chrome Review Firefox Review Edge Review

Made by Harold Alan. If you find it useful, a ⭐ on GitHub goes a long way.

About

⭐ Browser extension that fetches missing Spotify lyrics, restores native scripts, and romanizes & translates songs in 132 languages – live inside Spotify.

Topics

Resources

License

Stars

Watchers

Forks

Contributors