Source code for araviq6.util.controller

"""
Media controller
================

:mod:`araviq6.util.controller` provides widget to control media player.

"""

from araviq6.qt_compat import QtCore, QtGui, QtMultimedia, QtWidgets
from typing import Optional

try:
    from typing import Protocol
except ImportError:
    from typing_extensions import Protocol  # type: ignore[assignment]


__all__ = [
    "ClickableSlider",
    "SignalProtocol",
    "PlayerProtocol",
    "MediaController",
]


[docs] class ClickableSlider(QtWidgets.QSlider): """``QSlider`` whose groove can be clicked to move to position.""" # https://stackoverflow.com/questions/52689047 def mousePressEvent(self, event: QtGui.QMouseEvent): if event.button() == QtCore.Qt.MouseButton.LeftButton: val = self.pixelPosToRangeValue(event.position()) self.setValue(val) super().mousePressEvent(event) def pixelPosToRangeValue(self, pos: QtCore.QPointF) -> int: opt = QtWidgets.QStyleOptionSlider() self.initStyleOption(opt) gr = self.style().subControlRect( QtWidgets.QStyle.ComplexControl.CC_Slider, opt, QtWidgets.QStyle.SubControl.SC_SliderGroove, self, ) sr = self.style().subControlRect( QtWidgets.QStyle.ComplexControl.CC_Slider, opt, QtWidgets.QStyle.SubControl.SC_SliderHandle, self, ) if self.orientation() == QtCore.Qt.Orientation.Horizontal: sliderLength = sr.width() sliderMin = gr.x() sliderMax = gr.right() - sliderLength + 1 else: sliderLength = sr.height() sliderMin = gr.y() sliderMax = gr.bottom() - sliderLength + 1 if self.orientation() == QtCore.Qt.Orientation.Horizontal: p = pos.x() - sr.center().x() + sr.topLeft().x() else: p = pos.y() - sr.center().y() + sr.topLeft().y() return QtWidgets.QStyle.sliderValueFromPosition( self.minimum(), self.maximum(), int(p - sliderMin), sliderMax - sliderMin, opt.upsideDown, # type: ignore[attr-defined] )
[docs] class SignalProtocol(Protocol): def connect( self, receiver, type: QtCore.Qt.ConnectionType = QtCore.Qt.ConnectionType.AutoConnection, ): ... def disconnect(self, receiver): ...
[docs] class PlayerProtocol(Protocol): durationChanged: SignalProtocol positionChanged: SignalProtocol playbackStateChanged: SignalProtocol def playbackState(self) -> QtMultimedia.QMediaPlayer.PlaybackState: ... def play(self): ... def pause(self): ... def stop(self): ... def setPosition(self, position: int): ...
[docs] class MediaController(QtWidgets.QWidget): """ Widget to control :class:`QtMultimedia.QMediaPlayer`. This controller can change the playback state and media position by :meth:`playButton`, :meth:`stopButton`, and :meth:`slider`. :meth:`setPlayer` sets the player to be controlled by this widget. """ def __init__(self, parent=None): super().__init__(parent) self._slider = ClickableSlider() self._playButton = QtWidgets.QPushButton() self._stopButton = QtWidgets.QPushButton() self._player = None self._pausedBySliderPress = False self._playButton.clicked.connect(self._onPlayButtonClick) self._stopButton.clicked.connect(self._onStopButtonClick) self._slider.sliderPressed.connect(self._onSliderPress) self._slider.sliderMoved.connect(self._onSliderMove) self._slider.sliderReleased.connect(self._onSliderRelease) layout = QtWidgets.QHBoxLayout() play_icon = self.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_MediaPlay ) self._playButton.setIcon(play_icon) layout.addWidget(self._playButton) stop_icon = self.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_MediaStop ) self._stopButton.setIcon(stop_icon) layout.addWidget(self._stopButton) self._slider.setOrientation(QtCore.Qt.Orientation.Horizontal) layout.addWidget(self._slider) self.setLayout(layout)
[docs] def player(self) -> Optional[PlayerProtocol]: """Media player which is controlled by *self*.""" return self._player
@QtCore.Slot() def _onPlayButtonClick(self): """Play or pause :meth:`player`.""" player = self.player() if player is not None: if ( player.playbackState() == QtMultimedia.QMediaPlayer.PlaybackState.PlayingState ): player.pause() else: player.play() @QtCore.Slot() def _onStopButtonClick(self): """Stop :meth:`player`.""" player = self.player() if player is not None: player.stop() @QtCore.Slot() def _onSliderPress(self): """If the media was playing, pause and move to the pressed position.""" player = self.player() if player is not None: if ( player.playbackState() == QtMultimedia.QMediaPlayer.PlaybackState.PlayingState ): self._pausedBySliderPress = True player.pause() player.setPosition(self._slider.value()) @QtCore.Slot(int) def _onSliderMove(self, position: int): """Move the media to current slider position.""" player = self.player() if player is not None: player.setPosition(position) @QtCore.Slot() def _onSliderRelease(self): """If the media was paused by slider press, play the media.""" player = self.player() if player is not None and self._pausedBySliderPress: player.play() self._pausedBySliderPress = False
[docs] def setPlayer(self, player: Optional[PlayerProtocol]): """Set :meth:`player` and connect the signals.""" old_player = self.player() if old_player is not None: old_player.durationChanged.disconnect( # type: ignore[attr-defined] self._onMediaDurationChange ) old_player.positionChanged.disconnect( # type: ignore[attr-defined] self._onMediaPositionChange ) old_player.playbackStateChanged.disconnect( # type: ignore[attr-defined] self._onPlaybackStateChange ) self._player = player if player is not None: player.durationChanged.connect( # type: ignore[attr-defined] self._onMediaDurationChange ) player.positionChanged.connect( # type: ignore[attr-defined] self._onMediaPositionChange ) player.playbackStateChanged.connect( # type: ignore[attr-defined] self._onPlaybackStateChange )
@QtCore.Slot("qint64") def _onMediaDurationChange(self, duration: int): """Set the slider range to media duration.""" self._slider.setRange(0, duration) @QtCore.Slot("qint64") def _onMediaPositionChange(self, position: int): """Update the slider position to video position.""" self._slider.setValue(position) @QtCore.Slot(QtMultimedia.QMediaPlayer.PlaybackState) def _onPlaybackStateChange(self, state: QtMultimedia.QMediaPlayer.PlaybackState): """Switch the play icon and pause icon by *state*.""" if state == QtMultimedia.QMediaPlayer.PlaybackState.PlayingState: pause_icon = self.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_MediaPause ) self._playButton.setIcon(pause_icon) else: play_icon = self.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_MediaPlay ) self._playButton.setIcon(play_icon)