Chapter 33 audio and video

Posted by robinstott on Thu, 06 Jan 2022 03:20:14 +0100

If you can add a prompt tone to the button in the application, the user will feel better every time he clicks the button, and the program will look more likable. If you want to play games, you have higher requirements for sound. Of course, it is not only sound, but also the function of video playback is occasionally needed in the program. PyQt5 has a very good ability to process multimedia. In this chapter, we will introduce the classes related to audio and video in detail, and take you to make a simple music player to consolidate. It is hoped that the partners who have read this chapter can well master the method of adding sound and video to the program.

33.1 QSound

To simply and quickly play wav audio files, use the QSound class and call the play() method:

import sys
from PyQt5.QtMultimedia import QSound
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.sound = QSound('sound.wav', self)              # 1

        self.play_btn = QPushButton('Play Sound', self)
        self.play_btn.clicked.connect(self.sound.play)      # 2


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Pass in the path of the wav file to instantiate a QSound class;

  2. Connect the signal of the button with the play() slot function, so that every time you click the button, the sound will be played.

sound. The download address of wav file is as follows: http://s.aigei.com/src/aud/wav/71/71efd17922dc42fd928a8a6841028d5d.wav?download/%E6%8C%89%E9%92%AE32%28Button32%29_%E7%88%B1%E7%BB%99%E7%BD%91_aigei_com.wav&e=1547381220&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:FQplW3rItO42p0YVkoWVSuLlseY=

Looking at the following documents, we can see that this QSound class also provides these common methods:

We add a stop function to the above program, that is, use the stop() method:

import sys
from PyQt5.QtMultimedia import QSound
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.sound = QSound('sound.wav')
        self.sound.setLoops(QSound.Infinite)                # 1

        self.play_btn = QPushButton('Play Sound', self)
        self.stop_btn = QPushButton('Stop Sound', self)
        self.play_btn.clicked.connect(self.sound.play)
        self.stop_btn.clicked.connect(self.sound.stop)

        self.h_layout = QHBoxLayout()
        self.h_layout.addWidget(self.play_btn)
        self.h_layout.addWidget(self.stop_btn)
        self.setLayout(self.h_layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Because the audio is very short, in order to demonstrate the function, call the setLoops() method to pass in QSound The infinite parameter lets the sound play in an infinite loop. Passing in the corresponding positive integer will play the corresponding number of times. After playing, click the stop button to stop the sound.

The operation screenshot is as follows:

[the external link picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-SnaPwgBt-1641378935439)(data:image/svg+xml;utf8, )]

33.2 QSoundEffect

This class can be used to play uncompressed audio files (typically wav files), but we can use this class to play audio files in a low latency manner, and can perform more detailed operations on files. This class is very suitable for playing interactive sound effects, such as pop-up prompt sound, game sound, etc

import sys
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtMultimedia import QSoundEffect
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QSlider, QCheckBox, QHBoxLayout, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.sound_effect = QSoundEffect(self)
        self.sound_effect.setSource(QUrl.fromLocalFile('sound.wav'))    # 1
        self.sound_effect.setVolume(1.0)                                # 2

        self.play_btn = QPushButton('Play Sound', self)
        self.play_btn.clicked.connect(self.sound_effect.play)

        self.slider = QSlider(Qt.Horizontal, self)                      # 3
        self.slider.setRange(0, 10)
        self.slider.setValue(10)
        self.slider.valueChanged.connect(self.set_volume_func)

        self.checkbox = QCheckBox('Mute', self)                         # 4
        self.checkbox.stateChanged.connect(self.mute_func)

        self.h_layout = QHBoxLayout()
        self.v_layout = QVBoxLayout()
        self.h_layout.addWidget(self.play_btn)
        self.h_layout.addWidget(self.checkbox)
        self.v_layout.addWidget(self.slider)
        self.v_layout.addLayout(self.h_layout)
        self.setLayout(self.v_layout)

    def set_volume_func(self):
        self.sound_effect.setVolume(self.slider.value()/10)

    def mute_func(self):
        if self.sound_effect.isMuted():
            self.sound_effect.setMuted(False)
        else:
            self.sound_effect.setMuted(True)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Instantiate the QSoundEffect class, and then call setSource() to set up the audio source, requiring the QUrl type parameter to be passed in.

  2. The setVolume() method can set the volume when playing audio. The parameter is a floating-point value. 1.0 stands for full volume playback, and 0.0 stands for mute;

  3. We add a QSlider slider control to control the volume in the slot function set_ volume_ In func(), we divide the current value of the slider by 10 to obtain a floating-point value, which is used as a parameter of the setVolume() method;

  4. Qccheckbox check box to control whether mute is required in mute_ In the func() slot function, we first use the isMuted() method to determine whether the current is muted. If not, we call the setMute() method to set it accordingly.

