diff --git a/Beta-0.1.3.py b/Beta-0.1.3.py deleted file mode 100644 index ac9a240..0000000 --- a/Beta-0.1.3.py +++ /dev/null @@ -1,849 +0,0 @@ -import sys -import requests -import pygame -import os -from PyQt5.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QTreeWidget, QTreeWidgetItem, QLineEdit, QLabel, QPushButton, - QSplitter, QTabWidget, QTextEdit, QMessageBox, QAction, - QSlider, QButtonGroup, QProgressDialog -) -from PyQt5.QtCore import Qt, pyqtSignal, QThread, QTimer, QObject -from PyQt5.QtGui import QColor, QBrush, QFont - -# --- 初始化 pygame 音频模块 --- -pygame.mixer.init() - -# --- 配置区 --- -BASE_URL = "https://shanwogou.cn/audio/" -API_URL = BASE_URL + "api.php" -# 本地音频存储路径(自动创建./audio目录) -AUDIO_SAVE_DIR = "./audio" -os.makedirs(AUDIO_SAVE_DIR, exist_ok=True) -# 音乐分类配置(重命名+颜色) -CATEGORY_CONFIG = { - 'all': {'name': '全部音乐', 'color': '#b89e81', 'text_color': '#5d4037'}, - 'cantonese': {'name': '粤语歌曲', 'color': '#c8e6c9', 'text_color': '#2e7d32'}, - 'mandarin': {'name': '国语歌曲', 'color': '#fff3e0', 'text_color': '#e65100'}, - 'waiyu': {'name': '外语歌曲', 'color': '#e3f2fd', 'text_color': '#0d47a1'}, - 'classic': {'name': '经典老歌', 'color': '#efebe9', 'text_color': '#3e2723'}, - 'other': {'name': '其他音乐', 'color': '#f3e5f5', 'text_color': '#6a1b9a'} -} - -# --- API 交互模块 --- -def fetch_api_data(url, params=None): - """通用 GET 请求函数,解析 JSON 响应""" - try: - response = requests.get(url, params=params, timeout=15) - response.raise_for_status() # 触发 HTTP 错误(如 404/500) - return response.json() - except requests.exceptions.RequestException as e: - print(f"API 请求失败: {str(e)}") - return None - -def get_all_music(): - """获取所有音乐""" - return fetch_api_data(API_URL, params={"action": "getAllMusic"}) - -def get_music_tags(): - """获取所有音乐标签""" - return fetch_api_data(API_URL, params={"action": "getMusicTags"}) - -def get_announcements(): - """获取公告列表""" - return fetch_api_data(API_URL, params={"action": "getAnnouncements"}) - -def get_music_url(music_id): - """获取音乐的直接播放地址(mp3 链接)""" - return fetch_api_data(API_URL, params={"action": "getMusicUrl", "id": music_id}) - -# --- 下载线程(完整下载)--- -class DownloadThread(QThread): - """完整下载音乐文件的线程""" - progress_updated = pyqtSignal(int) # 下载进度 - download_complete = pyqtSignal(str) # 下载完成信号(文件路径) - error_occurred = pyqtSignal(str) # 错误信号 - - def __init__(self, music_id, music_title, music_artist): - super().__init__() - self.music_id = music_id - self.music_title = music_title - self.music_artist = music_artist - self.is_canceled = False - self.session = requests.Session() - - def run(self): - try: - # 1. 获取音乐播放链接 - music_url_data = get_music_url(self.music_id) - if not music_url_data or not music_url_data.get("success", False): - self.error_occurred.emit("未获取到有效的播放链接") - return - - play_url = music_url_data["data"].get("play_url") - if not play_url: - self.error_occurred.emit("API 未返回直接播放地址(play_url)") - return - - # 2. 准备文件路径 - safe_title = "".join([c for c in self.music_title if c.isalnum() or c in ' _-']) - safe_artist = "".join([c for c in self.music_artist if c.isalnum() or c in ' _-']) - self.final_file = os.path.join(AUDIO_SAVE_DIR, f"{safe_artist} - {safe_title}.mp3") - - # 3. 检查是否已下载 - if os.path.exists(self.final_file): - self.download_complete.emit(self.final_file) - return - - # 4. 开始完整下载 - response = self.session.get(play_url, stream=True, timeout=30) - response.raise_for_status() - - # 获取总文件大小 - total_size = int(response.headers.get("content-length", 0)) - if total_size == 0: - self.error_occurred.emit("无法获取文件大小,无法下载") - return - - # 5. 写入文件 - downloaded_size = 0 - with open(self.final_file, "wb") as f: - for chunk in response.iter_content(chunk_size=8192): - if self.is_canceled: # 检查是否取消下载 - if os.path.exists(self.final_file): - os.remove(self.final_file) - return - - if chunk: - f.write(chunk) - downloaded_size += len(chunk) - - # 计算进度 - progress = int((downloaded_size / total_size) * 100) - self.progress_updated.emit(progress) - - # 6. 下载完成 - if not self.is_canceled and os.path.exists(self.final_file): - self.download_complete.emit(self.final_file) - - except Exception as e: - if not self.is_canceled: # 只有非取消的错误才发送信号 - self.error_occurred.emit(f"下载错误:{str(e)}") - # 清理不完整文件 - if os.path.exists(self.final_file): - try: - os.remove(self.final_file) - except: - pass - - def cancel(self): - """取消下载""" - self.is_canceled = True - -# --- 数据加载工作类 --- -class DataLoadWorker(QObject): - """数据加载工作类(与主线程分离,避免UI卡顿)""" - music_loaded = pyqtSignal(dict) - tags_loaded = pyqtSignal(dict) - announcements_loaded = pyqtSignal(dict) - finished = pyqtSignal() - - def run(self): - """加载音乐、标签、公告数据""" - # 1. 加载音乐 - music_data = get_all_music() - self.music_loaded.emit(music_data) - - # 2. 加载标签 - tags_data = get_music_tags() - self.tags_loaded.emit(tags_data) - - # 3. 加载公告 - announcements_data = get_announcements() - self.announcements_loaded.emit(announcements_data) - - # 4. 发送完成信号 - self.finished.emit() - -# --- 主窗口类 --- -class SunsetMusicApp(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("落日音乐 for PC") - self.setGeometry(100, 100, 1200, 700) - self.setMinimumSize(1000, 600) # 最小窗口尺寸 - - # 数据存储 - self.all_music = [] # 所有音乐列表 - self.filtered_music = [] # 筛选后音乐列表 - self.current_playing_file = ""# 当前播放的文件路径 - self.current_playing_music = None # 当前播放的音乐信息 - self.download_thread = None # 下载线程实例 - self.is_playing = False # 播放状态标记 - - # 过滤条件初始化 - self.current_keyword = "" - self.current_category = "all" # 默认显示全部 - - # 分类按钮组(用于管理按钮选中状态) - self.category_buttons = {} - self.category_button_group = QButtonGroup(self) - self.category_button_group.setExclusive(True) # 互斥选择 - - # 初始化 UI - self.init_ui() - # 加载音乐、标签、公告数据 - self.load_data_in_background() - # 初始化播放进度定时器 - self.init_playback_timer() - - def init_ui(self): - """构建完整 UI 界面""" - central_widget = QWidget() - self.setCentralWidget(central_widget) - main_layout = QVBoxLayout(central_widget) - - # 1. 菜单栏 - self.create_menu_bar() - - # 2. 顶部控制区(搜索+刷新) - top_control_layout = QHBoxLayout() - top_control_layout.setContentsMargins(10, 10, 10, 5) - - # 搜索框 - self.search_input = QLineEdit() - self.search_input.setPlaceholderText("搜索:标题/艺术家...") - self.search_input.setFixedHeight(30) - self.search_input.textChanged.connect(self.filter_music_by_search) - top_control_layout.addWidget(self.search_input, stretch=5) - - # 刷新按钮 - self.refresh_btn = QPushButton("刷新列表") - self.refresh_btn.setFixedHeight(30) - self.refresh_btn.setFixedWidth(100) - self.refresh_btn.clicked.connect(self.load_data_in_background) - top_control_layout.addWidget(self.refresh_btn, stretch=0) - - main_layout.addLayout(top_control_layout) - - # 3. 中部主内容区(分割窗口:左侧分类+列表 + 右侧播放器) - main_splitter = QSplitter(Qt.Horizontal) - main_splitter.setContentsMargins(10, 5, 10, 10) - - # 左侧区域(分类+列表) - left_widget = QWidget() - left_layout = QVBoxLayout(left_widget) - - # 3.1 分类区域(垂直布局的按钮) - self.category_container = QWidget() - self.category_layout = QVBoxLayout(self.category_container) - self.category_layout.setSpacing(5) # 按钮之间的间距 - self.category_layout.setContentsMargins(0, 0, 0, 0) - self.category_container.setFixedHeight(200) # 容纳垂直排列的按钮 - left_layout.addWidget(self.category_container) - - # 初始化分类按钮(垂直排列) - self.init_categories() - - # 3.2 音乐列表 - self.music_tree = QTreeWidget() - self.music_tree.setColumnCount(4) - self.music_tree.setHeaderLabels(["标题", "艺术家", "分类", "时长"]) - # 设置列宽 - self.music_tree.setColumnWidth(0, 300) # 标题列 - self.music_tree.setColumnWidth(1, 180) # 艺术家列 - self.music_tree.setColumnWidth(2, 100) # 分类列 - self.music_tree.setColumnWidth(3, 80) # 时长列 - # 单选模式 + 双击播放 - self.music_tree.setSelectionMode(QTreeWidget.SingleSelection) - self.music_tree.itemDoubleClicked.connect(self.on_music_double_click) - left_layout.addWidget(self.music_tree) - - main_splitter.addWidget(left_widget) - - # 右侧区域:标签页(播放器 + 公告) - self.right_tab = QTabWidget() - self.right_tab.setMinimumWidth(500) - - # 右侧1:播放器面板 - self.player_panel = QWidget() - self.init_player_panel() # 初始化播放控制UI - self.right_tab.addTab(self.player_panel, "音乐播放器") - - # 右侧2:公告面板 - self.announcement_panel = QTextEdit() - self.announcement_panel.setReadOnly(True) - self.announcement_panel.setStyleSheet("font-size: 14px; padding: 10px;") - self.right_tab.addTab(self.announcement_panel, "最新公告") - - main_splitter.addWidget(self.right_tab) - main_splitter.setSizes([600, 600]) # 左右窗口比例 - main_layout.addWidget(main_splitter, stretch=1) - - # 4. 底部状态栏 - self.status_bar = self.statusBar() - self.status_bar.showMessage("就绪:未播放音乐") - - def create_menu_bar(self): - """创建菜单栏(文件+帮助)""" - menubar = self.menuBar() - - # 文件菜单 - file_menu = menubar.addMenu("文件") - exit_action = QAction("退出", self) - exit_action.setShortcut("Ctrl+Q") - exit_action.triggered.connect(self.close) - file_menu.addAction(exit_action) - - # 帮助菜单 - help_menu = menubar.addMenu("帮助") - about_action = QAction("关于", self) - about_action.triggered.connect(self.show_about_dialog) - help_menu.addAction(about_action) - - def init_categories(self): - """初始化分类为垂直排列的按钮,每个按钮单独一行""" - # 清除现有按钮 - for btn in self.category_buttons.values(): - self.category_button_group.removeButton(btn) - btn.deleteLater() - self.category_buttons.clear() - - for category, config in CATEGORY_CONFIG.items(): - # 创建按钮 - btn = QPushButton(config['name']) - btn.setMinimumHeight(30) # 按钮高度 - btn.setCheckable(True) # 可选中 - btn.setStyleSheet(f""" - QPushButton {{ - background-color: {config['color']}; - color: {config['text_color']}; - border-radius: 4px; - font-weight: bold; - padding: 5px; - text-align: center; - }} - QPushButton:checked {{ - border: 2px solid #3498db; /* 选中状态边框 */ - }} - QPushButton:hover {{ - opacity: 0.9; /* 鼠标悬停效果 */ - }} - """) - - # 存储分类标识 - btn.setProperty("category", category) - - # 连接点击事件 - btn.clicked.connect(self.on_category_button_clicked) - - # 添加到布局和按钮组 - self.category_layout.addWidget(btn) - self.category_button_group.addButton(btn) - self.category_buttons[category] = btn - - # 默认选中"全部音乐" - if 'all' in self.category_buttons: - self.category_buttons['all'].setChecked(True) - - def on_category_button_clicked(self): - """处理分类按钮点击事件""" - # 获取点击的按钮 - btn = self.sender() - if btn: - # 获取分类标识 - self.current_category = btn.property("category") - self.apply_music_filters() - - def init_player_panel(self): - """初始化播放器面板""" - panel_layout = QVBoxLayout(self.player_panel) - panel_layout.setContentsMargins(20, 20, 20, 20) - - # 1. 当前播放音乐信息 - self.music_info_label = QLabel("当前未播放音乐") - self.music_info_label.setAlignment(Qt.AlignCenter) - self.music_info_label.setStyleSheet(""" - font-size: 16px; - font-weight: bold; - color: #2c3e50; - margin-bottom: 20px; - padding: 10px; - border-radius: 5px; - background-color: #f5f5f5; - """) - panel_layout.addWidget(self.music_info_label) - - # 2. 播放进度条 - self.play_progress = QSlider(Qt.Horizontal) - self.play_progress.setRange(0, 100) - self.play_progress.setValue(0) - self.play_progress.setDisabled(True) - self.play_progress.sliderReleased.connect(self.seek_playback) - panel_layout.addWidget(self.play_progress) - - # 3. 时间显示(已播放/总时长) - time_layout = QHBoxLayout() - self.current_time_label = QLabel("00:00") - self.total_time_label = QLabel("00:00") - time_layout.addWidget(self.current_time_label) - time_layout.addStretch(1) - time_layout.addWidget(self.total_time_label) - panel_layout.addLayout(time_layout) - - # 4. 播放控制按钮 - control_layout = QHBoxLayout() - control_layout.setAlignment(Qt.AlignCenter) - - self.prev_btn = QPushButton("上一曲") - self.prev_btn.setFixedSize(80, 40) - self.prev_btn.setDisabled(True) - self.prev_btn.clicked.connect(self.play_previous) - control_layout.addWidget(self.prev_btn) - - self.play_pause_btn = QPushButton("播放") - self.play_pause_btn.setFixedSize(100, 40) - self.play_pause_btn.setDisabled(True) - self.play_pause_btn.clicked.connect(self.toggle_play_pause) - control_layout.addWidget(self.play_pause_btn) - - self.next_btn = QPushButton("下一曲") - self.next_btn.setFixedSize(80, 40) - self.next_btn.setDisabled(True) - self.next_btn.clicked.connect(self.play_next) - control_layout.addWidget(self.next_btn) - - panel_layout.addLayout(control_layout) - - def init_playback_timer(self): - """初始化播放进度定时器""" - self.playback_timer = QTimer(self) - self.playback_timer.setInterval(1000) # 1秒=1000毫秒 - self.playback_timer.timeout.connect(self.update_playback_progress) - - def show_about_dialog(self): - """显示关于对话框""" - QMessageBox.about( - self, "关于落日音乐", - "落日音乐 for PC\n\n" - "版本:1.0.0\n" - "核心功能:\n" - "- 音乐分类与搜索\n" - "- 本地音频缓存\n" - "- 完整播放控制" - ) - - # --- 数据加载相关函数 --- - def load_data_in_background(self): - """后台加载音乐、标签、公告数据""" - self.status_bar.showMessage("正在加载数据...") - self.set_widgets_enabled(False) # 禁用UI控件 - - # 启动数据加载线程 - self.data_thread = QThread() - self.data_worker = DataLoadWorker() - self.data_worker.moveToThread(self.data_thread) - - # 连接线程信号 - self.data_thread.started.connect(self.data_worker.run) - self.data_worker.music_loaded.connect(self.on_music_data_loaded) - self.data_worker.tags_loaded.connect(self.on_tags_data_loaded) - self.data_worker.announcements_loaded.connect(self.on_announcements_loaded) - self.data_worker.finished.connect(self.data_thread.quit) - self.data_worker.finished.connect(lambda: self.set_widgets_enabled(True)) - self.data_worker.finished.connect(lambda: self.status_bar.showMessage("数据加载完成")) - - self.data_thread.start() - - def set_widgets_enabled(self, enabled): - """设置核心UI控件是否可用""" - self.search_input.setEnabled(enabled) - self.refresh_btn.setEnabled(enabled) - self.music_tree.setEnabled(enabled) - for btn in self.category_buttons.values(): - btn.setEnabled(enabled) - - def on_music_data_loaded(self, music_data): - """音乐数据加载完成后的处理""" - if not music_data or not music_data.get("success", False): - QMessageBox.warning(self, "警告", "音乐数据加载失败!") - return - - self.all_music = music_data["data"] - self.filtered_music = self.all_music.copy() - self.populate_music_tree() # 填充音乐列表 - self.status_bar.showMessage(f"加载完成:共 {len(self.all_music)} 首音乐") - - def on_tags_data_loaded(self, tags_data): - """标签数据加载完成后的处理(预留)""" - pass # 分类系统已由CATEGORY_CONFIG定义 - - def on_announcements_loaded(self, announcements_data): - """公告数据加载完成后的处理""" - self.announcement_panel.clear() - if not announcements_data or not announcements_data.get("success", False): - self.announcement_panel.setText("公告加载失败或暂无公告") - return - - announcements = announcements_data["data"] - for ann in announcements: - time_str = ann.get("time", "未知时间") - content = ann.get("nr", "无公告内容") - self.announcement_panel.append(f"【{time_str}】\n{content}\n" + "-"*50 + "\n") - - def populate_music_tree(self): - """将音乐数据填充到 TreeWidget 列表中""" - self.music_tree.clear() - for idx, music in enumerate(self.filtered_music): - # 获取分类配置 - category = music.get("category", "other") - cat_config = CATEGORY_CONFIG.get(category, CATEGORY_CONFIG["other"]) - - # 格式化时长 - duration = music.get("duration", "") - try: - duration_sec = int(duration) - duration_str = f"{duration_sec//60:02d}:{duration_sec%60:02d}" - except: - duration_str = "未知" - - # 创建列表项 - item = QTreeWidgetItem([ - music.get("title", "未知标题"), - music.get("artist", "未知艺术家"), - cat_config["name"], # 使用美化后的分类名称 - duration_str - ]) - - # 设置分类列的颜色 - item.setBackground(2, QBrush(QColor(cat_config["color"]))) - item.setForeground(2, QBrush(QColor(cat_config["text_color"]))) - - # 存储完整音乐信息 - item.setData(0, Qt.UserRole, music) - # 交替行颜色 - if idx % 2 == 0: - for col in [0, 1, 3]: - item.setBackground(col, QBrush(QColor(245, 245, 245))) - - self.music_tree.addTopLevelItem(item) - - # --- 音乐筛选相关函数 --- - def filter_music_by_search(self): - """根据搜索关键词筛选音乐""" - self.current_keyword = self.search_input.text().lower() - self.apply_music_filters() - - def apply_music_filters(self): - """应用筛选条件(关键词+分类)""" - self.filtered_music = [] - for music in self.all_music: - # 分类筛选 - category_match = True - if self.current_category != "all": - category_match = (music.get("category", "") == self.current_category) - - # 关键词筛选 - keyword_match = True - if self.current_keyword: - keyword = self.current_keyword - title = music.get("title", "").lower() - artist = music.get("artist", "").lower() - keyword_match = (keyword in title) or (keyword in artist) - - # 同时满足才加入筛选结果 - if category_match and keyword_match: - self.filtered_music.append(music) - - self.populate_music_tree() - self.highlight_search_keyword() - - def highlight_search_keyword(self): - """高亮音乐列表中的搜索关键词""" - if not self.current_keyword: - return - - # 遍历所有列表项,匹配关键词并高亮 - for idx in range(self.music_tree.topLevelItemCount()): - item = self.music_tree.topLevelItem(idx) - for col in range(2): # 只高亮标题和艺术家列 - text = item.text(col).lower() - if self.current_keyword in text: - item.setBackground(col, QBrush(QColor(255, 255, 153))) # 浅黄色高亮 - else: - # 恢复交替行颜色 - if idx % 2 == 0: - item.setBackground(col, QBrush(QColor(245, 245, 245))) - else: - item.setBackground(col, QBrush(QColor(255, 255, 255))) - - # --- 音乐播放相关函数 --- - def on_music_double_click(self, item, column): - """双击音乐列表项:下载并播放""" - # 获取当前选中的音乐信息 - music = item.data(0, Qt.UserRole) - if not music: - return - - # 1. 停止当前播放和下载(如果有) - self.stop_playback() - if self.download_thread and self.download_thread.isRunning(): - self.download_thread.cancel() - self.download_thread.wait() - - # 2. 记录当前播放的音乐信息 - self.current_playing_music = music - music_id = music.get("id") - music_title = music.get("title", "未知音乐") - music_artist = music.get("artist", "未知艺术家") - music_category = music.get("category", "other") - - # 获取分类名称 - cat_name = CATEGORY_CONFIG.get(music_category, CATEGORY_CONFIG["other"])["name"] - - # 3. 更新UI显示 - self.music_info_label.setText(f"{music_title} - {music_artist}\n【{cat_name}】") - self.status_bar.showMessage(f"准备下载:{music_title}") - - # 4. 创建下载进度对话框 - self.download_dialog = QProgressDialog(f"正在下载 {music_title}...", "取消", 0, 100, self) - self.download_dialog.setWindowTitle("下载中") - self.download_dialog.setWindowModality(Qt.WindowModal) - self.download_dialog.canceled.connect(self.cancel_download) - - # 5. 启动下载线程 - self.download_thread = DownloadThread(music_id, music_title, music_artist) - # 连接信号 - self.download_thread.progress_updated.connect(self.download_dialog.setValue) - self.download_thread.download_complete.connect(self.on_download_complete) - self.download_thread.error_occurred.connect(self.on_download_error) - # 启动线程 - self.download_thread.start() - - def cancel_download(self): - """取消下载""" - if self.download_thread and self.download_thread.isRunning(): - self.download_thread.cancel() - self.status_bar.showMessage("下载已取消") - - def on_download_complete(self, file_path): - """下载完成后播放音乐""" - if hasattr(self, 'download_dialog'): - self.download_dialog.close() - - if not os.path.exists(file_path): - self.on_playback_error("下载文件不存在") - return - - # 开始播放 - self.current_playing_file = file_path - self.start_playback() - - def on_download_error(self, error_msg): - """下载错误处理""" - if hasattr(self, 'download_dialog'): - self.download_dialog.close() - - self.on_playback_error(error_msg) - - def start_playback(self): - """开始播放音频""" - if not self.current_playing_file or not os.path.exists(self.current_playing_file): - self.on_playback_error("播放文件不存在") - return - - try: - # 加载并播放音频 - pygame.mixer.music.load(self.current_playing_file) - pygame.mixer.music.play() - self.is_playing = True - - # 更新UI状态 - self.play_pause_btn.setText("暂停") - self.play_pause_btn.setEnabled(True) - self.prev_btn.setEnabled(len(self.filtered_music) > 1) - self.next_btn.setEnabled(len(self.filtered_music) > 1) - self.play_progress.setDisabled(False) - - # 获取并显示总时长 - total_duration = pygame.mixer.Sound(self.current_playing_file).get_length() # 秒 - self.total_duration = total_duration - self.total_time_label.setText(self.format_time(total_duration)) - - # 启动播放进度定时器 - self.playback_timer.start() - self.status_bar.showMessage( - f"正在播放:{self.current_playing_music['title']}" - ) - - except Exception as e: - self.on_playback_error(f"播放失败:{str(e)}") - - def on_playback_error(self, error_msg): - """播放错误的处理""" - # 重置UI - self.play_progress.setDisabled(True) - self.play_pause_btn.setText("播放") - self.play_pause_btn.setEnabled(False) - self.prev_btn.setEnabled(False) - self.next_btn.setEnabled(False) - - # 更新状态信息 - self.music_info_label.setText(f"播放失败:{error_msg}") - self.status_bar.showMessage(f"错误:{error_msg}") - - # 弹出错误提示 - QMessageBox.warning(self, "播放错误", error_msg) - - def toggle_play_pause(self): - """切换播放/暂停状态""" - if not self.current_playing_file: - return - - if self.is_playing: - pygame.mixer.music.pause() - self.is_playing = False - self.play_pause_btn.setText("播放") - self.playback_timer.stop() - if self.current_playing_music: - self.status_bar.showMessage( - f"已暂停:{self.current_playing_music['title']}" - ) - else: - pygame.mixer.music.unpause() - self.is_playing = True - self.play_pause_btn.setText("暂停") - self.playback_timer.start() - if self.current_playing_music: - self.status_bar.showMessage( - f"继续播放:{self.current_playing_music['title']}" - ) - - def stop_playback(self): - """停止当前播放""" - if pygame.mixer.music.get_busy() or self.is_playing: - pygame.mixer.music.stop() - self.is_playing = False - - # 重置UI - self.playback_timer.stop() - self.play_progress.setValue(0) - self.current_time_label.setText("00:00") - self.total_time_label.setText("00:00") - self.play_pause_btn.setText("播放") - self.play_pause_btn.setEnabled(False) - self.prev_btn.setEnabled(False) - self.next_btn.setEnabled(False) - - self.current_playing_file = "" - - def update_playback_progress(self): - """更新播放进度条和时间显示""" - if not self.current_playing_file or not self.is_playing: - return - - # 获取当前播放位置(秒) - current_pos = pygame.mixer.music.get_pos() / 1000 # 毫秒转秒 - # 计算进度百分比 - progress = int((current_pos / self.total_duration) * 100) - progress = min(progress, 100) # 避免超过100% - - # 更新UI - self.play_progress.setValue(progress) - self.current_time_label.setText(self.format_time(current_pos)) - - # 检查是否播放完成 - if current_pos >= self.total_duration - 0.5: # 留出一点误差空间 - self.play_next() # 自动播放下一曲 - - def seek_playback(self): - """拖动进度条跳转播放位置""" - if not self.current_playing_file or not self.is_playing: - return - - # 获取进度条百分比对应的播放位置(秒) - progress = self.play_progress.value() - target_pos = (progress / 100) * self.total_duration - # 跳转播放位置 - pygame.mixer.music.set_pos(target_pos) - # 更新时间显示 - self.current_time_label.setText(self.format_time(target_pos)) - - def play_previous(self): - """播放上一曲""" - if not self.current_playing_music or len(self.filtered_music) <= 1: - return - - # 找到当前播放音乐在筛选列表中的索引 - current_idx = -1 - current_id = self.current_playing_music.get("id") - for idx, music in enumerate(self.filtered_music): - if str(music.get("id")) == str(current_id): - current_idx = idx - break - - # 播放前一项(循环到最后一项如果当前是第一项) - if current_idx == 0: - target_idx = len(self.filtered_music) - 1 - else: - target_idx = current_idx - 1 - - # 触发双击播放 - target_item = self.music_tree.topLevelItem(target_idx) - self.on_music_double_click(target_item, 0) - - def play_next(self): - """播放下一曲""" - if not self.current_playing_music or len(self.filtered_music) <= 1: - return - - # 找到当前播放音乐在筛选列表中的索引 - current_idx = -1 - current_id = self.current_playing_music.get("id") - for idx, music in enumerate(self.filtered_music): - if str(music.get("id")) == str(current_id): - current_idx = idx - break - - # 播放后一项(循环到第一项如果当前是最后一项) - if current_idx == len(self.filtered_music) - 1: - target_idx = 0 - else: - target_idx = current_idx + 1 - - # 触发双击播放 - target_item = self.music_tree.topLevelItem(target_idx) - self.on_music_double_click(target_item, 0) - - @staticmethod - def format_time(seconds): - """将秒数格式化为 分:秒""" - minutes = int(seconds // 60) - secs = int(seconds % 60) - return f"{minutes:02d}:{secs:02d}" - - def closeEvent(self, event): - """窗口关闭时的清理操作""" - # 停止播放 - self.stop_playback() - # 停止下载线程 - if hasattr(self, "download_thread") and self.download_thread.isRunning(): - self.download_thread.cancel() - self.download_thread.wait() - # 退出 pygame - pygame.mixer.quit() - event.accept() - -# --- 程序入口 --- -if __name__ == "__main__": - # 解决 PyQt5 中文显示问题 - QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) - app = QApplication(sys.argv) - - # 设置全局样式 - app.setStyle("Fusion") - - # 启动应用 - window = SunsetMusicApp() - window.show() - sys.exit(app.exec_())