QMediaPlayer negative playbackRate doesn't rewind video - python

I am trying to use QMediaPlayer to create a.. media player. I want to have an ability to rewind videos on press of a button.
But setting a negative playbackRate via QMediaPlayer.setPlaybackRate doesn't seem to put video on rewind. It just keeps playing forward.
I don't want to change the position of the video, I want the video to play at negative speed. Through some logic of mine, setting playbackRate to a negative value would make the video play in reverse. But that just doesn't happen. If you can't understand me clearly, here is a video of what the playback should look like.
Here is some barebones code to reproduce the problem:
import sys
from PyQt5.QtCore import Qt, QUrl, QEvent
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import QMainWindow, QApplication
class VideoWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
videoWidget = QVideoWidget()
self.setCentralWidget(videoWidget)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.mediaPlayer.setVideoOutput(videoWidget)
self.mediaPlayer.setMedia(
QMediaContent(QUrl.fromLocalFile(r"<some video file>.mp4"))
)
self.mediaPlayer.play()
app.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.KeyPress:
key = event.key()
if key == Qt.Key_A:
self.mediaPlayer.setPlaybackRate(1.0)
elif key == Qt.Key_Y:
self.mediaPlayer.setPlaybackRate(-1.0) # This doesn't work! :(
return super().eventFilter(source, event)
def closeEvent(self, event):
self.mediaPlayer.setMedia(QMediaContent())
if __name__ == '__main__':
app = QApplication(sys.argv)
player = VideoWindow()
player.resize(640, 480)
player.show()
exitCode = app.exec_()
sys.exit(exitCode)
I tried searching for a solution, but I got nothing more than what documentation says (emphasis mine):
Values less than zero can be set and indicate the media will rewind at
the multiplier of the standard pace.
However, I am not seeing this effect.
I did notice this:
Not all playback services support change of the playback rate.
Is it possible that I can't rewind an mp4? Perhaps I need to install / change something?

QMediaPlayer.playbackRate property holds the playback rate of the current media.
This value is a multiplier applied to the media's standard play rate.
Press the Key_A, Key_Z, Key_Y keys to see how it works.
If you want to rewind the video, you should use QMediaPlayer.position property, which holds the playback position of the current media.
The value is the current playback position, expressed in milliseconds since the beginning of the media.
Press the Key_M, Key_P keys to see how it works.
import sys
from PyQt5.QtCore import Qt, QUrl, QEvent
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import QMainWindow, QApplication
class VideoWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
videoWidget = QVideoWidget()
self.setCentralWidget(videoWidget)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.mediaPlayer.setVideoOutput(videoWidget)
self.mediaPlayer.setMedia(
QMediaContent(QUrl.fromLocalFile(r"<some video file>.mp4"))
)
self.mediaPlayer.play()
app.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.KeyPress:
key = event.key()
# It Playback Rate !
if key == Qt.Key_A:
#self.mediaPlayer.setPlaybackRate(1.0)
self.mediaPlayer.setPlaybackRate(1.5)
elif key == Qt.Key_Z:
self.mediaPlayer.setPlaybackRate(0.8)
elif key == Qt.Key_Y:
self.mediaPlayer.setPlaybackRate(1.0)
# setPosition(int), argument is in milliseconds.
elif key == Qt.Key_M:
self.mediaPlayer.setPosition(self.mediaPlayer.position() - 10000)
elif key == Qt.Key_P:
self.mediaPlayer.setPosition(self.mediaPlayer.position() + 10000)
return super().eventFilter(source, event)
def closeEvent(self, event):
self.mediaPlayer.setMedia(QMediaContent())
if __name__ == '__main__':
app = QApplication(sys.argv)
player = VideoWindow()
player.resize(640, 480)
player.show()
exitCode = app.exec_()
sys.exit(exitCode)

Related

How to change the audio output device of QMediaPlayer in PySide2/PyQt5