The operation screenshot is as follows:

33.3 QMovie

Qmove is usually used to play dynamic images with short time and no sound, that is, files of gif and mng types. This class also provides many methods for us to manipulate dynamic images. Now let's learn from the following example:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-ZfD3qMpd-1641378935447)(data:image/svg+xml;utf8, )]

The program will control gif images through various buttons, such as stop, fast forward, screenshot, etc.

The code is as follows:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QMovie, QIcon
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QHBoxLayout, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.movie = QMovie(self)                               # 1
        self.movie.setFileName('images/zootopia.gif')
        self.movie.jumpToFrame(0)

        self.label = QLabel(self)                               # 2
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setMovie(self.movie)

        self.speed = 100                                        # 3

        self.start_btn = QPushButton(self)                      # 4
        self.pause_btn = QPushButton(self)
        self.stop_btn = QPushButton(self)
        self.fast_btn = QPushButton(self)
        self.back_btn = QPushButton(self)
        self.screenshot_btn = QPushButton(self)

        self.start_btn.setIcon(QIcon('images/start.png'))
        self.pause_btn.setIcon(QIcon('images/pause.png'))
        self.stop_btn.setIcon(QIcon('images/stop.png'))
        self.fast_btn.setIcon(QIcon('images/fast_forward.png'))
        self.back_btn.setIcon(QIcon('images/back_forward.png'))
        self.screenshot_btn.setIcon(QIcon('images/screenshot.png'))

        self.start_btn.clicked.connect(lambda: self.btn_func(self.start_btn))
        self.pause_btn.clicked.connect(lambda: self.btn_func(self.pause_btn))
        self.stop_btn.clicked.connect(lambda: self.btn_func(self.stop_btn))
        self.fast_btn.clicked.connect(lambda: self.btn_func(self.fast_btn))
        self.back_btn.clicked.connect(lambda: self.btn_func(self.back_btn))
        self.screenshot_btn.clicked.connect(lambda: self.btn_func(self.screenshot_btn))

        self.h_layout = QHBoxLayout()
        self.v_layout = QVBoxLayout()

        self.h_layout.addWidget(self.back_btn)
        self.h_layout.addWidget(self.start_btn)
        self.h_layout.addWidget(self.pause_btn)
        self.h_layout.addWidget(self.stop_btn)
        self.h_layout.addWidget(self.fast_btn)
        self.h_layout.addWidget(self.screenshot_btn)
        self.v_layout.addWidget(self.label)
        self.v_layout.addLayout(self.h_layout)
        self.setLayout(self.v_layout)

    def btn_func(self, btn):                                    # 5  
        if btn == self.start_btn:
            self.movie.start()
        elif btn == self.pause_btn:
            self.movie.setPaused(True)
        elif btn == self.stop_btn:
            self.movie.stop()
            self.movie.jumpToFrame(0)
        elif btn == self.fast_btn:
            self.speed *= 2
            self.movie.setSpeed(self.speed)
        elif btn == self.back_btn:
            self.speed /= 2
            self.movie.setSpeed(self.speed)
        elif btn == self.screenshot_btn:
            self.movie.currentPixmap().save('zootopia.png')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Instantiate a QMovie object, and then call the setFileName() method to set the gif file to be played. The animation consists of frames, and the jumpToFrame() method can choose to let the gif jump to a frame. Here we pass in 0, which means that the gif file will jump to the first frame when the program initializes. If jumpToFrame(0) is not called, the program will start running as follows:

However, if a number greater than 0 is first passed in, it is the same as the figure above, that is, we can display the first frame at most during initialization. If you want to jump to a frame using jumpToFrame(), the frame must be played first;

  1. QLabel control is used to display QMovie;

  2. self. The speed variable is used to control the playback speed, which is used in the subsequent fast forward and fast backward functions;

  3. Instantiate several buttons: start, pause, stop, fast forward, fast backward and screenshot. Then, the icon of the button is set and the signal is connected with the slot;

  4. In the slot function, we first judge the type of the incoming button, and then perform the corresponding operation:

  • start_btn: call the start() method to start playing the gif file
  • pause_btn: call setPaused(True) to pause playback, and pass in False to continue playback
  • stop_btn: call the stop() method to stop playing. At this time, if you call the start() method again, the gif file will be played from the first frame. Here, we use jumpToFrame(0) to jump directly to the first frame after stopping playing (this line of code is invalid on MacOS)
  • fast_btn and back_btn: adjust self first The speed variable size is passed into the setSpeed() method to control the playback speed
  • screenshot_btn: use the currentPixmap() method to get the current frame (QPixmap type at this time), and then call the save() method to save the frame locally

