Skip to main content

Overview

The YouTube Player provides an alternative music streaming solution using YouTube/Invidious as the backend. It features real-time search suggestions, intelligent filtering, paginated results, and direct audio streaming without the need for YouTube accounts.
This player uses Invidious instances (privacy-friendly YouTube frontend) to fetch content and avoid YouTube API restrictions.

Key Features

Live Suggestions

Get real-time search suggestions as you type with 300ms debouncing

Smart Filtering

Automatically filters out official videos and irrelevant content

Paginated Search

Search across 6 pages with results grouped in sets of 5

Direct Audio

Stream audio directly without YouTube’s web player

How It Works

1

Search Input

User types a song name or artist, triggering debounced suggestions after 300ms
2

Fetch Results

The app queries Invidious instances to fetch video results
3

Filter & Display

Results are filtered based on search term relevance and displayed in groups
4

Extract Audio

When a track is selected, the app extracts audio source URLs from the video page
5

Stream

The first accessible audio link is loaded into the HTML5 audio player

Core Functions

fetchSuggestions()

Fetches and displays search suggestions as the user types, with fallback URLs for reliability.
scripts.js
function fetchSuggestions(songName) {
  const url1 = 'https://inv.nadeko.net/search?q=' + encodeURIComponent(songName);
  const url2 = 'https://yewtu.be/search?q=' + encodeURIComponent(songName);

  // Intentar con la primera URL
  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(() => {
        // Si la primera URL falla, intentar con la segunda
        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);
            suggestionsContainer.innerHTML = '<div class="suggestion">Error al cargar las sugerencias.</div>';
        });
    });
}
Features:
  • Dual URL fallback for reliability
  • Uses allorigins.win as a CORS proxy
  • Error handling with user feedback

processSuggestionsData()

Parses the HTML response and filters suggestions based on search term relevance.
scripts.js
function processSuggestionsData(data) {
    const parser = new DOMParser();
    const doc = parser.parseFromString(data, 'text/html');
    const divs = doc.querySelectorAll('div.video-card-row');

    const newSuggestions = Array.from(divs)
        .map(div => {
            const link = div.querySelector('a');
            const titleElement = link ? link.querySelector('p') : null;
            const title = titleElement ? titleElement.innerText : 'Sin título';
            const videoId = link ? link.getAttribute('href').split('v=')[1] : null;
            const lengthElement = div.querySelector('.length');
            const length = lengthElement ? lengthElement.innerText : 'Desconocida';
            return { title, videoId, length };
        })
        .filter(suggestion => {
            const searchTerm = originalSearchTerm.toLowerCase();
            const songTitle = suggestion.title.toLowerCase();

            const includesSearchTerm = (
                songTitle.includes(searchTerm) || 
                searchTerm.split(' ').every(term => songTitle.includes(term))
            );

            const excludesOfficial = !songTitle.includes('oficial') && !songTitle.includes('official');

            return includesSearchTerm && excludesOfficial;
        });

    // Limitar a 5 sugerencias
    const limitedSuggestions = newSuggestions.slice(0, 5);

    if (JSON.stringify(limitedSuggestions) !== JSON.stringify(currentSuggestions)) {
        currentSuggestions = limitedSuggestions;
        showItems(currentSuggestions, suggestionsContainer, true);
    }
}
Filtering Logic:
  • Checks if the title includes the search term (partial match)
  • Verifies all search words are present
  • Excludes videos with “official” or “oficial” in the title
  • Limits results to 5 suggestions
The filter excludes official videos to prioritize audio-only or music-focused uploads that stream more reliably.

fetchSearchResults()

Searches across multiple pages to gather comprehensive results.
scripts.js
async function fetchSearchResults(songName) {
    const maxPages = 6; // Número máximo de páginas a buscar
    let allResults = [];

    for (let page = 1; page <= maxPages; page++) {
        const url = `https://yewtu.be/search?q=${encodeURIComponent(songName)}&page=${page}`;

        try {
            const response = await fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(url));
            if (!response.ok) throw new Error('Network response was not ok.');

            const data = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(data, 'text/html');
            const divs = doc.querySelectorAll('div.video-card-row');

            const searchResults = Array.from(divs)
                .map(div => {
                    const link = div.querySelector('a');
                    const titleElement = link ? link.querySelector('p') : null;
                    const title = titleElement ? titleElement.innerText : 'Sin título';
                    const videoId = link ? link.getAttribute('href').split('v=')[1] : null;
                    return { title, videoId };
                })
                .filter(result => {
                    const searchTerm = originalSearchTerm.toLowerCase();
                    const songTitle = result.title.toLowerCase();

                    const includesSearchTerm = (
                        songTitle.includes(searchTerm) ||
                        searchTerm.split(' ').every(term => songTitle.includes(term))
                    );

                    const excludesOfficial = !songTitle.includes('oficial') && !songTitle.includes('official');

                    return includesSearchTerm && excludesOfficial;
                });

            allResults = allResults.concat(searchResults);
        } catch (error) {
            console.error('There was a problem with the fetch operation:', error);
        }
    }

    // Agrupar resultados en bloques de 5
    groupedResults = [];
    for (let i = 0; i < allResults.length; i += 5) {
        groupedResults.push(allResults.slice(i, i + 5));
    }

    currentGroupIndex = 0;
    showGroupedResults();
}
Key Features:
  • Searches 6 pages asynchronously
  • Applies same filtering logic as suggestions
  • Groups results in batches of 5
  • Handles network errors gracefully

fetchSourceCode()

Extracts audio source URLs from the YouTube/Invidious video page.
scripts.js
function fetchSourceCode(videoId, groupIndex = currentGroupIndex, resultIndex = 0) {
    const watchUrl = `https://yewtu.be/watch?v=${videoId}&listen=1`;
    const allOriginsUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(watchUrl)}`;

    fetch(allOriginsUrl)
        .then(response => {
            if (response.ok) return response.text();
            throw new Error('Network response was not ok.');
        })
        .then(data => {
            const parser = new DOMParser();
            const doc = parser.parseFromString(data, 'text/html');
            const sourceElements = doc.querySelectorAll('source[src]');
            const audioElements = doc.querySelectorAll('audio[src]');

            // Obtener todos los enlaces de audio
            const sourceLinks = Array.from(sourceElements).map(source => source.src);
            const audioLinks = Array.from(audioElements).map(audio => audio.src);
            const allLinks = [...sourceLinks, ...audioLinks];

            // Verificar la accesibilidad de los enlaces
            checkAccessibleLinks(allLinks, videoId, groupIndex, resultIndex);
        })
        .catch(error => {
            console.error('Error fetching source code:', error);
            sourceLinksContainer.textContent = 'No se pudo cargar el código fuente.';
            findNextVideo(groupIndex, resultIndex);
        });
}
Parameters:
  • videoId: YouTube video ID
  • groupIndex: Current result group index
  • resultIndex: Index within the group
Process:
  1. Fetches the video watch page with &listen=1 parameter
  2. Parses HTML to find <source> and <audio> elements
  3. Extracts all audio URLs
  4. Validates URL accessibility
The &listen=1 parameter tells Invidious to return an audio-only version of the page, optimizing for music playback.

Debouncing

The search input uses debouncing to prevent excessive API calls:
scripts.js
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
Benefits:
  • Reduces API calls by 90%+
  • Improves performance
  • Prevents rate limiting
  • Better user experience

Result Navigation

Results are displayed in groups with navigation controls:
scripts.js
function showGroupedResults() {
    filteredContent.innerHTML = '';

    const currentGroup = groupedResults[currentGroupIndex];
    showItems(currentGroup, filteredContent);

    const groupInfo = document.createElement('div');
    groupInfo.classList.add('group-info');
    groupInfo.textContent = `Grupo ${currentGroupIndex + 1} de ${groupedResults.length}`;
    filteredContent.appendChild(groupInfo);

    if (groupedResults.length > 1) {
        const separator = document.createElement('div');
        separator.classList.add('group-separator');

        if (currentGroupIndex > 0) {
            const prevButton = document.createElement('button');
            prevButton.textContent = '<';
            prevButton.addEventListener('click', function () {
                currentGroupIndex--;
                showGroupedResults();
            });
            separator.appendChild(prevButton);
        }

        if (currentGroupIndex < groupedResults.length - 1) {
            const nextButton = document.createElement('button');
            nextButton.textContent = '>';
            nextButton.addEventListener('click', function () {
                currentGroupIndex++;
                showGroupedResults();
            });
            separator.appendChild(nextButton);
        }

        filteredContent.appendChild(separator);
    }
}

UI Styling

The player features a Spotify-inspired dark theme:
styles.css
body {
    font-family: Arial, sans-serif;
    background-color: #121212;
    color: #ffffff;
    margin: 0;
    padding: 20px;
}

h1 {
    color: #1db954;
    text-align: center;
    margin-bottom: 20px;
}

.suggestion {
    cursor: pointer;
    padding: 10px;
    background-color: #282828;
    border-radius: 10px;
    margin-bottom: 5px;
    color: #ffffff;
    transition: background-color 0.3s;
}

.suggestion:hover {
    background-color: #383838;
}

Responsive Design

styles.css
@media (min-width: 768px) {
    #song-name, #search-button {
        width: 50%;
        margin-left: 25%;
    }

    #filtered-content, #source-links {
        width: 70%;
        margin-left: 15%;
    }
}

Autoplay Management

Modern browsers restrict autoplay. The player handles this with user interaction detection:
scripts.js
document.addEventListener('click', enableAutoplay, { once: true });

function enableAutoplay() {
    console.log('Interacción del usuario detectada. La reproducción automática está habilitada.');
}
The first click anywhere on the page enables autoplay. This is required by browser autoplay policies.

Implementation Files

// Main YouTube player logic
// Contains all search and playback functions
// File location: ~/workspace/source/scripts.js

Invidious Instances

The player uses multiple Invidious instances for reliability:
  • inv.nadeko.net: Primary instance for suggestions
  • yewtu.be: Fallback instance and primary for search/playback
These are privacy-friendly YouTube frontends that don’t require API keys or user accounts.

CORS Proxy

All external requests use api.allorigins.win as a CORS proxy:
fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(targetUrl))
The CORS proxy is a third-party service. Consider hosting your own proxy for production use to ensure reliability and control.

Best Practices

Debounce Input

Always use debouncing on search inputs to prevent API spam

Fallback URLs

Implement fallback Invidious instances for reliability

Filter Results

Filter out irrelevant content to improve user experience

Handle Errors

Gracefully handle network errors and provide fallback options

Advantages Over Spotify Player

No Account Required

Users don’t need a Spotify or YouTube account to listen

Wider Catalog

Access to all YouTube content, not just Spotify’s catalog

No Playback Limits

No 30-second preview restrictions

Privacy-Focused

Uses Invidious for privacy-friendly streaming

Limitations

Instance Reliability: Invidious instances can go offline. Always implement fallbacks.
CORS Proxy Dependency: The app relies on a third-party CORS proxy which could become unavailable.
Audio Quality: Audio quality depends on available formats from YouTube/Invidious.

Next Steps

Spotify Player

Compare with the Spotify-based player implementation

Download Generator

Learn how to generate downloadable music links

Build docs developers (and LLMs) love