首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

【Python 原创】开发本地音乐播放器开源-提供源码【小白学习研究使用】

使用Python和PyQt6开发的本地音乐播放器,它提供了基本的音乐播放功能,如播放、暂停、停止、切换歌曲等,同时支持多种播放模式。以下是该音乐播放器的详细介绍:

1. 功能概述

文件选择

:支持打开单个音乐文件和整个音乐文件夹。

播放控制

:提供播放、暂停、停止、上一首、下一首等基本播放控制功能。

播放模式

:支持顺序播放、单曲循环、全部循环和随机播放四种播放模式。

音量控制

:可以通过滑块调节音量大小。

播放进度显示

:显示当前播放歌曲的进度和总时长。

播放列表

:显示已添加的歌曲列表,支持双击播放选中的歌曲。

2. 代码结构

2.1 导入必要的库

import sys

import os

import random

from PyQt6.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout,

                          QHBoxLayout, QFileDialog, QLabel, QSlider, QListWidget,

                          QWidget, QStyle, QComboBox, QSplitter)

from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput

from PyQt6.QtCore import Qt, QUrl, QTimer, QTime

from PyQt6.QtGui import QIcon

2.2 定义MusicPlayer类

该类继承自QMainWindow,是音乐播放器的主窗口类。

__init__方法

:初始化窗口标题、大小,创建媒体播放器和音频输出对象,初始化播放列表和当前播放索引,调用init_ui方法初始化 UI,设置计时器和信号槽连接。

init_ui方法

:创建主布局,包括顶部的歌曲信息显示、中间的播放列表和底部的控制按钮。设置样式表美化界面。

open_file方法

:打开单个音乐文件,并将其添加到播放列表中。

open_folder方法

:打开一个音乐文件夹,将文件夹中的所有音乐文件添加到播放列表中。

add_song方法

:将指定的音乐文件添加到播放列表中,并显示歌曲信息。

play_song方法

:播放指定索引的歌曲,更新歌曲信息和播放按钮图标。

play_pause方法

:实现播放和暂停功能,切换播放按钮图标。

stop方法

:停止播放,重置播放按钮图标和进度条。

play_previous方法

:播放上一首歌曲。

play_next方法

:根据当前播放模式播放下一首歌曲。

set_volume方法

:设置音量大小。

update_time方法

:更新播放时间。

update_position方法

:更新播放进度条和时间标签。

update_duration方法

:更新播放进度条的范围。

set_position方法

:设置播放位置。

media_status_changed方法

:处理媒体状态改变事件,如播放结束时根据播放模式切换歌曲。

change_play_mode方法

:更改播放模式。

2.3 主程序入口

if __name__ == "__main__":

  try:

      app = QApplication(sys.argv)

      player = MusicPlayer()

      player.show()

      sys.exit(app.exec())

  except Exception as e:

      print(f"主程序运行时出错: {e}")3. 界面设计

顶部

:显示当前播放歌曲的信息,包括歌曲名称、专辑、大小和播放时间,以及播放进度条。

中间

:显示播放列表,用户可以双击列表中的歌曲进行播放。

底部

:提供文件选择按钮、播放控制按钮、音量控制滑块和播放模式选择下拉框。

4. 样式美化

使用样式表对界面进行美化,设置了窗口背景颜色、按钮颜色、滑块样式等,使界面更加美观。

5. 异常处理

在各个方法中使用try-except语句捕获可能出现的异常,并打印错误信息,提高程序的健壮性。

6. 注意事项

代码中add_song方法里尝试获取歌曲时长时,仅对.wav文件进行了处理,对于其他格式的文件可能无法获取正确的时长信息。

代码中缺少wave库的导入,需要在文件开头添加import wave才能正常获取.wav文件的时长。

科技之星提供开源源码

***简约版:

import sys

import os

import random

from PyQt6.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout,

                          QHBoxLayout, QFileDialog, QLabel, QSlider, QListWidget,

                          QWidget, QStyle, QComboBox, QSplitter)

from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput

from PyQt6.QtCore import Qt, QUrl, QTimer, QTime

from PyQt6.QtGui import QIcon