Picture download address:

When performing some time-consuming operations, we usually display a loading animation to ease the user's mood. QMovie is often used in this way. Here is a simple example:

import sys
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)

        self.label = QLabel('None', self)
        self.label.setAlignment(Qt.AlignCenter)

        self.movie = QMovie(self)
        self.movie.setFileName('loading.gif')

        self.btn = QPushButton('Start', self)                  
        self.btn.clicked.connect(self.start_countdown_func)

        self.thread = MyThread()
        self.thread.ok_signal.connect(self.show_result_func)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.label)
        self.v_layout.addWidget(self.btn)
        self.setLayout(self.v_layout)

    def start_countdown_func(self):
        self.label.setMovie(self.movie)
        self.movie.start()
        self.thread.start()

    def show_result_func(self):
        self.movie.stop()
        self.label.setText("Time's Up!")


class MyThread(QThread):
    ok_signal = pyqtSignal()

    def __init__(self):
        super(MyThread, self).__init__()
        self.countdown = 1000000

    def run(self):
        while self.countdown > 0:
            self.countdown -= 1
            print(self.countdown)

        self.ok_signal.emit()
        self.countdown = 1000000


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

When we click the button, we turn on Multithreading for Time-consuming operations. In self When the countdown variable is also greater than 0, loading. Is displayed gif. Etc. self When the countdown variable is less than 0, the loop ends and the signal is transmitted, self Label displays "Time's Up!" Text.

The screenshot of the operation is as follows, in the initial state:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-kjz01mez-1641378935448)(data:image/svg+xml;utf8, )]

After clicking the Start button, the multithread performs time-consuming operation, and the program interface displays loading gif:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-UZiWh9za-1641378935448)(data:image/svg+xml;utf8, )]

After the Time-consuming operation, self Label displays "Time's Up!" Text:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-HGOZiRvJ-1641378935449)(data:image/svg+xml;utf8, )]

33.4 QMediaPlayer

QMediaPlayer is a high-level media player class. It is very powerful. With it, we can play both audio and video (Phonon is used in PyQt4 to play audio and video, which has been replaced by QMediaPlayer). Here is a simple example of playing audio:

import sys
from PyQt5.Qt import QUrl
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.player = QMediaPlayer(self)            # 1
        self.media_content = QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/music.mp3'))  # 2
        # self.player.setMedia(QMediaContent(QUrl('http://example.com/music.mp3')))
        self.player.setMedia(self.media_content)    # 3
        self.player.setVolume(80)                   # 4
        self.player.play()                          # 5


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. First, instantiate a QMediaPlayer;

  2. To set the audio file to be played, first instantiate an object of type QMediaContent. When instantiating, the path of the file to be passed in (can be a local file or a network file). The QMediaContent object is then passed as a parameter into the setMedia() method.

  3. Call the setMedia() method to set the playback content;

  4. Call setVolume() to set the volume. 100 is the default value (maximum volume), and 0 is equivalent to mute;

  5. Call the play() method to play the audio file.

Audio files (and video files used later) have been placed in the following links. Readers can go to download or use their own media files:

Link: https://pan.baidu.com/s/1BKCkPgTsogYgLfKuG1Pv6Q Password: 2y5y

The above is just playing an audio file. What if you want to play a series of files? In this case, you need to use the QMediaPlaylist class:

import sys
from PyQt5.Qt import QUrl
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.playlist = QMediaPlaylist(self)                    # 1
        self.player = QMediaPlayer(self)
        self.player.setPlaylist(self.playlist)

        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/music1.mp3')))  # 2
        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/music2.mp3')))
        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/music3.mp3')))
        self.playlist.setPlaybackMode(QMediaPlaylist.Loop)      # 3
        self.playlist.setCurrentIndex(2)                        # 4

        self.player.setVolume(80)                               # 5
        self.player.play()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Instantiate the QMediaPlaylist object and the QMediaPlayer object, then call the setPlaylist() method of the QMediaPlayer instance into the QMediaPlaylist instance to set the playlist of the player.

  2. Call addMedia() to pass in the file to be played. The type is QMediaContent;

  3. The setPlaybackMode() method can set the playback mode, including the following:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-minx5Quk-1641378935450)(data:image/svg+xml;utf8, )]

  1. setCurrentIndex() can set the file to be played currently, and 2 means to play the third file;

  2. Call the setVolume() method to set the volume, and call the play() method to play.

Playing video files is similar to playing audio, but to play video, we also need to use the VideoWidget control, which is used for video and image output. We just need to hang it on the player:

