Skip to main content

Overview

SoundWave is a client-side web application that integrates multiple music sources into a unified player interface. The architecture is built around vanilla JavaScript with a modular file structure that separates concerns between different music sources and their implementations.

File Structure

The application consists of the following core files:
index.html
file
Primary Spotify integration page with embedded iframe playerLocation: /index.htmlPurpose: Provides Spotify search functionality using an external token server
index2.html
file
Spotify download link generator interfaceLocation: /index2.htmlPurpose: Generates direct download links from Spotify URLs with audio playback
index3.html
file
Alternative Spotify player implementationLocation: /index3.htmlPurpose: Lightweight Spotify search and playback interface
scripts.js & back.js
file
Core JavaScript modules for Invidious/YouTube integrationLocation: /scripts.js, /back.jsPurpose: Handle YouTube music search via Invidious instances with audio extraction
styles.css
file
Global stylesheet with dark theme designLocation: /styles.cssPurpose: Consistent Spotify-inspired styling across the application

Core Components

1. Spotify Integration Module

The Spotify integration is split across three HTML implementations, each serving different use cases:

Token Management

let accessToken = '';

// Obtener el token al cargar la página
async function getAccessToken() {
    const response = await fetch('https://jsnode-ab0e.onrender.com', { method: 'POST' });
    const data = await response.json();
    accessToken = data.access_token;
}
Reference: index.html:96-103
The token is fetched from an external server (jsnode-ab0e.onrender.com) on page load and stored in memory. This server acts as a proxy to manage Spotify API credentials.

Search Implementation

async function searchSongs() {
    const query = document.getElementById('search-query').value.trim();
    if (!query) {
        alert('Por favor, ingresa un término de búsqueda.');
        return;
    }

    const response = await fetch(`https://jsnode-ab0e.onrender.com/search?query=${encodeURIComponent(query)}&type=track&accessToken=${accessToken}`);
    const data = await response.json();

    const songList = document.getElementById('song-list');
    songList.innerHTML = '';

    data.tracks.items.forEach(track => {
        const li = document.createElement('li');
        li.textContent = `${track.name} - ${track.artists.map(artist => artist.name).join(', ')}`;
        li.dataset.embedUrl = `https://open.spotify.com/embed/track/${track.id}?utm_source=generator&theme=0`;
        li.addEventListener('click', () => playTrack(li.dataset.embedUrl));
        songList.appendChild(li);
    });
}
Reference: index.html:106-126

2. Invidious Integration Module

The Invidious integration provides YouTube music playback through privacy-focused frontends:

Suggestion System with Debouncing

function debounce(func, wait) {
    let timeout;
    return function (...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}

document.getElementById('song-name').addEventListener('input', debounce(function () {
    const songName = this.value;
    suggestionsContainer.innerHTML = '';

    if (songName) {
        originalSearchTerm = songName.toLowerCase();
        fetchSuggestions(songName);
    } else {
        clearSuggestionsAndResults();
    }
}, 300)); // 300 ms delay
Reference: scripts.js:19-40 and back.js:19-40
The debounce function prevents excessive API calls by waiting 300ms after the user stops typing before triggering a search.

Multi-Instance Fallback Strategy

function fetchSuggestions(songName) {
  const url1 = 'https://inv.nadeko.net/search?q=' + encodeURIComponent(songName);
  const url2 = 'https://yewtu.be/search?q=' + encodeURIComponent(songName);

  fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(url1))
    .then(response => {
        if (response.ok) return response.text();
        throw new Error('Network response was not ok.');
    })
    .then(data => {
        processSuggestionsData(data);
    })
    .catch(() => {
        fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(url2))
        .then(response => {
            if (response.ok) return response.text();
            throw new Error('Network response was not ok.');
        })
        .then(data => {
            processSuggestionsData(data);
        })
        .catch(error => {
            console.error('Error al obtener sugerencias:', error);
        });
    });
}
Reference: scripts.js:104-134
The application uses multiple Invidious instances (inv.nadeko.net, yewtu.be) with automatic failover. If one instance is down, it falls back to the next.

3. State Management

Global state variables track the current application state:
let selectedVideoId = null;
let selectedTitle = null;
let currentSuggestions = [];
let originalSearchTerm = '';
let currentGroupIndex = 0;
let groupedResults = [];
Reference: scripts.js:5-10

Data Flow

Spotify Search Flow

  1. User Input: User enters search query
  2. Token Retrieval: Access token fetched from external server
  3. API Request: Query sent to token server’s search endpoint
  4. Result Parsing: Track data extracted and formatted
  5. UI Update: Results rendered as clickable list items
  6. Playback: Selected track loads in Spotify embed iframe

