Skip to content

Commit

Permalink
Multimedia Player
Browse files Browse the repository at this point in the history
  • Loading branch information
se7enXF committed Dec 27, 2020
1 parent 05e98e7 commit f9e9f8c
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 9 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
!.gitignore
/Lesson_5.结合OpenCV实现视频播放器/*.png
*.mp4
*.mp3
__pycache__
!Lesson_10.AutoSizeImage/icon.png
!Lesson_11.MultimediaPlayer/icon.png
11 changes: 6 additions & 5 deletions EzQtTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ def __init__(self,

def add_layout_widget(self, parent: QtWidgets.QWidget,
widget: QtWidgets.QWidget,
layout: QtWidgets.QLayout=None,
stretch: int=None,
margins: int=None,
space: int=None) -> QtWidgets.QWidget:
layout: QtWidgets.QLayout = None,
stretch: int = None,
margins: [int, list] = None,
space: int = None
) -> QtWidgets.QWidget:
"""
给parent控件上添加widget。当parent首次添加widget,需要指定其布局格式。
:param parent: 父控件
Expand All @@ -91,7 +92,7 @@ def add_layout_widget(self, parent: QtWidgets.QWidget,
stretch = self.default_layout_stretch

if current_layout:
current_layout.addWidget(widget, stretch=stretch)
current_layout.addWidget(widget)
else:
if not layout:
if self.default_layout == 'H':
Expand Down
1 change: 0 additions & 1 deletion Lesson_10.AutoSizeImage/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ def __init__(self, *args, **kwargs):
self.contextMenu.sizeHint()

def contextMenuEvent(self, *args, **kwargs):
print(QCursor.pos(), self.contextMenu.size())
self.contextMenu.popup(QCursor.pos())

def menuSlot(self, act):
Expand Down
4 changes: 3 additions & 1 deletion Lesson_10.AutoSizeImage/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ Lesson5 的布局中直接放置了一个label,虽然设置了属性 scaledCon

## 写在最后

TODO
终于写完毕业论文了,可以好好研究以下Qt的各种问题。已经解决了多媒体播放的问题,请看
下一讲([第十一课](../Lesson_11.MultimediaPlayer/readme.md))。更新时间为
2020年12月27日。

File renamed without changes
212 changes: 212 additions & 0 deletions Lesson_11.MultimediaPlayer/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# -*- coding:utf-8 -*-
"""
@Author: Fei Xue
@E-mail: [email protected]
@File : main.py.py
@Date : 2020/10/15
@Info : 多媒体播放器,支持视频,音频,必须安装本地媒体解码器
作者使用解码器:https://github.com/Nevcairiel/LAVFilters
"""

from EzQtTools import *
import sys
from PySide2.QtMultimedia import *
from PySide2.QtMultimediaWidgets import QVideoWidget
from PySide2.QtCore import QUrl


class MainWindow(EzMainWindow):

def __init__(self, **kwargs):
EzMainWindow.__init__(self, **kwargs)
self.player_widget = VideoPlayer()
self.__init__widget()
self.total_time = 0
self.now_time = 0
self.play_rate = 1

def __init__widget(self):
# up, down
self.image_area = self.add_layout_widget(self.central_widget, QtWidgets.QWidget(), stretch=1)
self.control_area = self.add_layout_widget(self.central_widget, QtWidgets.QWidget(), stretch=0)

# image
self.image = self.add_layout_widget(self.image_area, self.player_widget.video_widget)
# progress widget
self.progress = self.add_layout_widget(self.control_area, QtWidgets.QWidget())
# control widget
self.control = self.add_layout_widget(self.control_area, QtWidgets.QWidget())

# button in control
self.back = self.add_layout_widget(self.control, QtWidgets.QPushButton('正常速度'), QtWidgets.QHBoxLayout())
self.play = self.add_layout_widget(self.control, QtWidgets.QPushButton('播放'))
self.next = self.add_layout_widget(self.control, QtWidgets.QPushButton('加速播放'))
self.back.setEnabled(False)
self.play.setEnabled(False)
self.next.setEnabled(False)

# progress area
self.progress_bar = self.add_layout_widget(self.progress, QtWidgets.QSlider(QtCore.Qt.Horizontal),
QtWidgets.QHBoxLayout(), space=20)
self.progress_text = self.add_layout_widget(self.progress, QtWidgets.QLabel('00:00:00/00:00:00'))
self.progress_bar.setEnabled(False)
# volume control
self.volume = self.add_layout_widget(self.progress, QtWidgets.QSlider(QtCore.Qt.Horizontal),
QtWidgets.QHBoxLayout())
self.volume.setFixedWidth(100)
self.volume.setMinimum(0)
self.volume.setMaximum(100)
self.volume.setSingleStep(1)
self.volume.setValue(50)

# menubar
self.open = QtWidgets.QMenu('打开')
self.menuBar().addMenu(self.open)
# menu
self.open_video = QtWidgets.QAction('打开视频')
self.open.addAction(self.open_video)

# connections
self.open_video.triggered.connect(self.open_video_play)
self.play.clicked.connect(self.play_or_pause)
self.progress_bar.sliderMoved.connect(self.img_slider_move)
self.volume.sliderMoved.connect(self.volume_slider_move)
self.player_widget.dur_changed.connect(self.auto_set_img_slider_max_pos)
self.player_widget.pos_changed.connect(self.auto_move_img_slider)
self.player_widget.state_changed.connect(self.auto_reset_play_end)
self.back.clicked.connect(self.play_rate_reset)
self.next.clicked.connect(self.play_rate_raise)

def open_video_play(self):
video_path = self.open_file_dialog(default_dir='', types=['mp4', 'flv', 'avi', 'mp3'])
if video_path:
self.player_widget.video_widget.show()
self.player_widget.set_media(video_path)
self.player_widget.play()

self.play.setText('暂停')
self.back.setEnabled(True)
self.play.setEnabled(True)
self.next.setEnabled(True)
self.progress_bar.setEnabled(True)

def play_or_pause(self):
if self.play.text() == '暂停':
self.play.setText('播放')
self.player_widget.pause()
else:
self.play.setText('暂停')
self.player_widget.play()

def reset_all(self):
self.back.setEnabled(False)
self.play.setEnabled(False)
self.next.setEnabled(False)
self.progress_bar.setEnabled(False)
self.play.setText('播放')
self.progress_text.setText('00:00:00 / 00:00:00')
self.progress_bar.setValue(0)
self.player_widget.video_widget.hide()

def img_slider_move(self, pos: int):
self.player_widget.set_pos(pos)

def volume_slider_move(self, pos: int):
self.player_widget.set_volume(pos)

def auto_move_img_slider(self, pos: int):
self.progress_bar.setValue(pos)
self.now_time = pos
now, total = self.calculate_time(self.now_time, self.total_time)
self.progress_text.setText(f'{now} / {total}')

def auto_set_img_slider_max_pos(self, dur: int):
self.progress_bar.setMaximum(dur)
self.total_time = dur

def auto_reset_play_end(self, state: int):
if state == 0:
self.reset_all()

def calculate_time(self, now: int, total: int):
current_sec = now // 1000
total_seconds = total // 1000
c_time = "{}:{}:{}".format(int(current_sec/3600), int((current_sec % 3600)/60), int(current_sec % 60))
t_time = "{}:{}:{}".format(int(total_seconds / 3600), int((total_seconds % 3600) / 60), int(total_seconds % 60))
return c_time, t_time

def play_rate_reset(self):
self.play_rate = 1
self.player_widget.set_play_rate(self.play_rate)

def play_rate_raise(self):
self.play_rate += 1
self.player_widget.set_play_rate(self.play_rate)


class VideoPlayer(QtWidgets.QWidget):

dur_changed = QtCore.Signal(int)
pos_changed = QtCore.Signal(int)
state_changed = QtCore.Signal(int)

def __init__(self, parent=None):
super(VideoPlayer, self).__init__(parent=parent)
"""
需要安装媒体解码器才可以正常使用QMediaPlayer
作者使用 LAVFilters 来解码视频和音频。 URL:https://github.com/Nevcairiel/LAVFilters
"""
self.video_player = QMediaPlayer()
self.video_widget = QVideoWidget()
self.video_player.setVideoOutput(self.video_widget)
self.video_player.setVolume(50)

# 信号连接
self.video_player.durationChanged.connect(self.__dur_changed)
self.video_player.positionChanged.connect(self.__pos_changed)
self.video_player.stateChanged.connect(self.__state_changed)

def set_media(self, media_path: str):
media = QMediaContent(QUrl.fromLocalFile(media_path))
media = QMediaContent(media)
self.video_player.setMedia(media)

def set_volume(self, v: int = 50):
self.video_player.setVolume(v)

def set_play_rate(self, speed: int = 1):
self.video_player.setPlaybackRate(speed)

def set_pos(self, pos: int):
self.video_player.setPosition(pos)

def get_max_pos(self):
return self.video_player.duration()

def get_now_pos(self):
return self.video_player.position()

def get_video_widget(self):
return self.video_widget

def pause(self):
self.video_player.pause()

def play(self):
self.video_player.play()

def __dur_changed(self, dur: int):
self.dur_changed.emit(dur)

def __pos_changed(self, pos: int):
self.pos_changed.emit(pos)

def __state_changed(self, state: int):
self.state_changed.emit(state)


if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow(icon='icon.png', title='音视频播放器', default_layout_margins=2, default_layout_space=2)
window.show()
sys.exit(app.exec_())
25 changes: 25 additions & 0 deletions Lesson_11.MultimediaPlayer/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 多媒体播放器

之前尝试使用 Qt 的 QMediaPlayer 来播放视频或者音乐,一直没有成功。最近有时间了,
研究了一下,发现是Qt没有多媒体解码器。加载文件一直卡在 loading media 或者
format error 的状态。网络上搜到了一个很简洁的解码器,下载安装各种问题都解决了。
具体方法请看 main.py 中的说明。

## 设计方法

音频播放可以没有窗体,视频播放需要有图像的载体,QtMultimediaWidgets 中已经
包含了图像的载体窗口 QVideoWidget,我们只需要将其放置到主窗口的某个容器中即可。
我已经定义好了 VideoPlayer 类作为媒体播放器,这个类中定义了两个信号,一个是
媒体播放进度的信号,另一个是播放状态的信号。通过对这两个信号处理,可以控制播放
进度条自动移动。

## 程序说明

1. 所有程序放置在 main.py 中
2. 我计划将 EzQt 工具规范化,之后设计为一个使用 pip 安装的库。借鉴 Django
的方法来自动生成工程文件,减少 Qt 开发的代码量。

## 写在最后

TODO

2 changes: 1 addition & 1 deletion Lesson_7.主窗口的构成/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@

### 最后
* 熟悉掌握窗口的结构,以后控件部署会有条不紊。
* 下一讲([第八课](..\Lesson_8.窗口嵌套\readme.md))会介绍多窗口的继承和嵌套。
* 下一讲([第八课](../Lesson_8.窗口嵌套/readme.md))会介绍多窗口的继承和嵌套。

0 comments on commit f9e9f8c

Please sign in to comment.