Skip to content

Commit

Permalink
Major updates
Browse files Browse the repository at this point in the history
- Update to work with new pychromecast API.
- Fix reboot (at least on 1st gen chromecasts).
- Fix jumpy slider when watching some streams (like hulu).
- Update pyproject.toml to use latest flit features.
  • Loading branch information
soreau committed Jul 3, 2022
1 parent f1d53f0 commit a2e3ec6
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 47 deletions.
117 changes: 82 additions & 35 deletions cattqt/cattqt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@
import sys
import math
import signal
import requests
import catt.api
import subprocess
from catt.api import CattDevice
import pychromecast
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QDir, QPointF, QTimer, QTime, QThread, pyqtSignal
from PyQt5.QtCore import (
Qt,
QDir,
QPointF,
QTimer,
QTime,
QThread,
pyqtSignal,
QEventLoop,
)


def time_to_seconds(time):
Expand Down Expand Up @@ -62,7 +72,7 @@ def __init__(self, s, d, c, i):
def on_progress_tick(self):
s = self._self
self.time = self.time.addSecs(1)
duration = self.device._cast.media_controller.status.duration
duration = self.get_duration(self.cast.media_controller.status)
if duration and duration != 0 and time_to_seconds(self.time) >= int(duration):
# If progress is at the end, stop the device progress timer
self.set_state_idle(self.index)
Expand Down Expand Up @@ -137,16 +147,27 @@ def update_ui_idle(self):
s.set_icon(s.play_button, "SP_MediaPlay")
self.update_text()

def set_dial_value(self):
def set_dial_value(self, cast):
s = self._self
v = self.device._cast.status.volume_level * 100
v = cast.status.volume_level * 100
s.dial.valueChanged.disconnect(s.on_dial_moved)
if v != 0:
self.unmute_volume = v
s.dial.setValue(int(v))
s.set_volume_label(v)
s.dial.valueChanged.connect(s.on_dial_moved)

def get_duration(self, status):
duration = 0
x = status.media_custom_data.get("extraParams")
if x == None:
duration = status.duration
else:
e = x.get("entity")
b = e.get("bundle")
duration = b.get("duration")
return duration

def split_seconds(self, s):
hours = s // 3600
minutes = (s - (hours * 3600)) // 60
Expand Down Expand Up @@ -176,8 +197,8 @@ def set_text(self, s, status_text, title):
s.status_label.setText("Idle")

def update_text(self):
title = self.device._cast.media_controller.title
status_text = self.device._cast.status.status_text
title = self.cast.media_controller.title
status_text = self.cast.status.status_text
s = self._self
if not self.playing:
if self.stopping:
Expand Down Expand Up @@ -229,7 +250,10 @@ def reboot_device(self):
return
s.stop(d, "Rebooting..")
try:
d.cast.reboot()
requests.post(
"http://" + d.device.ip_addr + ":8008/setup/reboot",
json={"params": "now"},
)
print(d.device.name, "rebooting")
s.play_button.setEnabled(False)
s.stop_button.setEnabled(False)
Expand Down Expand Up @@ -399,7 +423,8 @@ def __init__(self, s):
self.s = s

def run(self):
self.s.devices = catt.api.discover()
self.s.chromecasts, browser = pychromecast.discovery.discover_chromecasts()
browser.stop_discovery()


class CattReadThread(QThread):
Expand Down Expand Up @@ -436,6 +461,10 @@ class App(QMainWindow):
stopping_timer_cancel = pyqtSignal(int)
start_singleshot_timer = pyqtSignal(Device)

def closeEvent(self, event):
self.clean_up()
sys.exit(0)

def create_devices_layout(self):
self.devices_layout = QHBoxLayout()
self.combo_box = ComboBox(self)
Expand Down Expand Up @@ -535,8 +564,6 @@ def __init__(self, app, version):
self.reconnect_volume = int(arg)
except Exception as e:
print(e)
app.aboutToQuit.connect(self.clean_up)
app.focusChanged.connect(self.focus_changed)
self.initUI()