How can I set the audio output of a QMediaPlayer to a specific output in Windows 7 and later?
This was really easy in PySide (using Phonon) but I can't find a way to do it in PySide2.
There are some related questions already, like this old but still not solved one, or this one that asks exactly what I want.
They are both in c++ and its difficult to convert it to PySide2.
The second one is answered with this code:
QMediaService *svc = player->service();
if (svc != nullptr)
{
QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
(svc->requestControl(QAudioOutputSelectorControl_iid));
if (out != nullptr)
{
out->setActiveOutput(this->ui->comboBox->currentText());
svc->releaseControl(out);
}
}
Another one with an attempt to python conversion didn't work also.
I tried to convert them to Python code, but the result was not successful.
Here is my minimal attempt:
import sys
from PySide2 import QtMultimedia
from PySide2.QtCore import QUrl, Qt
from PySide2.QtMultimedia import QMediaPlayer, QMediaContent
from PySide2.QtWidgets import (QPushButton, QSlider, QHBoxLayout, QVBoxLayout,
QFileDialog, QStyle, QApplication, QDialog, QComboBox)
class Window(QDialog):
def __init__(self):
super().__init__()
self.out_combo = QComboBox()
mode = QtMultimedia.QAudio.AudioOutput
devices = QtMultimedia.QAudioDeviceInfo.availableDevices(mode)
for item in [(dev.deviceName(), dev) for dev in devices]:
self.out_combo.addItem(item[0], item[1])
self.out_combo.currentIndexChanged.connect(self.out_device_changed)
openBtn = QPushButton('Open file')
openBtn.clicked.connect(self.open_file)
self.playBtn = QPushButton()
self.playBtn.setEnabled(False)
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playBtn.clicked.connect(self.play_file)
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 0)
self.slider.sliderMoved.connect(self.set_position)
hor_layout = QHBoxLayout()
hor_layout.setContentsMargins(0, 0, 0, 0)
hor_layout.addWidget(openBtn)
hor_layout.addWidget(self.playBtn)
hor_layout.addWidget(self.slider)
ver_layout = QVBoxLayout()
ver_layout.addWidget(self.out_combo)
ver_layout.addLayout(hor_layout)
self.setLayout(ver_layout)
self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.player.stateChanged.connect(self.mediastate_changed)
self.player.positionChanged.connect(self.position_changed)
self.player.durationChanged.connect(self.duration_changed)
self.show()
def open_file(self):
file_name, _ = QFileDialog.getOpenFileName(self, "Open file")
if file_name != '':
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(file_name)))
# self.label.setText(basename(file_name))
self.playBtn.setEnabled(True)
def play_file(self):
if self.player.state() == QMediaPlayer.PlayingState:
self.player.pause()
else:
self.player.play()
def mediastate_changed(self, state):
if self.player.state() == QMediaPlayer.PlayingState:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
def position_changed(self, position):
self.slider.setValue(position)
def duration_changed(self, duration):
self.slider.setRange(0, duration)
def set_position(self, position):
self.player.setPosition(position)
def out_device_changed(self, idx):
device = self.out_combo.itemData(idx)
service = self.player.service()
if service:
out = service.requestControl("org.qt-project.qt.mediastreamscontrol/5.0")
if out:
out.setActiveOutput(self.out_combo.currentText())
service.releaseControl(out)
else:
print("No output found!")
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())

My PyQt5 script is skipping a line inside a function

I want to make a simple browser GUI for learning purposes with PyQt5. A function that I want is to have in the status bar a text "Online". If the user clicks somewhere else from the browser and the application loses focus, a message will appear in the status bar indicating that, and after a few seconds the browser will change the url to google.
If I run the following code everything works fine as expected, when app loses focus it navigates to google. However, the message "Application lost focus ..." doesn't appear in the statusbar. It simply skips that line. If I remove the seturl and time.sleep line, the script will change the text as expected.
Why is it skipping that line? (Line 55)
import sys
import time
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QFrame ,QMainWindow, QLabel
from PyQt5.QtWebEngineWidgets import *
class VLine(QFrame):
# a simple VLine, like the one you get from designer
def __init__(self):
super(VLine, self).__init__()
self.setFrameShape(self.VLine|self.Sunken)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setFocus()
app.focusChanged.connect(self.on_focusChanged)
self.browser = QWebEngineView()
self.browser.setContextMenuPolicy(Qt.PreventContextMenu)
self.browser.setUrl(QUrl('http://stackoverflow.com'))
self.setCentralWidget(self.browser)
self.showMaximized()
self.date = QDate.currentDate()
font = QFont('Arial', 16, QFont.Bold)
self.statusBar().setFont(font)
timer = QTimer(self)
timer.timeout.connect(self.showTime)
timer.start(1000)
self.lbl1 = QLabel(self)
self.lbl1.setStyleSheet('border: 0; color: red;')
self.lbl1.setFont(font)
self.statusBar().reformat()
self.statusBar().setStyleSheet('border: 0; background-color: #FFF8DC;')
self.statusBar().setStyleSheet("QStatusBar::item {border: none;}")
self.statusBar().addPermanentWidget(VLine()) # <---
self.statusBar().addPermanentWidget(self.lbl1)
self.statusBar().addPermanentWidget(VLine())
def showTime(self):
current_time = QTime.currentTime()
label_time = current_time.toString('hh:mm:ss')
self.statusBar().showMessage('Time: ' + label_time + ' || Date: ' + self.date.toString('dd.MM.yyyy'))
def on_focusChanged(self):
if self.isActiveWindow() == False:
print(f"\nwindow is the active window: {self.isActiveWindow()}")
self.lbl1.setText('Application lost focus. Returning to Google in 5 seconds')
time.sleep(5)
self.browser.setUrl(QUrl('http://google.com'))
self.lbl1.setText('Online')
else:
print(f"window is the active window: {self.isActiveWindow()}")
self.lbl1.setText('Online')
app = QApplication(sys.argv)
QApplication.setApplicationName('Browser')
window = MainWindow()
app.exec_()
You are confusing how the focusChanged signal works: it emits a signal regarding the focus changes within the program.
What you need is to override the changeEvent of the window and intercept an ActivationChange event type.
class MainWindow(QMainWindow):
# ...
def changeEvent(self, event):
if event.type() == event.ActivationChange:
# ...
That said, NEVER put a blocking function within the main thread.
Remove the time.sleep and never think about using it again for this kind of things.
Add a function for that redirect, and create a QTimer that you can start when losing focus (and stop if regaining it again before the timeout).
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.focusLostTimer = QTimer(
interval=5000, singleShot=True, timeout=self.focusRedirect)
def focusRedirect(self):
self.browser.setUrl(QUrl('http://google.com'))
def changeEvent(self, event):
if event.type() == event.ActivationChange:
if not self.isActiveWindow():
self.focusLostTimer.start()
else:
self.focusLostTimer.stop()

QSpinbox Check if up or down button is pressed

Is there a simple way to check if the up or down button of a QT spinbox is pressed? I've seen this in a forum on QT but don't know how to deconstruct it or is far complex for me to understand and I'm coding in PyQT5.
void MySpinBox::mousePressEvent(QMouseEvent* event)
QSpinBox::mousePressEvent(event);
QStyleOptionSpinBox opt;
this->initStyleOption(&opt);
if( this->style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxUp).contains(event->pos()) )
// UP BUTTON PRESSED
else if( this->style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxDown).contains(event->pos()) )
//DOWN BUTTON PRESSED
Also I have this 2 variables that contain the spinbox value
version_spinValue = self.ui.version_sbox.value()
work_spinValue = self.ui.work_sbox.value()
All I want to do is when the version value spinbox up button is pressed, the work spinbox value resets to 1, and when it goes down, it just do nothing (or just print a simple text that the down button is pressed).
You have to use the hitTestComplexControl() method to know which element was clicked:
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QSpinBox, QStyle, QStyleOptionSpinBox
class SpinBox(QSpinBox):
upClicked = pyqtSignal()
downClicked = pyqtSignal()
def mousePressEvent(self, event):
super().mousePressEvent(event)
opt = QStyleOptionSpinBox()
self.initStyleOption(opt)
control = self.style().hitTestComplexControl(
QStyle.CC_SpinBox, opt, event.pos(), self
)
if control == QStyle.SC_SpinBoxUp:
self.upClicked.emit()
elif control == QStyle.SC_SpinBoxDown:
self.downClicked.emit()
def main():
import sys
app = QApplication(sys.argv)
w = SpinBox()
w.upClicked.connect(lambda: print("up"))
w.downClicked.connect(lambda: print("down"))
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Python-vlc error: HTTP connection failure

