Search code examples
pythonpyqtpyqt6

Music Player Crash PyQt6


I load playlist by clicking "Add to playlist". When I click on any item in the list and player works fine. But if i try to click another track in the list, the program crashes. I have noticed the following factors:

  1. I click certain track and then click it once again, everything works fine and I can choose any other track from the list withous crashes.
  2. I click certain track and then rewind music with slider position or buttons, I can choose any other track from the list withous crashes too.
  3. But if I click certain track and then click another one without rewinding first selected track, program crashes.
  4. Also I noticed this type of error everytime I select new track: [mp3float @ 000001A17DE3A040] Could not update timestamps for skipped samples.

I tried to fix it but it didn't work. Thank you in advance.

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.setInterval(5000)
        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)

    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.sliderMoved.connect(self.set_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()
        index = self.playlist_widget.row(item)
        if self.player.playbackState() == QMediaPlayer.PlaybackState.PausedState:
            self.player.play()
        else:
            if (self.playlist):
                url = QUrl.fromLocalFile(self.playlist[index])  # Создаем объект QUrl из строки с путем к файлу
                self.player.durationChanged.connect(self.initSlider)
                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.setSource(url)
            self.player.durationChanged.connect(self.initSlider)
            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)

    """Метод конвертации значения слайдера (от 0 до 100) 
        к виду (от 0.0 до 1.0) и преобразование шкалы громкости
         в логарифмическую(удобную для восприятия человеком)"""

    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())

I thought it might be necessary to reset Source, before i select new track, but I didn't find the command. I want to change tracks without crashes. Also I've noticed it crashed on line 180: self.player.setSource(url)


Solution

  • I've found some kind of solution. Before I set new source to play I stop the player, then I check if player is stopped. After that I call a function with a delay using QTimer.singleShot

    self.player.stop()
            if self.player.playbackState() == QMediaPlayer.PlaybackState.StoppedState:
                QTimer.singleShot(1, lambda: self.source_setter(url))
    
    def source_setter(self, url):
        self.player.setSource(url)
        self.player.play()