def discover_loop(self):
Expand All @@ -548,7 +575,7 @@ def discover_loop(self):
while splash_thread.isRunning():
QThread.usleep(250)
QApplication.processEvents()
self.num_devices = len(self.devices)
self.num_devices = len(self.chromecasts)
if self.num_devices == 0:
self.splash.hide()
print("No devices found")
Expand Down Expand Up @@ -589,28 +616,38 @@ def initUI(self):
self.play_next.connect(self.on_play_next)
self.stopping_timer_cancel.connect(self.on_stopping_timer_cancel)
self.start_singleshot_timer.connect(self.on_start_singleshot_timer)
self.devices = []
self.device_list = []
if self.num_devices > 1:
text = "devices found"
else:
text = "device found"
print(self.num_devices, text)
i = 0
for d in self.devices:
cast = pychromecast.Chromecast(d.ip_addr)
loop = QEventLoop()
for d in self.chromecasts:
chromecasts, browser = pychromecast.get_listed_chromecasts(
friendly_names=[d.friendly_name]
)
cast = chromecasts[0]
cast.wait()
device = Device(self, d, cast, i)
catt_device = CattDevice(name=d.friendly_name)
device = Device(self, catt_device, cast, i)
cast.media_controller.register_status_listener(device.media_listener)
cast.register_status_listener(device.status_listener)
cast.register_connection_listener(device.connection_listener)
device.disconnect_volume = round(cast.status.volume_level * 100)
device.filename = d._cast.media_controller.title
device.filename = cast.media_controller.title
self.device_list.append(device)
self.combo_box.addItem(d.name)
self.devices.append(catt_device)
self.combo_box.addItem(cast.name)
if i == 0:
device.set_dial_value()
print(d.name)
device.set_dial_value(cast)
print(cast.name)
i = i + 1
if cast.media_controller.is_playing:
cast.media_controller.update_status()
self.app.focusChanged.connect(self.focus_changed)
self.combo_box.currentIndexChanged.connect(self.on_index_changed)
self.main_layout.addLayout(self.devices_layout)
self.main_layout.addLayout(self.control_layout)
Expand All @@ -627,6 +664,7 @@ def initUI(self):
self.splash.finish()
self.raise_()
self.activateWindow()
loop.exec()

def focus_changed(self, event):
try:
Expand Down Expand Up @@ -853,7 +891,7 @@ def on_index_changed(self):
enabled = d.playing and not d.live
self.skip_forward_button.setEnabled(enabled)
self.progress_slider.setEnabled(enabled)
duration = d.device._cast.media_controller.status.duration
duration = d.get_duration(d.cast.media_controller.status)
if duration != None:
self.progress_slider.setMaximum(int(duration))
self.set_progress(time_to_seconds(d.time))
Expand All @@ -867,15 +905,15 @@ def on_index_changed(self):
enabled = not d.rebooting
self.play_button.setEnabled(enabled)
self.stop_button.setEnabled(enabled)
d.set_dial_value()
d.set_dial_value(d.cast)
d.update_text()

def on_skip_click(self):
i = self.combo_box.currentIndex()
d = self.get_device_from_index(i)
if d == None:
return
duration = d.device._cast.media_controller.status.duration
duration = d.get_duration(d.cast.media_controller.status)
if d.filename != None:
d.kill_catt_process()
self.on_stop_click()
Expand Down Expand Up @@ -911,7 +949,7 @@ def toggle_mute(self):
if d.muted:
d.device.volume(d.unmute_volume / 100)
else:
d.unmute_volume = d.device._cast.status.volume_level * 100
d.unmute_volume = d.cast.status.volume_level * 100
d.device.volume(0.0)

def seek(self, d, value):
Expand All @@ -926,12 +964,12 @@ def on_progress_value_changed(self):
d = self.get_device_from_index(i)
if d.progress_clicked:
return
if d.device._cast.media_controller.status.supports_seek:
if d.cast.media_controller.status.supports_seek:
v = self.progress_slider.value()
self.stop_timer.emit(i)
self.set_time(i, v)
self.progress_label.setText(d.time.toString("hh:mm:ss"))
duration = d.device._cast.media_controller.status.duration
duration = d.get_duration(d.cast.media_controller.status)
if duration and v != int(duration):
self.seek(d, v)
else:
Expand All @@ -953,7 +991,7 @@ def on_progress_released(self):
return
value = self.progress_slider.value()
d.progress_clicked = False
if d.device._cast.media_controller.status.supports_seek:
if d.cast.media_controller.status.supports_seek:
if value > self.current_progress or value < self.current_progress:
self.set_time(i, value)
self.progress_label.setText(d.time.toString("hh:mm:ss"))
Expand Down Expand Up @@ -987,18 +1025,25 @@ def event_pending_expired(self):
self.volume_status_event_pending = False

