1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
# lrc-karaoke-player


A karaoke-oriented media player. Supports simultaneous playback of a main video/audio file, a secondary audio track, LRC lyrics, and SRV3 (YouTube Timed Text) subtitles.
https://github.com/Patchwork-Archive/Patchwork-Karaoke/assets/21994085/5106bb53-d962-45e9-9a6b-6368dd1c6437
---
## Build
```sh
pnpm i
pnpm run dev
```
---
## Features
- **LRC lyrics** — scrolling line-by-line lyrics with highlight animation
- **SRV3 subtitles** — YouTube Timed Text rendered over the video
- **Dual audio** — mix a main media file with a secondary audio track (e.g. vocals + instrumental)
- **Audio/Video balance** — slider to blend the two audio sources
- **Timing offsets** — independently adjust LRC sync and Audio #2 sync in milliseconds
- **Drag and drop** — drop any supported file directly onto the player
- **Resizable panes** — drag the divider between the lyrics and video panels
- **MoekyunKaraoke codes** — shareable codes that load a full session from remote URLs
---
## Supported File Types
| Slot | Accepted formats |
|------|-----------------|
| Media (file1) | `mp4`, `webm`, `ogg`, `mp3`, `wav`, `flac`, and any format the browser supports |
| Audio #2 (file2) | Same as above |
| Lyrics | `.lrc` |
| Subtitles | `.srv3` (YouTube Timed Text) |
The purpose of having `Audio #2` is if you want to dynamically change the volume of 2 tracks. As an example, you may want `Media` to be an insturmental, while `Audio #2` to be acapella/vocal-isolated version. This way you can dynamically change the volume of either.
---
## MoekyunKaraoke Code
A MoekyunKaraoke code is a **Base64-encoded JSON object** that encodes remote URLs and timing settings for a session. Paste one into the input field on the player page, or pass it as a `?code=` URL query parameter to share a fully-loaded session as a single link.
### Encoding / decoding
```js
// Encode
const code = btoa(JSON.stringify(payload));
// Decode (done internally by the player)
const payload = JSON.parse(atob(code));
```
### Fields
All fields are optional — include only the ones you need.
| Field | Type | Description |
|-------|------|-------------|
| `lrc` | `string` (URL) | URL to an `.lrc` lyrics file. Fetched by the player at load time. |
| `srv3` | `string` (URL) | URL to a `.srv3` YouTube Timed Text subtitle file. Fetched by the player at load time. |
| `file1` | `string` (URL) | URL to the main video or audio file. |
| `file2` | `string` (URL) | URL to the supplemental (secondary) audio file. |
| `offset` | `number` (ms) | LRC timing offset in milliseconds. Negative values shift lyrics earlier; positive values shift them later. |
| `offset2` | `number` (ms) | Audio #2 timing offset in milliseconds. |
### Example payload
```json
{
"lrc": "https://example.com/song.lrc",
"file1": "https://example.com/song.webm",
"offset": -800
}
```
Full session with every field:
```json
{
"lrc": "https://example.com/song.lrc",
"srv3": "https://example.com/song.srv3",
"file1": "https://example.com/song.webm",
"file2": "https://example.com/instrumental.mp3",
"offset": -800,
"offset2": 120
}
```
### Sharing via URL
```
https://your-player-domain.com/?code=eyJsciI6Imh0dHBzOi8v...
```
### Notes
- `lrc` and `srv3` are fetched client-side and require the hosting server to send permissive CORS headers (`Access-Control-Allow-Origin: *`).
- `file1` and `file2` are set as media `src` attributes directly and are subject to the same CORS restrictions for cross-origin URLs.
> See [`KARAOKE_CODE.md`](./KARAOKE_CODE.md) for the full format reference including code generation examples in JavaScript and Python.
|