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
Search Input
User types a song name or artist, triggering debounced suggestions after 300ms
Fetch Results
The app queries Invidious instances to fetch video results
Filter & Display
Results are filtered based on search term relevance and displayed in groups
Extract Audio
When a track is selected, the app extracts audio source URLs from the video page
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.
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.
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.
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.
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:
Fetches the video watch page with &listen=1 parameter
Parses HTML to find <source> and <audio> elements
Extracts all audio URLs
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:
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:
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:
body {
font-family : Arial , sans-serif ;
background-color : #121212 ;
color : #ffffff ;
margin : 0 ;
padding : 20 px ;
}
h1 {
color : #1db954 ;
text-align : center ;
margin-bottom : 20 px ;
}
.suggestion {
cursor : pointer ;
padding : 10 px ;
background-color : #282828 ;
border-radius : 10 px ;
margin-bottom : 5 px ;
color : #ffffff ;
transition : background-color 0.3 s ;
}
.suggestion:hover {
background-color : #383838 ;
}
Responsive Design
@media ( min-width : 768 px ) {
#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:
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
scripts.js
back.js
styles.css
// 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