Complete 1.2 JavaScript Separation and Organization - modularize all inline JavaScript into 6 dedicated modules (app.js, api.js, ui-controls.js, chat.js, video-player.js) for better maintainability and code organization
This commit is contained in:
parent
e87b1af062
commit
2d807852e8
5 changed files with 1574 additions and 0 deletions
540
video-player.js
Normal file
540
video-player.js
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
// Video Player Module
|
||||
// Handles Video.js initialization, stream management, and player controls
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
AppModules.require('api', 'ui-controls');
|
||||
AppModules.register('video-player');
|
||||
|
||||
// Stream status management
|
||||
function updateStreamStatus(status, color) {
|
||||
const badge = document.querySelector('.stream-badge');
|
||||
if (badge) {
|
||||
badge.textContent = status;
|
||||
badge.style.background = color || 'var(--dodgers-red)';
|
||||
AppLogger.log(`📊 Stream status updated: ${status} (${color || 'red'})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Video.js player initialization
|
||||
function initializePlayer() {
|
||||
AppLogger.log('🎬 Starting Video.js initialization...');
|
||||
|
||||
AppState.player = videojs('video-player', {
|
||||
html5: {
|
||||
vhs: {
|
||||
overrideNative: true,
|
||||
withCredentials: false,
|
||||
handlePartialData: true,
|
||||
smoothQualityChange: true,
|
||||
allowSeeksWithinUnsafeLiveWindow: true,
|
||||
handleManifestRedirects: true,
|
||||
useBandwidthFromLocalStorage: true,
|
||||
maxPlaylistRetries: 10,
|
||||
blacklistDuration: 5,
|
||||
bandwidth: 4194304
|
||||
}
|
||||
},
|
||||
liveui: true,
|
||||
liveTracker: {
|
||||
trackingThreshold: 30,
|
||||
liveTolerance: 15
|
||||
},
|
||||
errorDisplay: false,
|
||||
responsive: false,
|
||||
controlBar: {
|
||||
volumePanel: {
|
||||
inline: false
|
||||
},
|
||||
pictureInPictureToggle: true
|
||||
}
|
||||
});
|
||||
|
||||
// Volume enforcement and restoration
|
||||
setupVolumeManagement();
|
||||
|
||||
// Stream status checking
|
||||
setupStreamMonitoring();
|
||||
|
||||
// Error handling
|
||||
setupPlayerErrorHandling();
|
||||
|
||||
// Event handlers
|
||||
setupPlayerEvents();
|
||||
|
||||
AppLogger.log('Video.js player initialized successfully');
|
||||
}
|
||||
|
||||
// Volume management system
|
||||
function setupVolumeManagement() {
|
||||
AppState.player.ready(function() {
|
||||
AppLogger.log('Player is ready');
|
||||
|
||||
// Immediate volume fix
|
||||
enforceSavedVolume();
|
||||
|
||||
// Load saved volume or set default
|
||||
const savedVolume = localStorage.getItem('playerVolume');
|
||||
if (savedVolume !== null) {
|
||||
const volumeValue = parseFloat(savedVolume);
|
||||
AppState.player.volume(volumeValue);
|
||||
AppLogger.log('🔊 Restored saved volume:', volumeValue);
|
||||
} else {
|
||||
AppState.player.volume(AppConfig.video.defaultVolume);
|
||||
AppLogger.log('🔊 Set default volume:', AppConfig.video.defaultVolume);
|
||||
}
|
||||
|
||||
// Volume monitoring and enforcement
|
||||
AppState.player.on('volumechange', function() {
|
||||
if (!enforceSavedVolume()) {
|
||||
// User's intentional change - save it
|
||||
if (!AppState.player.muted()) {
|
||||
const newVolume = AppState.player.volume();
|
||||
localStorage.setItem('playerVolume', newVolume);
|
||||
AppLogger.log('💾 Saved volume setting:', newVolume);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// PROACTIVE STREAM STATUS CHECK
|
||||
checkInitialStreamStatus();
|
||||
});
|
||||
}
|
||||
|
||||
// Volume enforcement function
|
||||
function enforceSavedVolume() {
|
||||
const currentVolume = AppState.player.volume();
|
||||
if (currentVolume === 1.0) {
|
||||
const savedVolume = parseFloat(localStorage.getItem('playerVolume') || AppConfig.video.defaultVolume.toString());
|
||||
if (savedVolume !== 1.0) {
|
||||
AppLogger.log('🚫 BLOCKED: Volume reset to 100% - forcing back to:', savedVolume);
|
||||
AppState.player.volume(savedVolume);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stream status and monitoring
|
||||
function checkInitialStreamStatus() {
|
||||
AppLogger.log('🔍 Checking initial stream status...');
|
||||
|
||||
API.checkStreamStatus()
|
||||
.then(isOnline => {
|
||||
if (!isOnline) {
|
||||
AppLogger.log('🔄 Stream offline on load - switching to placeholder immediately');
|
||||
switchToPlaceholder();
|
||||
} else {
|
||||
AppLogger.log('✅ Stream online on load - starting HLS playback');
|
||||
attemptHLSPlayback();
|
||||
startStreamMonitoring();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startStreamMonitoring() {
|
||||
if (!AppState.recoveryInterval) {
|
||||
AppLogger.log('👁️ Starting stream monitoring...');
|
||||
AppState.recoveryInterval = setInterval(() => {
|
||||
if (AppState.streamMode !== 'placeholder') {
|
||||
API.checkStreamStatus()
|
||||
.then(data => {
|
||||
if (!data.online) {
|
||||
AppLogger.log('⚠️ Stream went offline during playback - switching to placeholder');
|
||||
switchToPlaceholder();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('❗ Stream monitoring check failed:', error);
|
||||
});
|
||||
}
|
||||
}, AppConfig.video.streamMonitoringInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// HLS playback attempt
|
||||
function attemptHLSPlayback() {
|
||||
AppLogger.log('🎬 Attempting HLS stream playback...');
|
||||
|
||||
var playPromise = AppState.player.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.catch(function(error) {
|
||||
AppLogger.log('Auto-play was prevented:', error);
|
||||
UIControls.showToast('Click play to start stream');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Player error handling
|
||||
function setupPlayerErrorHandling() {
|
||||
// Warning events from Video.js
|
||||
AppState.player.on('warning', function(event) {
|
||||
AppLogger.log('VideoJS warning:', event.detail);
|
||||
if (!AppState.isRetrying && !AppState.isRecovering && AppState.streamMode === 'live') {
|
||||
AppState.errorRetryCount++;
|
||||
AppLogger.log('Warning count now:', AppState.errorRetryCount);
|
||||
if (AppState.errorRetryCount >= 3) {
|
||||
AppLogger.log('Threshold reached, switching to placeholder');
|
||||
switchToPlaceholder();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Error handling
|
||||
AppState.player.on('error', function() {
|
||||
var error = AppState.player.error();
|
||||
AppLogger.log('Player error:', error);
|
||||
|
||||
if (AppState.isRetrying || AppState.streamMode === 'placeholder') return;
|
||||
|
||||
if (error && (error.code === 2 || error.code === 4)) {
|
||||
if (AppState.errorRetryCount < AppConfig.video.maxRetries) {
|
||||
AppState.isRetrying = true;
|
||||
AppState.errorRetryCount++;
|
||||
|
||||
AppLogger.log(`Attempting recovery (${AppState.errorRetryCount}/${AppConfig.video.maxRetries})...`);
|
||||
updateStreamStatus('RECONNECTING...', '#ffc107');
|
||||
|
||||
setTimeout(() => {
|
||||
AppState.player.error(null);
|
||||
AppState.player.pause();
|
||||
AppState.player.reset();
|
||||
|
||||
// Restore volume
|
||||
enforceSavedVolume();
|
||||
|
||||
// Try playing again
|
||||
if (AppState.player.paused()) {
|
||||
AppState.player.play().catch(e => AppLogger.log('Play attempt failed:', e));
|
||||
}
|
||||
|
||||
AppState.isRetrying = false;
|
||||
|
||||
// Soft reload fallback
|
||||
setTimeout(() => {
|
||||
if (AppState.player.error()) {
|
||||
AppLogger.log('Soft reloading source...');
|
||||
const currentTime = AppState.player.currentTime();
|
||||
AppState.player.src(AppState.player.currentSrc());
|
||||
AppState.player.play().catch(e => AppLogger.log('Play after reload failed:', e));
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
}, 1000 * Math.min(AppState.errorRetryCount, 3));
|
||||
|
||||
} else {
|
||||
AppLogger.error('Max retries exceeded, switching to placeholder video');
|
||||
switchToPlaceholder();
|
||||
}
|
||||
} else if (error && error.code === 3) {
|
||||
AppLogger.error('Decode error, switching to placeholder:', error);
|
||||
switchToPlaceholder();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Player event setup
|
||||
function setupPlayerEvents() {
|
||||
// Reset error count on successful playback
|
||||
AppState.player.on('loadeddata', function() {
|
||||
AppState.errorRetryCount = 0;
|
||||
AppState.isRetrying = false;
|
||||
if (AppState.streamMode === 'live') {
|
||||
updateStreamStatus('LIVE', 'var(--dodgers-red)');
|
||||
}
|
||||
});
|
||||
|
||||
AppState.player.on('playing', function() {
|
||||
AppState.errorRetryCount = 0;
|
||||
AppState.isRetrying = false;
|
||||
if (AppState.streamMode === 'live') {
|
||||
updateStreamStatus('LIVE', 'var(--dodgers-red)');
|
||||
}
|
||||
});
|
||||
|
||||
AppState.player.on('waiting', function() {
|
||||
AppLogger.log('Player is buffering...');
|
||||
if (!AppState.isRetrying && AppState.errorRetryCount === 0 && AppState.streamMode === 'live') {
|
||||
updateStreamStatus('BUFFERING...', '#ffc107');
|
||||
}
|
||||
|
||||
// Extended buffering detection
|
||||
const waitingStartTime = Date.now();
|
||||
let offlineConfirmCount = 0;
|
||||
|
||||
function checkBufferingStatus(attempt = 1, maxAttempts = 5) {
|
||||
API.checkStreamStatus()
|
||||
.then(data => {
|
||||
if (!data.online) {
|
||||
offlineConfirmCount++;
|
||||
AppLogger.log(`⚠️ Confirmed offline - Attempts: ${offlineConfirmCount}/${maxAttempts}`);
|
||||
|
||||
if (offlineConfirmCount >= 2 || attempt >= maxAttempts) {
|
||||
AppLogger.log('❌ Stream confirmed offline - switching to placeholder');
|
||||
switchToPlaceholder();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
offlineConfirmCount = 0;
|
||||
}
|
||||
|
||||
if (offlineConfirmCount < 2 && attempt < maxAttempts) {
|
||||
setTimeout(() => checkBufferingStatus(attempt + 1, maxAttempts), 1500);
|
||||
}
|
||||
})
|
||||
.catch(apiError => {
|
||||
AppLogger.log('❗ API check failed:', apiError);
|
||||
if (attempt < maxAttempts) {
|
||||
setTimeout(() => checkBufferingStatus(attempt + 1, maxAttempts), 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkBufferingStatus();
|
||||
});
|
||||
|
||||
// Context menu prevention
|
||||
document.getElementById('video-player').addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
AppState.player.ready(function() {
|
||||
const videoElement = AppState.player.el().querySelector('video');
|
||||
if (videoElement) {
|
||||
videoElement.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Stream switching functions
|
||||
function switchToPlaceholder() {
|
||||
if (AppState.streamMode === 'placeholder') {
|
||||
AppLogger.log('⚠️ Already in placeholder mode, ignoring switch request');
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.log('🔄 START: Switching to placeholder video');
|
||||
AppLogger.log('📊 State before switch:', {
|
||||
streamMode: AppState.streamMode,
|
||||
errorRetryCount: AppState.errorRetryCount,
|
||||
isRecovering: AppState.isRecovering
|
||||
});
|
||||
|
||||
AppState.streamMode = 'placeholder';
|
||||
|
||||
if (AppState.recoveryInterval) {
|
||||
clearInterval(AppState.recoveryInterval);
|
||||
AppState.recoveryInterval = null;
|
||||
}
|
||||
if (AppState.recoveryTimeout) {
|
||||
clearTimeout(AppState.recoveryTimeout);
|
||||
AppState.recoveryTimeout = null;
|
||||
}
|
||||
|
||||
updateStreamStatus('SWITCHING...', '#ffc107');
|
||||
|
||||
try {
|
||||
AppState.player.pause();
|
||||
AppState.player.reset();
|
||||
enforceSavedVolume();
|
||||
|
||||
setTimeout(() => {
|
||||
AppState.player.src({
|
||||
src: 'placeholder.mp4',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
AppState.player.load();
|
||||
setTimeout(() => enforceSavedVolume(), 200);
|
||||
|
||||
AppState.player.play()
|
||||
.then(() => {
|
||||
AppLogger.log('Placeholder video started successfully');
|
||||
const savedVolume = localStorage.getItem('playerVolume') || AppConfig.video.defaultVolume.toString();
|
||||
const targetVolume = parseFloat(savedVolume);
|
||||
AppState.player.volume(targetVolume);
|
||||
updateStreamStatus('PLACEHOLDER', '#17a2b8');
|
||||
UIControls.showToast('Stream offline - showing placeholder video');
|
||||
startRecoveryChecks();
|
||||
})
|
||||
.catch(e => {
|
||||
AppLogger.log('Placeholder play failed:', e);
|
||||
updateStreamStatus('PLACEHOLDER', '#17a2b8');
|
||||
UIControls.showToast('Stream offline - showing placeholder video');
|
||||
startRecoveryChecks();
|
||||
});
|
||||
|
||||
}, 1000);
|
||||
|
||||
} catch (e) {
|
||||
AppLogger.log('Error switching to placeholder:', e);
|
||||
updateStreamStatus('PLACEHOLDER', '#17a2b8');
|
||||
startRecoveryChecks();
|
||||
}
|
||||
}
|
||||
|
||||
function switchToLive() {
|
||||
if (AppState.streamMode === 'live') {
|
||||
AppLogger.log('⚠️ Already in live mode, ignoring switch request');
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.log('🔄 START: Switching back to live stream');
|
||||
AppLogger.log('📊 State before switch:', {
|
||||
streamMode: AppState.streamMode,
|
||||
isRecovering: AppState.isRecovering
|
||||
});
|
||||
|
||||
AppState.streamMode = 'live';
|
||||
AppState.isRecovering = false;
|
||||
|
||||
if (AppState.recoveryInterval) {
|
||||
clearInterval(AppState.recoveryInterval);
|
||||
AppState.recoveryInterval = null;
|
||||
}
|
||||
|
||||
updateStreamStatus('SWITCHING...', '#ffc107');
|
||||
|
||||
try {
|
||||
AppState.player.pause();
|
||||
AppState.player.reset();
|
||||
enforceSavedVolume();
|
||||
|
||||
setTimeout(() => {
|
||||
AppState.player.src({
|
||||
src: '?proxy=stream',
|
||||
type: 'application/x-mpegURL'
|
||||
});
|
||||
|
||||
AppState.errorRetryCount = 0;
|
||||
AppState.isRetrying = false;
|
||||
|
||||
AppState.player.load();
|
||||
setTimeout(() => enforceSavedVolume(), 200);
|
||||
|
||||
AppState.player.play()
|
||||
.then(() => {
|
||||
AppLogger.log('Live stream started successfully');
|
||||
const savedVolume = localStorage.getItem('playerVolume') || AppConfig.video.defaultVolume.toString();
|
||||
const targetVolume = parseFloat(savedVolume);
|
||||
AppState.player.volume(targetVolume);
|
||||
updateStreamStatus('RECONNECTING...', '#ffc107');
|
||||
UIControls.showToast('Stream back online - switching to live');
|
||||
|
||||
setTimeout(() => {
|
||||
if (AppState.streamMode === 'live') {
|
||||
updateStreamStatus('LIVE', 'var(--dodgers-red)');
|
||||
}
|
||||
}, 2000);
|
||||
})
|
||||
.catch(e => {
|
||||
AppLogger.log('Live stream play failed:', e);
|
||||
const savedVolume = localStorage.getItem('playerVolume') || AppConfig.video.defaultVolume.toString();
|
||||
const targetVolume = parseFloat(savedVolume);
|
||||
AppState.player.volume(targetVolume);
|
||||
updateStreamStatus('LIVE', 'var(--dodgers-red)');
|
||||
UIControls.showToast('Stream back online - switching to live');
|
||||
});
|
||||
|
||||
}, 1000);
|
||||
|
||||
} catch (e) {
|
||||
AppLogger.log('Error switching to live:', e);
|
||||
AppState.player.src({ src: '?proxy=stream', type: 'application/x-mpegURL' });
|
||||
AppState.player.load();
|
||||
}
|
||||
}
|
||||
|
||||
// Recovery check system
|
||||
function startRecoveryChecks() {
|
||||
AppLogger.log('🔍 Starting fast recovery checks every 2 seconds for 5 checks');
|
||||
|
||||
let recoveryCheckCount = 0;
|
||||
let recoveryTimeout;
|
||||
|
||||
function checkRecovery() {
|
||||
if (AppState.streamMode !== 'placeholder' || recoveryCheckCount >= AppConfig.video.recoveryCheckCount) {
|
||||
if (recoveryCheckCount >= AppConfig.video.recoveryCheckCount) {
|
||||
AppLogger.log('⏹️ Fast recovery checks complete - stream still offline');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryCheckCount++;
|
||||
AppLogger.log(`🔍 Recovery check ${recoveryCheckCount}/${AppConfig.video.recoveryCheckCount}...`);
|
||||
|
||||
API.checkStreamStatus()
|
||||
.then(data => {
|
||||
if (data.online && AppState.streamMode === 'placeholder') {
|
||||
AppLogger.log('✅ Stream back online during recovery check - switching');
|
||||
AppState.isRecovering = true;
|
||||
switchToLive();
|
||||
} else {
|
||||
if (recoveryCheckCount < AppConfig.video.recoveryCheckCount) {
|
||||
recoveryTimeout = setTimeout(checkRecovery, AppConfig.video.recoveryCheckInterval);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.log(`❗ Recovery check ${recoveryCheckCount} failed:`, error);
|
||||
if (recoveryCheckCount < AppConfig.video.recoveryCheckCount) {
|
||||
recoveryTimeout = setTimeout(checkRecovery, AppConfig.video.recoveryCheckInterval);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkRecovery();
|
||||
}
|
||||
|
||||
// Manual recovery function
|
||||
function manualRecovery() {
|
||||
AppLogger.log('🔄 Manual recovery triggered by user');
|
||||
AppState.errorRetryCount = 0;
|
||||
AppState.isRetrying = false;
|
||||
|
||||
UIControls.showToast('🔄 Manual stream refresh triggered');
|
||||
|
||||
AppState.player.reset();
|
||||
AppState.player.error(null);
|
||||
|
||||
AppState.player.src({
|
||||
src: '?proxy=stream',
|
||||
type: 'application/x-mpegURL'
|
||||
});
|
||||
|
||||
AppState.player.load();
|
||||
|
||||
setTimeout(() => {
|
||||
AppState.player.play().catch(e => AppLogger.log('Manual recovery play failed:', e));
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Public API
|
||||
window.VideoPlayer = {
|
||||
initializePlayer: initializePlayer,
|
||||
switchToPlaceholder: switchToPlaceholder,
|
||||
switchToLive: switchToLive,
|
||||
manualRecovery: manualRecovery,
|
||||
updateStreamStatus: updateStreamStatus
|
||||
};
|
||||
|
||||
// Initialize player when DOM and Video.js are ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check if Video.js is loaded
|
||||
if (typeof videojs === 'undefined') {
|
||||
AppLogger.error('Video.js not found - video player will not initialize');
|
||||
return;
|
||||
}
|
||||
|
||||
// Small delay to ensure DOM is fully ready
|
||||
setTimeout(initializePlayer, 50);
|
||||
});
|
||||
|
||||
AppLogger.log('Video Player module loaded');
|
||||
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue