patch player
This commit is contained in:
parent
4760a92942
commit
45267b7c7e
140
screens/media.py
140
screens/media.py
@ -1,3 +1,5 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QWidget,
|
QWidget,
|
||||||
QLabel,
|
QLabel,
|
||||||
@ -7,7 +9,7 @@ from PySide6.QtWidgets import (
|
|||||||
QSlider,
|
QSlider,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QSize
|
from PySide6.QtCore import Qt, QSize, QTimer
|
||||||
from PySide6.QtGui import QFont
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
|
|
||||||
@ -26,21 +28,21 @@ class MediaScreen(QWidget):
|
|||||||
info_col.setContentsMargins(0, 0, 0, 0)
|
info_col.setContentsMargins(0, 0, 0, 0)
|
||||||
info_col.setSpacing(6)
|
info_col.setSpacing(6)
|
||||||
|
|
||||||
source = QLabel("Источник: Bluetooth")
|
self.source = QLabel("Источник: Bluetooth")
|
||||||
source.setObjectName("MediaSource")
|
self.source.setObjectName("MediaSource")
|
||||||
source.setFont(QFont("", 14, 600))
|
self.source.setFont(QFont("", 14, 600))
|
||||||
|
|
||||||
title = QLabel("Название трека")
|
self.title = QLabel("Название трека")
|
||||||
title.setObjectName("MediaTitle")
|
self.title.setObjectName("MediaTitle")
|
||||||
title.setFont(QFont("", 22, 700))
|
self.title.setFont(QFont("", 22, 700))
|
||||||
|
|
||||||
artist = QLabel("Исполнитель")
|
self.artist = QLabel("Исполнитель")
|
||||||
artist.setObjectName("MediaArtist")
|
self.artist.setObjectName("MediaArtist")
|
||||||
artist.setFont(QFont("", 16, 600))
|
self.artist.setFont(QFont("", 16, 600))
|
||||||
|
|
||||||
info_col.addWidget(source)
|
info_col.addWidget(self.source)
|
||||||
info_col.addWidget(title)
|
info_col.addWidget(self.title)
|
||||||
info_col.addWidget(artist)
|
info_col.addWidget(self.artist)
|
||||||
info_col.addStretch(1)
|
info_col.addStretch(1)
|
||||||
|
|
||||||
cover = QLabel("COVER")
|
cover = QLabel("COVER")
|
||||||
@ -55,10 +57,28 @@ class MediaScreen(QWidget):
|
|||||||
controls.setContentsMargins(0, 0, 0, 0)
|
controls.setContentsMargins(0, 0, 0, 0)
|
||||||
controls.setSpacing(12)
|
controls.setSpacing(12)
|
||||||
|
|
||||||
|
time_row = QHBoxLayout()
|
||||||
|
time_row.setContentsMargins(0, 0, 0, 0)
|
||||||
|
time_row.setSpacing(10)
|
||||||
|
|
||||||
|
self.time_pos = QLabel("0:00")
|
||||||
|
self.time_pos.setObjectName("MediaTimePos")
|
||||||
|
self.time_pos.setFont(QFont("", 12, 600))
|
||||||
|
|
||||||
|
self.time_total = QLabel("0:00")
|
||||||
|
self.time_total.setObjectName("MediaTimeTotal")
|
||||||
|
self.time_total.setFont(QFont("", 12, 600))
|
||||||
|
self.time_total.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||||
|
|
||||||
|
time_row.addWidget(self.time_pos)
|
||||||
|
time_row.addStretch(1)
|
||||||
|
time_row.addWidget(self.time_total)
|
||||||
|
|
||||||
progress = QSlider(Qt.Horizontal)
|
progress = QSlider(Qt.Horizontal)
|
||||||
progress.setObjectName("MediaProgress")
|
progress.setObjectName("MediaProgress")
|
||||||
progress.setRange(0, 100)
|
progress.setRange(0, 100)
|
||||||
progress.setValue(35)
|
progress.setValue(35)
|
||||||
|
self.progress = progress
|
||||||
|
|
||||||
transport = QHBoxLayout()
|
transport = QHBoxLayout()
|
||||||
transport.setContentsMargins(0, 0, 0, 0)
|
transport.setContentsMargins(0, 0, 0, 0)
|
||||||
@ -67,14 +87,17 @@ class MediaScreen(QWidget):
|
|||||||
btn_prev = QPushButton("⏮")
|
btn_prev = QPushButton("⏮")
|
||||||
btn_prev.setObjectName("MediaTransportBtn")
|
btn_prev.setObjectName("MediaTransportBtn")
|
||||||
btn_prev.setFixedSize(QSize(72, 72))
|
btn_prev.setFixedSize(QSize(72, 72))
|
||||||
|
btn_prev.clicked.connect(self._prev)
|
||||||
|
|
||||||
btn_play = QPushButton("▶")
|
btn_play = QPushButton("▶")
|
||||||
btn_play.setObjectName("MediaTransportBtnPrimary")
|
btn_play.setObjectName("MediaTransportBtnPrimary")
|
||||||
btn_play.setFixedSize(QSize(96, 72))
|
btn_play.setFixedSize(QSize(96, 72))
|
||||||
|
btn_play.clicked.connect(self._toggle_play)
|
||||||
|
|
||||||
btn_next = QPushButton("⏭")
|
btn_next = QPushButton("⏭")
|
||||||
btn_next.setObjectName("MediaTransportBtn")
|
btn_next.setObjectName("MediaTransportBtn")
|
||||||
btn_next.setFixedSize(QSize(72, 72))
|
btn_next.setFixedSize(QSize(72, 72))
|
||||||
|
btn_next.clicked.connect(self._next)
|
||||||
|
|
||||||
transport.addStretch(1)
|
transport.addStretch(1)
|
||||||
transport.addWidget(btn_prev)
|
transport.addWidget(btn_prev)
|
||||||
@ -99,6 +122,7 @@ class MediaScreen(QWidget):
|
|||||||
volume_row.addWidget(volume_lbl)
|
volume_row.addWidget(volume_lbl)
|
||||||
volume_row.addWidget(volume, 1)
|
volume_row.addWidget(volume, 1)
|
||||||
|
|
||||||
|
controls.addLayout(time_row)
|
||||||
controls.addWidget(progress)
|
controls.addWidget(progress)
|
||||||
controls.addLayout(transport)
|
controls.addLayout(transport)
|
||||||
controls.addLayout(volume_row)
|
controls.addLayout(volume_row)
|
||||||
@ -116,3 +140,93 @@ class MediaScreen(QWidget):
|
|||||||
root.addLayout(header)
|
root.addLayout(header)
|
||||||
root.addLayout(controls)
|
root.addLayout(controls)
|
||||||
root.addLayout(soft_keys)
|
root.addLayout(soft_keys)
|
||||||
|
|
||||||
|
self._poll_timer = QTimer(self)
|
||||||
|
self._poll_timer.timeout.connect(self._refresh_metadata)
|
||||||
|
self._poll_timer.start(2000)
|
||||||
|
self._refresh_metadata()
|
||||||
|
|
||||||
|
def _toggle_play(self):
|
||||||
|
status = self._player_status()
|
||||||
|
if status == "playing":
|
||||||
|
self._run_btctl(["menu player", "pause"])
|
||||||
|
else:
|
||||||
|
self._run_btctl(["menu player", "play"])
|
||||||
|
QTimer.singleShot(300, self._refresh_metadata)
|
||||||
|
|
||||||
|
def _prev(self):
|
||||||
|
self._run_btctl(["menu player", "previous"])
|
||||||
|
QTimer.singleShot(300, self._refresh_metadata)
|
||||||
|
|
||||||
|
def _next(self):
|
||||||
|
self._run_btctl(["menu player", "next"])
|
||||||
|
QTimer.singleShot(300, self._refresh_metadata)
|
||||||
|
|
||||||
|
def _player_status(self) -> str | None:
|
||||||
|
out = self._run_btctl(["menu player", "show"])
|
||||||
|
for line in out.splitlines():
|
||||||
|
if "Status:" in line:
|
||||||
|
return line.split("Status:", 1)[1].strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _refresh_metadata(self):
|
||||||
|
out = self._run_btctl(["menu player", "show"])
|
||||||
|
title = None
|
||||||
|
artist = None
|
||||||
|
source = None
|
||||||
|
position = None
|
||||||
|
duration = None
|
||||||
|
for line in out.splitlines():
|
||||||
|
if "Name:" in line:
|
||||||
|
source = line.split("Name:", 1)[1].strip()
|
||||||
|
if "Track.Title:" in line:
|
||||||
|
title = line.split("Track.Title:", 1)[1].strip()
|
||||||
|
if "Track.Artist:" in line:
|
||||||
|
artist = line.split("Track.Artist:", 1)[1].strip()
|
||||||
|
if "Position:" in line:
|
||||||
|
position = self._parse_hex_value(line)
|
||||||
|
if "Track.Duration:" in line:
|
||||||
|
duration = self._parse_hex_value(line)
|
||||||
|
if title:
|
||||||
|
self.title.setText(title)
|
||||||
|
if artist:
|
||||||
|
self.artist.setText(artist)
|
||||||
|
if source:
|
||||||
|
self.source.setText(f"Источник: {source}")
|
||||||
|
if duration is not None and duration > 0:
|
||||||
|
self.progress.setRange(0, duration)
|
||||||
|
self.time_total.setText(self._format_time(duration))
|
||||||
|
if position is not None:
|
||||||
|
self.progress.setValue(position)
|
||||||
|
self.time_pos.setText(self._format_time(position))
|
||||||
|
|
||||||
|
def _run_btctl(self, commands: list[str]) -> str:
|
||||||
|
script = "\n".join(commands + ["back", "quit"]) + "\n"
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["bluetoothctl"],
|
||||||
|
input=script,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=3,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
except (subprocess.SubprocessError, OSError):
|
||||||
|
return ""
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
def _parse_hex_value(self, line: str) -> int | None:
|
||||||
|
start = line.find("0x")
|
||||||
|
if start == -1:
|
||||||
|
return None
|
||||||
|
hex_part = line[start:].split()[0]
|
||||||
|
try:
|
||||||
|
return int(hex_part, 16)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _format_time(self, ms: int) -> str:
|
||||||
|
total_seconds = max(ms, 0) // 1000
|
||||||
|
minutes = total_seconds // 60
|
||||||
|
seconds = total_seconds % 60
|
||||||
|
return f"{minutes}:{seconds:02d}"
|
||||||
|
|||||||
@ -72,6 +72,7 @@ QWidget { background: #F4F6F8; color: #111827; }
|
|||||||
#MediaProgress::handle:horizontal { width: 18px; margin: -6px 0; background: #111827; border-radius: 9px; }
|
#MediaProgress::handle:horizontal { width: 18px; margin: -6px 0; background: #111827; border-radius: 9px; }
|
||||||
#MediaVolume::groove:horizontal { height: 8px; background: #E5E7EB; border-radius: 4px; }
|
#MediaVolume::groove:horizontal { height: 8px; background: #E5E7EB; border-radius: 4px; }
|
||||||
#MediaVolume::handle:horizontal { width: 18px; margin: -6px 0; background: #111827; border-radius: 9px; }
|
#MediaVolume::handle:horizontal { width: 18px; margin: -6px 0; background: #111827; border-radius: 9px; }
|
||||||
|
#MediaTimePos, #MediaTimeTotal { color: rgba(107,114,128,0.95); }
|
||||||
#MediaTransportBtn {
|
#MediaTransportBtn {
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
|||||||
@ -67,6 +67,7 @@ QWidget { background: #0B0E11; color: #E6EAF0; }
|
|||||||
#MediaProgress::handle:horizontal { width: 18px; margin: -6px 0; background: #E6EAF0; border-radius: 9px; }
|
#MediaProgress::handle:horizontal { width: 18px; margin: -6px 0; background: #E6EAF0; border-radius: 9px; }
|
||||||
#MediaVolume::groove:horizontal { height: 8px; background: #1B2330; border-radius: 4px; }
|
#MediaVolume::groove:horizontal { height: 8px; background: #1B2330; border-radius: 4px; }
|
||||||
#MediaVolume::handle:horizontal { width: 18px; margin: -6px 0; background: #E6EAF0; border-radius: 9px; }
|
#MediaVolume::handle:horizontal { width: 18px; margin: -6px 0; background: #E6EAF0; border-radius: 9px; }
|
||||||
|
#MediaTimePos, #MediaTimeTotal { color: rgba(138,147,166,0.95); }
|
||||||
#MediaTransportBtn {
|
#MediaTransportBtn {
|
||||||
background: #141A22;
|
background: #141A22;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user