import sys
from PyQt5.Qt import QUrl, QVideoWidget
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.playlist = QMediaPlaylist(self)
        self.video_widget = QVideoWidget(self)              # 1
        self.video_widget.resize(self.width(), self.height())

        self.player = QMediaPlayer(self)
        self.player.setPlaylist(self.playlist)
        self.player.setVideoOutput(self.video_widget)       # 2

        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/video1.mp4')))  # 3
        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/video2.mp4')))
        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/Users/louis/Downloads/video3.mp4')))
        self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
        self.playlist.setCurrentIndex(2)

        self.player.setVolume(80)
        self.player.play()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Instantiate the QVideoWidget control and adjust the initial size of the control to the window size;

  2. Call the setVideoOutput() method of the player and pass in the QVideoWidget instance to set the video playback device;

  3. Change the media file to video (MP3 - > mp4).

The screenshot of the operation is as follows. The video is played circularly from paramount to Fox and then to Disney:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-b8i5MszS-1641378935450)(data:image/svg+xml;utf8, )]

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-QnM9E847-1641378935451)(data:image/svg+xml;utf8, )]

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-ypMFEOuz-1641378935452)(data:image/svg+xml;utf8, )]

33.5 making a simple music player

In section 34.4, we just introduced the basic usage of QMediaPlayer. Next, let's learn about its other methods by making a simple music player:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-7Plv8gul-1641378935452)(data:image/svg+xml;utf8, )]

The functions of each part of the player are as follows:

  1. The playback progress bar displays and controls the playback progress, and the – / - - on the right is used to display the remaining time;

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-DzOaC330-1641378935453)(data:image/svg+xml;utf8, )]

  1. The volume slider controls the volume, and the speaker button on the right controls whether to mute;

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-hN3tsH61-1641378935453)(data:image/svg+xml;utf8, )]

  1. Previous / play and pause / next;

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-44HBfWkP-1641378935454)(data:image/svg+xml;utf8, )]

  1. This button controls the playback mode: List loop, single loop and random playback;

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-S4SD3wI3-1641378935454)(data:image/svg+xml;utf8, )]

  1. Show and hide the list below

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-gf5L7DsA-1641378935455)(data:image/svg+xml;utf8, )]

  1. This list control is used to display playback content

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-wwHhRPql-1641378935455)(data:image/svg+xml;utf8, )]

Now let's write the code. Here are the modules used:

import sys
from PyQt5.Qt import QUrl
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication, QWidget, QListWidget, QLabel, QSlider, QPushButton, QHBoxLayout, \
    QVBoxLayout

Download address of each icon:

Link: https://pan.baidu.com/s/1aZTiIdthwdI5MRl8jjtP1g Password: 65k3

First, let's finish the interface layout:

import sys
from PyQt5.Qt import QUrl
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication, QWidget, QListWidget, QLabel, QSlider, QPushButton, QHBoxLayout, \
    QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()

        self.time_label = QLabel(self)                                  # 1
        self.volume_slider = QSlider(self)
        self.progress_slider = QSlider(self)
        self.sound_btn = QPushButton(self)
        self.previous_btn = QPushButton(self)
        self.play_pause_btn = QPushButton(self)
        self.next_btn = QPushButton(self)
        self.mode_btn = QPushButton(self)
        self.list_btn = QPushButton(self)
        self.list_widget = QListWidget(self)

        self.h1_layout = QHBoxLayout()
        self.h2_layout = QHBoxLayout()
        self.all_v_layout = QVBoxLayout()

        self.widget_init()
        self.layout_init()

    def widget_init(self):
        self.time_label.setText('--/--')
        self.volume_slider.setRange(0, 100)                             # 2
        self.volume_slider.setValue(100)
        self.volume_slider.setOrientation(Qt.Horizontal)
        self.progress_slider.setEnabled(False)                          # 3
        self.progress_slider.setOrientation(Qt.Horizontal)
        self.sound_btn.setIcon(QIcon('images/sound_on.png'))            # 4
        self.previous_btn.setIcon(QIcon('images/previous.png'))
        self.play_pause_btn.setIcon(QIcon('images/play.png'))
        self.next_btn.setIcon(QIcon('images/next.png'))
        self.mode_btn.setIcon(QIcon('images/list_loop.png'))
        self.list_btn.setIcon(QIcon('images/show.png'))

    def layout_init(self):
        self.h1_layout.addWidget(self.progress_slider)                  # 5
        self.h1_layout.addWidget(self.time_label)
        self.h2_layout.addWidget(self.volume_slider)
        self.h2_layout.addWidget(self.sound_btn)
        self.h2_layout.addWidget(self.previous_btn)
        self.h2_layout.addWidget(self.play_pause_btn)
        self.h2_layout.addWidget(self.next_btn)
        self.h2_layout.addWidget(self.mode_btn)
        self.h2_layout.addWidget(self.list_btn)

        self.all_v_layout.addLayout(self.h1_layout)
        self.all_v_layout.addLayout(self.h2_layout)
        self.all_v_layout.addWidget(self.list_widget)
        self.all_v_layout.setSizeConstraint(QVBoxLayout.SetFixedSize)   # 6

        self.setLayout(self.all_v_layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())
  1. Controls needed for instantiation:
  • time_label: display remaining time
  • volume_slider: volume slider
  • progress_slider: play progress bar
  • sound_btn: horn button
  • previous_btn: previous button
  • play_pause_btn: play and pause button
  • next_btn: Next button
  • mode_btn: play mode button
  • list_btn: show and hide list buttons
  • list_widget: the list control displays all playlists
  1. As mentioned above, the volume value range of QMediaPlayer is 0-100, so we set it to volume_ Volume value range of slider;

  2. The playback progress bar cannot be used before the media file is played;

  3. Set the icons of each button;

  4. Layout:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-jBAAGIdx-1641378935456)(data:image/svg+xml;utf8, )]

  1. Displaying and hiding the list will change the size of the window. Here, we set the sizeConstraint property of the layout to the SetFixedSize value, so that users cannot modify the size of the window, but let the layout manager adjust it by itself. If the window size changes after setting, it can be displayed in the best size.

