如果能往应用程序中的按钮上面加个提示音,这样用户每次点击按钮的感觉就会更好,程序也就显得更加讨人喜欢。而要做游戏的话,那对声音的要求就更高了。当然不仅仅是声音,程序中偶尔也需要用到视频播放的功能。PyQt5对多媒体的处理能力已经非常不错了,本章我们会详细介绍跟音频和视频有关的类,并带大家一起做一个简单的音乐播放器来巩固。希望读完本章的小伙伴们能够很好的掌握往程序中添加声音和视频的方法。
33.1 QSound
想要简单快速地播放wav音频文件,使用QSound类并调用play()方法即可:
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_())
-
传入wav文件的路径来实例化一个QSound类;
-
将按钮的信号和play()槽函数进行连接,这样每次点击按钮就会播放声音了。
sound.wav文件下载地址如下: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=
看下文档我们看到这个QSound类还提供这些常用的方法:
我们给上面这个程序加个停止播放的功能,也就是使用stop()方法:
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_())
- 因为音频本来就很短,为了演示功能,就调用setLoops()方法传入QSound.Infinite参数让声音无限循环播放。传入相应的正整数会播放相应的次数。播放后再点击停止按钮就可以停止声音了。
运行截图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnaPwgBt-1641378935439)(data:image/svg+xml;utf8, )]
该程序会通过各个按钮来控制gif图像,比如停止,快进,截图等。
代码如下:
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_())
- 实例化一个QMovie对象,再调用setFileName()方法来设置要播放的gif文件。动画由一张张帧组成,而jumpToFrame()这个方法可以选择让gif跳转到某一帧。这里我们传入0,意思是让程序初始化时就让gif文件跳转到第一帧。如果没有调用jumpToFrame(0),那程序刚开始运行起来会是下面这样:
但是如果刚开始传入比0大的数也是上图这样,也就是说在初始化的时候我们最多显示第一帧,如果要使用jumpToFrame()跳转到某一帧的话该帧必须先播放过;
-
QLabel控件用于显示QMovie;
-
self.speed变量用于控制播放速度,在之后快进和快退功能中用到;
-
实例化几个按钮,分别是:开始、暂停、停止、快进、快退和截图。接着设置按钮的图标,并进行信号和槽的连接;
-
在槽函数中,我们先判断传入的按钮种类,再进行相应操作:
- start_btn: 调用start()方法来开始播放gif文件
- pause_btn: 调用setPaused(True)来暂停播放,传入False则继续播放
- stop_btn: 调用stop()方法停止播放,此时如果再调用start()方法的话,那gif文件会从第一帧开始播放。这里我们再使用jumpToFrame(0)目的是为了在停止播放后,画面直接跳转到第一帧(在MacOS上这行代码无效)
- fast_btn和back_btn: 先调整self.speed变量大小,在传入setSpeed()方法中来控制播放速度
- screenshot_btn: 使用currentPixmap()方法获取当前帧(此时还是QPixmap类型),接着再调用save()方法将该帧保存到本地
图片下载地址:
- start.png: https://www.easyicon.net/download/png/1222647/64/
- pause.png: https://www.easyicon.net/download/png/1222644/64/
- stop.png: https://www.easyicon.net/download/png/1222651/64/
- fast_forward.png: https://www.easyicon.net/download/png/1222639/64/
- back_forward.png: https://www.easyicon.net/download/png/1222638/64/
- screenshot.png: https://www.easyicon.net/download/png/1168943/64/
当进行某些耗时操作时,我们通常会显示一个加载动画来缓解用户心情,QMovie其实经常被拿来这么用。下面是一个简单例子:
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_())
当点击按钮时,我们开启多线程来进行耗时操作,在self.countdown变量还大于0时,显示loading.gif。等self.countdown变量小于0时,循环结束,发射信号,self.label显示"Time’s Up!"文本。
运行截图如下,刚开始状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kjz01mez-1641378935448)(data:image/svg+xml;utf8, )]
耗时操作结束后,self.label显示“Time’s Up!”文本:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HGOZiRvJ-1641378935449)(data:image/svg+xml;utf8, )]
-
setCurrentIndex()可以设置当前要进行播放文件,传入2代表播放第三个文件;
-
调用setVolume()方法来设置音量,调用play()方法进行播放。
播放视频文件跟播放音频的代码差不多,不过要播放视频的话我们还需要用到VideoWidget这个控件,该控件用于视频和图像输出,我们只要将它挂到播放器上就可以啦:
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_())
-
实例化QVideoWidget控件,并将该控件的初始大小调整为窗口大小;
-
调用播放器的setVideoOutput()方法并传入QVideoWidget实例来设置视频播放设备;
-
将媒体文件换为视频(mp3->mp4)。
运行截图如下,视频从派拉蒙到福克斯再到迪士尼循环播放:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b8i5MszS-1641378935450)(data:image/svg+xml;utf8, )]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypMFEOuz-1641378935452)(data:image/svg+xml;utf8, )]
该播放器各部分功能如下:
- 播放进度条显示和控制播放进度,右边的–/--用于显示剩余时间;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DzOaC330-1641378935453)(data:image/svg+xml;utf8, )]
- 上一首/播放和暂停/下一首;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-44HBfWkP-1641378935454)(data:image/svg+xml;utf8, )]
- 显示和隐藏下方列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gf5L7DsA-1641378935455)(data:image/svg+xml;utf8, )]
现在我们来编写代码,下面是用到的模块:
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
各图标下载地址:
链接:https://pan.baidu.com/s/1aZTiIdthwdI5MRl8jjtP1g 密码:65k3
首先我们来完成界面布局:
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_())
- 实例化需要用到的控件:
- time_label: 显示剩余时间
- volume_slider: 音量滑动条
- progress_slider: 播放进度条
- sound_btn: 喇叭按钮
- previous_btn: 上一首按钮
- play_pause_btn: 播放和暂停按钮
- next_btn: 下一首按钮
- mode_btn: 播放模式按钮
- list_btn: 显示和隐藏列表按钮
- list_widget: 列表控件显示全部播放列表
-
上面提到QMediaPlayer的音量值范围为0-100,所以我们将其设置为volume_slider的音量值范围;
-
媒体文件未播放前,播放进度条无法使用;
-
设置各个按钮的图标;
-
布局:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBAAGIdx-1641378935456)(data:image/svg+xml;utf8, )]
然后实现各个按钮的功能。在signal_init()函数中完成信号和槽的连接(别忘了将signal_init()放到类的初始化函数__init__()中):
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))
我们将按钮全部连接到btn_func()槽函数上:
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'))
-
如果是喇叭按钮按下的话,调用QMediaPlayer类的isMuted()方法判断是否已经静音,再调用setMuted()方法来作出相应的操作,按钮图标同时改变;
-
如果是上一首按钮的话,则要先调用QMediaPlaylist的currentIndex()方法来知道当前播放文件的索引。如果等于0的话说明正在播放第一个文件,那此时已经没有上一首了,所以我们应该播放最后一首,最后一首的索引也就是mediaCount()-1;如果不等于0那就直接调用QMediaPlayer类的previous()方法来播放上一首歌曲;
-
先判断播放器的状态,如果正在播放的话,则暂停,否则开始/继续播放。下面是QMediaPlayer.state()可返回的值:
-
逻辑同第2点类似;
-
如果是播放模式按钮的话,就相应的调用setPlaybackMode()方法来改变播放模式,同时还要改变按钮的图标;
-
如果是列表按钮,就先判断列表控件当前是否可见,再做出相应操作即可;
各个按钮的功能已经完成了,我们接下来完成音量滑动条的功能以及列表双击播放的功能。在signal_init()中加上这两行代码:
self.volume_slider.valueChanged.connect(self.volume_slider_func)
self.list_widget.doubleClicked.connect(self.list_play_func)
volume_slider_func()槽函数如下:
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'))
注:ValueChanged信号每次被发射的话都会带一个value值,也就是滑动条当前的值。之后的sliderMoved, durationChanged和positionChanged信号也是同理,详情见文档。
每次音量滑动条值发生改变,就调用setVolume()方法将播放器的音量设置成相应的值,如果音量为0的话还要记得将喇叭按钮图标改成静音图标:
list_play_func()槽函数如下:
def list_play_func(self):
self.playlist.setCurrentIndex(self.list_widget.currentRow())
self.player.play()
self.play_pause_btn.setIcon(QIcon('images/pause.png'))
首先调用QListWidget类的currentRow()方法获取到当前被双击的索引,然后传入QMediaPlaylist类的setCurrentIndex()方法中,接着调用QMediaPlayer类的play()方法进行播放即可。注意一个细节就是既然已经开始播放的话就要将play_pause_btn的图标改成暂停图标pause.png。
最后要完成的就是播放进度条和剩余时间显示。要完成这两个功能我们肯定要先获取到文件的时长,而QMediaPlayer正好有个duration()方法可以用:
该方法返回当前媒体文件时长,单位是毫秒。不过需要注意的就是在刚开始播放时调用duration()方法只会获取到0,所以文档建议用durationChanged信号来监听获取时长,也就是说我们要在一个槽函数中获取该。知道文件总时长的话,我们就可以设置progress_slider的值范围了。
注:遗憾的一点是,duration()和durationChanged在MacOS上无发使用,值始终为0(Windows和Linux上可以使用)。
播放进度肯定是要改变的,进度条上的按钮肯定要慢慢向右移动,也就是说我们要不断知道当前已经播放了多少时长,并将其设置为progress_slider的当前值。QMediaPlayer类的position()方法可以获取到当前已经播放的时长,而positionChanged信号会在position值发生改变的时候发射。
既然已经知道了要用的方法,那我们就可以开工了。在signal_init()函数中加上下面两行代码:
self.player.durationChanged.connect(self.get_duration_func)
self.player.positionChanged.connect(self.get_position_func)
get_duration_func()槽函数如下:
def get_duration_func(self, d):
self.progress_slider.setRange(0, d)
self.progress_slider.setEnabled(True)
self.get_time_func(d)
设置progress_slider范围,并将其设为可用状态,在get_time_func()函数中我们更新时间:
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))
首先获取分钟数和秒数,然后判断是否已经播放完毕(即分秒都为0)。如果播放结束的话,则将时间文本设为–/--,而且还要将播放按钮的图标设为play.png;否则就设置文本设为剩余时间。
get_position()槽函数如下,非常简单,就是设置progress_slider的当前值:
def get_position_func(self, p):
self.progress_slider.setValue(p)
此时运行程序,播放某个文件的话就可以看到进度条走动了,而且时间也会显示出来。现在还差最后一个,就是滑动进度条来改变播放进度。我们在signal_init()中加上如下代码:
self.progress_slider.sliderMoved.connect(self.update_position_func)
注:不能使用valueChanged信号来进行连接,否则会跟positionChanged()信号会起冲突,而sliderMoved这个信号是只有在用户手动改变滑动条值得情况下才会发射。
update_position_func()槽函数如下:
def update_position_func(self, v):
self.player.setPosition(v)
d = self.progress_slider.maximum() - v
self.get_time_func(d)
调用QMediaPlayer的setPosition()方法并传入progress_slider的值来设置当前播放进度,那剩余时间就是进度条最大值减去当前值,接着我们将剩余时间d传入get_time_func来更新即可。
完整代码如下:
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 小结
-
QSound和QSoundEffect只能用来播放wav格式的音频文件,后者可以提供更加精细化的操作;
-
QMovie用来播放动态图像;
-
QMediaPlayer可以播放多种类型的媒体文件,用它来播放和控制音频和视频的方法都是相同的,读者可以自己再制作一个简单视频播放器来练练手;
-
duration()方法和durationChanged()信号在MacOS上起不了作用,还有按钮图标改变慢一拍。