@@ -3,6 +3,7 @@ import { stations, categories, moods, moodIcons, searchStations, getStationsByCa
33import audioManager from './services/AudioManager.js' ;
44import youtubeManager from './services/YouTubeManager.js' ;
55import historyService from './services/HistoryService.js' ;
6+ import youtubeSearchService from './services/YouTubeSearchService.js' ;
67import { createIconElement } from './utils/icons.js' ;
78import consentService from './services/ConsentService.js' ;
89import { LegalPages } from './pages/LegalPages.js' ;
@@ -87,6 +88,8 @@ function init() {
8788
8889 renderHistory ( ) ;
8990
91+ youtubeSearchService . init ( ) ;
92+
9093 router . register ( '/' , ( ) => switchMode ( 'radio' ) ) ;
9194 router . register ( '/youtube' , ( ) => switchMode ( 'youtube' ) ) ;
9295 router . register ( '/privacy' , ( ) => {
@@ -416,11 +419,20 @@ function setupEventListeners() {
416419 if ( e . key === 'Enter' ) playYouTubeVideo ( ) ;
417420 } ) ;
418421
422+ document . getElementById ( 'youtube-search-btn' ) . addEventListener ( 'click' , searchYouTubeVideos ) ;
423+ document . getElementById ( 'youtube-search' ) . addEventListener ( 'keypress' , ( e ) => {
424+ if ( e . key === 'Enter' ) searchYouTubeVideos ( ) ;
425+ } ) ;
426+
419427 document . getElementById ( 'clear-history-btn' ) . addEventListener ( 'click' , ( ) => {
420428 historyService . clearHistory ( ) ;
421429 renderHistory ( ) ;
422430 } ) ;
423431
432+ document . getElementById ( 'clear-search-results-btn' ) . addEventListener ( 'click' , ( ) => {
433+ clearSearchResults ( ) ;
434+ } ) ;
435+
424436 const enableYoutubeBtn = document . getElementById ( 'enable-youtube-btn' ) ;
425437 if ( enableYoutubeBtn ) {
426438 enableYoutubeBtn . addEventListener ( 'click' , ( ) => {
@@ -453,6 +465,11 @@ function switchMode(mode) {
453465 youtubeSearchContainer . style . display = mode === 'youtube' ? 'block' : 'none' ;
454466 }
455467
468+ const searchResultsContainer = document . getElementById ( 'youtube-search-results' ) ;
469+ if ( searchResultsContainer ) {
470+ searchResultsContainer . style . display = mode === 'youtube' ? 'none' : 'none' ;
471+ }
472+
456473 if ( mode === 'youtube' ) {
457474 audioManager . pause ( ) ;
458475 if ( ! consentService . hasConsent ( 'youtube' ) ) {
@@ -667,6 +684,106 @@ function playYouTubeVideo() {
667684 updatePlaybackUrl ( 'v' , videoId ) ;
668685}
669686
687+ async function searchYouTubeVideos ( ) {
688+ const searchInput = document . getElementById ( 'youtube-search' ) ;
689+ const query = searchInput . value . trim ( ) ;
690+
691+ if ( ! query ) {
692+ clearSearchResults ( ) ;
693+ return ;
694+ }
695+
696+ const searchBtn = document . getElementById ( 'youtube-search-btn' ) ;
697+ const resultsContainer = document . getElementById ( 'youtube-search-results' ) ;
698+
699+ try {
700+ if ( ! youtubeSearchService . isReady ( ) ) {
701+ showSearchError ( 'YouTube search not initialized. Please refresh the page.' ) ;
702+ return ;
703+ }
704+
705+ searchBtn . disabled = true ;
706+ searchBtn . textContent = 'Searching...' ;
707+
708+ const results = await youtubeSearchService . search ( query , 12 ) ;
709+
710+ if ( results . length === 0 ) {
711+ showSearchError ( 'No results found' ) ;
712+ } else {
713+ renderSearchResults ( results ) ;
714+ }
715+ } catch ( error ) {
716+ console . error ( 'Search error:' , error ) ;
717+ showSearchError ( error . message || 'Search failed. Please try again.' ) ;
718+ } finally {
719+ searchBtn . disabled = false ;
720+ searchBtn . textContent = 'Search' ;
721+ }
722+ }
723+
724+ function renderSearchResults ( results ) {
725+ const container = document . getElementById ( 'search-results-grid' ) ;
726+ const resultsContainer = document . getElementById ( 'youtube-search-results' ) ;
727+
728+ container . innerHTML = '' ;
729+ resultsContainer . style . display = 'block' ;
730+
731+ results . forEach ( video => {
732+ const card = document . createElement ( 'div' ) ;
733+ card . className = 'search-result-card' ;
734+
735+ card . innerHTML = `
736+ <div class="search-result-thumbnail">
737+ <img src="${ video . thumbnail } " alt="${ video . title } " />
738+ <div class="search-result-overlay">
739+ <div class="play-overlay-icon"></div>
740+ </div>
741+ </div>
742+ <div class="search-result-info">
743+ <div class="search-result-title">${ video . title } </div>
744+ <div class="search-result-channel">${ video . channel } </div>
745+ ${ video . duration ? `<div class="search-result-duration">${ video . duration } </div>` : '' }
746+ </div>
747+ ` ;
748+
749+ const playOverlayIcon = card . querySelector ( '.play-overlay-icon' ) ;
750+ playOverlayIcon . appendChild ( createIconElement ( 'play' , 32 ) ) ;
751+
752+ card . addEventListener ( 'click' , ( ) => {
753+ playYouTubeVideoById ( video . videoId , video . title ) ;
754+ } ) ;
755+
756+ container . appendChild ( card ) ;
757+ } ) ;
758+ }
759+
760+ function playYouTubeVideoById ( videoId , title ) {
761+ clearSearchResults ( ) ;
762+ document . getElementById ( 'youtube-url' ) . value = `https://www.youtube.com/watch?v=${ videoId } ` ;
763+ playYouTubeVideo ( ) ;
764+ }
765+
766+ function clearSearchResults ( ) {
767+ const container = document . getElementById ( 'search-results-grid' ) ;
768+ const resultsContainer = document . getElementById ( 'youtube-search-results' ) ;
769+
770+ container . innerHTML = '' ;
771+ resultsContainer . style . display = 'none' ;
772+ document . getElementById ( 'youtube-search' ) . value = '' ;
773+ }
774+
775+ function showSearchError ( message ) {
776+ const container = document . getElementById ( 'search-results-grid' ) ;
777+ const resultsContainer = document . getElementById ( 'youtube-search-results' ) ;
778+
779+ container . innerHTML = `
780+ <div class="search-error-message">
781+ <p>${ message } </p>
782+ </div>
783+ ` ;
784+ resultsContainer . style . display = 'block' ;
785+ }
786+
670787function togglePlayback ( ) {
671788 if ( currentMode === 'radio' ) {
672789 if ( audioManager . isPlaying ) {
0 commit comments