Next, let's set up the qmediaplayer player and add playback content to the list control. Initializing functions__ init__ Instantiate qmediaplayer and qmediaplaylist in () (#1 and #2 codes):

self.h1_layout = QHBoxLayout()
self.h2_layout = QHBoxLayout()
self.all_v_layout = QVBoxLayout()

self.playlist = QMediaPlaylist(self)        # 1
self.player = QMediaPlayer(self)            # 2

self.widget_init()
self.layout_init()

Then in the widget_ Set the initial state in init() function:

def widget_init(self):
    self.time_label.setText('--/--')
    self.volume_slider.setRange(0, 100)
    self.volume_slider.setValue(100)
    self.volume_slider.setOrientation(Qt.Horizontal)
    self.progress_slider.setEnabled(False)
    self.progress_slider.setOrientation(Qt.Horizontal)
    self.sound_btn.setIcon(QIcon('images/sound_on.png'))
    self.previous_btn.setIcon(QIcon('images/previous.png'))
    self.play_pause_btn.setIcon(QIcon('images/play.png'))
    self.next_btn.setIcon(QIcon('images/next.png'))
    self.mode_btn.setIcon(QIcon('images/list_loop.png'))
    self.list_btn.setIcon(QIcon('images/show.png'))

    self.player.setPlaylist(self.playlist)
    self.media_list = ['/Users/louis/Downloads/music1.mp3',                 # 1
                       '/Users/louis/Downloads/music2.mp4',
                       '/Users/louis/Downloads/music3.mp3']
    for m in self.media_list:
        self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(m)))
    self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)

    self.list_widget.addItems([m.split('/')[-1] for m in self.media_list])  # 2
  1. media_list is used to store the absolute path of each media file;

  2. The file name should be added to the list, not the path, so use the split() method to get the file name at the end of the path.

The program displays as follows:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-0R56V6ow-1641378935456)(data:image/svg+xml;utf8, )]

