/**
* يمن فيديو - مشغل الريلز المحسن 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.like_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