1/** 2 * Video Gallery Module 3 * 4 * Kullanım: 5 * <div class="video-gallery-container" 6 * data-playlist-id="PLxxxxxx" â YouTube Playlist ID 7 * data-api-key="YOUR_API_KEY" â YouTube Data API v3 Key 8 * ...> 9 * 10 * Ãoklu instance: Aynı sayfada birden fazla .video-gallery-container olabilir, 11 * her biri tamamen baÄımsız çalıÅır. 12 * 13 * Fallback: data-playlist-id yoksa HTML'deki statik .playlist-item öÄeleri okunur. 14 */ 15 16// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 17// 1. YouTube IFrame API'yi tek seferlik yükle 18// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 19(function () { 20 if (window._ytApiLoading) return; // Zaten yükleniyor, tekrar ekleme 21 window._ytApiLoading = true; 22 23 var tag = document.createElement('script'); 24 tag.src = 'https://www.youtube.com/iframe_api'; 25 document.head.appendChild(tag); 26})(); 27 28// Henüz hazır olmayan instance'ları tutacak kuyruk 29window._ytGalleryQueue = window._ytGalleryQueue || []; 30 31// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 32// 2. YouTube IFrame API hazır olduÄunda tüm instance'ları baÅlat 33// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 34window.onYouTubeIframeAPIReady = function () { 35 // Kuyruktaki instance'ları baÅlat 36 window._ytGalleryQueue.forEach(function (gallery) { 37 gallery._createPlayer(); 38 }); 39 window._ytGalleryQueue = []; 40 window._ytApiReady = true; 41}; 42 43// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 44// 3. VideoGallery Sınıfı 45// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 46function VideoGallery(containerEl) { 47 this.container = containerEl; 48 this.player = null; 49 this.videoList = []; 50 this.currentIndex = 0; 51 52 // Her instance için benzersiz ID oluÅtur 53 this.uid = 'ytg-' + Math.random().toString(36).substr(2, 8); 54 55 // Config'i data attribute'lardan oku 56 this.apiKey = containerEl.getAttribute('data-api-key') || ''; 57 this.playlistId = containerEl.getAttribute('data-playlist-id') || ''; 58 59 this.textConfig = { 60 playing : containerEl.getAttribute('data-text-playing') || '⺠Åu an Ãalıyor', 61 start : containerEl.getAttribute('data-text-start') || 'BaÅlatmak için tıklayın', 62 next : containerEl.getAttribute('data-text-next') || 'Sıradaki Video', 63 notFound : containerEl.getAttribute('data-text-not-found') || 'Oynatılacak video bulunamadı.', 64 select : containerEl.getAttribute('data-text-select') || 'Bir video seçin...' 65 }; 66 67 // Container'ın içindeki elemanlara referanslar 68 this.$container = $(containerEl); 69 this.$playerWrap = this.$container.find('.video-player-wrapper'); 70 this.$playlistWrap = this.$container.find('.playlist-wrapper'); 71 this.$playlistItems= this.$container.find('#playlist-items'); 72 this.$videoTitle = this.$container.find('#video-title'); 73 this.$count = this.$container.find('#playlist-count'); 74 75 // Player container'ına benzersiz ID ver (çakıÅma önleme)
76 this.playerContainerId = 'yt-player-' + this.uid; 77 this.$container.find('#yt-video-player-container') 78 .attr('id', this.playerContainerId); 79 80 // playlist-items ve diÄer ID'leri de benzersiz yap 81 var itemsId = 'playlist-items-' + this.uid; 82 this.$playlistItems.attr('id', itemsId); 83 this.$playlistItems = this.$container.find('#' + itemsId); 84} 85 86// âââ init ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 87VideoGallery.prototype.init = function () { 88 var self = this; 89 90 if (this.playlistId && this.apiKey) { 91 // Playlist ID varsa API'den çek 92 this._showLoading(); 93 this._fetchPlaylist(this.playlistId, null, function () { 94 self._hideLoading(); 95 self._setup(); 96 }); 97 } else { 98 // Statik DOM'dan oku (eski yöntem) 99 this._parseFromDOM(); 100 this._setup(); 101 } 102}; 103 104// âââ YouTube API'den playlist çek (pagination destekli) ââââââââââââââââââââââ 105VideoGallery.prototype._fetchPlaylist = function (playlistId, pageToken, callback) { 106 var self = this; 107 var url = 'https://www.googleapis.com/youtube/v3/playlistItems' 108 + '?part=snippet' 109 + '&maxResults=50' 110 + '&playlistId=' + encodeURIComponent(playlistId) 111 + '&key=' + encodeURIComponent(this.apiKey) 112 + (pageToken ? '&pageToken=' + pageToken : ''); 113 114 $.ajax({ 115 url: url, 116 type: 'GET', 117 success: function (data) { 118 if (data.items) { 119 data.items.forEach(function (item, i) { 120 var snippet = item.snippet; 121 var videoId = snippet.resourceId && snippet.resourceId.videoId; 122 if (!videoId) return; // SilinmiÅ/private video 123 124 // Thumbnail: mevcut en iyi kaliteyi seç 125 var thumb = (snippet.thumbnails.medium && snippet.thumbnails.medium.url) 126 || (snippet.thumbnails.default && snippet.thumbnails.default.url) 127 || 'https://img.youtube.com/vi/' + videoId + '/mqdefault.jpg'; 128 129 self.videoList.push({ 130 index : self.videoList.length, 131 title : snippet.title, 132 url : 'https://www.youtube.com/watch?v=' + videoId, 133 videoId : videoId, 134 thumb : thumb 135 }); 136 }); 137 } 138 139 // Daha fazla sayfa var mı? 140 if (data.nextPageToken) { 141 self._fetchPlaylist(playlistId, data.nextPageToken, callback); 142 } else { 143 self._buildPlaylistDOM(); 144 callback(); 145 } 146 }, 147 error: function (xhr) { 148 console.error('[VideoGallery] Playlist fetch hatası:', xhr.responseText); 149 self._hideLoading(); 150 self.$videoTitle.text('Playlist yüklenemedi. API Key veya Playlist ID\'yi kontrol edin.'); 151 } 152 }); 153}; 154 155// âââ Playlist DOM'unu oluÅtur (API'den gelen verilerle) ââââââââââââââââââââââ 156VideoGallery.prototype._buildPlaylistDOM = function () { 157 var self = this; 158 this.$playlistItems.empty(); 159 160 this.videoList.forEach(function (video) { 161 var $item = $( 162 '<a href="javascript:void(0)" class="list-group-item playlist-item">' + 163 '<div class="media">' + 164 '<div class="media-left">' + 165 '<img class="media-object" src="' + video.thumb + '" alt="Thumbnail" style="width:60px;border-radius:4px;">' + 166 '</div>' + 167 '<div class="media-body">' + 168 '<h5 class="media-heading">' + self._escHtml(video.title) + '</h5>' + 169 '<small>' + self.textConfig.next + '</small>' + 170 '</div>' + 171 '</div>' + 172 '</a>' 173 ); 174 $item.attr('data-index', video.index); 175 $item.attr('data-title', video.title); 176 $item.attr('data-url', video.url); 177 $item.attr('data-video-id', video.videoId); 178 self.$playlistItems.append($item); 179 }); 180}; 181 182// âââ Statik DOM'dan video listesini oku (fallback) âââââââââââââââââââââââââââ 183VideoGallery.prototype._parseFromDOM = function () { 184 var self = this; 185 this.videoList = []; 186 this.$playlistItems.find('.playlist-item').each(function (index) { 187 var $this = $(this); 188 var url = $this.data('url') || ''; 189 var videoId = self._getVideoId(url); 190 var thumb = $this.find('img').attr('src') 191 || 'https://img.youtube.com/vi/' + videoId + '/mqdefault.jpg'; 192 193 self.videoList.push({ 194 index : index, 195 title : $this.data('title') || '', 196 url : url, 197 videoId : videoId, 198 thumb : thumb, 199 element : $this 200 }); 201 $this.attr('data-index', index); 202 }); 203}; 204 205// âââ Player ve UI hazırla ââââââââââââââââââââââââââââââââââââââââââââââââââââ 206VideoGallery.prototype._setup = function () { 207 if (this.videoList.length === 0) { 208 this.$videoTitle.text(this.textConfig.notFound); 209 this.$playerWrap.hide(); 210 this.$playlistWrap.hide(); 211 return; 212 } 213 214 // Tek video modunda playlist panelini gizle 215 if (this.videoList.length <= 1) { 216 this.$playlistWrap.hide(); 217 this.$playerWrap.removeClass('col-md-8').addClass('col-md-12'); 218 } else { 219 this.$playlistWrap.show(); 220 this.$count.text(this.videoList.length); 221 } 222 223 this.$videoTitle.text(this.videoList[0].title); 224 this._updateActiveItem(0, false); 225 226 // Player oluÅtur 227 if (window._ytApiReady) { 228 this._createPlayer(); 229 } else { 230 // API henüz hazır deÄil, kuyruÄa ekle 231 window._ytGalleryQueue.push(this); 232 } 233 234 // Tıklama event'ı â event delegation, instance'a özel 235 this._bindEvents(); 236}; 237 238// âââ YT.Player oluÅtur âââââââââââââââââââââââââââââââââââââââââââââââââââââââ 239VideoGallery.prototype._createPlayer = function () { 240 var self = this; 241 var firstVideoId = this.videoList[0] && this.videoList[0].videoId; 242 243 setTimeout(function () { 244 try { 245 self.player = new YT.Player(self.playerContainerId, { 246 height : '100%', 247 width : '100%', 248 videoId : firstVideoId, 249 playerVars: { 250 autoplay : 0, 251 rel : 0, 252 modestbranding : 1, 253 controls : 1 254 }, 255 events: { 256 onReady : function () { console.log('[VideoGallery] Player hazır:', self.uid); }, 257 onStateChange: function (e) { self._onStateChange(e); }, 258 onError : function (e) { self._onPlayerError(e); } 259 } 260 }); 261 } catch (err) { 262 console.error('[VideoGallery] Player oluÅturulamadı:', err); 263 } 264 }, 100); 265}; 266 267// âââ Video yükle âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 268VideoGallery.prototype._loadVideo = function (index) { 269 if (index < 0 || index >= this.videoList.length) return; 270 271 this.currentIndex = index; 272 var video = this.videoList[index]; 273 274 if (this.player && typeof this.player.loadVideoById === 'function') { 275 this.player.loadVideoById(video.videoId); 276 } 277 278 this.$videoTitle.text(video.title); 279 this._updateActiveItem(index, true); 280}; 281 282// âââ Sıradaki videoya geç ââââââââââââââââââââââââââââââââââââââââââââââââââââ 283VideoGallery.prototype._onStateChange = function (event) { 284 if (event.data === YT.PlayerState.ENDED) { 285 var next = this.currentIndex + 1; 286 if (next < this.videoList.length) { 287 this._loadVideo(next); 288 } 289 } 290}; 291
292// âââ Hata yakalama â embed izni yoksa sıradakine geç ââââââââââââââââââââââââ 293VideoGallery.prototype._onPlayerError = function (event) { 294 // 101, 150 â video sahibi embedding'e izin vermiyor 295 // 100 â video silinmiÅ / private 296 // 5 â HTML5 player hatası 297 var skipCodes = [5, 100, 101, 150]; 298 if (skipCodes.indexOf(event.data) !== -1) { 299 console.warn('[VideoGallery] Video oynatılamadı (kod ' + event.data + '), sıradakine geçiliyor...'); 300 var next = this.currentIndex + 1; 301 var self = this; 302 if (next < this.videoList.length) { 303 this.$videoTitle.text('â Bu video oynatılamıyor, sıradakine geçiliyor...'); 304 setTimeout(function () { self._loadVideo(next); }, 1500); 305 } else { 306 this.$videoTitle.text('â ï¸ Bu video gömülü oynatmaya izin vermiyor.'); 307 } 308 } else { 309 console.error('[VideoGallery] Player hatası (kod: ' + event.data + ')'); 310 } 311}; 312 313// âââ Aktif playlist öÄesini güncelle âââââââââââââââââââââââââââââââââââââââââ 314VideoGallery.prototype._updateActiveItem = function (index, isPlaying) { 315 var self = this; 316 this.$playlistItems.find('.playlist-item').each(function () { 317 $(this).removeClass('active'); 318 $(this).find('.media-body small').text(self.textConfig.next); 319 }); 320 321 var video = this.videoList[index]; 322 var $el = video.element || this.$playlistItems.find('[data-index="' + index + '"]'); 323 324 if ($el && $el.length) { 325 $el.addClass('active'); 326 $el.find('.media-body small').text(isPlaying ? this.textConfig.playing : this.textConfig.start); 327 328 // Scroll 329 var container = this.$playlistItems; 330 var itemTop = $el.position() ? $el.position().top : 0; 331 var scrollTop = container.scrollTop(); 332 var containerTop = container.position() ? container.position().top : 0; 333 334 container.animate({ 335 scrollTop: itemTop + scrollTop - containerTop - (container.height() / 2) + 50 336 }, 400); 337 } 338}; 339 340// âââ Event binding (instance'a özel, event delegation) âââââââââââââââââââââââ 341VideoGallery.prototype._bindEvents = function () { 342 var self = this; 343 this.$playlistItems.on('click', '.playlist-item', function (e) { 344 e.preventDefault(); 345 var idx = parseInt($(this).attr('data-index'), 10); 346 self._loadVideo(idx); 347 }); 348}; 349 350// âââ Loading state ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 351VideoGallery.prototype._showLoading = function () { 352 this.$playlistItems.html( 353 '<div class="gallery-loading">' + 354 '<div class="gallery-spinner"></div>' + 355 '<span>Playlist yükleniyor...</span>' + 356 '</div>' 357 ); 358}; 359 360VideoGallery.prototype._hideLoading = function () { 361 this.$playlistItems.find('.gallery-loading').remove(); 362}; 363 364// âââ URL'den Video ID çıkar âââââââââââââââââââââââââââââââââââââââââââââââââââ 365VideoGallery.prototype._getVideoId = function (url) { 366 var match = (url || '').match( 367 /(?:youtu\.be\/|youtube\.com\/(?:.*[?&]v=|embed\/|v\/))([a-zA-Z0-9_-]{11})/ 368 ); 369 return (match && match[1]) ? match[1] : ''; 370}; 371 372// âââ HTML escape ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 373VideoGallery.prototype._escHtml = function (str) { 374 return String(str) 375 .replace(/&/g, '&') 376 .replace(/</g, '<') 377 .replace(/>/g, '>') 378 .replace(/"/g, '"'); 379}; 380 381// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 382// 4. Sayfa yüklendiÄinde tüm container'ları baÅlat 383// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 384$(document).ready(function () { 385 $('.video-gallery-container').each(function () { 386 var gallery = new VideoGallery(this); 387 gallery.init(); 388 }); 389});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.