class MusicPlayer(QMainWindow):

  def __init__(self):

      try:

          super().__init__()

          # 设置窗口标题和大小

          self.setWindowTitle("本地音乐播放器")

          self.setGeometry(100, 100, 800, 150)  # 修改窗口大小为 800x150

          # 媒体播放器和音频输出

          self.player = QMediaPlayer()

          self.audio_output = QAudioOutput()

          self.player.setAudioOutput(self.audio_output)

          # 播放列表

          self.playlist = []

          self.current_index = -1

          # 初始化UI

          self.init_ui()

          # 计时器用于更新播放时间

          self.timer = QTimer(self)

          self.timer.timeout.connect(self.update_time)

          self.timer.start(100)  # 每100ms更新一次

          # 连接信号和槽

          self.player.positionChanged.connect(self.update_position)

          self.player.durationChanged.connect(self.update_duration)

          self.player.mediaStatusChanged.connect(self.media_status_changed)

          # 播放模式:0-顺序播放,1-单曲循环,2-全部循环,3-随机播放

          self.play_mode = 0

      except Exception as e:

          print(f"初始化时出错: {e}")

  def init_ui(self):

      try:

          # 创建主布局

          main_layout = QVBoxLayout()

          # 顶部:歌曲信息

          top_widget = QWidget()

          top_layout = QVBoxLayout(top_widget)

          # 歌曲信息

          info_layout = QHBoxLayout()

          self.song_label = QLabel("未播放任何歌曲")

          self.song_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)

          self.song_label.setStyleSheet("font-size: 20px; font-weight: bold;")  # 增大字体大小

          self.album_label = QLabel("专辑: -")

          self.album_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)

          self.album_label.setStyleSheet("font-size: 20px;")  # 增大字体大小

          self.size_label = QLabel("大小: -")

          self.size_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)

          self.size_label.setStyleSheet("font-size: 20px;")  # 增大字体大小

          self.time_label = QLabel("00:00/00:00")

          self.time_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)

          self.time_label.setStyleSheet("font-size: 20px;")  # 增大字体大小

          info_layout.addWidget(self.song_label)

          info_layout.addWidget(self.album_label)

          info_layout.addWidget(self.size_label)

          info_layout.addWidget(self.time_label)

          # 进度条

          self.progress_slider = QSlider(Qt.Orientation.Horizontal)

          self.progress_slider.sliderMoved.connect(self.set_position)

          top_layout.addLayout(info_layout)

          top_layout.addWidget(self.progress_slider)

          # 中间:播放列表

          middle_widget = QWidget()

          middle_layout = QVBoxLayout(middle_widget)

          list_label = QLabel("播放列表")

          list_label.setStyleSheet("font-size: 20px; font-weight: bold; margin-bottom: 5px;")  # 增大字体大小

          self.song_list = QListWidget()

          self.song_list.doubleClicked.connect(self.play_selected_song)

          middle_layout.addWidget(list_label)

          middle_layout.addWidget(self.song_list)

          # 底部:控制按钮

          bottom_widget = QWidget()

          bottom_layout = QHBoxLayout(bottom_widget)

          # 文件选择按钮

          self.open_file_btn = QPushButton("打开文件")

          self.open_file_btn.clicked.connect(self.open_file)

          self.open_folder_btn = QPushButton("打开文件夹")

          self.open_folder_btn.clicked.connect(self.open_folder)

          # 播放控制按钮

          self.prev_btn = QPushButton()

          self.prev_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaSkipBackward))

          self.prev_btn.clicked.connect(self.play_previous)

          self.play_btn = QPushButton()

          self.play_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))

          self.play_btn.clicked.connect(self.play_pause)

          self.stop_btn = QPushButton()

          self.stop_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaStop))

          self.stop_btn.clicked.connect(self.stop)

          self.next_btn = QPushButton()

          self.next_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaSkipForward))

          self.next_btn.clicked.connect(self.play_next)

          # 音量控制

          self.volume_slider = QSlider(Qt.Orientation.Horizontal)

          self.volume_slider.setMaximumWidth(100)

          self.volume_slider.setRange(0, 100)

          self.volume_slider.setValue(70)

          self.volume_slider.valueChanged.connect(self.set_volume)

          self.audio_output.setVolume(0.7)  # 0.0 to 1.0

          volume_label = QLabel("音量:")

          volume_label.setStyleSheet("font-size: 20px;")  # 增大字体大小

          # 播放模式

          self.mode_combo = QComboBox()

          self.mode_combo.addItems(["顺序播放", "单曲循环", "全部循环", "随机播放"])

          self.mode_combo.setStyleSheet("font-size: 20px;")  # 增大字体大小

          self.mode_combo.currentIndexChanged.connect(self.change_play_mode)

          # 添加所有按钮到底部布局

          bottom_layout.addWidget(self.open_file_btn)

          bottom_layout.addWidget(self.open_folder_btn)

          bottom_layout.addWidget(self.prev_btn)

          bottom_layout.addWidget(self.play_btn)

          bottom_layout.addWidget(self.stop_btn)

          bottom_layout.addWidget(self.next_btn)

          bottom_layout.addWidget(volume_label)

          bottom_layout.addWidget(self.volume_slider)

          bottom_layout.addWidget(self.mode_combo)

          # 使用QSplitter来分割界面

          splitter = QSplitter(Qt.Orientation.Vertical)

          splitter.addWidget(top_widget)

          splitter.addWidget(middle_widget)

          splitter.addWidget(bottom_widget)

          # 设置分割器的初始大小

          splitter.setSizes([200, 300, 100])

          main_layout.addWidget(splitter)

          # 设置中央窗口

          central_widget = QWidget()

          central_widget.setLayout(main_layout)

          self.setCentralWidget(central_widget)

          # 设置样式

          self.setStyleSheet("""

              QMainWindow {

                  background-color: #f5f5f5;

              }

              QPushButton {

                  background-color: #4CAF50;

                  color: white;

                  border: none;

                  padding: 8px 16px;

                  border-radius: 4px;

                  font-size: 20px;  # 增大按钮字体大小

              }

              QPushButton:hover {

                  background-color: #45a049;

              }

              QPushButton:pressed {

                  background-color: #3d8b40;

              }

              QSlider::groove:horizontal {

                  border: 1px solid #bbb;

                  background: white;

                  height: 10px;

                  border-radius: 4px;

              }

              QSlider::handle:horizontal {

                  background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #4CAF50, stop:1 #45a049);

                  border: 1px solid #5c5c5c;

                  width: 18px;

                  margin: -4px 0;

                  border-radius: 8px;

              }

              QListWidget {

                  background-color: white;

                  border: 1px solid #ddd;

                  border-radius: 4px;

              }

              QLabel {

                  color: #333;

              }

              QComboBox {

                  border: 1px solid #ddd;

                  border-radius: 4px;

                  padding: 5px;

                  background-color: white;

              }

          """)

      except Exception as e:

          print(f"初始化UI时出错: {e}")

  def open_file(self):

      try:

          file_path, _ = QFileDialog.getOpenFileName(

              self, "打开音乐文件", "", "音频文件 (*.mp3 *.wav *.flac *.m4a)"

          )

          if file_path:

              self.add_song(file_path)

              self.play_song(0)

      except Exception as e:

          print(f"打开文件时出错: {e}")

  def open_folder(self):

      try:

          folder_path = QFileDialog.getExistingDirectory(self, "打开音乐文件夹")

          if folder_path:

              for file_name in os.listdir(folder_path):

                  if file_name.endswith(('.mp3', '.wav', '.flac', '.m4a')):

                      file_path = os.path.join(folder_path, file_name)

                      self.add_song(file_path)

              if self.playlist:

                  self.play_song(0)

      except Exception as e:

          print(f"打开文件夹时出错: {e}")

  def add_song(self, file_path):

      try:

          file_name = os.path.basename(file_path)

          self.playlist.append(file_path)

          self.song_list.addItem(file_name)

          # 获取文件大小

          file_size = os.path.getsize(file_path) / (1024 * 1024)  # MB

          file_size_str = f"{file_size:.2f} MB"

          # 尝试获取歌曲时长

          duration_str = "未知时长"

          try:

              if file_path.endswith('.wav'):

                  with wave.open(file_path, 'r') as wf:

                      frames = wf.getnframes()

                      rate = wf.getframerate()

                      duration = frames / float(rate)

                      duration_str = str(QTime(0, 0).addSecs(int(duration)).toString("mm:ss"))

          except:

              pass

          # 更新歌曲信息

          song_info = f"{file_name} - {duration_str} - {file_size_str}"

          self.song_list.item(self.song_list.count() - 1).setToolTip(song_info)

      except Exception as e:

          print(f"添加歌曲时出错: {e}")

  def play_song(self, index):

      try:

          if 0 <= index < len(self.playlist):

              self.current_index = index

              file_path = self.playlist[index]

              file_name = os.path.basename(file_path)

              # 更新歌曲信息

              self.song_label.setText(f"正在播放: {file_name}")

              # 获取文件大小

              file_size = os.path.getsize(file_path) / (1024 * 1024)  # MB

              self.size_label.setText(f"大小: {file_size:.2f} MB")

              # 加载媒体 - 修改此处以兼容PyQt6.4+

              self.player.setSource(QUrl.fromLocalFile(file_path))

              self.player.errorOccurred.connect(self.handle_media_error)

              self.player.play()

              # 更新播放按钮图标

              self.play_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPause))

              # 选中当前播放的歌曲

              self.song_list.setCurrentRow(index)

      except Exception as e:

          print(f"播放歌曲时出错: {e}")

  def handle_media_error(self, error, error_string):

      print(f"媒体播放错误: {error_string}")

  def play_selected_song(self):

      try:

          index = self.song_list.currentRow()

          if index >= 0:

              self.play_song(index)

      except Exception as e:

          print(f"播放选中歌曲时出错: {e}")

  def play_pause(self):

      try:

          if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:

              self.player.pause()

              self.play_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))

          else:

              if self.player.mediaStatus() == QMediaPlayer.MediaStatus.NoMedia and self.playlist:

                  self.play_song(0)

              else:

                  self.player.play()

                  self.play_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPause))

      except Exception as e:

          print(f"播放/暂停操作时出错: {e}")

  def stop(self):

      try:

          self.player.stop()

          self.play_btn.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))

          self.progress_slider.setValue(0)

          self.time_label.setText("00:00/00:00")

      except Exception as e:

          print(f"停止播放时出错: {e}")

  def play_previous(self):

      try:

          if not self.playlist:

              return

          if self.current_index > 0:

              self.play_song(self.current_index - 1)

          else:

              self.play_song(len(self.playlist) - 1)

      except Exception as e:

          print(f"播放上一首时出错: {e}")

  def play_next(self):

      try:

          if not self.playlist:

              return

          if self.play_mode == 3:  # 随机播放

              next_index = random.randint(0, len(self.playlist) - 1)

              self.play_song(next_index)

          else:

              if self.current_index < len(self.playlist) - 1:

                  self.play_song(self.current_index + 1)

              else:

                  if self.play_mode == 2:  # 全部循环

                      self.play_song(0)

                  else:  # 顺序播放,播放完最后一首停止

                      self.stop()

      except Exception as e:

          print(f"播放下一首时出错: {e}")

  def set_volume(self, value):

      try:

          self.audio_output.setVolume(value / 100.0)

      except Exception as e:

          print(f"设置音量时出错: {e}")

  def update_time(self):

      try:

          pass

      except Exception as e:

          print(f"更新时间时出错: {e}")

  def update_position(self, position):

      try:

          self.progress_slider.setValue(position)

          # 更新时间标签

          current_time = QTime(0, 0).addMSecs(position)

          total_time = QTime(0, 0).addMSecs(self.player.duration())

          self.time_label.setText(f"{current_time.toString('mm:ss')}/{total_time.toString('mm:ss')}")

      except Exception as e:

          print(f"更新播放位置时出错: {e}")

  def update_duration(self, duration):

      try:

          self.progress_slider.setRange(0, duration)

      except Exception as e:

          print(f"更新播放时长时出错: {e}")

  def set_position(self, position):

      try:

          self.player.setPosition(position)

      except Exception as e:

          print(f"设置播放位置时出错: {e}")

  def media_status_changed(self, status):

      try:

          if status == QMediaPlayer.MediaStatus.EndOfMedia:

              if self.play_mode == 1:  # 单曲循环

                  self.player.setPosition(0)

                  self.player.play()

              else:

                  self.play_next()

      except Exception as e:

          print(f"媒体状态改变时出错: {e}")

  def change_play_mode(self, index):

      try:

          self.play_mode = index

      except Exception as e:

          print(f"更改播放模式时出错: {e}")