def on_add_device(self, ip):
name = ""
for d in self.device_list:
if d.device.ip_addr == ip:
last_volume = d.disconnect_volume
self.devices.remove(d.device)
self.device_list.remove(d)
name = d.device.name
break
d = CattDevice(ip_addr=ip)
d._cast.wait()
device = Device(self, d, d._cast, self.combo_box.count())
d._cast.media_controller.register_status_listener(device.media_listener)
d._cast.register_status_listener(device.status_listener)
self.devices.append(d)
chromecasts, browser = pychromecast.get_listed_chromecasts(
friendly_names=[name]
)
d = chromecasts[0]
d.wait()
browser.stop_discovery()
catt_device = CattDevice(name=name)
device = Device(self, catt_device, d, self.combo_box.count())
d.media_controller.register_status_listener(device.media_listener)
d.register_status_listener(device.status_listener)
self.devices.append(catt_device)
self.device_list.append(device)
self.combo_box.addItem(d.name)
if self.combo_box.currentIndex() == device.index:
Expand All @@ -1009,7 +1054,7 @@ def on_add_device(self, ip):
device.disconnect_volume = last_volume
if self.reconnect_volume == -1:
if last_volume != round(device.cast.status.volume_level * 100):
d.volume(last_volume / 100)
device.device.volume(last_volume / 100)
if device.index == self.combo_box.currentIndex():
self.set_volume_label(last_volume)
else:
Expand Down Expand Up @@ -1142,11 +1187,13 @@ def handle_media_status(self, s, d, i, status, update_ui):
d.live = status.stream_type == "LIVE"
d.set_state_playing(i, status.current_time)
if update_ui:
d.update_ui_playing(status.current_time, status.duration)
duration = d.get_duration(status)
d.update_ui_playing(status.current_time, duration)
elif status.player_state == "PAUSED":
d.set_state_paused(i, status.current_time)
if update_ui:
d.update_ui_paused(status.current_time, status.duration)
duration = d.get_duration(status)
d.update_ui_paused(status.current_time, duration)
elif status.player_state == "IDLE" or status.player_state == "UNKNOWN":
d.set_state_idle(i)
if update_ui:
Expand Down Expand Up @@ -1178,7 +1225,7 @@ def new_cast_status(self, status):
elif not d.muted and v == 0:
d.muted = True
if not s.volume_status_event_pending:
d.set_dial_value()
d.set_dial_value(d.cast)
else:
s.set_volume_label(v)
if v > 0:
Expand Down
33 changes: 21 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
[build-system]
build-backend = "flit.buildapi"
build-backend = "flit_core.buildapi"
requires=[
"six",
"flit",
"flit_core >=3.2,<4",
"catt",
"cattqt",
"pyqt5",
"pychromecast >=3.2.3",
"chardet >=3.0.4",
"pychromecast >=12.1.4",
]
requires-python="3"

[tool.flit.metadata]
module = "cattqt"
author = "Scott Moreau"
author-email = "[email protected]"
home-page = "https://github.com/soreau/catt-qt"
classifiers = ["License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"]
description-file="README.rst"
[project]
name = "cattqt"
requires-python = ">=3.0"
readme = "README.rst"
authors = [
{name = "Scott Moreau", email = "[email protected]"},
]
classifiers = [
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
]
dynamic = ["version", "description"]

[tool.flit.scripts]
catt-qt = "cattqt:main"
[project.urls]
Source = "https://github.com/soreau/catt-qt"

[project.scripts]
catt-qt = "cattqt:main"

0 comments on commit a2e3ec6

Please sign in to comment.