fix
This commit is contained in:
parent
7bcac9466a
commit
7c7c54e617
7 changed files with 3616 additions and 6 deletions
|
|
@ -1 +1 @@
|
|||
{"access_token": "BQBWe7sCCLW9H6GTItLTJ6Fjw7YK6a_quPz3y2RgAXqTLwlQEMFLDO6xCmD8efMEe2OhV4B-MwzbcktuHT67wejexVVcnAJasEFH9RbVHUaVCP7MNgHuirirRazH6QweOd01DL3iieeN6lvGfRJ97BBkfQ-u5StX3foZZuxI4HOhCrALuIKJjWfROcjMRdy-qZeap_8nas0HwKOqowAxzqyjhRki0iZH128vrZe1sfCUM4895t-ULsMBWy2GAjTd", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752303544, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||
{"access_token": "BQCzY4NDr26JqIBSxwATrH7MQMG5i9kCZxPvvtjcoVTyFZPRYPOEltaZEqN44ukQhNDo6C8fvkhlSPS6tk2N7zVitnBk9bT2IRAQiQGpxRS67kNlUfxUJSw52YAW83vECziyLfUfOkLs-E2LzoJDQtq_QOx18b_wO883mT1C8OyNFjVGvvjV5Srf8oSMJ4KN6WYwMpllfTbno7YlT7R5oPOtjgNquuUOhLHRWWp9KXEermO-MZWtWl_OxBAlhhmi", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752342994, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||
3290
logs/app.log
3290
logs/app.log
File diff suppressed because it is too large
Load diff
5
main.py
5
main.py
|
|
@ -137,6 +137,11 @@ class MainWindow(QMainWindow):
|
|||
self.downloads_page.track_stopped.connect(self.sidebar.media_player.clear_track)
|
||||
self.downloads_page.track_finished.connect(self.sidebar.media_player.clear_track)
|
||||
|
||||
# Connect loading animation signals
|
||||
self.downloads_page.track_loading_started.connect(lambda result: self.sidebar.media_player.show_loading())
|
||||
self.downloads_page.track_loading_finished.connect(lambda result: self.sidebar.media_player.hide_loading())
|
||||
self.downloads_page.track_loading_progress.connect(lambda progress, result: self.sidebar.media_player.set_loading_progress(progress))
|
||||
|
||||
# Connect sidebar media player signals to downloads page
|
||||
self.sidebar.media_player.play_pause_requested.connect(self.downloads_page.handle_sidebar_play_pause)
|
||||
self.sidebar.media_player.stop_requested.connect(self.downloads_page.handle_sidebar_stop)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -643,6 +643,7 @@ class StreamingThread(QThread):
|
|||
streaming_started = pyqtSignal(str, object) # Message, search_result
|
||||
streaming_finished = pyqtSignal(str, object) # Message, search_result
|
||||
streaming_failed = pyqtSignal(str, object) # Error message, search_result
|
||||
streaming_progress = pyqtSignal(float, object) # Progress percentage (0-100), search_result
|
||||
|
||||
def __init__(self, soulseek_client, search_result):
|
||||
super().__init__()
|
||||
|
|
@ -697,12 +698,56 @@ class StreamingThread(QThread):
|
|||
# Standard streaming - wait for complete download
|
||||
max_wait_time = 45 # Wait up to 45 seconds
|
||||
poll_interval = 2 # Check every 2 seconds
|
||||
found_file = None
|
||||
|
||||
# Track elapsed time for fallback progress estimation
|
||||
start_time = time.time()
|
||||
estimated_duration = 15.0 # Estimate 15 seconds for typical download
|
||||
last_progress_sent = 0.0
|
||||
found_file = None # Initialize found_file outside the loop
|
||||
|
||||
for wait_count in range(max_wait_time // poll_interval):
|
||||
if self._stop_requested:
|
||||
break
|
||||
|
||||
# Calculate fallback progress based on elapsed time
|
||||
elapsed_time = time.time() - start_time
|
||||
estimated_progress = min(95.0, (elapsed_time / estimated_duration) * 95.0) # Cap at 95%
|
||||
|
||||
# Check download progress via slskd API
|
||||
api_progress = None
|
||||
try:
|
||||
download_status = loop.run_until_complete(self._check_download_progress())
|
||||
if download_status:
|
||||
api_progress = download_status.progress
|
||||
print(f"API Download progress: {api_progress:.1f}%")
|
||||
|
||||
# Check if download is complete
|
||||
if download_status.state.lower().startswith('completed') or api_progress >= 100:
|
||||
print(f"✓ Download completed via API status: {download_status.state}")
|
||||
# Try to find the actual file - with retries for file system sync
|
||||
for retry_count in range(5): # Try up to 5 times with delays
|
||||
found_file = self._find_downloaded_file(download_path)
|
||||
if found_file:
|
||||
print(f"✓ Found completed file after {retry_count} retries: {found_file}")
|
||||
break
|
||||
else:
|
||||
print(f"⏳ File not found yet, waiting... (retry {retry_count + 1}/5)")
|
||||
time.sleep(1) # Wait 1 second for file system to sync
|
||||
|
||||
if found_file:
|
||||
break # Exit main polling loop
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not check download progress: {e}")
|
||||
|
||||
# Use API progress if available, otherwise use estimated progress
|
||||
current_progress = api_progress if api_progress is not None and api_progress > 0 else estimated_progress
|
||||
|
||||
# Only emit progress updates if there's a meaningful change (>5%)
|
||||
if current_progress - last_progress_sent >= 5.0:
|
||||
self.streaming_progress.emit(current_progress, self.search_result)
|
||||
print(f"Progress update: {current_progress:.1f}% (API: {api_progress}, Estimated: {estimated_progress:.1f}%)")
|
||||
last_progress_sent = current_progress
|
||||
|
||||
# Search for the downloaded file in the downloads directory
|
||||
found_file = self._find_downloaded_file(download_path)
|
||||
|
||||
|
|
@ -721,7 +766,8 @@ class StreamingThread(QThread):
|
|||
# Clean up empty directories left behind
|
||||
self._cleanup_empty_directories(download_path, found_file)
|
||||
|
||||
# Signal that streaming is ready
|
||||
# Signal that streaming is ready (100% progress)
|
||||
self.streaming_progress.emit(100.0, self.search_result)
|
||||
self.streaming_finished.emit(f"Stream ready: {os.path.basename(found_file)}", self.search_result)
|
||||
self.temp_file_path = stream_path
|
||||
print(f"✓ Stream file ready for playback: {stream_path}")
|
||||
|
|
@ -737,7 +783,39 @@ class StreamingThread(QThread):
|
|||
time.sleep(poll_interval)
|
||||
else:
|
||||
# Timed out waiting for file
|
||||
print(f"❌ Polling loop completed, timeout reached. found_file = {found_file}")
|
||||
self.streaming_failed.emit("Stream download timed out - file not found", self.search_result)
|
||||
|
||||
# Post-polling file processing
|
||||
print(f"📋 After polling loop: found_file = {found_file}")
|
||||
if found_file:
|
||||
print(f"🎯 Processing found file: {found_file}")
|
||||
|
||||
# Move the file to Stream folder with original filename
|
||||
original_filename = os.path.basename(found_file)
|
||||
stream_path = os.path.join(stream_folder, original_filename)
|
||||
|
||||
try:
|
||||
# Move file to Stream folder
|
||||
print(f"📁 Moving file from {found_file} to {stream_path}")
|
||||
shutil.move(found_file, stream_path)
|
||||
print(f"✅ Successfully moved file to stream folder: {stream_path}")
|
||||
|
||||
# Clean up empty directories left behind
|
||||
self._cleanup_empty_directories(download_path, found_file)
|
||||
|
||||
# Signal that streaming is ready (100% progress)
|
||||
self.streaming_progress.emit(100.0, self.search_result)
|
||||
self.streaming_finished.emit(f"Stream ready: {os.path.basename(found_file)}", self.search_result)
|
||||
self.temp_file_path = stream_path
|
||||
print(f"🎵 Stream file ready for playback: {stream_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error moving file to stream folder: {e}")
|
||||
self.streaming_failed.emit(f"Failed to prepare stream file: {e}", self.search_result)
|
||||
else:
|
||||
print(f"❌ No file found after polling loop completed")
|
||||
self.streaming_failed.emit("Stream download failed - file not found after completion", self.search_result)
|
||||
|
||||
else:
|
||||
self.streaming_failed.emit("Streaming failed to start", self.search_result)
|
||||
|
|
@ -761,6 +839,33 @@ class StreamingThread(QThread):
|
|||
except Exception as e:
|
||||
print(f"Error cleaning up streaming event loop: {e}")
|
||||
|
||||
async def _check_download_progress(self):
|
||||
"""Check download progress via slskd API for the current streaming download"""
|
||||
try:
|
||||
# Get all current downloads and find ours by filename match
|
||||
all_downloads = await self.soulseek_client.get_all_downloads()
|
||||
print(f"🔍 Looking for download - Target: {self.search_result.username}:{os.path.basename(self.search_result.filename)}")
|
||||
print(f"🔍 Found {len(all_downloads)} total downloads in API")
|
||||
|
||||
# Look for our specific file by filename and username
|
||||
target_filename = os.path.basename(self.search_result.filename)
|
||||
target_username = self.search_result.username
|
||||
|
||||
for i, download in enumerate(all_downloads):
|
||||
download_filename = os.path.basename(download.filename)
|
||||
print(f"📁 Download {i+1}: {download.username}:{download_filename} - State: {download.state} - Progress: {download.progress}%")
|
||||
|
||||
if (download_filename == target_filename and
|
||||
download.username == target_username):
|
||||
print(f"✅ Found matching download: {download.state} - {download.progress}%")
|
||||
return download
|
||||
|
||||
print(f"❌ No matching download found for {target_username}:{target_filename}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error checking download progress: {e}")
|
||||
return None
|
||||
|
||||
def _find_downloaded_file(self, download_path):
|
||||
"""Find the downloaded audio file in the downloads directory tree"""
|
||||
import os
|
||||
|
|
@ -2896,6 +3001,9 @@ class DownloadsPage(QWidget):
|
|||
track_stopped = pyqtSignal()
|
||||
track_finished = pyqtSignal()
|
||||
track_position_updated = pyqtSignal(float, float) # current_position, duration in seconds
|
||||
track_loading_started = pyqtSignal(object) # Track result object when streaming starts
|
||||
track_loading_finished = pyqtSignal(object) # Track result object when streaming completes
|
||||
track_loading_progress = pyqtSignal(float, object) # Progress percentage (0-100), track result object
|
||||
|
||||
def __init__(self, soulseek_client=None, parent=None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -4342,6 +4450,7 @@ class DownloadsPage(QWidget):
|
|||
streaming_thread = StreamingThread(self.soulseek_client, search_result)
|
||||
streaming_thread.streaming_started.connect(self.on_streaming_started, Qt.ConnectionType.QueuedConnection)
|
||||
streaming_thread.streaming_finished.connect(self.on_streaming_finished, Qt.ConnectionType.QueuedConnection)
|
||||
streaming_thread.streaming_progress.connect(self.on_streaming_progress, Qt.ConnectionType.QueuedConnection)
|
||||
streaming_thread.streaming_failed.connect(self.on_streaming_failed, Qt.ConnectionType.QueuedConnection)
|
||||
streaming_thread.finished.connect(
|
||||
functools.partial(self.on_streaming_thread_finished, streaming_thread),
|
||||
|
|
@ -4372,6 +4481,9 @@ class DownloadsPage(QWidget):
|
|||
except RuntimeError:
|
||||
# Button was deleted, ignore
|
||||
pass
|
||||
|
||||
# Emit signal for media player loading animation
|
||||
self.track_loading_started.emit(search_result)
|
||||
|
||||
def on_streaming_finished(self, message, search_result):
|
||||
"""Handle streaming completion - start actual audio playback"""
|
||||
|
|
@ -4419,6 +4531,7 @@ class DownloadsPage(QWidget):
|
|||
pass
|
||||
# Emit track started signal for sidebar media player
|
||||
if hasattr(self, 'current_track_result') and self.current_track_result:
|
||||
self.track_loading_finished.emit(self.current_track_result)
|
||||
self.track_started.emit(self.current_track_result)
|
||||
else:
|
||||
print(f"❌ Failed to start audio playback")
|
||||
|
|
@ -4452,6 +4565,21 @@ class DownloadsPage(QWidget):
|
|||
pass
|
||||
self.currently_playing_button = None
|
||||
|
||||
def on_streaming_progress(self, progress_percent, search_result):
|
||||
"""Handle streaming progress updates"""
|
||||
print(f"Streaming progress: {progress_percent:.1f}% for {search_result.filename}")
|
||||
|
||||
# Check if this progress is for the currently requested track
|
||||
if hasattr(self, 'current_track_result') and self.current_track_result:
|
||||
current_track_id = f"{self.current_track_result.username}:{self.current_track_result.filename}"
|
||||
progress_track_id = f"{search_result.username}:{search_result.filename}"
|
||||
|
||||
if current_track_id == progress_track_id:
|
||||
# Emit progress signal for media player
|
||||
self.track_loading_progress.emit(progress_percent, search_result)
|
||||
else:
|
||||
print(f"🚫 Ignoring progress for old streaming result: {search_result.filename}")
|
||||
|
||||
def on_streaming_failed(self, error_msg, search_result):
|
||||
"""Handle streaming failure"""
|
||||
print(f"Streaming failed: {error_msg}")
|
||||
|
|
|
|||
193
ui/sidebar.py
193
ui/sidebar.py
|
|
@ -1,7 +1,7 @@
|
|||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLabel, QFrame, QSizePolicy, QSpacerItem, QSlider, QProgressBar)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QRect, QTimer
|
||||
from PyQt6.QtGui import QFont, QPalette, QIcon, QPixmap, QPainter, QFontMetrics
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QRect, QTimer, pyqtProperty
|
||||
from PyQt6.QtGui import QFont, QPalette, QIcon, QPixmap, QPainter, QFontMetrics, QColor, QLinearGradient
|
||||
|
||||
class ScrollingLabel(QLabel):
|
||||
"""A label that smoothly scrolls text horizontally when it's too long to fit"""
|
||||
|
|
@ -285,6 +285,173 @@ class StatusIndicator(QWidget):
|
|||
""")
|
||||
self.service_label.setStyleSheet("color: #b3b3b3; font-weight: 400;")
|
||||
|
||||
class LoadingAnimation(QWidget):
|
||||
"""Thin horizontal loading animation for media player with dual-mode capability"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFixedHeight(12) # Increased height for text overlay
|
||||
self._progress = 0.0
|
||||
self._is_active = False
|
||||
self._mode = "indefinite" # "indefinite" or "determinate"
|
||||
self._determinate_progress = 0.0 # 0-100% for determinate mode
|
||||
|
||||
# Animation setup for indefinite mode
|
||||
self.animation = QPropertyAnimation(self, b"progress")
|
||||
self.animation.setDuration(1200) # 1.2 second cycle
|
||||
self.animation.setStartValue(0.0)
|
||||
self.animation.setEndValue(1.0)
|
||||
self.animation.setLoopCount(-1) # Infinite loop
|
||||
self.animation.setEasingCurve(QEasingCurve.Type.InOutSine)
|
||||
|
||||
# Progress value animation for smooth transitions in determinate mode
|
||||
self.progress_animation = QPropertyAnimation(self, b"determinate_progress")
|
||||
self.progress_animation.setDuration(300) # Smooth 300ms transitions
|
||||
self.progress_animation.setEasingCurve(QEasingCurve.Type.OutCubic)
|
||||
|
||||
# Completion glow effect
|
||||
self._glow_opacity = 0.0
|
||||
self.glow_animation = QPropertyAnimation(self, b"glow_opacity")
|
||||
self.glow_animation.setDuration(800) # Slower glow pulse
|
||||
self.glow_animation.setStartValue(0.0)
|
||||
self.glow_animation.setEndValue(1.0)
|
||||
self.glow_animation.setLoopCount(3) # Pulse 3 times
|
||||
self.glow_animation.setEasingCurve(QEasingCurve.Type.InOutSine)
|
||||
|
||||
self.hide() # Start hidden
|
||||
|
||||
@pyqtProperty(float)
|
||||
def progress(self):
|
||||
return self._progress
|
||||
|
||||
@progress.setter
|
||||
def progress(self, value):
|
||||
self._progress = value
|
||||
self.update()
|
||||
|
||||
@pyqtProperty(float)
|
||||
def determinate_progress(self):
|
||||
return self._determinate_progress
|
||||
|
||||
@determinate_progress.setter
|
||||
def determinate_progress(self, value):
|
||||
self._determinate_progress = value
|
||||
self.update()
|
||||
|
||||
@pyqtProperty(float)
|
||||
def glow_opacity(self):
|
||||
return self._glow_opacity
|
||||
|
||||
@glow_opacity.setter
|
||||
def glow_opacity(self, value):
|
||||
self._glow_opacity = value
|
||||
self.update()
|
||||
|
||||
def start_animation(self):
|
||||
"""Start the indefinite loading animation"""
|
||||
self._is_active = True
|
||||
self._mode = "indefinite"
|
||||
self.show()
|
||||
self.animation.start()
|
||||
|
||||
def set_progress(self, percentage):
|
||||
"""Set determinate progress (0-100%) with smooth animation"""
|
||||
if not self._is_active:
|
||||
self._is_active = True
|
||||
self.show()
|
||||
|
||||
# Switch to determinate mode
|
||||
if self._mode == "indefinite":
|
||||
self._mode = "determinate"
|
||||
self.animation.stop() # Stop indefinite animation
|
||||
|
||||
# Animate to new progress value
|
||||
self.progress_animation.setStartValue(self._determinate_progress)
|
||||
self.progress_animation.setEndValue(percentage)
|
||||
self.progress_animation.start()
|
||||
|
||||
# Trigger completion glow effect when reaching 100%
|
||||
if percentage >= 100 and self._determinate_progress < 100:
|
||||
self.glow_animation.start()
|
||||
|
||||
def stop_animation(self):
|
||||
"""Stop the loading animation"""
|
||||
self._is_active = False
|
||||
self._mode = "indefinite"
|
||||
self.animation.stop()
|
||||
self.progress_animation.stop()
|
||||
self.glow_animation.stop()
|
||||
self.hide()
|
||||
self._progress = 0.0
|
||||
self._determinate_progress = 0.0
|
||||
self._glow_opacity = 0.0
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Custom paint event for dual-mode animation with text overlay"""
|
||||
if not self._is_active:
|
||||
return
|
||||
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
|
||||
|
||||
width = self.width()
|
||||
height = self.height()
|
||||
progress_bar_height = 4 # Bottom 4px for progress bar
|
||||
text_height = height - progress_bar_height # Top area for text
|
||||
|
||||
# Background for progress bar area
|
||||
progress_rect = self.rect()
|
||||
progress_rect.setTop(text_height)
|
||||
painter.fillRect(progress_rect, QColor(40, 40, 40))
|
||||
|
||||
if self._mode == "indefinite":
|
||||
# Indefinite mode: animated gradient wave
|
||||
gradient_width = width * 0.3 # 30% of total width
|
||||
center_x = self._progress * width
|
||||
|
||||
for i in range(int(gradient_width)):
|
||||
alpha = max(0, 255 - (abs(i - gradient_width/2) * 8))
|
||||
color = QColor(29, 185, 84, int(alpha)) # Spotify green with fade
|
||||
x = int(center_x - gradient_width/2 + i)
|
||||
if 0 <= x < width:
|
||||
painter.fillRect(x, text_height, 1, progress_bar_height, color)
|
||||
|
||||
else: # determinate mode
|
||||
# Determinate mode: progress bar with percentage
|
||||
progress_width = (self._determinate_progress / 100.0) * width
|
||||
|
||||
# Progress bar with gradient
|
||||
if progress_width > 0:
|
||||
progress_fill_rect = QRect(0, text_height, int(progress_width), progress_bar_height)
|
||||
|
||||
# Create subtle gradient for progress bar
|
||||
gradient = QLinearGradient(0, text_height, progress_width, text_height)
|
||||
gradient.setColorAt(0, QColor(29, 185, 84)) # Spotify green
|
||||
gradient.setColorAt(1, QColor(30, 215, 96)) # Lighter green
|
||||
|
||||
painter.fillRect(progress_fill_rect, gradient)
|
||||
|
||||
# Add animated glow effect during completion
|
||||
if self._glow_opacity > 0:
|
||||
glow_alpha = int(120 * self._glow_opacity) # Max alpha of 120
|
||||
glow_color = QColor(29, 185, 84, glow_alpha)
|
||||
|
||||
# Expand glow slightly beyond progress bar for effect
|
||||
glow_rect = QRect(0, text_height - 1, width, progress_bar_height + 2)
|
||||
painter.fillRect(glow_rect, glow_color)
|
||||
|
||||
# Percentage text overlay (elegant, small font)
|
||||
if text_height > 0 and self._determinate_progress > 0:
|
||||
font = QFont("Segoe UI", 7, QFont.Weight.Medium) # Small, elegant font
|
||||
painter.setFont(font)
|
||||
painter.setPen(QColor(180, 180, 180)) # Light gray text
|
||||
|
||||
percentage_text = f"{int(self._determinate_progress)}%"
|
||||
text_rect = QRect(0, 0, width, text_height)
|
||||
painter.drawText(text_rect, Qt.AlignmentFlag.AlignCenter, percentage_text)
|
||||
|
||||
class MediaPlayer(QWidget):
|
||||
# Signals for media control
|
||||
play_pause_requested = pyqtSignal()
|
||||
|
|
@ -321,6 +488,10 @@ class MediaPlayer(QWidget):
|
|||
layout.setContentsMargins(16, 10, 16, 10)
|
||||
layout.setSpacing(10)
|
||||
|
||||
# Loading animation at the top
|
||||
self.loading_animation = LoadingAnimation()
|
||||
layout.addWidget(self.loading_animation)
|
||||
|
||||
# Always visible header with basic controls
|
||||
self.header = self.create_header()
|
||||
layout.addWidget(self.header)
|
||||
|
|
@ -581,6 +752,9 @@ class MediaPlayer(QWidget):
|
|||
# Set to playing state (show pause button since track just started)
|
||||
self.set_playing_state(True)
|
||||
|
||||
# Hide loading animation now that track is ready
|
||||
self.hide_loading()
|
||||
|
||||
# Hide no track message and show player
|
||||
self.no_track_label.setVisible(False)
|
||||
|
||||
|
|
@ -606,8 +780,9 @@ class MediaPlayer(QWidget):
|
|||
self.current_track = None
|
||||
self.is_playing = False
|
||||
|
||||
# Stop any scrolling animation
|
||||
# Stop any animations
|
||||
self.track_info.stop_scrolling()
|
||||
self.hide_loading()
|
||||
|
||||
# Update UI
|
||||
self.track_info.setText("No track")
|
||||
|
|
@ -636,6 +811,18 @@ class MediaPlayer(QWidget):
|
|||
volume = value / 100.0 # Convert to 0.0-1.0
|
||||
self.volume_changed.emit(volume)
|
||||
|
||||
def show_loading(self):
|
||||
"""Show and start the loading animation"""
|
||||
self.loading_animation.start_animation()
|
||||
|
||||
def hide_loading(self):
|
||||
"""Hide and stop the loading animation"""
|
||||
self.loading_animation.stop_animation()
|
||||
|
||||
def set_loading_progress(self, percentage):
|
||||
"""Set loading progress percentage (0-100)"""
|
||||
self.loading_animation.set_progress(percentage)
|
||||
|
||||
|
||||
class ModernSidebar(QWidget):
|
||||
page_changed = pyqtSignal(str)
|
||||
|
|
|
|||
Loading…
Reference in a new issue