Invidious Search Flow

Pagination System

The Invidious integration implements a multi-page search:
async function fetchSearchResults(songName) {
    const maxPages = 6;
    let allResults = [];

    for (let page = 1; page <= maxPages; page++) {
        const url = `https://yewtu.be/search?q=${encodeURIComponent(songName)}&page=${page}`;
        
        const response = await fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(url));
        const data = await response.text();
        
        // Parse and filter results
        allResults = allResults.concat(searchResults);
    }

    // Group results in blocks of 5
    groupedResults = [];
    for (let i = 0; i < allResults.length; i += 5) {
        groupedResults.push(allResults.slice(i, i + 5));
    }
}
Reference: scripts.js:181-239

Component Communication

Event-Driven Architecture

The application uses DOM events for component communication:
// Enable autoplay after user interaction
document.addEventListener('click', enableAutoplay, { once: true });

function enableAutoplay() {
    console.log('Interacción del usuario detectada. La reproducción automática está habilitada.');
}
Reference: scripts.js:12-17

DOM Manipulation Pattern

Results are rendered using DocumentFragment for performance:
function showItems(items, container, isSuggestion = false) {
    const fragment = document.createDocumentFragment();

    items.forEach(item => {
        const itemElement = document.createElement('div');
        itemElement.textContent = item.title;
        itemElement.classList.add('suggestion');
        itemElement.addEventListener('click', function () {
            // Handle click
        });
        fragment.appendChild(itemElement);
    });

    container.innerHTML = '';
    container.appendChild(fragment);
    container.style.display = 'block';
}
Reference: scripts.js:63-102
Using DocumentFragment minimizes DOM reflows by batching multiple element insertions into a single operation.

Error Handling Strategy

Network Resilience

The application implements multiple fallback mechanisms:
  1. Multiple Invidious Instances: Automatic failover between instances
  2. CORS Proxy: All external requests routed through api.allorigins.win
  3. Graceful Degradation: Error messages displayed without breaking functionality
fetch(allOriginsUrl)
    .then(response => {
        if (response.ok) return response.text();
        throw new Error('Network response was not ok.');
    })
    .catch(error => {
        console.error('Error fetching source code:', error);
        sourceLinksContainer.textContent = 'No se pudo cargar el código fuente.';
        findNextVideo(groupIndex, resultIndex);
    });
Reference: scripts.js:291-315

Performance Optimizations

1. Debounced Search Input

300ms debounce prevents excessive API calls during typing.

2. Result Pagination

Results are grouped in blocks of 5 to reduce initial rendering time.

3. Lazy Audio Loading

Audio sources are only fetched when a user selects a specific track.

4. DOM Fragment Usage

Batch DOM insertions minimize layout recalculations.

Security Considerations

CORS Bypass: The application uses api.allorigins.win as a CORS proxy. This introduces a third-party dependency that proxies all Invidious requests.
Token Exposure: Spotify access tokens are exposed in URL parameters when making search requests. Consider using headers for sensitive data.

Deployment Architecture

The application has a distributed architecture:
┌─────────────────┐
│   Client HTML   │
│   (Browser)     │
└────────┬────────┘

    ┌────┴────┬──────────────┬────────────┐
    │         │              │            │
┌───▼──────┐ ┌▼────────────┐ ┌▼──────────▼┐
│ Spotify  │ │ Token Server│ │  Invidious │
│  Embed   │ │  (Render)   │ │  Instances │
└──────────┘ └─────────────┘ └────────────┘

                              ┌─────▼──────┐
                              │ AllOrigins │
                              │    Proxy   │
                              └────────────┘
  • Client: Static HTML/JS/CSS files served directly
  • Token Server: Hosted on Render.com (jsnode-ab0e.onrender.com)
  • Invidious: Multiple public instances (inv.nadeko.net, yewtu.be)
  • CORS Proxy: api.allorigins.win for cross-origin requests

Extension Points

The modular architecture allows for easy extension:
  1. Additional Music Sources: Add new HTML pages following the existing pattern
  2. Alternative Invidious Instances: Update the URL list in fetchSuggestions
  3. Enhanced Filtering: Modify the filter logic in processSuggestionsData
  4. Custom Styling: Override styles in styles.css

Next Steps

API Integration

Learn about external API usage and integration patterns

Styling System

Explore the CSS architecture and theming system

Build docs developers (and LLMs) love