const TVApp = { config: { defaultPoster: "https://images.wallpaperscraft.com/image/single/patterns_dark_background_82322_1920x1080.jpg", apiEndpoint: "api.php", channelInputTimeout: 1500, infoDisplayDuration: 5000, deviceIdDisplayDuration: 30000, specialCode: '786' // Code to show device ID }, // App State state: { channels: [], currentChannel: 0, player: null, channelInput: '', channelInputTimeout: null, lastKeyPressTime: 0, deviceId: '', specialCodeInput: '', isFullscreen: false }, // Initialize the app init: function() { this.setupEventListeners(); this.state.deviceId = this.getDeviceId(); this.fetchChannels(); }, // Generate a unique device ID generateDeviceId: function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }, // Get or create device ID from cookie getDeviceId: function() { const name = 'deviceId='; const decodedCookie = decodeURIComponent(document.cookie); const ca = decodedCookie.split(';'); for(let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') { c = c.substring(1); } if (c.indexOf(name) === 0) { return c.substring(name.length, c.length); } } // If not found, create new device ID const newDeviceId = this.generateDeviceId(); // Set cookie to expire in 1 year const d = new Date(); d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000)); document.cookie = `deviceId=${newDeviceId};expires=${d.toUTCString()};path=/`; return newDeviceId; }, // Show device ID showDeviceId: function() { const el = document.getElementById('device-id-display'); if (!el) return; el.textContent = `Device ID: ${this.state.deviceId}`; el.style.display = 'block'; setTimeout(() => { el.style.display = 'none'; }, this.config.deviceIdDisplayDuration); }, // Fetch channels from API fetchChannels: async function() { try { const response = await fetch(`${this.config.apiEndpoint}?get_channels=1&device_id=${this.state.deviceId}`); const data = await response.json(); if (data.channels && data.channels.length > 0) { this.state.channels = data.channels .map(channel => ({ No: channel.no, name: channel.name, url: channel.url, drm: channel.drm })) .filter(channel => channel.url); // Filter out channels without URLs if (this.state.channels.length === 0) { this.showError("No channels with valid streams found"); return; } document.getElementById('loading').style.display = 'none'; // Check URL for specific channel parameter const urlParams = new URLSearchParams(window.location.search); const channelId = urlParams.get('id'); if (channelId) { const matchIndex = this.state.channels.findIndex(c => c.id === channelId); if (matchIndex !== -1) { this.state.currentChannel = matchIndex; } } this.loadChannel(this.state.currentChannel); } else { this.showError("No channels found in the response"); } } catch (error) { console.error("Error fetching channels:", error); this.showError("Failed to load channels. Please try again later."); } }, // Load a channel by index loadChannel: function(index) { if (index < 0 || index >= this.state.channels.length) return; const channel = this.state.channels[index]; if (!channel.url) { this.showChannelInfo(`No stream URL for ${channel.name}`); return; } // Remove previous player if exists if (this.state.player) { try { this.state.player.remove(); } catch (e) { console.log("Error removing previous player:", e); } } document.getElementById("player").style.opacity = 0; // Delay player setup to allow fade effect setTimeout(() => { try { const playerConfig = { file: channel.url, image: this.config.defaultPoster, width: "100%", height: "100%", autostart: true, mute: false, volume: 50, controls: true, abouttext: "Video Player", aboutlink: "https://example.com", skin: { name: "netflix", active: "#fff", inactive: "#ccc", background: "rgba(0,0,0,0.5)" } }; // Add DRM config if available if (channel.drm && channel.drm.clearkey) { playerConfig.drm = { clearkey: { keyId: channel.drm.clearkey.keyId, key: channel.drm.clearkey.key } }; } // Setup new player this.state.player = jwplayer("player").setup(playerConfig); this.state.player.on('ready', () => { document.getElementById("player").style.opacity = 1; this.showChannelInfo(`${channel.No} - ${channel.name}`); }); this.state.player.on('error', (e) => { console.error('JWPlayer error:', e); this.showChannelInfo(`Error loading ${channel.name}`); // Try next channel on error setTimeout(() => this.nextChannel(), 2000); }); this.state.player.on('fullscreen', (state) => { this.state.isFullscreen = state.fullscreen; }); } catch (e) { console.error("Player setup failed:", e); this.showChannelInfo("Player failed to initialize"); } }, 300); }, // Navigate to next channel nextChannel: function() { this.state.currentChannel = (this.state.currentChannel + 1) % this.state.channels.length; this.loadChannel(this.state.currentChannel); }, // Navigate to previous channel prevChannel: function() { this.state.currentChannel = (this.state.currentChannel - 1 + this.state.channels.length) % this.state.channels.length; this.loadChannel(this.state.currentChannel); }, // Show channel information overlay showChannelInfo: function(text) { const el = document.getElementById('channel-info'); if (!el) return; el.textContent = text; el.style.opacity = 1; clearTimeout(window.channelInfoTimeout); window.channelInfoTimeout = setTimeout(() => { el.style.opacity = 0; }, this.config.infoDisplayDuration); }, // Show error message showError: function(text) { const el = document.getElementById('loading'); if (el) { el.textContent = text; } }, // Show volume information showVolume: function(level) { const el = document.getElementById('volume-info'); if (!el) return; el.textContent = `Volume: ${Math.round(level)}%`; el.style.opacity = 1; clearTimeout(window.volumeInfoTimeout); window.volumeInfoTimeout = setTimeout(() => { el.style.opacity = 0; }, this.config.infoDisplayDuration); }, // Show channel input display showChannelInput: function() { const el = document.getElementById('channel-input-display'); if (!el) return; el.style.display = 'block'; el.innerHTML = `Channel: ${this.state.channelInput}`; clearTimeout(window.channelInputTimeout); window.channelInputTimeout = setTimeout(() => { el.style.display = 'none'; this.state.channelInput = ''; }, this.config.channelInputTimeout); }, // Update channel input display updateChannelInputDisplay: function() { const valueEl = document.getElementById('channel-input-value'); if (valueEl) { valueEl.textContent = this.state.channelInput; } }, // Handle keyboard input handleKeyPress: function(e) { const key = e.key; const now = Date.now(); // Reset input if time between keypresses is too long if (now - this.state.lastKeyPressTime > 2000) { this.state.channelInput = ''; this.state.specialCodeInput = ''; } this.state.lastKeyPressTime = now; // Check for special code (786) if (key === '7' || key === '8' || key === '6') { this.state.specialCodeInput += key; if (this.state.specialCodeInput === this.config.specialCode) { this.showDeviceId(); this.state.specialCodeInput = ''; return; } // Reset if the sequence is broken if (this.state.specialCodeInput.length >= this.config.specialCode.length || (this.state.specialCodeInput.length === 1 && key !== '7') || (this.state.specialCodeInput.length === 2 && key !== '8')) { this.state.specialCodeInput = ''; } } else { this.state.specialCodeInput = ''; } // Handle numeric input for channel selection if (!isNaN(key)) { this.state.channelInput += key; this.showChannelInput(); this.updateChannelInputDisplay(); clearTimeout(this.state.channelInputTimeout); this.state.channelInputTimeout = setTimeout(() => { if (this.state.channelInput.length > 0) { const paddedInput = this.state.channelInput.padStart(3, '0'); const matchIndex = this.state.channels.findIndex(c => c.No === paddedInput); if (matchIndex !== -1) { this.state.currentChannel = matchIndex; this.loadChannel(this.state.currentChannel); } else { this.showChannelInfo(`Channel ${paddedInput} not found`); } this.state.channelInput = ''; const inputDisplay = document.getElementById('channel-input-display'); if (inputDisplay) inputDisplay.style.display = 'none'; } }, this.config.channelInputTimeout); return; } // Handle control keys switch (key) { case 'ArrowRight': this.nextChannel(); break; case 'ArrowLeft': this.prevChannel(); break; case 'ArrowUp': if (this.state.player) { const vol = Math.min(this.state.player.getVolume() + 10, 100); this.state.player.setVolume(vol); this.showVolume(vol); } break; case 'ArrowDown': if (this.state.player) { const vol = Math.max(this.state.player.getVolume() - 10, 0); this.state.player.setVolume(vol); this.showVolume(vol); } break; case 'm': case 'M': if (this.state.player) this.state.player.setMute(!this.state.player.getMute()); break; case ' ': if (this.state.player) this.state.player.setFullscreen(true); break; case 'f': case 'F': if (this.state.player) this.state.player.setFullscreen(!this.state.isFullscreen); break; case 'Escape': if (this.state.isFullscreen && this.state.player) { this.state.player.setFullscreen(false); } break; } }, // Setup event listeners setupEventListeners: function() { document.addEventListener('keydown', (e) => this.handleKeyPress(e)); // Add click event for fullscreen toggle document.getElementById('player')?.addEventListener('click', () => { if (this.state.player && !this.state.isFullscreen) { this.state.player.setFullscreen(true); } }); } }; // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { TVApp.init(); });