@karaplay/kar-player
v1.3.2
Published
KAR (Karaoke) file player library with MIDI playback and lyric rendering support
Maintainers
Readme
@karaplay/kar-player
🎤 Professional KAR (Karaoke) file player library with MIDI playback and advanced lyric rendering
A high-performance, production-ready karaoke player library built for React and Next.js applications. Features accurate timing, beautiful themes, and extensive customization options.
✨ Key Features
🎯 Core Capabilities
- KAR & EMK Support - Play both KAR and EMK (Extreme Karaoke) files with automatic conversion
- MIDI Playback - High-quality MIDI synthesis using SpessaSynth
- Advanced Lyric Rendering - Professional-grade lyric display with multiple modes
- Perfect Timing - Character-based progress calculation for pixel-perfect synchronization
- Thai Language Optimized - Proper handling of Thai vowels and diacritics
🎨 Rendering Features
- Line-Level Wipe Effect - Smooth gradient animation that follows actual word timing
- Multiple Display Modes - Single-line, 2-line, 3-line, extreme-karaoke, and full modes
- Beautiful Themes - Default, karaoke, minimal, extreme-karaoke, and text-only themes
- Text-Only Mode - Export styled text for custom backgrounds
- GPU Acceleration - Optimized for smooth 60fps rendering on low-end devices
⚙️ Configuration
- Environment Variables - Configure via
.envfiles (paths, colors, fonts, spacing) - Google Fonts - Auto-load from 5 Thai fonts (Kanit, Prompt, Anuphan, Chakra Petch, Sarabun)
- Custom Themes - Create your own themes with CSS variables
- Flexible Architecture - Works in browser and Node.js (server-side conversion)
🚀 Performance
- Sliding Window Rendering - Minimal DOM elements (only visible lines)
- RequestAnimationFrame - Smooth 60fps animation
- Memoization - Prevents unnecessary re-renders
- Page Visibility API - Auto-resume on tab switch
📦 Installation
npm install @karaplay/kar-player@latest buffer@^6.0.3Next.js Configuration
If using Next.js, add webpack fallbacks to next.config.js:
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
buffer: require.resolve('buffer/'),
};
}
return config;
},
};🎮 Quick Start
Basic React Usage
import { KaraokePlayer } from '@karaplay/kar-player';
function App() {
const player = new KaraokePlayer({
processorUrl: process.env.NEXT_PUBLIC_SPESSASYNTH_PROCESSOR_URL || '/spessasynth_processor.min.js',
soundFont: process.env.NEXT_PUBLIC_SOUNDFONT_URL || '/assets/sounds/GeneralUserGS.sf3',
soundBankName: 'main',
lyricConfig: {
displayMode: 'three-line',
theme: 'karaoke'
}
});
// Load and play
await player.loadKarFile('/path/to/song.kar');
player.play();
return <div ref={player.lyricContainerRef} />;
}React Hooks (Recommended)
import { useLyricRenderer } from '@karaplay/kar-player';
function LyricDisplay({ lyrics, currentTime }) {
const { containerRef } = useLyricRenderer(lyrics, currentTime, {
displayMode: 'two-line',
theme: 'karaoke',
highlightMode: 'line'
});
return <div ref={containerRef} className="lyric-container" />;
}🎵 Rendering Modes
Line-Level Wipe Effect (v1.2.0+)
All rendering now uses line-level approach:
- ONE element per line (no word spans)
- Character-based progress - Accurate timing based on word positions
- CSS gradient wipe - Smooth animation across entire line
- Preserved spaces - All whitespace maintained
// Renders as:
<div class="kar-lyric-line kar-line-current" style="--line-progress: 0.5">
สวัสดีครับ ยินดีต้อนรับ
</div>Progress Calculation (v1.2.4+)
Accurate character-based progress:
// Example:
// "Hello " (6 chars) at 1000ms
// "World" (5 chars) at 2000ms
// At 1500ms (middle of "Hello "):
// 3 chars highlighted (50% of 6)
// Progress: 3/11 = 0.27 (27%)
// At 2500ms (middle of "World"):
// 6 + 2.5 chars highlighted
// Progress: 8.5/11 = 0.77 (77%)🎨 Display Modes
1. Two-Line Mode (two-line)
<useLyricRenderer displayMode="two-line" />- Shows 2 lines: current + next
- Perfect for TV displays
- Creates exactly 2 DOM elements
2. Three-Line Mode (three-line)
<useLyricRenderer displayMode="three-line" />- Shows 3 lines: prev + current + next
- Best for desktop/mobile
- Creates exactly 3 DOM elements
3. Extreme Karaoke Mode (extreme-karaoke)
<useLyricRenderer displayMode="extreme-karaoke" />- Alternating top/bottom display
- Like professional karaoke boxes
- Creates exactly 2 DOM elements
4. Single-Line Mode (single-line)
<useLyricRenderer displayMode="single-line" />- Shows only current line
- Minimal UI
- Creates exactly 1 DOM element
5. Full Mode (full)
<useLyricRenderer displayMode="full" />- Shows all lyrics
- Scrollable view
- Creates N DOM elements (N = visible lines in viewport)
🎨 Themes
Built-in Themes
// 1. Default Theme
<useLyricRenderer theme="default" />
// 2. Karaoke Theme (recommended)
<useLyricRenderer theme="karaoke" />
// 3. Minimal Theme
<useLyricRenderer theme="minimal" />
// 4. Extreme Karaoke Theme
<useLyricRenderer theme="extreme-karaoke" />
// 5. Text-Only Theme
<useLyricRenderer theme="text-only" />Custom Theme
const myTheme = {
name: 'my-theme',
colors: {
active: '#ff0000',
inactive: '#666666',
background: '#000000'
},
fonts: {
family: 'Kanit, sans-serif',
size: '2rem',
weight: 600
},
spacing: {
lineHeight: '1.5',
wordSpacing: '-0.05em',
letterSpacing: '0'
}
};
<useLyricRenderer theme={myTheme} />📝 Text-Only Mode (v0.5.1+)
Export styled text without container styling for custom backgrounds:
import { useLyricText } from '@karaplay/kar-player';
function CustomLyrics({ lyrics, currentTime }) {
const { textElements } = useLyricText(lyrics, currentTime, {
activeColor: '#ff0000',
inactiveColor: '#666666'
});
return (
<div style={{ background: 'url(my-bg.jpg)' }}>
{textElements.map((line, i) => (
<div
key={i}
style={line.style}
className={line.className}
>
{line.text}
</div>
))}
</div>
);
}🎭 EMK Support (v0.12.0+)
Client-Side (Browser)
import { convertEmkToKarBrowser } from '@karaplay/kar-player';
// Convert EMK to KAR in browser
const result = await convertEmkToKarBrowser('/path/to/song.emk');
if (result.success && result.karBuffer) {
await player.loadKarBuffer(result.karBuffer);
player.play();
}Server-Side (Node.js)
// Import from /server subpath
import { convertEmkToKarServer } from '@karaplay/kar-player/server';
// Next.js API Route
export async function POST(request: Request) {
const formData = await request.formData();
const file = formData.get('file');
const result = await convertEmkToKarServer(
'/tmp/song.emk',
{ outputPath: '/tmp/song.kar' }
);
if (result.success) {
return Response.json({ karPath: result.karPath });
}
}⚙️ Environment Configuration
.env Configuration
# SpessaSynth Processor (Required)
NEXT_PUBLIC_SPESSASYNTH_PROCESSOR_URL=/spessasynth_processor.min.js
# SoundFont (Required)
NEXT_PUBLIC_SOUNDFONT_URL=/assets/sounds/GeneralUserGS.sf3
# Legacy paths (deprecated, use above instead)
NEXT_PUBLIC_SPESSASYNTH_LIB_PATH=/spessasynth_lib.min.js
NEXT_PUBLIC_SPESSASYNTH_CORE_PATH=/spessasynth_core.min.js
NEXT_PUBLIC_DEFAULT_SOUNDFONT=/soundfont.sf2
# Lyric Styling
NEXT_PUBLIC_KAR_PLAYER_ACTIVE_COLOR=#ffeb3b
NEXT_PUBLIC_KAR_PLAYER_INACTIVE_COLOR=rgba(255,255,255,0.5)
NEXT_PUBLIC_KAR_PLAYER_LINE_HEIGHT=1.3
NEXT_PUBLIC_KAR_PLAYER_WORD_SPACING=-0.05em
NEXT_PUBLIC_KAR_PLAYER_LETTER_SPACING=-0.02em
# Google Fonts (Kanit, Prompt, Anuphan, Chakra Petch, Sarabun)
NEXT_PUBLIC_KAR_PLAYER_GOOGLE_FONT=Kanit
NEXT_PUBLIC_KAR_PLAYER_FONT_WEIGHT=400Make sure these files are available in your public directory:
public/spessasynth_processor.min.jspublic/assets/sounds/GeneralUserGS.sf3
Auto-Load Google Fonts
import { loadGoogleFont } from '@karaplay/kar-player';
// Auto-load from .env
loadGoogleFont(); // Uses NEXT_PUBLIC_KAR_PLAYER_GOOGLE_FONT
// Or specify manually
loadGoogleFont('Prompt', '600');🚀 Performance Optimizations
1. Use Optimized Renderer
import { useLyricRendererOptimized } from '@karaplay/kar-player';
// Better performance with RAF + diffing
const { containerRef } = useLyricRendererOptimized(lyrics, currentTime, {
displayMode: 'two-line',
theme: 'karaoke'
});2. Sliding Window Rendering
Automatically removes old DOM elements (only keeps visible lines):
// Automatically enabled in all renderers
// 2-line mode = exactly 2 elements in DOM
// 3-line mode = exactly 3 elements in DOM3. GPU Acceleration
Built-in CSS optimizations:
.kar-lyric-line {
will-change: background;
transform: translateZ(0);
backface-visibility: hidden;
contain: layout style;
content-visibility: auto;
}4. Memoization
// All hooks use React.useMemo internally
const { textElements } = useLyricText(lyrics, currentTime, options);📊 API Reference
KaraokePlayer Class
const player = new KaraokePlayer({
processorUrl: string, // Required: SpessaSynth processor URL
soundFont: string | ArrayBuffer, // Required: SoundFont URL or buffer
soundBankName?: string, // Optional: Default 'main'
lyricConfig?: {
displayMode?: 'single-line' | 'two-line' | 'three-line' | 'extreme-karaoke' | 'full',
theme?: string | LyricTheme,
highlightMode?: 'line' | 'word' | 'progressive' | 'none',
autoScroll?: boolean,
smoothScroll?: boolean
}
});
// Methods
await player.loadKarFile(path: string): Promise<LoadKarFileResult>
await player.loadKarBuffer(buffer: ArrayBuffer): Promise<LoadKarFileResult>
player.play(): void
player.pause(): void
player.stop(): void
player.seekTo(time: number): void
player.setVolume(volume: number): voidExample with environment variables:
const player = new KaraokePlayer({
processorUrl: process.env.NEXT_PUBLIC_SPESSASYNTH_PROCESSOR_URL!,
soundFont: process.env.NEXT_PUBLIC_SOUNDFONT_URL!,
soundBankName: 'main',
lyricConfig: {
displayMode: 'three-line',
theme: 'karaoke'
}
});React Hooks
useLyricRenderer
const {
containerRef,
currentLineRef,
forceUpdate,
scrollToLine
} = useLyricRenderer(
lyrics: LyricLine[],
currentTime: number,
options?: {
displayMode?: 'single-line' | 'two-line' | 'three-line' | 'extreme-karaoke' | 'full',
theme?: string | LyricTheme,
highlightMode?: 'line' | 'word' | 'progressive' | 'none',
textOnly?: boolean,
disableBackground?: boolean,
autoScroll?: boolean,
smoothScroll?: boolean,
classNames?: {
container?: string,
line?: string,
current?: string,
prev?: string,
next?: string
}
}
);useLyricText (Text-Only Mode)
const {
textElements,
getLineStyle,
getLineClass,
currentLineIndex,
progress
} = useLyricText(
lyrics: LyricLine[],
currentTime: number,
options?: {
displayMode?: string,
inactiveColor?: string,
activeColor?: string,
currentLineColor?: string,
prevLineColor?: string,
nextLineColor?: string,
currentFontSize?: string,
prevFontSize?: string,
nextFontSize?: string
}
);
// textElements structure:
interface LyricTextElement {
lineIndex: number;
position: 'prev' | 'current' | 'next';
text: string; // Full line text
style: CSSProperties; // Inline styles with gradient wipe
className: string; // kar-lyric-line kar-line-{position}
progress: number; // 0-1 for wipe effect
}Progress Calculator Utilities
import {
calculateLineProgress,
calculateLineProgressWordByWord,
getProgressDebugInfo
} from '@karaplay/kar-player';
// Character-based progress (recommended)
const progress = calculateLineProgress(
parts: LyricPart[],
currentTime: number
): number; // 0-1
// Word-by-word progress (no partial)
const progress = calculateLineProgressWordByWord(
parts: LyricPart[],
currentTime: number
): number; // 0-1
// Debug timing
const info = getProgressDebugInfo(
parts: LyricPart[],
currentTime: number
): {
currentTime: number;
totalChars: number;
charsHighlighted: number;
progress: number;
currentPartIndex: number;
currentPartText: string;
currentPartTime: number;
};📚 Documentation
- CHANGELOG.md - Version history and breaking changes
- ENV_CONFIG.md - Complete .env configuration guide
- TEXT_ONLY_MODE.md - Custom background rendering
- PERFORMANCE.md - Optimization techniques
- EMK_SUPPORT.md - EMK file handling
- GOOGLE_FONTS.md - Thai font integration
- INSTALL_GUIDE.md - Next.js & Firebase setup
- VERIFY_EXPORTS.md - Verify package exports
🎯 Use Cases
1. Professional Karaoke App
import { KaraokePlayer } from '@karaplay/kar-player';
const player = new KaraokePlayer({
processorUrl: '/spessasynth_processor.min.js',
soundFont: '/assets/sounds/GeneralUserGS.sf3',
soundBankName: 'main',
lyricConfig: {
displayMode: 'extreme-karaoke',
theme: 'karaoke'
}
});2. Web Karaoke Platform
<useLyricRendererOptimized
displayMode="three-line"
theme="minimal"
highlightMode="line"
/>3. TV Karaoke Box
<useLyricRenderer
displayMode="two-line"
theme="extreme-karaoke"
classNames={{ container: 'tv-display' }}
/>4. Mobile Karaoke App
<useLyricRenderer
displayMode="single-line"
theme="minimal"
textOnly={true}
/>🛠️ Development
# Install dependencies
npm install
# Build
npm run build
# Test
npm test
# Test with coverage
npm run test:coverage
# Watch mode
npm run test:watch🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT © [Your Name]
🙏 Acknowledgments
- SpessaSynth - MIDI synthesis engine
- midi-file - MIDI file parsing
- pako - Compression/decompression
- iconv-lite - Encoding conversion
- @karaplay/file-coder - EMK file support
📊 Stats
- Bundle Size: ~140 KB (minified)
- TypeScript: 100% type coverage
- Tests: 147 unit tests
- Browser Support: Chrome, Firefox, Safari, Edge (latest 2 versions)
- Node.js: 16+ (for server-side conversion)
Built with ❤️ for the karaoke community 🎤🎵
