Skip to content

Creating Lyrics Addons

ivLis edited this page Jan 22, 2026 · 1 revision

Creating Lyrics Addons

한국어

This guide explains how to create custom lyrics provider addons for ivLyrics.


Overview

Lyrics Addons provide lyrics from various sources. They are registered with LyricsAddonManager and users can enable/disable them and set priority order.


Addon Structure

Basic Template

(function() {
    'use strict';

    const addon = {
        // Required: Unique identifier
        id: 'my-lyrics-provider',

        // Required: Display name
        name: 'My Lyrics Provider',

        // Required: Author name
        author: 'Your Name',

        // Required: Description
        description: {
            en: 'Fetch lyrics from My Service',
            ko: 'My Service에서 가사 가져오기'
        },

        // Required: Supported lyrics types
        supports: {
            karaoke: false,    // Word-by-word sync
            synced: true,      // Line-by-line sync
            unsynced: true     // No timing
        },

        // Optional: ivLyrics Sync support
        supportsIvSync: false,

        // Optional: Settings UI
        getSettingsUI: function() {
            return null;
        },

        // Optional: Initialize addon
        init: async function() {
            console.log('My Lyrics Provider initialized');
        },

        // Required: Get lyrics
        getLyrics: async function(info) {
            // info: { uri, title, artist, album, duration }
            // Return: LyricsResult or null
        }
    };

    function register() {
        if (window.LyricsAddonManager) {
            window.LyricsAddonManager.register(addon);
        } else {
            setTimeout(register, 100);
        }
    }
    register();
})();

Required Fields

id

  • Unique identifier string
  • Use lowercase with hyphens
  • Example: 'my-lyrics-provider'

name

  • Display name shown in settings
  • Example: 'My Lyrics Provider'

author

  • Creator name
  • Example: 'Your Name'

description

Can be string or localized object:

description: {
    en: 'English description',
    ko: '한국어 설명'
}

supports

Define which lyrics types the provider can return:

supports: {
    karaoke: false,    // Word-by-word highlighting
    synced: true,      // Line-level timestamps
    unsynced: true     // Plain lyrics
}

Lyrics Result Format

Unsynced Lyrics

{
    type: 'unsynced',
    provider: 'my-lyrics-provider',
    lines: [
        { text: 'First line of lyrics' },
        { text: 'Second line of lyrics' },
        { text: '' },  // Empty line for spacing
        { text: 'Third line of lyrics' }
    ]
}

Synced Lyrics

{
    type: 'synced',
    provider: 'my-lyrics-provider',
    lines: [
        {
            startTime: 0,       // milliseconds
            endTime: 3500,      // milliseconds (optional)
            text: 'First line'
        },
        {
            startTime: 3500,
            endTime: 7000,
            text: 'Second line'
        }
    ]
}

Karaoke Lyrics

{
    type: 'karaoke',
    provider: 'my-lyrics-provider',
    lines: [
        {
            startTime: 0,
            endTime: 3500,
            text: 'First line words',
            words: [
                { startTime: 0, endTime: 800, text: 'First' },
                { startTime: 800, endTime: 1500, text: 'line' },
                { startTime: 1500, endTime: 3500, text: 'words' }
            ]
        }
    ]
}

getLyrics Method

Parameters

async getLyrics(info) {
    // info object contains:
    // - uri: Spotify track URI (e.g., 'spotify:track:xxx')
    // - title: Track title
    // - artist: Artist name
    // - album: Album name
    // - duration: Track duration in milliseconds
}

Return Values

// Success with lyrics
return {
    type: 'synced',
    provider: 'my-lyrics-provider',
    lines: [...]
};

// No lyrics found
return null;

// Error (also return null, optionally log error)
console.error('Failed to fetch lyrics:', error);
return null;

Example: LRCLIB-style Provider

(function() {
    'use strict';

    const addon = {
        id: 'my-lrc-provider',
        name: 'My LRC Provider',
        author: 'Developer',
        description: 'Fetch LRC format lyrics',
        
        supports: {
            karaoke: false,
            synced: true,
            unsynced: true
        },

        getLyrics: async function(info) {
            try {
                // Build search URL
                const url = new URL('https://api.example.com/lyrics');
                url.searchParams.set('track', info.title);
                url.searchParams.set('artist', info.artist);

                const response = await fetch(url.toString());
                
                if (!response.ok) {
                    return null;
                }

                const data = await response.json();

                if (!data.lyrics) {
                    return null;
                }

                // Parse LRC format
                if (data.syncedLyrics) {
                    return this.parseSyncedLyrics(data.syncedLyrics);
                } else if (data.plainLyrics) {
                    return this.parsePlainLyrics(data.plainLyrics);
                }

                return null;
            } catch (error) {
                console.error('[MyLRCProvider] Error:', error);
                return null;
            }
        },

        parseSyncedLyrics: function(lrcText) {
            const lines = [];
            const regex = /\[(\d{2}):(\d{2})\.(\d{2,3})\](.*)/g;
            let match;

            while ((match = regex.exec(lrcText)) !== null) {
                const minutes = parseInt(match[1]);
                const seconds = parseInt(match[2]);
                const ms = parseInt(match[3].padEnd(3, '0'));
                const text = match[4].trim();

                const startTime = (minutes * 60 + seconds) * 1000 + ms;

                lines.push({ startTime, text });
            }

            // Calculate end times
            for (let i = 0; i < lines.length - 1; i++) {
                lines[i].endTime = lines[i + 1].startTime;
            }
            if (lines.length > 0) {
                lines[lines.length - 1].endTime = 
                    lines[lines.length - 1].startTime + 5000;
            }

            return {
                type: 'synced',
                provider: 'my-lrc-provider',
                lines
            };
        },

        parsePlainLyrics: function(text) {
            const lines = text.split('\n').map(line => ({
                text: line.trim()
            }));

            return {
                type: 'unsynced',
                provider: 'my-lrc-provider',
                lines
            };
        }
    };

    function register() {
        if (window.LyricsAddonManager) {
            window.LyricsAddonManager.register(addon);
        } else {
            setTimeout(register, 100);
        }
    }
    register();
})();

Settings

Get/set addon-specific settings:

// Get setting
const value = window.LyricsAddonManager.getAddonSetting(
    'my-lyrics-provider',
    'settingKey',
    'defaultValue'
);

// Set setting
window.LyricsAddonManager.setAddonSetting(
    'my-lyrics-provider',
    'settingKey',
    'newValue'
);

File Placement

Add your addon to manifest.json:

{
    "subfiles_extension": [
        "Addon_Lyrics_MyProvider.js"
    ]
}

Testing

  1. Create your addon file
  2. Add to manifest.json
  3. Run spicetify apply
  4. Open Settings > Lyrics Providers
  5. Enable your provider and adjust priority
  6. Play a song to test

Best Practices

  1. Search Accuracy: Use fuzzy matching for title/artist
  2. Fallback: Try multiple search strategies
  3. Error Handling: Return null gracefully on errors
  4. Performance: Cache results when possible
  5. Rate Limiting: Respect API limits

Provider Priority

Users can reorder providers. The manager tries providers in order until one returns lyrics:

User Priority: [Spotify, MyProvider, LRCLIB]

1. Try Spotify → No lyrics → Continue
2. Try MyProvider → Returns lyrics → Stop

Related

Clone this wiki locally