/** * يمن فيديو - مشغل الريلز المحسن v3.0 * نظام مقسم إلى وحدات للسهولة في الصيانة والتطوير * @author riyadhali * @version 3.0.0 * @created 2025-07-21 */ class YemenVideoShortsPlayer { constructor() { // ===== 1. CORE CONFIGURATION ===== this.config = { version: '3.0.0', autoplay: true, muted: true, preloadNext: true, touchGestures: true, keyboardShortcuts: true }; // ===== 2. STATE MANAGEMENT ===== this.state = { videos: [], currentIndex: 0, isPlaying: false, isMuted: true, isFullscreen: false, isLoading: false, hasMore: true, isSubscriber: false, context: { type: 'general', id: null, title: 'ريلز يمن فيديو' } }; // ===== 3. MODULES INITIALIZATION ===== this.utils = new UtilsModule(this); this.api = new APIModule(this); this.video = new VideoModule(this); this.ui = new UIModule(this); this.filters = new FiltersModule(this); this.touch = new TouchModule(this); // ===== 4. PLAYERS REGISTRY ===== this.players = { youtube: {}, facebook: {}, twitter: {}, tiktok: {}, native: {} }; this.activePlayer = null; this.activeVideoId = null; // ===== 5. INITIALIZATION ===== this.init(); } /** * ===== CORE INITIALIZATION ===== */ async init() { try { console.log('🚀 تهيئة مشغل الريلز v3.0...'); await this.loadInitialData(); this.setupUI(); this.setupEventListeners(); this.filters.setup(); // تأكد من تهيئة YouTube API this.video.setupYouTubeAPI(); if (this.state.videos.length > 0) { await this.video.renderVideos(); await this.video.playCurrentVideo(); } else { await this.loadContextVideos(); } console.log('✅ تم تهيئة المشغل بنجاح'); } catch (error) { console.error('❌ خطأ في التهيئة:', error); this.ui.showError('فشل في تحميل المشغل'); } finally { this.ui.hideLoadingScreen(); } } async loadInitialData() { const dataElement = document.getElementById('initial-data'); if (!dataElement) throw new Error('لم يتم العثور على البيانات الأولية'); try { const data = JSON.parse(dataElement.textContent); this.state.videos = data.videos || []; this.state.currentIndex = data.currentVideoIndex || 0; this.state.isSubscriber = data.isSubscriber || false; this.state.context = data.context || this.state.context; this.filters.loadInitialFilters(data.initialFilters || {}); this.api.setupEndpoints(data.endpoints || {}); if (this.state.videos[this.state.currentIndex]) { this.activeVideoId = this.state.videos[this.state.currentIndex].id; } console.log(`📊 تم تحميل ${this.state.videos.length} فيديو`); } catch (error) { console.error('❌ خطأ في تحليل البيانات:', error); throw error; } } async loadContextVideos() { try { this.state.isLoading = true; this.ui.showLoading(); const data = await this.api.fetchVideos({ limit: 10, offset: 0, ...this.filters.current }); if (data.success && data.videos?.length > 0) { this.state.videos = data.videos; this.state.currentIndex = 0; this.state.hasMore = data.has_more !== false; this.activeVideoId = data.videos[0].id; await this.video.renderVideos(); await this.video.playCurrentVideo(); } else { this.ui.showError('لا توجد فيديوهات للعرض'); } } catch (error) { console.error('❌ خطأ في تحميل الفيديوهات:', error); this.ui.showError('فشل في تحميل الفيديوهات'); } finally { this.state.isLoading = false; this.ui.hideLoading(); } } setupUI() { this.ui.setupContainers(); this.ui.setupControlButtons(); this.ui.setupPanels(); } setupEventListeners() { if (this.config.keyboardShortcuts) { document.addEventListener('keydown', this.handleKeyPress.bind(this)); } if (this.config.touchGestures) { this.touch.setup(); } window.addEventListener('resize', this.utils.debounce(this.handleResize.bind(this), 250)); document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); window.addEventListener('popstate', this.handlePopState.bind(this)); document.addEventListener('fullscreenchange', this.handleFullscreenChange.bind(this)); } // ===== EVENT HANDLERS ===== handleKeyPress(event) { if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') return; const keyActions = { 'ArrowUp': () => this.video.previousVideo(), 'ArrowDown': () => this.video.nextVideo(), ' ': () => this.video.togglePlayPause(), 'k': () => this.video.togglePlayPause(), 'm': () => this.video.toggleMute(), 'f': () => this.ui.toggleFullscreen(), 's': () => this.shareCurrentVideo(), 'Escape': () => this.ui.closeAllPanels() }; const action = keyActions[event.key]; if (action) { event.preventDefault(); action(); } } handleVisibilityChange() { if (document.hidden && this.state.isPlaying) { this.video.pauseVideo(); } else if (!document.hidden && this.config.autoplay) { this.video.playVideo(); } } handleResize() { Object.values(this.players.youtube).forEach(player => { if (player?.getIframe) { const iframe = player.getIframe(); if (iframe) { iframe.style.width = '100%'; iframe.style.height = '100%'; } } }); } handlePopState(e) { if (e.state?.videoIndex !== undefined) { this.video.goToVideo(e.state.videoIndex, false); } } handleFullscreenChange() { this.state.isFullscreen = !!document.fullscreenElement; this.ui.updateFullscreenButton(); } // ===== PUBLIC METHODS ===== async nextVideo() { await this.video.nextVideo(); } async previousVideo() { await this.video.previousVideo(); } togglePlayPause() { this.video.togglePlayPause(); } shareCurrentVideo() { if (this.activeVideoId) { this.utils.shareVideo(this.activeVideoId); } } showVideoInfo(videoId) { this.ui.showVideoInfo(videoId); } // ===== CLEANUP ===== destroy() { console.log('🧹 تنظيف موارد المشغل...'); this.video.stopAllVideos(); this.touch.cleanup(); this.ui.cleanup(); this.utils.saveUserPreferences(); document.removeEventListener('keydown', this.handleKeyPress); document.removeEventListener('visibilitychange', this.handleVisibilityChange); window.removeEventListener('resize', this.handleResize); Object.values(this.players.youtube).forEach(player => { if (player?.destroy) { try { player.destroy(); } catch (e) { console.warn(e); } } }); if (window.yemenShortsPlayer === this) { window.yemenShortsPlayer = null; } console.log('✅ تم تنظيف الموارد'); } } /** * ===== VIDEO MODULE ===== * إدارة تشغيل وعرض الفيديوهات */ class VideoModule { constructor(player) { this.player = player; this.progressInterval = null; this.setupYouTubeAPI(); // أضف هذا } async renderVideos() { if (!this.player.state.videos.length) { this.player.ui.showError('لا توجد فيديوهات للعرض'); return; } const container = document.getElementById('videos-container'); container.innerHTML = ''; this.player.state.videos.forEach((video, index) => { const slide = this.createVideoSlide(video, index); container.appendChild(slide); }); this.setActiveVideo(this.player.state.currentIndex); } createVideoSlide(video, index) { const slide = document.createElement('div'); slide.className = 'video-slide'; slide.dataset.videoId = video.id; slide.dataset.videoIndex = index; if (index === this.player.state.currentIndex) { slide.classList.add('active'); } const videoTitle = video.processed_title || video.title || ''; const channelName = video.channel_title || ''; const channelAvatar = video.channel_avatar ? `` : ''; const videoPageUrl = video.videoPageUrl || `/video/${video.slug || video.id}`; slide.innerHTML = `
${this.createVideoElement(video)}
${video.source_name || 'YouTube'}
${this.player.utils.escapeHtml(videoTitle)}
${channelAvatar} ${this.player.utils.escapeHtml(channelName)}
${video.view_count_formatted || ''}
${video.time_ago || ''}
عرض كامل
`; return slide; } createVideoElement(video) { const platform = (video.source_name || 'youtube').toLowerCase(); switch (platform) { case 'youtube': return `
`; case 'facebook': return ``; case 'twitter': case 'x': return `

جارٍ التحميل...

`; case 'tiktok': return ``; default: return ``; } } setupYouTubeAPI() { window.onYouTubeIframeAPIReady = () => { console.log('✅ YouTube API جاهز'); }; if (!window.YT) { const script = document.createElement('script'); script.src = 'https://www.youtube.com/iframe_api'; script.async = true; document.head.appendChild(script); } } async playCurrentVideo() { const currentVideo = this.player.state.videos[this.player.state.currentIndex]; if (!currentVideo) return; try { await this.stopAllVideos(); this.player.activeVideoId = currentVideo.id; this.setActiveVideo(this.player.state.currentIndex); const platform = (currentVideo.source_name || 'youtube').toLowerCase(); switch (platform) { case 'youtube': await this.playYouTubeVideo(currentVideo); break; case 'facebook': await this.playFacebookVideo(currentVideo); break; case 'twitter': case 'x': await this.playTwitterVideo(currentVideo); break; case 'tiktok': await this.playTikTokVideo(currentVideo); break; default: await this.playNativeVideo(currentVideo); } this.updateNavigationButtons(); this.player.utils.updateBrowserHistory(currentVideo); } catch (error) { console.error('❌ خطأ في التشغيل:', error); this.showVideoError(currentVideo.id); } } async playYouTubeVideo(video) { return new Promise((resolve, reject) => { const playerId = `youtube-player-${video.id}`; if (this.player.players.youtube[video.id]) { this.player.activePlayer = this.player.players.youtube[video.id]; this.player.activePlayer.playVideo(); resolve(); return; } if (!window.YT?.Player) { setTimeout(() => this.playYouTubeVideo(video), 100); return; } try { this.player.players.youtube[video.id] = new YT.Player(playerId, { height: '100%', width: '100%', videoId: video.external_id || video.video_id, playerVars: { autoplay: this.player.config.autoplay ? 1 : 0, controls: 0, rel: 0, loop: 1, playlist: video.external_id || video.video_id, mute: this.player.state.isMuted ? 1 : 0, playsinline: 1, modestbranding: 1, fs: 0, disablekb: 1 }, events: { onReady: (event) => { this.player.activePlayer = event.target; if (this.player.config.autoplay) { event.target.playVideo(); } resolve(); }, onStateChange: (event) => this.onYouTubeStateChange(event, video.id), onError: (event) => { console.error('YouTube Error:', event.data); this.showVideoError(video.id); reject(new Error(`YouTube Error: ${event.data}`)); } } }); } catch (error) { reject(error); } }); } onYouTubeStateChange(event, videoId) { const playPauseIcon = document.getElementById(`play-pause-${videoId}`); const progressBar = document.getElementById(`progress-${videoId}`); switch (event.data) { case YT.PlayerState.PLAYING: this.player.state.isPlaying = true; if (playPauseIcon) { playPauseIcon.innerHTML = ''; playPauseIcon.classList.remove('visible'); } this.startProgressTracking(event.target, progressBar); break; case YT.PlayerState.PAUSED: this.player.state.isPlaying = false; if (playPauseIcon) { playPauseIcon.innerHTML = ''; playPauseIcon.classList.add('visible'); } this.stopProgressTracking(); break; case YT.PlayerState.ENDED: this.player.state.isPlaying = false; if (this.player.config.autoplay) { setTimeout(() => this.nextVideo(), 1000); } break; } } async playFacebookVideo(video) { const iframe = document.getElementById(`facebook-player-${video.id}`); if (iframe) { iframe.contentWindow.postMessage('play', '*'); } } async playTwitterVideo(video) { const container = document.getElementById(`twitter-player-${video.id}`); if (!container) return; try { container.innerHTML = ''; if (video.external_id) { if (!window.twttr) { await this.loadTwitterScript(); } if (window.twttr?.widgets) { await window.twttr.widgets.createTweet(video.external_id, container, { theme: 'dark', width: 'auto', align: 'center', conversation: 'none' }); } } else { throw new Error('لا يوجد معرف صحيح'); } } catch (error) { container.innerHTML = `

غير متاح للعرض

عرض في تويتر
`; } } loadTwitterScript() { return new Promise((resolve, reject) => { if (window.twttr) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://platform.twitter.com/widgets.js'; script.async = true; script.onload = () => { const checkTwttr = () => { if (window.twttr?.widgets) { resolve(); } else { setTimeout(checkTwttr, 100); } }; checkTwttr(); }; script.onerror = () => reject(new Error('فشل في تحميل Twitter SDK')); document.head.appendChild(script); }); } async playTikTokVideo(video) { // TikTok video handling } async playNativeVideo(video) { const videoElement = document.getElementById(`native-player-${video.id}`); if (videoElement) { try { this.player.activePlayer = videoElement; videoElement.muted = this.player.state.isMuted; await videoElement.play(); this.player.state.isPlaying = true; } catch (error) { this.showVideoError(video.id); } } } async stopAllVideos() { Object.values(this.player.players.youtube).forEach(player => { if (player?.pauseVideo) { player.pauseVideo(); } }); document.querySelectorAll('video').forEach(video => { video.pause(); }); this.player.state.isPlaying = false; this.stopProgressTracking(); } startProgressTracking(player, progressBar) { this.stopProgressTracking(); this.progressInterval = setInterval(() => { if (player && progressBar && player.getCurrentTime) { const currentTime = player.getCurrentTime(); const duration = player.getDuration(); if (duration > 0) { const progress = (currentTime / duration) * 100; progressBar.style.width = `${Math.min(progress, 100)}%`; progressBar.classList.add('active'); } } }, 1000); } stopProgressTracking() { if (this.progressInterval) { clearInterval(this.progressInterval); this.progressInterval = null; } } async nextVideo() { if (this.player.state.currentIndex < this.player.state.videos.length - 1) { this.player.state.currentIndex++; await this.playCurrentVideo(); if (this.player.state.currentIndex >= this.player.state.videos.length - 3 && this.player.state.hasMore && !this.player.state.isLoading) { await this.loadMoreVideos(); } } else if (this.player.state.hasMore) { await this.loadMoreVideos(); } else { this.player.ui.showToast('لا توجد فيديوهات أخرى', 'info'); } } async previousVideo() { if (this.player.state.currentIndex > 0) { this.player.state.currentIndex--; await this.playCurrentVideo(); } else { this.player.ui.showToast('هذا أول فيديو', 'info'); } } async goToVideo(index, updateHistory = true) { if (index >= 0 && index < this.player.state.videos.length) { this.player.state.currentIndex = index; await this.playCurrentVideo(); } } togglePlayPause() { if (this.player.activePlayer) { if (this.player.state.isPlaying) { this.pauseVideo(); } else { this.playVideo(); } } } playVideo() { if (this.player.activePlayer) { if (this.player.activePlayer.playVideo) { this.player.activePlayer.playVideo(); } else if (this.player.activePlayer.play) { this.player.activePlayer.play(); } } } pauseVideo() { if (this.player.activePlayer) { if (this.player.activePlayer.pauseVideo) { this.player.activePlayer.pauseVideo(); } else if (this.player.activePlayer.pause) { this.player.activePlayer.pause(); } } } toggleMute() { this.player.state.isMuted = !this.player.state.isMuted; if (this.player.activePlayer) { if (this.player.activePlayer.mute && this.player.activePlayer.unMute) { this.player.state.isMuted ? this.player.activePlayer.mute() : this.player.activePlayer.unMute(); } else if (this.player.activePlayer.muted !== undefined) { this.player.activePlayer.muted = this.player.state.isMuted; } } this.player.ui.updateMuteButton(); this.player.ui.showToast( this.player.state.isMuted ? 'تم كتم الصوت' : 'تم إلغاء كتم الصوت', 'info' ); } setActiveVideo(index) { const slides = document.querySelectorAll('.video-slide'); slides.forEach((slide, i) => { slide.classList.remove('active', 'prev', 'next'); if (i === index) slide.classList.add('active'); else if (i < index) slide.classList.add('prev'); else slide.classList.add('next'); }); } updateNavigationButtons() { const prevBtn = document.getElementById('prev-video'); const nextBtn = document.getElementById('next-video'); if (prevBtn) { prevBtn.disabled = this.player.state.currentIndex === 0; } if (nextBtn) { const isLastVideo = this.player.state.currentIndex === this.player.state.videos.length - 1; nextBtn.disabled = isLastVideo && !this.player.state.hasMore; } } showVideoError(videoId) { const errorElement = document.getElementById(`error-${videoId}`); if (errorElement) { errorElement.style.display = 'block'; } const playPauseIcon = document.getElementById(`play-pause-${videoId}`); if (playPauseIcon) { playPauseIcon.innerHTML = ''; playPauseIcon.classList.add('visible', 'error'); } this.player.ui.showToast('فشل في تحميل الفيديو', 'error'); } async loadMoreVideos() { if (this.player.state.isLoading || !this.player.state.hasMore) return; this.player.state.isLoading = true; try { const data = await this.player.api.fetchVideos({ limit: 10, offset: this.player.state.videos.length, ...this.player.filters.current }); if (data.success && data.videos?.length > 0) { const startIndex = this.player.state.videos.length; this.player.state.videos.push(...data.videos); data.videos.forEach((video, i) => { const slide = this.createVideoSlide(video, startIndex + i); document.getElementById('videos-container').appendChild(slide); }); this.player.state.hasMore = data.has_more !== false; } else { this.player.state.hasMore = false; this.player.ui.showToast('لا توجد المزيد من الفيديوهات', 'info'); } } catch (error) { console.error('❌ خطأ في تحميل المزيد:', error); this.player.ui.showToast('فشل في تحميل المزيد', 'error'); } finally { this.player.state.isLoading = false; } } } /** * ===== UI MODULE ===== * إدارة واجهة المستخدم والتحكمات */ class UIModule { constructor(player) { this.player = player; this.toastContainer = null; } setupContainers() { this.container = document.getElementById('shorts-container'); this.videosContainer = document.getElementById('videos-container'); if (!this.container || !this.videosContainer) { throw new Error('لم يتم العثور على حاويات الفيديو'); } this.toastContainer = document.getElementById('toast-container'); } setupControlButtons() { const buttons = { 'prev-video': () => this.player.video.previousVideo(), 'next-video': () => this.player.video.nextVideo(), 'mute-btn': () => this.player.video.toggleMute(), 'fullscreen-btn': () => this.toggleFullscreen(), 'search-btn': () => this.toggleSearchPanel(), 'filters-btn': () => this.toggleFiltersPanel(), 'menu-btn': () => this.toggleMenuPanel() }; Object.entries(buttons).forEach(([id, handler]) => { const button = document.getElementById(id); if (button) { button.addEventListener('click', handler); } }); } setupPanels() { const closeBtns = document.querySelectorAll('.close-btn'); closeBtns.forEach(btn => { btn.addEventListener('click', (e) => { const panel = e.target.closest('.search-panel, .filters-panel, .menu-panel'); if (panel) { panel.classList.remove('active'); } }); }); const searchInput = document.getElementById('search-input'); if (searchInput) { let searchTimeout; searchInput.addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { this.performSearch(e.target.value); }, 500); }); } } toggleFullscreen() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen().catch(err => { console.error('Error enabling fullscreen:', err); }); } else { document.exitFullscreen(); } } updateMuteButton() { const muteBtn = document.getElementById('mute-btn'); if (muteBtn) { const icon = muteBtn.querySelector('i'); if (icon) { icon.className = this.player.state.isMuted ? 'fas fa-volume-mute' : 'fas fa-volume-up'; } } } updateFullscreenButton() { const fullscreenBtn = document.getElementById('fullscreen-btn'); if (fullscreenBtn) { const icon = fullscreenBtn.querySelector('i'); if (icon) { icon.className = this.player.state.isFullscreen ? 'fas fa-compress' : 'fas fa-expand'; } } } toggleSearchPanel() { const panel = document.getElementById('search-panel'); if (panel) { panel.classList.toggle('active'); if (panel.classList.contains('active')) { const input = document.getElementById('search-input'); if (input) { setTimeout(() => input.focus(), 300); } } } } toggleFiltersPanel() { const panel = document.getElementById('filters-panel'); if (panel) { panel.classList.toggle('active'); } } toggleMenuPanel() { const panel = document.getElementById('menu-panel'); if (panel) { panel.classList.toggle('active'); } } closeAllPanels() { const panels = document.querySelectorAll('.search-panel.active, .filters-panel.active, .menu-panel.active'); panels.forEach(panel => panel.classList.remove('active')); } async performSearch(query) { if (!query.trim()) return; try { const data = await this.player.api.perfor