Add video scrubbing and click to seek/change volume on sliders
This commit is contained in:
@@ -2,7 +2,7 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||||
QPushButton, QFrame, QSlider, QTextEdit, QApplication
|
QPushButton, QFrame, QSlider, QTextEdit, QApplication, QToolTip
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
||||||
from PyQt6.QtGui import QIcon
|
from PyQt6.QtGui import QIcon
|
||||||
@@ -10,6 +10,49 @@ import uuid
|
|||||||
|
|
||||||
from vlc_player import VLCSyncPlayer
|
from vlc_player import VLCSyncPlayer
|
||||||
|
|
||||||
|
class ClickableSlider(QSlider):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._get_tooltip_text = None
|
||||||
|
|
||||||
|
def set_tooltip_provider(self, provider_func):
|
||||||
|
self._get_tooltip_text = provider_func
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
super().mousePressEvent(event)
|
||||||
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
|
val = 0
|
||||||
|
if self.orientation() == Qt.Orientation.Horizontal:
|
||||||
|
if self.width() > 0:
|
||||||
|
val = self.minimum() + ((self.maximum() - self.minimum()) * event.pos().x()) / self.width()
|
||||||
|
else:
|
||||||
|
if self.height() > 0:
|
||||||
|
val = self.minimum() + ((self.maximum() - self.minimum()) * (self.height() - event.pos().y())) / self.height()
|
||||||
|
|
||||||
|
val = max(self.minimum(), min(self.maximum(), int(val)))
|
||||||
|
self.setValue(val)
|
||||||
|
self.sliderMoved.emit(val)
|
||||||
|
|
||||||
|
if self._get_tooltip_text:
|
||||||
|
text = self._get_tooltip_text(val)
|
||||||
|
if text:
|
||||||
|
QToolTip.showText(event.globalPosition().toPoint(), text, self)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
super().mouseMoveEvent(event)
|
||||||
|
if event.buttons() & Qt.MouseButton.LeftButton:
|
||||||
|
if self._get_tooltip_text:
|
||||||
|
text = self._get_tooltip_text(self.value())
|
||||||
|
if text:
|
||||||
|
QToolTip.showText(event.globalPosition().toPoint(), text, self)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
|
# Explicitly emit sliderReleased on mouse release
|
||||||
|
# to ensure single clicks on the track also trigger the release logic
|
||||||
|
self.sliderReleased.emit()
|
||||||
|
|
||||||
class ExpectedVlcEvent:
|
class ExpectedVlcEvent:
|
||||||
def __init__(self, action: str, req_id: str, target_val=None):
|
def __init__(self, action: str, req_id: str, target_val=None):
|
||||||
self.action = action
|
self.action = action
|
||||||
@@ -90,23 +133,25 @@ class RoomWidget(QWidget):
|
|||||||
self.play_btn.setObjectName("playBtn")
|
self.play_btn.setObjectName("playBtn")
|
||||||
|
|
||||||
# Time and SeekBar
|
# Time and SeekBar
|
||||||
self.seekbar = QSlider(Qt.Orientation.Horizontal)
|
self.seekbar = ClickableSlider(Qt.Orientation.Horizontal)
|
||||||
self.seekbar.setRange(0, 1000)
|
self.seekbar.setRange(0, 1000)
|
||||||
self.seekbar.setObjectName("seekBar")
|
self.seekbar.setObjectName("seekBar")
|
||||||
self.seekbar.sliderMoved.connect(self.on_seekbar_dragged)
|
self.seekbar.sliderMoved.connect(self.on_seekbar_dragged)
|
||||||
self.seekbar.sliderReleased.connect(self.on_seekbar_released)
|
self.seekbar.sliderReleased.connect(self.on_seekbar_released)
|
||||||
|
self.seekbar.set_tooltip_provider(self.get_seekbar_tooltip)
|
||||||
|
|
||||||
self.time_lbl = QLabel("00:00:00 / 00:00:00")
|
self.time_lbl = QLabel("00:00:00 / 00:00:00")
|
||||||
|
|
||||||
# Volume
|
# Volume
|
||||||
self.vol_icon = QLabel("🔊")
|
self.vol_icon = QLabel("🔊")
|
||||||
self.vol_icon.setObjectName("volIcon")
|
self.vol_icon.setObjectName("volIcon")
|
||||||
self.volume_slider = QSlider(Qt.Orientation.Horizontal)
|
self.volume_slider = ClickableSlider(Qt.Orientation.Horizontal)
|
||||||
self.volume_slider.setRange(0, 100)
|
self.volume_slider.setRange(0, 100)
|
||||||
self.volume_slider.setValue(100)
|
self.volume_slider.setValue(100)
|
||||||
self.volume_slider.setFixedWidth(100)
|
self.volume_slider.setFixedWidth(100)
|
||||||
self.volume_slider.setObjectName("volumeSlider")
|
self.volume_slider.setObjectName("volumeSlider")
|
||||||
self.volume_slider.valueChanged.connect(self.on_volume_changed)
|
self.volume_slider.valueChanged.connect(self.on_volume_changed)
|
||||||
|
self.volume_slider.set_tooltip_provider(lambda v: f"{v}%")
|
||||||
|
|
||||||
# Fullscreen
|
# Fullscreen
|
||||||
self.fullscreen_btn = QPushButton("⛶")
|
self.fullscreen_btn = QPushButton("⛶")
|
||||||
@@ -172,6 +217,14 @@ class RoomWidget(QWidget):
|
|||||||
|
|
||||||
self.play_btn.clicked.connect(self.toggle_playback)
|
self.play_btn.clicked.connect(self.toggle_playback)
|
||||||
|
|
||||||
|
def get_seekbar_tooltip(self, value):
|
||||||
|
length_ms = self.vlc_player.get_length()
|
||||||
|
if length_ms > 0:
|
||||||
|
target_ms = int((value / 1000.0) * length_ms)
|
||||||
|
s = max(0, target_ms) // 1000
|
||||||
|
return f"{s//3600:02d}:{(s%3600)//60:02d}:{s%60:02d}"
|
||||||
|
return ""
|
||||||
|
|
||||||
def setup_room(self, room_code: str, username: str, file_name: str, file_path: str, start_time_s: float = 0.0):
|
def setup_room(self, room_code: str, username: str, file_name: str, file_path: str, start_time_s: float = 0.0):
|
||||||
self.room_code = room_code
|
self.room_code = room_code
|
||||||
self.username = username
|
self.username = username
|
||||||
@@ -313,6 +366,8 @@ class RoomWidget(QWidget):
|
|||||||
s = max(0, ms) // 1000
|
s = max(0, ms) // 1000
|
||||||
return f"{s//3600:02d}:{(s%3600)//60:02d}:{s%60:02d}"
|
return f"{s//3600:02d}:{(s%3600)//60:02d}:{s%60:02d}"
|
||||||
self.time_lbl.setText(f"{fmt(target_ms)} / {fmt(length_ms)}")
|
self.time_lbl.setText(f"{fmt(target_ms)} / {fmt(length_ms)}")
|
||||||
|
# Scrub the video locally in real-time
|
||||||
|
self._tell_vlc_and_expect("seek", target_ms / 1000.0)
|
||||||
|
|
||||||
def on_seekbar_released(self):
|
def on_seekbar_released(self):
|
||||||
length_ms = self.vlc_player.get_length()
|
length_ms = self.vlc_player.get_length()
|
||||||
|
|||||||
Reference in New Issue
Block a user