Then realize the functions of each button. In signal_ Connect the signal and slot in the init() function (don't forget to put signal_init() in the initialization function of the class__ init__ (middle):

def signal_init(self):
    self.sound_btn.clicked.connect(lambda: self.btn_func(self.sound_btn))
    self.previous_btn.clicked.connect(lambda: self.btn_func(self.previous_btn))
    self.play_pause_btn.clicked.connect(lambda: self.btn_func(self.play_pause_btn))
    self.next_btn.clicked.connect(lambda: self.btn_func(self.next_btn))
    self.mode_btn.clicked.connect(lambda: self.btn_func(self.mode_btn))
    self.list_btn.clicked.connect(lambda: self.btn_func(self.list_btn))

We connect all the buttons to BTN_ On func() slot function:

def btn_func(self, btn):
    if btn == self.sound_btn:            # 1
        if self.player.isMuted():
            self.player.setMuted(False)
            self.sound_btn.setIcon(QIcon('images/sound_on'))
        else:
            self.player.setMuted(True)
            self.sound_btn.setIcon(QIcon('images/sound_off'))

    elif btn == self.previous_btn:       # 2
        if self.playlist.currentIndex() == 0:
            self.playlist.setCurrentIndex(self.playlist.mediaCount() - 1)
        else:
            self.playlist.previous()

    elif btn == self.play_pause_btn:     # 3
        if self.player.state() == 1:
            self.player.pause()
            self.play_pause_btn.setIcon(QIcon('images/play.png'))
        else:
            self.player.play()
            self.play_pause_btn.setIcon(QIcon('images/pause.png'))

    elif btn == self.next_btn:           # 4
        if self.playlist.currentIndex() == self.playlist.mediaCount() - 1:
            self.playlist.setCurrentIndex(0)
        else:
            self.playlist.next()

    elif btn == self.mode_btn:           # 5
        if self.playlist.playbackMode() == 2:
            self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
            self.mode_btn.setIcon(QIcon('images/item_loop.png'))

        elif self.playlist.playbackMode() == 3:
            self.playlist.setPlaybackMode(QMediaPlaylist.Random)
            self.mode_btn.setIcon(QIcon('images/random.png'))

        elif self.playlist.playbackMode() == 4:
            self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
            self.mode_btn.setIcon(QIcon('images/list_loop.png'))

    elif btn == self.list_btn:           # 6
        if self.list_widget.isHidden():
            self.list_widget.show()
            self.list_btn.setIcon(QIcon('images/show.png'))
        else:
            self.list_widget.hide()
            self.list_btn.setIcon(QIcon('images/hide.png'))
  1. If the horn button is pressed, call the isMuted() method of QMediaPlayer class to determine whether it has been muted, and then call the setMuted() method to make corresponding operations, and the button icon changes at the same time;

  2. If it is the previous button, first call the currentIndex() method of QMediaPlaylist to know the index of the currently playing file. If it is equal to 0, it means that the first file is playing, and there is no previous song at this time, so we should play the last song, and the index of the last song is mediaCount()-1; If it is not equal to 0, call the previous() method of QMediaPlayer class directly to play the previous song;

  3. First judge the status of the player. If it is playing, pause, otherwise start / continue playing. Here is qmediaplayer Value that state() can return:

  1. The logic is similar to point 2;

  2. If it is a play mode button, call the setPlaybackMode() method to change the play mode, and change the icon of the button at the same time;

  3. If it is a list button, first judge whether the list control is currently visible, and then make corresponding operations;

The functions of each button have been completed. Next, we will complete the functions of volume slider and double-click play in the list. In signal_ Add these two lines of code to init():

self.volume_slider.valueChanged.connect(self.volume_slider_func)
self.list_widget.doubleClicked.connect(self.list_play_func)

volume_slider_func() slot function is as follows:

def volume_slider_func(self, value):
    self.player.setVolume(value)
    if value == 0:
        self.sound_btn.setIcon(QIcon('images/sound_off.png'))
    else:
        self.sound_btn.setIcon(QIcon('images/sound_on.png'))

Note: every time the ValueChanged signal is transmitted, it will carry a value value, that is, the current value of the slider. The same is true for later sliderMoved, durationChanged and positionChanged signals. See the document for details.

Every time the value of the volume slider changes, call the setVolume() method to set the volume of the player to the corresponding value. If the volume is 0, remember to change the speaker button icon to the mute Icon:

list_play_func() slot function is as follows:

def list_play_func(self):
    self.playlist.setCurrentIndex(self.list_widget.currentRow())
    self.player.play()
    self.play_pause_btn.setIcon(QIcon('images/pause.png'))

First, call the currentRow() method of QListWidget class to get the currently double clicked index, then pass it into the setCurrentIndex() method of QMediaPlaylist class, and then call the play() method of QMediaPlayer class to play. One detail to note is that now that you have started playing, you should play_ pause_ Change the icon of BTN to pause icon pause png.

The last thing to be done is to play the progress bar and display the remaining time. To complete these two functions, we must first obtain the duration of the file, and QMediaPlayer happens to have a duration() method to use:

This method returns the duration of the current media file, in milliseconds. However, it should be noted that calling the duration() method at the beginning of playback will only get 0, so the document recommends using the durationChanged signal to listen for the acquisition duration, that is, we need to obtain the duration in a slot function. If we know the total length of the file, we can set progress_ The value range of slider is.

Note: Unfortunately, duration() and durationChanged are not used on MacOS, and the value is always 0 (available on windows and Linux).

The playback progress must be changed. The button on the progress bar must move slowly to the right, that is, we should constantly know how long it has been played and set it to progress_ The current value of the slider. The position() method of QMediaPlayer class can obtain the current playing time, and the positionChanged signal will be emitted when the position value changes.

Now that we know the method to use, we can start work. In signal_ Add the following two lines of code to the init() function:

self.player.durationChanged.connect(self.get_duration_func)
self.player.positionChanged.connect(self.get_position_func)

get_duration_func() slot function is as follows:

def get_duration_func(self, d):
    self.progress_slider.setRange(0, d)
    self.progress_slider.setEnabled(True)
    self.get_time_func(d)

Set progress_ The slider range and set it to available status in get_ time_ Update time in func() function:

def get_time_func(self, d):
    seconds = int(d / 1000)
    minutes = int(seconds / 60)
    seconds -= minutes * 60
    if minutes == 0 and seconds == 0:
        self.time_label.setText('--/--')
        self.play_pause_btn.setIcon(QIcon('images/play.png'))
    else:
        self.time_label.setText('{}:{}'.format(minutes, seconds))

First obtain the minutes and seconds, and then judge whether the playback has been completed (i.e. minutes and seconds are 0). If the playback ends, set the time text to – / -, and set the icon of the playback button to play pngļ¼› Otherwise, set the text to the remaining time.

get_ The position () slot function is as follows. It is very simple to set progress_ Current value of slider:

def get_position_func(self, p):
    self.progress_slider.setValue(p)

At this time, run the program, play a file, you can see the progress bar walking, and the time will be displayed. The last thing we need now is to slide the progress bar to change the playback progress. We're at signal_ Add the following code to init():

self.progress_slider.sliderMoved.connect(self.update_position_func)

Note: the valueChanged signal cannot be used for connection, otherwise it will conflict with the positionChanged() signal, and the sliderMoved signal will be transmitted only when the user manually changes the value of the slider.

update_position_func() slot function is as follows:

def update_position_func(self, v):
    self.player.setPosition(v)
    d = self.progress_slider.maximum() - v
    self.get_time_func(d)

Call the setPosition() method of QMediaPlayer and pass in progress_ To set the current playback progress, the remaining time is the maximum value of the progress bar minus the current value, and then we pass the remaining time d into get_time_func to update.

The complete code is as follows:

import sys
from PyQt5.Qt import QUrl
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication, QWidget, QListWidget, QLabel, QSlider, QPushButton, QHBoxLayout, \
    QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()

        self.time_label = QLabel(self)
        self.volume_slider = QSlider(self)
        self.progress_slider = QSlider(self)
        self.sound_btn = QPushButton(self)
        self.previous_btn = QPushButton(self)
        self.play_pause_btn = QPushButton(self)
        self.next_btn = QPushButton(self)
        self.mode_btn = QPushButton(self)
        self.list_btn = QPushButton(self)
        self.list_widget = QListWidget(self)

        self.h1_layout = QHBoxLayout()
        self.h2_layout = QHBoxLayout()
        self.all_v_layout = QVBoxLayout()

        self.playlist = QMediaPlaylist(self)        
        self.player = QMediaPlayer(self)

        self.widget_init()
        self.layout_init()
        self.signal_init()

    def widget_init(self):
        self.time_label.setText('--/--')
        self.volume_slider.setRange(0, 100)
        self.volume_slider.setValue(100)
        self.volume_slider.setOrientation(Qt.Horizontal)
        self.progress_slider.setEnabled(False)
        self.progress_slider.setOrientation(Qt.Horizontal)
        self.sound_btn.setIcon(QIcon('images/sound_on.png'))
        self.previous_btn.setIcon(QIcon('images/previous.png'))
        self.play_pause_btn.setIcon(QIcon('images/play.png'))
        self.next_btn.setIcon(QIcon('images/next.png'))
        self.mode_btn.setIcon(QIcon('images/list_loop.png'))
        self.list_btn.setIcon(QIcon('images/show.png'))

        self.player.setPlaylist(self.playlist)
        self.media_list = ['/Users/louis/Downloads/music1.mp3',
                           '/Users/louis/Downloads/music2.mp4',
                           '/Users/louis/Downloads/music3.mp3']
        for m in self.media_list:
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(m)))
        self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)

        self.list_widget.addItems([m.split('/')[-1] for m in self.media_list])

    def layout_init(self):
        self.h1_layout.addWidget(self.progress_slider)
        self.h1_layout.addWidget(self.time_label)
        self.h2_layout.addWidget(self.volume_slider)
        self.h2_layout.addWidget(self.sound_btn)
        self.h2_layout.addWidget(self.previous_btn)
        self.h2_layout.addWidget(self.play_pause_btn)
        self.h2_layout.addWidget(self.next_btn)
        self.h2_layout.addWidget(self.mode_btn)
        self.h2_layout.addWidget(self.list_btn)

        self.all_v_layout.addLayout(self.h1_layout)
        self.all_v_layout.addLayout(self.h2_layout)
        self.all_v_layout.addWidget(self.list_widget)
        self.all_v_layout.setSizeConstraint(QVBoxLayout.SetFixedSize)

        self.setLayout(self.all_v_layout)

    def signal_init(self):
        self.sound_btn.clicked.connect(lambda: self.btn_func(self.sound_btn))
        self.previous_btn.clicked.connect(lambda: self.btn_func(self.previous_btn))
        self.play_pause_btn.clicked.connect(lambda: self.btn_func(self.play_pause_btn))
        self.next_btn.clicked.connect(lambda: self.btn_func(self.next_btn))
        self.mode_btn.clicked.connect(lambda: self.btn_func(self.mode_btn))
        self.list_btn.clicked.connect(lambda: self.btn_func(self.list_btn))
        self.volume_slider.valueChanged.connect(self.volume_slider_func)
        self.list_widget.doubleClicked.connect(self.list_play_func)
        self.player.durationChanged.connect(self.get_duration_func)
        self.player.positionChanged.connect(self.get_position_func)
        self.progress_slider.sliderMoved.connect(self.update_position_func)

    def btn_func(self, btn):
        if btn == self.sound_btn:              
            if self.player.isMuted():
                self.player.setMuted(False)
                self.sound_btn.setIcon(QIcon('images/sound_on'))
            else:
                self.player.setMuted(True)
                self.sound_btn.setIcon(QIcon('images/sound_off'))

        elif btn == self.previous_btn:          
            if self.playlist.currentIndex() == 0:
                self.playlist.setCurrentIndex(self.playlist.mediaCount() - 1)
            else:
                self.playlist.previous()

        elif btn == self.play_pause_btn:      
            if self.player.state() == 1:
                self.player.pause()
                self.play_pause_btn.setIcon(QIcon('images/play.png'))
            else:
                self.player.play()
                self.play_pause_btn.setIcon(QIcon('images/pause.png'))

        elif btn == self.next_btn:              
            if self.playlist.currentIndex() == self.playlist.mediaCount() - 1:
                self.playlist.setCurrentIndex(0)
            else:
                self.playlist.next()

        elif btn == self.mode_btn:             
            if self.playlist.playbackMode() == 2:
                self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
                self.mode_btn.setIcon(QIcon('images/item_loop.png'))

            elif self.playlist.playbackMode() == 3:
                self.playlist.setPlaybackMode(QMediaPlaylist.Random)
                self.mode_btn.setIcon(QIcon('images/random.png'))

            elif self.playlist.playbackMode() == 4:
                self.playlist.setPlaybackMode(QMediaPlaylist.Sequential)
                self.mode_btn.setIcon(QIcon('images/list_loop.png'))

        elif btn == self.list_btn:             
            if self.list_widget.isHidden():
                self.list_widget.show()
                self.list_btn.setIcon(QIcon('images/show.png'))
            else:
                self.list_widget.hide()
                self.list_btn.setIcon(QIcon('images/hide.png'))

    def volume_slider_func(self, value):
        self.player.setVolume(value)
        if value == 0:
            self.sound_btn.setIcon(QIcon('images/sound_off.png'))
        else:
            self.sound_btn.setIcon(QIcon('images/sound_on.png'))

    def list_play_func(self):
        self.playlist.setCurrentIndex(self.list_widget.currentRow())
        self.player.play()
        self.play_pause_btn.setIcon(QIcon('images/pause.png'))

    def get_duration_func(self, d):
        self.progress_slider.setRange(0, d)
        self.progress_slider.setEnabled(True)
        self.get_time_func(d)

    def get_time_func(self, d):
        seconds = int(d / 1000)
        minutes = int(seconds / 60)
        seconds -= minutes * 60
        if minutes == 0 and seconds == 0:
            self.time_label.setText('--/--')
            self.play_pause_btn.setIcon(QIcon('images/play.png'))
        else:
            self.time_label.setText('{}:{}'.format(minutes, seconds))

    def get_position_func(self, p):
        self.progress_slider.setValue(p)

    def update_position_func(self, v):
        self.player.setPosition(v)
        d = self.progress_slider.maximum() - v
        self.get_time_func(d)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

33.6 summary

  1. QSound and QSound effect can only be used to play wav format audio files, and the latter can provide more refined operations;

  2. QMovie is used to play dynamic images;

  3. Qmedia player can play many types of media files. The method of playing and controlling audio and video with qmedia player is the same. Readers can make a simple video player to practice;

  4. The duration() method and durationChanged() signal do not work on MacOS, and the button icon changes slowly.

Topics: AI PyQt5