I'm trying to play an online video using python-vlc library.
First, I was using a simple version of the player to test it out:
import vlc
#example url
url="https://player.vimeo.com/external/363536021.hd.mp4?s=6f6e87d86a49149479ebdab6c8bc421aa89f327c&profile_id=175&oauth2_token_id=57447761"
player = vlc.Instance().media_player_new()
player.set_media(vlc.Instance().media_new(url))
player.play()
while str(player.get_state()) != "State.Ended":
pass
player.stop()
It plays the while video from URL without any problems!
Now I wanted to use the more complex version with pyqt5 GUI (original source-code here):
import sys
import os.path
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QMainWindow, QWidget, QFrame, QSlider, QHBoxLayout, QPushButton, \
QVBoxLayout, QAction, QFileDialog, QApplication
import vlc
class Player(QMainWindow):
def __init__(self, videoUrl, master=None):
QMainWindow.__init__(self, master)
self.setWindowTitle("Media Player")
# creating a basic vlc instance
self.instance = vlc.Instance()
# creating an empty vlc media player
self.mediaplayer = self.instance.media_player_new()
self.playableVideo = videoUrl
self.createUI()
self.isPaused = False
def createUI(self):
"""Set up the user interface, signals & slots
"""
self.widget = QWidget(self)
self.setCentralWidget(self.widget)
# In this widget, the video will be drawn
if sys.platform == "darwin": # for MacOS
from PyQt5.QtWidgets import QMacCocoaViewContainer
self.videoframe = QMacCocoaViewContainer(0)
else:
self.videoframe = QFrame()
self.palette = self.videoframe.palette()
self.palette.setColor (QPalette.Window,
QColor(0,0,0))
self.videoframe.setPalette(self.palette)
self.videoframe.setAutoFillBackground(True)
self.positionslider = QSlider(Qt.Horizontal, self)
self.positionslider.setToolTip("Position")
self.positionslider.setMaximum(5000)
self.positionslider.sliderMoved.connect(self.setPosition)
self.hbuttonbox = QHBoxLayout()
self.playbutton = QPushButton("Play")
self.hbuttonbox.addWidget(self.playbutton)
self.playbutton.clicked.connect(self.PlayPause)
self.stopbutton = QPushButton("Exit")
self.hbuttonbox.addWidget(self.stopbutton)
self.stopbutton.clicked.connect(self.Stop)
self.hbuttonbox.setAlignment(Qt.AlignHCenter)
self.vboxlayout = QVBoxLayout()
self.vboxlayout.addWidget(self.videoframe)
self.vboxlayout.addWidget(self.positionslider)
self.vboxlayout.addLayout(self.hbuttonbox)
self.widget.setLayout(self.vboxlayout)
self.timer = QTimer(self)
self.timer.setInterval(200)
self.timer.timeout.connect(self.updateUI)
def PlayPause(self):
"""Toggle play/pause status
"""
if self.mediaplayer.is_playing():
self.mediaplayer.pause()
self.playbutton.setText("Play")
self.isPaused = True
else:
if self.mediaplayer.play() == -1:
self.OpenFile()
return
self.mediaplayer.play()
self.playbutton.setText("Pause")
self.timer.start()
self.isPaused = False
def Stop(self):
"""Stop player
"""
self.mediaplayer.stop()
self.playbutton.setText("Play")
def OpenFile(self, filename=None):
"""Open a media file in a MediaPlayer
"""
if filename is None:
filename = self.playableVideo
if not filename:
return
# create the media
if sys.version < '3':
filename = unicode(filename)
self.media = self.instance.media_new(filename)
# put the media in the media player
self.mediaplayer.set_media(self.media)
# parse the metadata of the file
self.media.parse()
# set the title of the track as window title
self.setWindowTitle(self.media.get_meta(0))
# the media player has to be 'connected' to the QFrame
# (otherwise a video would be displayed in it's own window)
# this is platform specific!
# you have to give the id of the QFrame (or similar object) to
# vlc, different platforms have different functions for this
if sys.platform.startswith('linux'): # for Linux using the X Server
self.mediaplayer.set_xwindow(self.videoframe.winId())
elif sys.platform == "win32": # for Windows
self.mediaplayer.set_hwnd(self.videoframe.winId())
elif sys.platform == "darwin": # for MacOS
self.mediaplayer.set_nsobject(int(self.videoframe.winId()))
self.PlayPause()
def setPosition(self, position):
self.mediaplayer.set_position(position / 1000.0)
def updateUI(self):
self.positionslider.setValue(self.mediaplayer.get_position() * 1000)
if not self.mediaplayer.is_playing():
self.timer.stop()
if not self.isPaused:
self.Stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
player = Player("https://player.vimeo.com/external/363536021.hd.mp4?s=6f6e87d86a49149479ebdab6c8bc421aa89f327c&profile_id=175&oauth2_token_id=57447761")
player.show()
player.resize(640, 480)
if sys.argv[1:]:
player.OpenFile(sys.argv[1])
sys.exit(app.exec_())
This code keeps returning an access stream error: HTTP connection failure.
The problem is that as soon as you start playing the video it is not available since the download is not immediate but with your code you try to update the position. The solution is to update is to get the position after it started playing:
def updateUI(self):
if not self.mediaplayer.is_playing():
return
self.positionslider.setValue(self.mediaplayer.get_position() * 1000)
if not self.mediaplayer.is_playing():
self.timer.stop()
if not self.isPaused:
self.Stop()

capture a copy of other QWidget events?

I am working on a widget that needs to update itself when another widget it is matched with moves or is resized. Currently, I have the other widget do its own resizeEvent() and moveEvent() and inside that it emits a signal that my widget connects to.
However, I do not like this setup. Say later on I want to have my other widget do a different thing with its resizeEvent().
Is there a way for widget A (from widget A only) to be informed when widget B's resizeEvent() or moveEvent() is fired?
You could create a Helper class that is responsible for monitoring, so you do not need to overwrite the classes.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Helper(QObject):
def setWidgets(self, emmiter, receiver):
emmiter.installEventFilter(self)
self.emmiter = emmiter
self.receiver = receiver
def eventFilter(self, obj, event):
if obj == self.emmiter:
if event.type() == QEvent.Resize:
self.receiver.resize(self.emmiter.size())
elif event.type() == QEvent.Move:
self.receiver.move(event.pos())
return QObject.eventFilter(self, obj, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
helper = Helper()
w1 = QWidget()
w1.setWindowTitle("emmiter")
w2 = QWidget()
helper.setWidgets(w1, w2)
w1.show()
w2.show()
sys.exit(app.exec_())

Categories