PyQt6音乐播放器切换曲目崩溃问题求助
解决PyQt6音乐播放器曲目切换崩溃问题
问题根源
- 资源冲突:直接在播放状态下切换曲目,旧音频流未终止就加载新源,导致底层资源占用冲突,触发崩溃。
- 重复信号连接:每次播放时重复绑定
durationChanged到initSlider,多次触发会导致滑块逻辑混乱,加剧状态异常。 - 状态不同步:未处理播放器在播放状态下的切换流程,内部状态未重置就加载新曲目,引发逻辑错误。
修复方案
- 切换曲目前先调用
stop()终止当前播放,确保旧资源释放后再加载新曲目。 - 将
durationChanged信号的绑定移到初始化阶段,只执行一次连接。 - 增加空值判断,避免因列表为空或项目不存在导致的索引错误。
修改后的完整代码
import os import sys from PyQt6.QtCore import Qt, QUrl, QTimer from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput, QAudio from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QSlider, QVBoxLayout, QHBoxLayout, QLabel, QFileDialog, \ QListWidget, QListWidgetItem class MusicPlayer(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Music Player") self.setGeometry(100, 100, 400, 200) self.initUI() self.initPlayer() self.initSlider() self.initTimer() def initTimer(self): self.tmr0 = QTimer() self.tmr0.timeout.connect(self.on_time) self.tmr0.start(1) def on_time(self): if not self.position_slider.isSliderDown(): pos = self.player.position() self.position_slider.setSliderPosition(pos) def initSlider(self): self.position_slider.setMaximum(self.player.duration()) self.position_slider.setToolTip("Позиция трека") def initPlayer(self): self.player = QMediaPlayer() self.playlist = [] self.audio_output = QAudioOutput() self.player.setAudioOutput(self.audio_output) self.saved_volume = 0.5 self.audio_output.setVolume(self.saved_volume) # 仅初始化时绑定一次信号 self.player.durationChanged.connect(self.initSlider) def initUI(self): self.play_button = QPushButton("Play") self.play_button.clicked.connect(self.play_music) self.pause_button = QPushButton("Pause") self.pause_button.clicked.connect(self.pause_music) self.stop_button = QPushButton("Stop") self.stop_button.clicked.connect(self.stop_music) self.volume_slider = QSlider(Qt.Orientation.Horizontal) self.volume_slider.setValue(50) self.volume_slider.setMinimum(0) self.volume_slider.setMaximum(100) self.volume_slider.setToolTip("Volume") self.volume_slider.valueChanged.connect(self.set_volume) self.position_slider = QSlider(Qt.Orientation.Horizontal) self.position_slider.setToolTip("Position") self.position_slider.sliderReleased.connect(self.on_slider_release) self.backward_button = QPushButton("<< 10 sec") self.backward_button.clicked.connect(self.backward_music) self.forward_button = QPushButton("10 sec >>") self.forward_button.clicked.connect(self.forward_music) self.select_file_button = QPushButton("Add to playlist") self.select_file_button.clicked.connect(self.select_file) self.clear_playlist_button = QPushButton("Clear playlist") self.clear_playlist_button.clicked.connect(self.clear_playlist) self.playlist_widget = QListWidget() self.playlist_widget.itemDoubleClicked.connect(self.play_selected_track) self.track_label = QLabel("Track name") vbox = QVBoxLayout() hbox1 = QHBoxLayout() hbox2 = QHBoxLayout() hbox3 = QHBoxLayout() hbox1.addWidget(self.play_button) hbox1.addWidget(self.pause_button) hbox1.addWidget(self.stop_button) hbox1.addWidget(self.volume_slider) hbox2.addWidget(self.backward_button) hbox2.addWidget(self.position_slider) hbox2.addWidget(self.forward_button) hbox3.addWidget(self.track_label) hbox3.addWidget(self.select_file_button) hbox3.addWidget(self.clear_playlist_button) vbox.addWidget(self.playlist_widget) vbox.addLayout(hbox3) vbox.addLayout(hbox1) vbox.addLayout(hbox2) self.setLayout(vbox) def select_file(self): folder_path = QFileDialog.getExistingDirectory(self, "Выбрать папку с музыкальными файлами", "") if folder_path: files = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith(('.mp3', '.wav', '.ogg'))] file_names = [file for file in os.listdir(folder_path) if file.endswith(('.mp3', '.wav', '.ogg'))] for file in files: self.playlist.append(file) for file_name in file_names: item = QListWidgetItem(file_name) self.playlist_widget.addItem(item) def play_music(self): item = self.playlist_widget.currentItem() if not item or not self.playlist: return index = self.playlist_widget.row(item) if self.player.playbackState() == QMediaPlayer.PlaybackState.PausedState: self.player.play() else: url = QUrl.fromLocalFile(self.playlist[index]) # 切换前先终止当前播放 self.player.stop() self.player.setSource(url) self.audio_output.setVolume(self.saved_volume) self.player.play() def play_selected_track(self, item): index = self.playlist_widget.row(item) if 0 <= index < len(self.playlist): url = QUrl.fromLocalFile(self.playlist[index]) # 切换前先终止当前播放 self.player.stop() self.player.setSource(url) self.audio_output.setVolume(self.saved_volume) self.player.play() def pause_music(self): self.player.pause() def stop_music(self): self.player.stop() def set_volume(self): volume = self.volume_slider.value() self.saved_volume = self.real_volume(volume) self.audio_output.setVolume(self.saved_volume) def real_volume(self, slider_value): return QAudio.convertVolume( slider_value * .01, QAudio.VolumeScale.LogarithmicVolumeScale, QAudio.VolumeScale.LinearVolumeScale ) def backward_music(self): position_m = max(0, self.player.position() - 10000) self.player.setPosition(position_m) def forward_music(self): position_m = min(self.player.duration(), self.player.position() + 10000) self.player.setPosition(position_m) def on_slider_release(self): self.player.setPosition(self.position_slider.value()) def clear_playlist(self): self.playlist_widget.clear() self.playlist.clear() app = QApplication(sys.argv) playerM = MusicPlayer() playerM.show() sys.exit(app.exec())
关键修改说明
- 把
durationChanged信号的绑定移到initPlayer方法中,避免重复绑定导致的逻辑混乱。 - 在
play_music和play_selected_track中,切换曲目前调用stop()释放旧音频资源,解决资源冲突问题。 - 增加空项目和空列表的判断,防止因无效索引引发的错误。
内容的提问来源于stack exchange,提问作者RD60
相关产品推荐
相关产品推荐