if __name__ == "__main__":

  try:

      app = QApplication(sys.argv)

      player = MusicPlayer()

      player.show()

      sys.exit(app.exec())

  except Exception as e:

      print(f"主程序运行时出错: {e}")高级本地播放器源码:

# main.py

import sys

import os

import random

import numpy as np

import pyaudio

import pyqtgraph as pg

import mutagen

from pygame import mixer

from PyQt6.QtWidgets import (

  QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QFileDialog,

  QListWidget, QLabel, QHBoxLayout, QSlider, QMessageBox, QComboBox,

  QDialog, QLineEdit, QInputDialog, QStyle, QFrame, QTabWidget, QMenu

)

from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal, QUrl, QSize

from PyQt6.QtGui import (

  QIcon, QPixmap, QTransform, QPainter, QPalette, QColor, QFont,

  QBrush, QPen, QAction, QShortcut, QKeySequence

)

# 初始化混音器

mixer.init()

# ==================== 音频可视化模块 ====================

class AudioVisualizer(pg.PlotWidget):

  """实时音频波形可视化组件"""

  def __init__(self):

      super().__init__()

      self.setBackground("#1e1e2e")

      self.setMaximumHeight(150)

      self.curve = self.plot(pen=pg.mkPen('#89b4fa', width=2))

      self.data = np.zeros(1024)

      self.setMouseEnabled(x=False, y=False)

      self.hideAxis('bottom')

      self.hideAxis('left')

  def update_plot(self, new_data):

      """更新波形显示"""

      self.data = np.roll(self.data, -len(new_data))

      self.data[-len(new_data):] = new_data

      self.curve.setData(self.data)

