Faced with the problem of updating progressbar in Qt in python
Question:
Write player, and emerged question, when I launching song, I want to progressbar was updated in the time when goes music, made cycle, threw his in thread, values on renewal are transmitted through signal in qml, but in than the problem, these values are transmitted only then when I click on this button, but raze not in real time.
Main.py
progressMusicSignal = Signal(float, arguments=['progressMusic'])
@Slot('float')
def setValue(self, flagTrue):
global thread, que
if flagTrue == 1:
que = queue.Queue()
thread = Thread(target=lambda ques, arg1: ques.put(progressBarMusic(arg1)), args=(que, flagTrue),
daemon=True)
thread.start()
result = que.get()
self.progressMusicSignal.emit(result)
elif flagTrue == 2:
thread.join()
def playMusic(flagMusic=0):
if flagMusic == 1:
pygame.mixer.music.load(PATHLESS + MUSICFILEWAV)
pygame.mixer.music.play()
if flagMusic == 2:
pygame.mixer.music.pause()
if flagMusic == 3:
pygame.mixer.music.unpause()
def progressBarMusic(flagTrue):
if flagTrue == 1:
while True:
song = pygame.mixer.Sound(PATHLESS + MUSICFILEWAV)
getLengthMusic = pygame.mixer.Sound.get_length(song)
milSec = pygame.mixer.music.get_pos()
operationLength = getLengthMusic // 10
print(operationLength)
sec = milSec // 1000
secRes = milSec // 100
print(secRes)
operationSecPercent = (secRes / operationLength) / 100
print(operationSecPercent)
if sec != getLengthMusic:
return operationSecPercent
Main.qml
RoundButton {
id: plauPauseBtn
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
enabled: true
opacity: 1.0
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 14
font.family: "Tahoma"
onClicked: {
plauPauseBtn.opacity = 0.0;
plauPauseBtn.enabled = false;
stopPauseBtn.opacity = 1.0;
stopPauseBtn.enabled = true;
con.playMusicInt(1)
con.setValue(1)
}
}
RoundButton {
id: stopPauseBtn
x: 370
y: 15
width: 50
height: 50
text: "||"
enabled: false
opacity: 0.0
bottomPadding: 13
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 7
font.family: "Tahoma"
onClicked: {
con.playMusicInt(2)
con.setValue(2)
stopPauseBtn.opacity = 0.0;
stopPauseBtn.enabled = false;
playAgainBtn.opacity = 1.0;
playAgainBtn.enabled = true;
}
}
RoundButton {
id: playAgainBtn
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
enabled: false
opacity: 0.0
bottomPadding: 13
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 14
font.family: "Tahoma"
onClicked: {
con.playMusicInt(3)
con.setValue(1)
playAgainBtn.opacity = 0.0;
playAgainBtn.enabled = false;
stopPauseBtn.opacity = 1.0;
stopPauseBtn.enabled = true;
}
}
ProgressBar {
id: musicProgressBar
x: 0
y: 0
width: 800
height: 5
indeterminate: false
value: 0.0
}
Connections {
target: con
onProgressMusicSignal: {
musicProgressBar.value = progressMusic
}
}
Answers:
The code provided by the OP is understandable, so I will avoid analyzing it, so I will propose a solution from scratch.
In this case I have created a wrapper on pygame.mixer.music that exposes the properties of the source, the volume, the current state and has methods exposed through pyqtSlot, that class does not handle the logic of your application but is only a resource .
The logic of your application must be handled in QML regarding the state of the button, and in that case it is not necessary to create several buttons since only one in which you change the text is enough.
Considering the above, the solution is:
main.py
import os
import math
import pygame
from PyQt5 import QtCore, QtGui, QtQml
class PyGameSound(QtCore.QObject):
sourceChanged = QtCore.pyqtSignal()
volumeChanged = QtCore.pyqtSignal()
stateChanged = QtCore.pyqtSignal()
notifyIntervalChanged = QtCore.pyqtSignal()
progressChanged = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str, arguments=["message"])
class State:
PlayingState, PausedState, StoppedState = range(3)
QtCore.Q_ENUMS(State)
def __init__(self, parent=None):
super().__init__(parent)
self.destroyed.connect(self.on_destroyed)
pygame.mixer.init()
self._source = ""
self._notifyInterval = 1000
self._progress = 0.0
self._volume = 1.0
self._notify_timer = QtCore.QTimer(self, timeout=self.on_notify_callback)
self._state = PyGameSound.State.StoppedState
@QtCore.pyqtProperty(State, notify=stateChanged)
def state(self):
return self._state
def _update_state(self, state):
self._state = state
self.stateChanged.emit()
def on_notify_callback(self):
if self.source:
try:
song = pygame.mixer.Sound(self.source)
total = song.get_length()
pos = pygame.mixer.music.get_pos()
if pos >= 0:
percentage = pos / (total * 1000.0)
if math.isclose(
percentage, 1.0, abs_tol=self.notifyInterval / 1000.0
):
percentage = 1.0
self.progress = percentage
except pygame.error as message:
self.error.emit(str(message))
@QtCore.pyqtProperty(str, notify=sourceChanged)
def source(self):
return self._source
@source.setter
def source(self, source):
try:
pygame.mixer.music.load(source)
except pygame.error as message:
self.error.emit(str(message))
source = ""
if self._source != source:
self._source = source
self.sourceChanged.emit()
@QtCore.pyqtProperty(float, notify=volumeChanged)
def volume(self):
return pygame.mixer.music.get_volume()
@volume.setter
def volume(self, volume):
pygame.mixer.music.set_volume(volume)
self.volumeChanged.emit()
@QtCore.pyqtProperty(int, notify=notifyIntervalChanged)
def notifyInterval(self):
return self._notifyInterval
@notifyInterval.setter
def notifyInterval(self, interval):
if self._notifyInterval != interval:
self._notifyInterval = interval
is_active = self._notify_timer.isActive()
if is_active:
self._notify_timer.stop()
self._notify_timer.setInterval(self._notifyInterval)
if is_active:
self._notify_timer.start()
@QtCore.pyqtProperty(float, notify=progressChanged)
def progress(self):
return self._progress
@progress.setter
def progress(self, progress):
self._progress = progress
self.progressChanged.emit()
@QtCore.pyqtSlot()
def play(self):
try:
pygame.mixer.music.play()
self._notify_timer.start()
except pygame.error as message:
self.error.emit(str(message))
return
self._update_state(PyGameSound.State.PlayingState)
@QtCore.pyqtSlot()
def unpause(self):
pygame.mixer.music.unpause()
self._notify_timer.start()
self._update_state(PyGameSound.State.PlayingState)
@QtCore.pyqtSlot()
def pause(self):
pygame.mixer.music.pause()
self._notify_timer.stop()
self._update_state(PyGameSound.State.PausedState)
@QtCore.pyqtSlot()
def stop(self):
pygame.mixer.music.stop()
self._notify_timer.stop()
self._update_state(PyGameSound.State.StoppedState)
def on_destroyed(self):
pygame.mixer.quit()
if __name__ == "__main__":
import sys
current_dir = os.path.dirname(os.path.realpath(__file__))
QtQml.qmlRegisterType(PyGameSound, "PyGame", 1, 0, "PyGameSound")
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.5
import PyGame 1.0
ApplicationWindow{
visible: true
width: 640
height: 480
PyGameSound{
id: sound
notifyInterval: 10
source: "/path/of/music.wav"
volume: 1.0
onError: console.log(message)
}
RoundButton {
id: play_pause_button
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
display: AbstractButton.TextBesideIcon
font {
weight: Font.ExtraBold
capitalization: Font.MixedCase
strikeout: false
pointSize: 14
family: "Tahoma"
bold: false
underline: false
italic: false
}
onClicked: {
if(sound.state == PyGameSound.StoppedState){
sound.play()
play_pause_button.text = "||"
}
else if(sound.state == PyGameSound.PlayingState){
sound.pause()
play_pause_button.text = "u25b7"
}
else if(sound.state == PyGameSound.PausedState){
sound.unpause()
play_pause_button.text = "||"
}
}
}
ProgressBar {
id: musicProgressBar
width: parent.width
height: 5
indeterminate: false
value: sound.progress
}
}
Although the simplest solution is to use the Audio module:
from PyQt5 import QtCore, QtGui, QtQml
if __name__ == "__main__":
import os
import sys
current_dir = os.path.dirname(os.path.realpath(__file__))
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.5
import QtMultimedia 5.13
ApplicationWindow{
visible: true
width: 640
height: 480
Audio{
id: sound
notifyInterval: 10
source: "/path/of/music.wav"
}
RoundButton {
id: play_pause_button
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
display: AbstractButton.TextBesideIcon
font {
weight: Font.ExtraBold
capitalization: Font.MixedCase
strikeout: false
pointSize: 14
family: "Tahoma"
bold: false
underline: false
italic: false
}
onClicked: {
if(sound.playbackState == Audio.StoppedState){
sound.play()
play_pause_button.text = "||"
}
else if(sound.playbackState == Audio.PlayingState){
sound.pause()
play_pause_button.text = "u25b7"
}
else if(sound.playbackState == Audio.PausedState){
sound.play()
play_pause_button.text = "||"
}
}
}
ProgressBar {
id: musicProgressBar
width: parent.width
height: 5
indeterminate: false
value: sound.position/sound.duration
}
}
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
import time
class Example(QWidget):
def __init__(self):
super().__init__()
# calling initUI method
self.initUI()
# method for creating widgets
def initUI(self):
# creating progress bar
self.pbar = QProgressBar(self)
# setting its geometry
self.pbar.setGeometry(30, 40, 200, 25)
# creating push button
self.btn = QPushButton('Start', self)
# changing its position
self.btn.move(40, 80)
# adding action to push button
self.btn.clicked.connect(self.doAction)
# setting window geometry
self.setGeometry(300, 300, 280, 170)
# setting window action
self.setWindowTitle("Python")
# showing all the widgets
self.show()
# when button is pressed this method is being called
def doAction(self):
# setting for loop to set value of progress bar
for i in range(101):
# slowing down the loop
time.sleep(0.05)
# setting value to progress bar
self.pbar.setValue(i)
# main method
if __name__ == '__main__':
# create pyqt5 app
App = QApplication(sys.argv)
# create the instance of our Window
window = Example()
# start the app
sys.exit(App.exec())
I have not found a code where the "amount" of progress would be known and could be filled in as a proportion of the total, but it is also possible at the end of each part to simply +1 to the total amount of progress
Write player, and emerged question, when I launching song, I want to progressbar was updated in the time when goes music, made cycle, threw his in thread, values on renewal are transmitted through signal in qml, but in than the problem, these values are transmitted only then when I click on this button, but raze not in real time.
Main.py
progressMusicSignal = Signal(float, arguments=['progressMusic'])
@Slot('float')
def setValue(self, flagTrue):
global thread, que
if flagTrue == 1:
que = queue.Queue()
thread = Thread(target=lambda ques, arg1: ques.put(progressBarMusic(arg1)), args=(que, flagTrue),
daemon=True)
thread.start()
result = que.get()
self.progressMusicSignal.emit(result)
elif flagTrue == 2:
thread.join()
def playMusic(flagMusic=0):
if flagMusic == 1:
pygame.mixer.music.load(PATHLESS + MUSICFILEWAV)
pygame.mixer.music.play()
if flagMusic == 2:
pygame.mixer.music.pause()
if flagMusic == 3:
pygame.mixer.music.unpause()
def progressBarMusic(flagTrue):
if flagTrue == 1:
while True:
song = pygame.mixer.Sound(PATHLESS + MUSICFILEWAV)
getLengthMusic = pygame.mixer.Sound.get_length(song)
milSec = pygame.mixer.music.get_pos()
operationLength = getLengthMusic // 10
print(operationLength)
sec = milSec // 1000
secRes = milSec // 100
print(secRes)
operationSecPercent = (secRes / operationLength) / 100
print(operationSecPercent)
if sec != getLengthMusic:
return operationSecPercent
Main.qml
RoundButton {
id: plauPauseBtn
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
enabled: true
opacity: 1.0
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 14
font.family: "Tahoma"
onClicked: {
plauPauseBtn.opacity = 0.0;
plauPauseBtn.enabled = false;
stopPauseBtn.opacity = 1.0;
stopPauseBtn.enabled = true;
con.playMusicInt(1)
con.setValue(1)
}
}
RoundButton {
id: stopPauseBtn
x: 370
y: 15
width: 50
height: 50
text: "||"
enabled: false
opacity: 0.0
bottomPadding: 13
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 7
font.family: "Tahoma"
onClicked: {
con.playMusicInt(2)
con.setValue(2)
stopPauseBtn.opacity = 0.0;
stopPauseBtn.enabled = false;
playAgainBtn.opacity = 1.0;
playAgainBtn.enabled = true;
}
}
RoundButton {
id: playAgainBtn
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
enabled: false
opacity: 0.0
bottomPadding: 13
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 14
font.family: "Tahoma"
onClicked: {
con.playMusicInt(3)
con.setValue(1)
playAgainBtn.opacity = 0.0;
playAgainBtn.enabled = false;
stopPauseBtn.opacity = 1.0;
stopPauseBtn.enabled = true;
}
}
ProgressBar {
id: musicProgressBar
x: 0
y: 0
width: 800
height: 5
indeterminate: false
value: 0.0
}
Connections {
target: con
onProgressMusicSignal: {
musicProgressBar.value = progressMusic
}
}
The code provided by the OP is understandable, so I will avoid analyzing it, so I will propose a solution from scratch.
In this case I have created a wrapper on pygame.mixer.music that exposes the properties of the source, the volume, the current state and has methods exposed through pyqtSlot, that class does not handle the logic of your application but is only a resource .
The logic of your application must be handled in QML regarding the state of the button, and in that case it is not necessary to create several buttons since only one in which you change the text is enough.
Considering the above, the solution is:
main.py
import os
import math
import pygame
from PyQt5 import QtCore, QtGui, QtQml
class PyGameSound(QtCore.QObject):
sourceChanged = QtCore.pyqtSignal()
volumeChanged = QtCore.pyqtSignal()
stateChanged = QtCore.pyqtSignal()
notifyIntervalChanged = QtCore.pyqtSignal()
progressChanged = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str, arguments=["message"])
class State:
PlayingState, PausedState, StoppedState = range(3)
QtCore.Q_ENUMS(State)
def __init__(self, parent=None):
super().__init__(parent)
self.destroyed.connect(self.on_destroyed)
pygame.mixer.init()
self._source = ""
self._notifyInterval = 1000
self._progress = 0.0
self._volume = 1.0
self._notify_timer = QtCore.QTimer(self, timeout=self.on_notify_callback)
self._state = PyGameSound.State.StoppedState
@QtCore.pyqtProperty(State, notify=stateChanged)
def state(self):
return self._state
def _update_state(self, state):
self._state = state
self.stateChanged.emit()
def on_notify_callback(self):
if self.source:
try:
song = pygame.mixer.Sound(self.source)
total = song.get_length()
pos = pygame.mixer.music.get_pos()
if pos >= 0:
percentage = pos / (total * 1000.0)
if math.isclose(
percentage, 1.0, abs_tol=self.notifyInterval / 1000.0
):
percentage = 1.0
self.progress = percentage
except pygame.error as message:
self.error.emit(str(message))
@QtCore.pyqtProperty(str, notify=sourceChanged)
def source(self):
return self._source
@source.setter
def source(self, source):
try:
pygame.mixer.music.load(source)
except pygame.error as message:
self.error.emit(str(message))
source = ""
if self._source != source:
self._source = source
self.sourceChanged.emit()
@QtCore.pyqtProperty(float, notify=volumeChanged)
def volume(self):
return pygame.mixer.music.get_volume()
@volume.setter
def volume(self, volume):
pygame.mixer.music.set_volume(volume)
self.volumeChanged.emit()
@QtCore.pyqtProperty(int, notify=notifyIntervalChanged)
def notifyInterval(self):
return self._notifyInterval
@notifyInterval.setter
def notifyInterval(self, interval):
if self._notifyInterval != interval:
self._notifyInterval = interval
is_active = self._notify_timer.isActive()
if is_active:
self._notify_timer.stop()
self._notify_timer.setInterval(self._notifyInterval)
if is_active:
self._notify_timer.start()
@QtCore.pyqtProperty(float, notify=progressChanged)
def progress(self):
return self._progress
@progress.setter
def progress(self, progress):
self._progress = progress
self.progressChanged.emit()
@QtCore.pyqtSlot()
def play(self):
try:
pygame.mixer.music.play()
self._notify_timer.start()
except pygame.error as message:
self.error.emit(str(message))
return
self._update_state(PyGameSound.State.PlayingState)
@QtCore.pyqtSlot()
def unpause(self):
pygame.mixer.music.unpause()
self._notify_timer.start()
self._update_state(PyGameSound.State.PlayingState)
@QtCore.pyqtSlot()
def pause(self):
pygame.mixer.music.pause()
self._notify_timer.stop()
self._update_state(PyGameSound.State.PausedState)
@QtCore.pyqtSlot()
def stop(self):
pygame.mixer.music.stop()
self._notify_timer.stop()
self._update_state(PyGameSound.State.StoppedState)
def on_destroyed(self):
pygame.mixer.quit()
if __name__ == "__main__":
import sys
current_dir = os.path.dirname(os.path.realpath(__file__))
QtQml.qmlRegisterType(PyGameSound, "PyGame", 1, 0, "PyGameSound")
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.5
import PyGame 1.0
ApplicationWindow{
visible: true
width: 640
height: 480
PyGameSound{
id: sound
notifyInterval: 10
source: "/path/of/music.wav"
volume: 1.0
onError: console.log(message)
}
RoundButton {
id: play_pause_button
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
display: AbstractButton.TextBesideIcon
font {
weight: Font.ExtraBold
capitalization: Font.MixedCase
strikeout: false
pointSize: 14
family: "Tahoma"
bold: false
underline: false
italic: false
}
onClicked: {
if(sound.state == PyGameSound.StoppedState){
sound.play()
play_pause_button.text = "||"
}
else if(sound.state == PyGameSound.PlayingState){
sound.pause()
play_pause_button.text = "u25b7"
}
else if(sound.state == PyGameSound.PausedState){
sound.unpause()
play_pause_button.text = "||"
}
}
}
ProgressBar {
id: musicProgressBar
width: parent.width
height: 5
indeterminate: false
value: sound.progress
}
}
Although the simplest solution is to use the Audio module:
from PyQt5 import QtCore, QtGui, QtQml
if __name__ == "__main__":
import os
import sys
current_dir = os.path.dirname(os.path.realpath(__file__))
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.5
import QtMultimedia 5.13
ApplicationWindow{
visible: true
width: 640
height: 480
Audio{
id: sound
notifyInterval: 10
source: "/path/of/music.wav"
}
RoundButton {
id: play_pause_button
x: 370
y: 15
width: 50
height: 50
text: "u25b7"
display: AbstractButton.TextBesideIcon
font {
weight: Font.ExtraBold
capitalization: Font.MixedCase
strikeout: false
pointSize: 14
family: "Tahoma"
bold: false
underline: false
italic: false
}
onClicked: {
if(sound.playbackState == Audio.StoppedState){
sound.play()
play_pause_button.text = "||"
}
else if(sound.playbackState == Audio.PlayingState){
sound.pause()
play_pause_button.text = "u25b7"
}
else if(sound.playbackState == Audio.PausedState){
sound.play()
play_pause_button.text = "||"
}
}
}
ProgressBar {
id: musicProgressBar
width: parent.width
height: 5
indeterminate: false
value: sound.position/sound.duration
}
}
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
import time
class Example(QWidget):
def __init__(self):
super().__init__()
# calling initUI method
self.initUI()
# method for creating widgets
def initUI(self):
# creating progress bar
self.pbar = QProgressBar(self)
# setting its geometry
self.pbar.setGeometry(30, 40, 200, 25)
# creating push button
self.btn = QPushButton('Start', self)
# changing its position
self.btn.move(40, 80)
# adding action to push button
self.btn.clicked.connect(self.doAction)
# setting window geometry
self.setGeometry(300, 300, 280, 170)
# setting window action
self.setWindowTitle("Python")
# showing all the widgets
self.show()
# when button is pressed this method is being called
def doAction(self):
# setting for loop to set value of progress bar
for i in range(101):
# slowing down the loop
time.sleep(0.05)
# setting value to progress bar
self.pbar.setValue(i)
# main method
if __name__ == '__main__':
# create pyqt5 app
App = QApplication(sys.argv)
# create the instance of our Window
window = Example()
# start the app
sys.exit(App.exec())
I have not found a code where the "amount" of progress would be known and could be filled in as a proportion of the total, but it is also possible at the end of each part to simply +1 to the total amount of progress