import os import sys import urllib.parse import requests import time from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QSlider, QMessageBox, QTextEdit) from PyQt5.QtCore import Qt, QTimer import vlc import subprocess class VLCIPTVPlayer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("VLC IPTV Player") self.resize(1024, 768) self.php_port = 8888 self.php_dir = os.path.join(os.path.dirname(__file__), "php_files") os.makedirs(self.php_dir, exist_ok=True) self.php_process = None # Initialize UI first self.init_ui() # Then initialize VLC self.init_vlc() # Finally start PHP server (which will use the debug console) self.start_php_server() # Download and process files self.download_and_process_files() def init_ui(self): """Initialize the user interface""" main_widget = QWidget(self) self.setCentralWidget(main_widget) layout = QVBoxLayout() main_widget.setLayout(layout) # Debug console - create this first self.debug_console = QTextEdit() self.debug_console.setReadOnly(True) layout.addWidget(self.debug_console, 1) # Video frame self.video_frame = QWidget() self.video_frame.setStyleSheet("background-color: black;") layout.addWidget(self.video_frame, 3) # Playlist self.playlist = QListWidget() self.playlist.itemDoubleClicked.connect(self.play_selected) layout.addWidget(self.playlist, 1) # Controls control_layout = QHBoxLayout() self.play_button = QPushButton("Play") self.play_button.clicked.connect(self.play_selected) self.stop_button = QPushButton("Stop") self.stop_button.clicked.connect(self.stop) self.reload_button = QPushButton("Update Files") self.reload_button.clicked.connect(self.download_and_process_files) self.volume_slider = QSlider(Qt.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(50) self.volume_slider.valueChanged.connect(self.set_volume) control_layout.addWidget(self.play_button) control_layout.addWidget(self.stop_button) control_layout.addWidget(self.reload_button) control_layout.addWidget(QLabel("Volume:")) control_layout.addWidget(self.volume_slider) layout.addLayout(control_layout) def start_php_server(self): """Start the built-in PHP server with port fallback""" max_attempts = 5 for attempt in range(max_attempts): try: port = self.php_port + attempt os.chdir(self.php_dir) self.php_process = subprocess.Popen( ["php", "-S", f"localhost:{port}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0 ) # Verify server is running time.sleep(1) test_url = f"http://localhost:{port}/mac_list.php" try: response = requests.get(test_url, timeout=5) if "Mac List File is OK" in response.text: self.php_port = port # Update to successful port self.log_message(f"PHP server started on port {port}") return except: continue except Exception as e: continue # If we get here, all attempts failed self.log_message(f"Failed to start PHP server after {max_attempts} attempts", error=True) QMessageBox.critical(self, "PHP Server Error", "Could not start PHP server on any port.\n" "Please check if PHP is properly embedded.") def init_vlc(self): """Initialize VLC media player""" try: self.instance = vlc.Instance() self.media_player = self.instance.media_player_new() if sys.platform == "win32": self.media_player.set_hwnd(self.video_frame.winId()) else: self.media_player.set_xwindow(self.video_frame.winId()) self.log_message("VLC initialized successfully") except Exception as e: self.log_message(f"VLC initialization failed: {str(e)}", error=True) def log_message(self, message, error=False): """Add message to debug console""" timestamp = time.strftime("%H:%M:%S", time.localtime()) if error: self.debug_console.setTextColor(Qt.red) self.debug_console.append(f"[ERROR {timestamp}] {message}") else: self.debug_console.setTextColor(Qt.black) self.debug_console.append(f"[INFO {timestamp}] {message}") self.debug_console.setTextColor(Qt.black) self.debug_console.ensureCursorVisible() def download_and_process_files(self): """Download and process all required files""" files = { "myportal.php": "https://iptv.nywebforum.com/myportal.php?download=true", "mac_list.php": "https://iptv.nywebforum.com/mac_list.php?download=true", "playlist.m3u": "https://iptv.nywebforum.com/playlist.m3u" } for filename, url in files.items(): try: # Download to temporary file first temp_path = os.path.join(self.php_dir, f"temp_{filename}") response = requests.get(url, timeout=10) with open(temp_path, 'wb') as f: f.write(response.content) # Verify content with open(temp_path, 'r', encoding='utf-8') as f: content = f.read() if filename == "myportal.php" and "Mac List File is OK" in content: raise Exception("Downloaded wrong content for myportal.php") # Replace original file final_path = os.path.join(self.php_dir, filename) if os.path.exists(final_path): os.remove(final_path) os.rename(temp_path, final_path) self.log_message(f"Downloaded and verified {filename}") except Exception as e: self.log_message(f"Error processing {filename}: {str(e)}", error=True) if os.path.exists(temp_path): os.remove(temp_path) # Process URL replacements self.process_url_replacements() self.load_playlist() def process_url_replacements(self): """Replace all server URLs with localhost URLs""" try: playlist_path = os.path.join(self.php_dir, "playlist.m3u") if os.path.exists(playlist_path): with open(playlist_path, 'r', encoding='utf-8') as f: content = f.read() content = content.replace( "https://iptv.nywebforum.com", f"http://localhost:{self.php_port}" ) with open(playlist_path, 'w', encoding='utf-8') as f: f.write(content) self.log_message("URL replacements completed") except Exception as e: self.log_message(f"Error during URL replacement: {str(e)}", error=True) def load_playlist(self): """Load the processed playlist""" playlist_path = os.path.join(self.php_dir, "playlist.m3u") if os.path.exists(playlist_path): try: with open(playlist_path, 'r', encoding='utf-8') as f: lines = f.readlines() self.playlist.clear() for line in lines: line = line.strip() if line and not line.startswith("#"): # Ensure all URLs point to local server line = line.replace( "https://iptv.nywebforum.com", f"http://localhost:{self.php_port}" ) self.playlist.addItem(line) self.log_message("Playlist loaded successfully") except Exception as e: self.log_message(f"Error loading playlist: {str(e)}", error=True) def play_selected(self): """Play the selected stream with proper buffering and retry logic""" selected_items = self.playlist.selectedItems() if not selected_items: self.log_message("No stream selected", error=True) return self.stop() # Stop any existing playback stream_url = selected_items[0].text() self.log_message(f"Attempting to play: {stream_url}") try: # For PHP URLs, get the final stream URL if "myportal.php" in stream_url: try: response = requests.get(stream_url, timeout=15, allow_redirects=False) if 300 <= response.status_code < 400 and 'Location' in response.headers: stream_url = response.headers['Location'] self.log_message(f"PHP redirect to: {stream_url}") else: stream_url = response.text.strip() self.log_message(f"PHP returned: {stream_url[:200]}...") if not stream_url.startswith(("http://", "https://", "rtmp://", "rtsp://")): raise Exception(f"Invalid stream URL: {stream_url[:200]}...") except Exception as e: self.log_message(f"PHP server request failed: {str(e)}", error=True) raise # Configure media with proper buffering media = self.instance.media_new(stream_url) # Important buffer settings for chunked streams media.add_option(':network-caching=5000') # 5 second network cache media.add_option(':live-caching=3000') # 3 second live cache media.add_option(':file-caching=5000') # 5 second file cache media.add_option(':sout-mux-caching=2000') # 2 second mux cache media.add_option(':clock-jitter=0') # Reduce jitter media.add_option(':clock-synchro=0') # Disable clock synchro # Enable hardware decoding if available media.add_option(':avcodec-hw=any') self.media_player.set_media(media) # Set up event manager for error handling self.event_manager = self.media_player.event_manager() self.event_manager.event_attach(vlc.EventType.MediaPlayerEncounteredError, self._player_error) self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self._player_ended) # Start playback if self.media_player.play() == -1: raise Exception("VLC failed to start playback") # Set up playback monitoring self.playback_timeout = QTimer() self.playback_timeout.setSingleShot(True) self.playback_timeout.timeout.connect(self._check_playback) self.playback_timeout.start(5000) # Check after 5 seconds # Set up retry timer self.retry_url = stream_url # Store for retries self.retry_count = 0 self.max_retries = 3 except Exception as e: self.log_message(f"Playback error: {str(e)}", error=True) self._handle_playback_error() def _player_error(self, event): """Handle player errors""" self.log_message("Player encountered an error", error=True) self._handle_playback_error() def _player_ended(self, event): """Handle stream endings""" self.log_message("Stream ended unexpectedly", error=True) self._handle_playback_error() def _check_playback(self): """Check if playback is actually working""" if not self.media_player.is_playing(): self.log_message("Playback timeout - stream not playing", error=True) self._handle_playback_error() def _handle_playback_error(self): """Handle playback errors with retry logic""" self.stop() if hasattr(self, 'retry_url') and self.retry_count < self.max_retries: self.retry_count += 1 self.log_message(f"Retrying playback (attempt {self.retry_count}/{self.max_retries})") QTimer.singleShot(2000, lambda: self.play_selected()) # Retry after 2 seconds else: self.log_message("Max retries reached, giving up", error=True) def check_playback_status(self): """Check if playback actually started""" if not self.media_player.is_playing(): self.log_message("Playback timeout - stream not playing", error=True) self.stop() """Stop playback""" self.media_player.stop() self.log_message("Playback stopped") def set_volume(self, value): """Set player volume""" self.media_player.audio_set_volume(value) self.log_message(f"Volume set to {value}%") def closeEvent(self, event): """Clean up when closing""" if self.php_process: self.php_process.terminate() try: self.php_process.wait(timeout=1) except subprocess.TimeoutExpired: self.php_process.kill() super().closeEvent(event) def stop(self): """Stop playback and clean up""" if hasattr(self, 'media_player'): self.media_player.stop() if hasattr(self, 'playback_timeout') and self.playback_timeout.isActive(): self.playback_timeout.stop() self.log_message("Playback stopped") if __name__ == "__main__": # Check dependencies try: import requests import vlc except ImportError as e: QMessageBox.critical(None, "Missing Dependencies", f"Required packages missing:\n{str(e)}\n" "Please install with: pip install python-vlc requests") sys.exit(1) # Check if PHP is available try: subprocess.run(["php", "-v"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except: QMessageBox.critical(None, "PHP Not Found", "PHP is required but not found in system PATH.\n" "Please install PHP or place PHP binaries in the application directory.") sys.exit(1) app = QApplication(sys.argv) player = VLCIPTVPlayer() player.show() sys.exit(app.exec_())