class AudioAnalyzer(QThread):

  """音频分析线程"""

  data_ready = pyqtSignal(np.ndarray)

  def __init__(self):

      super().__init__()

      self.p = pyaudio.PyAudio()

      self.stream = None

      self.running = True

  def run(self):

      """采集音频数据"""

      try:

          self.stream = self.p.open(

              format=pyaudio.paInt16,

              channels=1,

              rate=44100,

              input=True,

              frames_per_buffer=1024,

              stream_callback=self.callback

          )

          while self.running:

              pass

      finally:

          if self.stream:

              self.stream.stop_stream()

              self.stream.close()

          self.p.terminate()

  def callback(self, in_data, frame_count, time_info, status):

      """音频回调函数"""

      data = np.frombuffer(in_data, dtype=np.int16)

      self.data_ready.emit(data.astype(float))

      return (in_data, pyaudio.paContinue)

  def stop(self):

      """停止采集"""

      self.running = False

# ==================== 均衡器模块 ====================

class EqualizerDialog(QDialog):

  """五段均衡器对话框"""

  def __init__(self, parent=None):

      super().__init__(parent)

      self.setWindowTitle("均衡器")

      self.setFixedSize(400, 300)

      self.init_ui()

  def init_ui(self):

      """界面初始化"""

      layout = QHBoxLayout()

      frequencies = [60, 230, 910, 4000, 14000]

      for freq in frequencies:

          freq_layout = QVBoxLayout()

          slider = QSlider(Qt.Orientation.Vertical)

          slider.setRange(-12, 12)

          slider.setValue(0)

          slider.setFixedSize(60, 200)

          slider.setStyleSheet("""

              QSlider::groove:vertical {

                  width: 4px;

                  background: #313244;

                  border-radius: 2px;

              }

              QSlider::handle:vertical {

                  height: 16px;

                  background: #89b4fa;

                  border: 1px solid #45475a;

                  border-radius: 8px;

                  margin: 0 -8px;

              }

          """)

          freq_label = QLabel(f"{freq}Hz")

          freq_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

          freq_layout.addWidget(slider)

          freq_layout.addWidget(freq_label)

          layout.addLayout(freq_layout)

      self.setLayout(layout)

# ==================== 主界面 ====================

class MusicPlayer(QMainWindow):

  def __init__(self):

      super().__init__()

      self.setup_ui()

      self.setup_audio()

      self.setup_connections()

      self.current_index = -1

      self.music_files = []

      self.play_mode = "列表循环"

      self.pause_pos = 0

  def setup_ui(self):

      """初始化界面"""

      self.setWindowTitle("高级音乐播放器")

      self.setGeometry(100, 100, 1000, 700)

      self.setWindowIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay))

      # 主布局

      self.central_widget = QWidget()

      self.setCentralWidget(self.central_widget)

      self.main_layout = QVBoxLayout(self.central_widget)

      self.main_layout.setSpacing(10)

      self.main_layout.setContentsMargins(15, 15, 15, 15)

      # 顶部信息区

      self.setup_header()

      # 可视化组件

      self.visualizer = AudioVisualizer()

      self.main_layout.addWidget(self.visualizer)

      # 播放列表区

      self.setup_playlist()

      # 控制区

      self.setup_controls()

  def setup_header(self):

      """顶部信息区域"""

      header_frame = QFrame()

      header_frame.setStyleSheet("background-color: #313244; border-radius: 8px;")

      header_layout = QGridLayout(header_frame)

      # 元数据显示

      self.title_label = QLabel("标题: -")

      self.artist_label = QLabel("艺术家: -")

      self.album_label = QLabel("专辑: -")

      self.duration_label = QLabel("时长: -")

      metadata_grid = QGridLayout()

      metadata_grid.addWidget(QLabel("标题:"), 0, 0)

      metadata_grid.addWidget(self.title_label, 0, 1)

      metadata_grid.addWidget(QLabel("艺术家:"), 1, 0)

      metadata_grid.addWidget(self.artist_label, 1, 1)

      metadata_grid.addWidget(QLabel("专辑:"), 2, 0)

      metadata_grid.addWidget(self.album_label, 2, 1)

      metadata_grid.addWidget(QLabel("时长:"), 3, 0)

      metadata_grid.addWidget(self.duration_label, 3, 1)

      header_layout.addLayout(metadata_grid, 0, 0)

      # 专辑封面

      self.album_cover = QLabel()

      self.album_cover.setFixedSize(180, 180)

      self.album_cover.setStyleSheet("border: 2px solid #45475a; border-radius: 8px;")

      header_layout.addWidget(self.album_cover, 0, 1, 4, 1)

      # 进度条

      self.progress_slider = QSlider(Qt.Orientation.Horizontal)

      self.progress_slider.setRange(0, 1000)

      self.progress_label = QLabel("00:00 / 00:00")

      progress_layout = QVBoxLayout()

      progress_layout.addWidget(self.progress_label)

      progress_layout.addWidget(self.progress_slider)

      header_layout.addLayout(progress_layout, 4, 0, 1, 2)

      self.main_layout.addWidget(header_frame)

  def setup_playlist(self):

      """播放列表区域"""

      playlist_frame = QFrame()

      playlist_frame.setStyleSheet("background-color: #313244; border-radius: 8px;")

      self.playlist_layout = QVBoxLayout(playlist_frame)

      # 搜索框

      self.search_box = QLineEdit()

      self.search_box.setPlaceholderText("搜索歌曲...")

      self.search_box.setClearButtonEnabled(True)

      self.playlist_layout.addWidget(self.search_box)

      # 播放列表

      self.playlist_widget = QListWidget()

      self.playlist_widget.setAlternatingRowColors(True)

      self.playlist_widget.setDragDropMode(QListWidget.DragDropMode.InternalMove)

      self.playlist_widget.setStyleSheet("""

          QListWidget {

              background-color: #1e1e2e;

              border: none;

              font-size: 14px;

          }

          QListWidget::item {

              padding: 8px;

              border-bottom: 1px solid #313244;

          }

          QListWidget::item:selected {

              background-color: #45475a;

          }

      """)

      self.playlist_layout.addWidget(self.playlist_widget)

      self.main_layout.addWidget(playlist_frame)

  def setup_controls(self):

      """控制区域"""

      control_frame = QFrame()

      control_frame.setStyleSheet("background-color: #313244; border-radius: 8px;")

      control_layout = QHBoxLayout(control_frame)

      control_layout.setContentsMargins(10, 10, 10, 10)

      # 基础按钮

      self.btn_open = QPushButton("打开文件")

      self.btn_folder = QPushButton("打开文件夹")

      self.btn_prev = QPushButton("上一首")

      self.btn_play = QPushButton("播放")

      self.btn_next = QPushButton("下一首")

      self.btn_stop = QPushButton("停止")

      self.btn_eq = QPushButton("均衡器")

      # 音量控制

      self.volume_slider = QSlider(Qt.Orientation.Horizontal)

      self.volume_slider.setRange(0, 100)

      self.volume_slider.setValue(50)

      volume_layout = QHBoxLayout()

      volume_layout.addWidget(QLabel("音量:"))

      volume_layout.addWidget(self.volume_slider)

      # 播放模式

      self.mode_combo = QComboBox()

      self.mode_combo.addItems(["列表循环", "单曲循环", "随机播放"])

      # 睡眠定时器

      self.timer_combo = QComboBox()

      self.timer_combo.addItems(["关闭定时", "30分钟", "1小时", "自定义"])

      # 布局管理

      control_layout.addWidget(self.btn_open)

      control_layout.addWidget(self.btn_folder)

      control_layout.addWidget(self.btn_prev)

      control_layout.addWidget(self.btn_play)

      control_layout.addWidget(self.btn_next)

      control_layout.addWidget(self.btn_stop)

      control_layout.addWidget(self.btn_eq)

      control_layout.addLayout(volume_layout)

      control_layout.addWidget(self.mode_combo)

      control_layout.addWidget(self.timer_combo)

      self.main_layout.addWidget(control_frame)

  def setup_audio(self):

      """初始化音频组件"""

      self.analyzer = AudioAnalyzer()

      self.analyzer.data_ready.connect(self.visualizer.update_plot)

      self.timer = QTimer()

      self.sleep_timer = QTimer()

  def setup_connections(self):

      """连接信号与槽"""

      # 按钮连接

      self.btn_open.clicked.connect(self.open_file)

      self.btn_folder.clicked.connect(self.open_folder)

      self.btn_prev.clicked.connect(self.play_prev)

      self.btn_play.clicked.connect(self.toggle_play)

      self.btn_next.clicked.connect(self.play_next)

      self.btn_stop.clicked.connect(self.stop)

      self.btn_eq.clicked.connect(self.open_equalizer)

      # 其他连接

      self.playlist_widget.itemDoubleClicked.connect(self.play_selected)

      self.volume_slider.valueChanged.connect(self.set_volume)

      self.mode_combo.currentTextChanged.connect(self.set_play_mode)

      self.timer.timeout.connect(self.update_progress)

      self.sleep_timer.timeout.connect(self.stop)

      self.search_box.textChanged.connect(self.filter_playlist)

      # 快捷键

      QShortcut(QKeySequence("Space"), self).activated.connect(self.toggle_play)

      QShortcut(QKeySequence("Ctrl+Right"), self).activated.connect(self.play_next)

      QShortcut(QKeySequence("Ctrl+Left"), self).activated.connect(self.play_prev)

  # ==================== 核心功能方法 ====================

  def open_file(self):

      """打开音频文件"""

      files, _ = QFileDialog.getOpenFileNames(

          self, "选择音频文件", "",

          "音频文件 (*.mp3 *.wav *.flac *.ogg *.m4a)"

      )

      if files:

          self.add_to_playlist(files)

  def open_folder(self):

      """打开文件夹"""

      folder = QFileDialog.getExistingDirectory(self, "选择文件夹")

      if folder:

          files = []

          for f in os.listdir(folder):

              if f.lower().endswith(('.mp3', '.wav', '.flac', '.ogg', '.m4a')):

                  files.append(os.path.join(folder, f))

          if files:

              self.add_to_playlist(files)

          else:

              self.show_warning("未找到音频文件")

  def add_to_playlist(self, files):

      """添加到播放列表"""

      new_files = [f for f in files if f not in self.music_files]

      if not new_files:

          return

      self.music_files.extend(new_files)

      for f in new_files:

          metadata = self.get_metadata(f)

          text = f"{metadata['artist']} - {metadata['title']}" if metadata else os.path.basename(f)

          self.playlist_widget.addItem(text)

      if self.current_index == -1:

          self.current_index = 0

  def get_metadata(self, file_path):

      """获取音频元数据"""

      try:

          audio = mutagen.File(file_path)

          if not audio:

              return None

          metadata = {

              'title': os.path.basename(file_path),

              'artist': '未知艺术家',

              'album': '未知专辑',

              'duration': self.format_duration(audio.info.length),

              'cover': None

          }

          # 统一元数据提取

          tags = audio.tags

          if tags:

              if 'title' in tags: metadata['title'] = tags['title'][0]

              if 'artist' in tags: metadata['artist'] = tags['artist'][0]

              if 'album' in tags: metadata['album'] = tags['album'][0]

          # 封面提取

          if hasattr(audio, 'pictures') and audio.pictures:

              metadata['cover'] = audio.pictures[0].data

          elif 'APIC:' in tags:

              metadata['cover'] = tags['APIC:'].data

          return metadata

      except Exception as e:

          print(f"元数据错误: {e}")

          return None

  def format_duration(self, seconds):

      """格式化时间"""

      minutes, seconds = divmod(int(seconds), 60)

      return f"{minutes:02d}:{seconds:02d}"

  def play_selected(self, item):

      """播放选中曲目"""

      index = self.playlist_widget.row(item)

      if 0 <= index < len(self.music_files):

          self.current_index = index

          self.play()

  def play(self):

      """开始播放"""

      if not self.music_files:

          return

      try:

          mixer.music.load(self.music_files[self.current_index])

          mixer.music.play()

          self.btn_play.setText("暂停")

          self.timer.start(1000)

          self.analyzer.start()

          # 显示元数据

          metadata = self.get_metadata(self.music_files[self.current_index])

          if metadata:

              self.title_label.setText(f"标题: {metadata['title']}")

              self.artist_label.setText(f"艺术家: {metadata['artist']}")

              self.album_label.setText(f"专辑: {metadata['album']}")

              self.duration_label.setText(f"时长: {metadata['duration']}")

              if metadata['cover']:

                  pixmap = QPixmap()

                  pixmap.loadFromData(metadata['cover'])

                  self.album_cover.setPixmap(pixmap.scaled(

                      180, 180, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation

                  ))

      except Exception as e:

          self.show_error(f"播放失败: {str(e)}")

  def toggle_play(self):

      """切换播放/暂停"""

      if mixer.music.get_busy():

          mixer.music.pause()

          self.btn_play.setText("播放")

          self.timer.stop()

          self.analyzer.stop()

      else:

          if self.pause_pos > 0:

              mixer.music.unpause()

          else:

              self.play()

          self.btn_play.setText("暂停")

          self.timer.start()

          self.analyzer.start()

  def stop(self):

      """停止播放"""

      mixer.music.stop()

      self.btn_play.setText("播放")

      self.timer.stop()

      self.progress_slider.setValue(0)

      self.progress_label.setText("00:00 / 00:00")

      self.analyzer.stop()

  def update_progress(self):

      """更新播放进度"""

      if mixer.music.get_busy():

          current = mixer.music.get_pos() // 1000

          total = self.get_current_duration()

          self.progress_slider.setValue(int(current / total * 1000))

          self.progress_label.setText(

              f"{self.format_duration(current)} / {self.format_duration(total)}"

          )

      else:

          self.handle_playback_end()

  def get_current_duration(self):

      """获取当前曲目总时长"""

      if self.current_index >= 0:

          audio = mutagen.File(self.music_files[self.current_index])

          return int(audio.info.length)

      return 0

  def handle_playback_end(self):

      """处理播放结束"""

      if self.play_mode == "列表循环":

          self.current_index = (self.current_index + 1) % len(self.music_files)

          self.play()

      elif self.play_mode == "单曲循环":

          self.play()

      elif self.play_mode == "随机播放":

          self.current_index = random.randint(0, len(self.music_files)-1)

          self.play()

      else:

          self.stop()

  def set_volume(self, value):

      """设置音量"""

      mixer.music.set_volume(value / 100)

  def set_play_mode(self, mode):

      """设置播放模式"""

      self.play_mode = mode

  def open_equalizer(self):

      """打开均衡器"""

      dialog = EqualizerDialog(self)

      dialog.exec()

  def filter_playlist(self, text):

      """过滤播放列表"""

      for i in range(self.playlist_widget.count()):

          item = self.playlist_widget.item(i)

          item.setHidden(text.lower() not in item.text().lower())

  def play_prev(self):

      """播放上一首"""

      if not self.music_files:

          return

      if self.play_mode == "随机播放":

          self.current_index = random.randint(0, len(self.music_files)-1)

      else:

          self.current_index = (self.current_index - 1) % len(self.music_files)

      self.play()

  def play_next(self):

      """播放下一首"""

      if not self.music_files:

          return

      if self.play_mode == "随机播放":

          self.current_index = random.randint(0, len(self.music_files)-1)

      else:

          self.current_index = (self.current_index + 1) % len(self.music_files)

      self.play()

  def show_warning(self, message):

      """显示警告提示"""

      QMessageBox.warning(self, "警告", message)

  def show_error(self, message):

      """显示错误提示"""

      QMessageBox.critical(self, "错误", message)

  def closeEvent(self, event):

      """关闭窗口时的清理操作"""

      self.analyzer.stop()

      mixer.quit()

      event.accept()

if __name__ == "__main__":

  # 检查依赖库

  required = ['PyQt6', 'pygame', 'pyaudio', 'mutagen', 'pyqtgraph', 'numpy']

  missing = []

  for lib in required:

      try:

          __import__(lib)

      except ImportError:

          missing.append(lib)

  if missing:

      app = QApplication([])

      msg = QMessageBox()

      msg.setIcon(QMessageBox.Critical)

      msg.setText("缺少必要依赖库!\n请使用以下命令安装:")

      msg.setDetailedText(f"pip install {' '.join(missing)}")

      msg.exec()

      sys.exit(1)

  # 启动应用程序

  app = QApplication(sys.argv)

  app.setStyle('Fusion')  # 使用融合样式

  # 设置全局字体

  font = QFont()

  font.setFamily("Microsoft YaHei")

  app.setFont(font)

  player = MusicPlayer()

  player.show()

  sys.exit(app.exec())

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O51_RevZQ5cZ7ZKa1k2KzPXg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券