mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
Refactor YTMClient to use Socket.IO for companion server communication
This commit is contained in:
@@ -8,4 +8,10 @@ google-auth-oauthlib==1.0.0
|
|||||||
google-auth-httplib2==0.1.0
|
google-auth-httplib2==0.1.0
|
||||||
google-api-python-client==2.86.0
|
google-api-python-client==2.86.0
|
||||||
freetype-py==2.5.1
|
freetype-py==2.5.1
|
||||||
spotipy
|
spotipy
|
||||||
|
Flask
|
||||||
|
unidecode
|
||||||
|
icalevents
|
||||||
|
python-socketio
|
||||||
|
python-engineio
|
||||||
|
websockets
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import requests
|
import socketio
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
@@ -13,6 +15,34 @@ class YTMClient:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.base_url = None
|
self.base_url = None
|
||||||
self.load_config()
|
self.load_config()
|
||||||
|
self.sio = socketio.Client(logger=True, engineio_logger=False)
|
||||||
|
self.last_known_track_data = None
|
||||||
|
self.is_connected = False
|
||||||
|
self._data_lock = threading.Lock()
|
||||||
|
self._connection_event = threading.Event()
|
||||||
|
|
||||||
|
@self.sio.event
|
||||||
|
def connect():
|
||||||
|
logging.info(f"Successfully connected to YTM Companion Socket.IO server at {self.base_url}")
|
||||||
|
self.is_connected = True
|
||||||
|
self._connection_event.set()
|
||||||
|
|
||||||
|
@self.sio.event
|
||||||
|
def connect_error(data):
|
||||||
|
logging.error(f"YTM Companion Socket.IO connection failed: {data}")
|
||||||
|
self.is_connected = False
|
||||||
|
self._connection_event.set()
|
||||||
|
|
||||||
|
@self.sio.event
|
||||||
|
def disconnect():
|
||||||
|
logging.info(f"Disconnected from YTM Companion Socket.IO server at {self.base_url}")
|
||||||
|
self.is_connected = False
|
||||||
|
|
||||||
|
@self.sio.on('ytm_track_update')
|
||||||
|
def on_track_update(data):
|
||||||
|
logging.debug(f"Received track update from YTM Companion: {data}")
|
||||||
|
with self._data_lock:
|
||||||
|
self.last_known_track_data = data
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
default_url = "http://localhost:9863"
|
default_url = "http://localhost:9863"
|
||||||
@@ -37,53 +67,72 @@ class YTMClient:
|
|||||||
logging.error(f"Error loading YTM config: {e}")
|
logging.error(f"Error loading YTM config: {e}")
|
||||||
self.base_url = default_url
|
self.base_url = default_url
|
||||||
logging.info(f"YTM Companion URL set to: {self.base_url}")
|
logging.info(f"YTM Companion URL set to: {self.base_url}")
|
||||||
|
if self.base_url and self.base_url.startswith("ws://"):
|
||||||
|
self.base_url = "http://" + self.base_url[5:]
|
||||||
|
elif self.base_url and self.base_url.startswith("wss://"):
|
||||||
|
self.base_url = "https://" + self.base_url[6:]
|
||||||
|
|
||||||
def _make_request(self, endpoint):
|
def _ensure_connected(self, timeout=5):
|
||||||
"""Helper method to make requests to the companion server."""
|
if not self.is_connected:
|
||||||
if not self.base_url:
|
logging.info(f"Attempting to connect to YTM Socket.IO server: {self.base_url}")
|
||||||
logging.error("YTM base URL not configured.")
|
try:
|
||||||
return None
|
self.sio.connect(self.base_url, transports=['websocket'], wait_timeout=timeout)
|
||||||
try:
|
self._connection_event.clear()
|
||||||
url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
|
if not self._connection_event.wait(timeout=timeout):
|
||||||
response = requests.get(url, timeout=1) # Short timeout
|
logging.warning(f"YTM Socket.IO connection attempt timed out after {timeout}s.")
|
||||||
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
return False
|
||||||
return response.json()
|
return self.is_connected
|
||||||
except requests.exceptions.ConnectionError:
|
except socketio.exceptions.ConnectionError as e:
|
||||||
# This is expected if the server isn't running
|
logging.error(f"YTM Socket.IO connection error: {e}")
|
||||||
logging.debug(f"Could not connect to YTM Companion server at {self.base_url}")
|
self.is_connected = False
|
||||||
return None
|
return False
|
||||||
except requests.exceptions.Timeout:
|
except Exception as e:
|
||||||
logging.warning(f"Timeout connecting to YTM Companion server at {self.base_url}")
|
logging.error(f"Unexpected error during YTM Socket.IO connection: {e}")
|
||||||
return None
|
self.is_connected = False
|
||||||
except requests.exceptions.RequestException as e:
|
return False
|
||||||
logging.error(f"Error requesting {endpoint} from YTM: {e}")
|
return True
|
||||||
return None
|
|
||||||
|
|
||||||
def is_available(self):
|
def is_available(self):
|
||||||
"""Checks if the YTM companion server is reachable."""
|
if not self.is_connected:
|
||||||
# Use a lightweight endpoint if available, otherwise try main query
|
return self._ensure_connected(timeout=2)
|
||||||
# For now, just try the main query endpoint
|
return True
|
||||||
return self._make_request('/query') is not None
|
|
||||||
|
|
||||||
def get_current_track(self):
|
def get_current_track(self):
|
||||||
"""Fetches the currently playing track from the YTM companion server."""
|
if not self._ensure_connected():
|
||||||
data = self._make_request('/query')
|
logging.warning("YTM client not connected, cannot get current track.")
|
||||||
# Add more specific error handling or data validation if needed
|
|
||||||
if data and 'track' in data and 'player' in data:
|
|
||||||
return data
|
|
||||||
else:
|
|
||||||
logging.debug("Received no or incomplete data from YTM /query")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Example Usage (for testing)
|
with self._data_lock:
|
||||||
|
if self.last_known_track_data:
|
||||||
|
return self.last_known_track_data
|
||||||
|
else:
|
||||||
|
logging.debug("No track data received yet from YTM Companion Socket.IO.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def disconnect_client(self):
|
||||||
|
if self.is_connected:
|
||||||
|
self.sio.disconnect()
|
||||||
|
logging.info("YTM Socket.IO client disconnected.")
|
||||||
|
|
||||||
|
# Example Usage (for testing - needs to be adapted for Socket.IO async nature)
|
||||||
# if __name__ == '__main__':
|
# if __name__ == '__main__':
|
||||||
# client = YTMClient()
|
# client = YTMClient()
|
||||||
# if client.is_available():
|
# if client.connect_client(): # Assuming connect_client is the new public method to initiate
|
||||||
# print("YTM Server is available.")
|
# print("YTM Server is available (Socket.IO).")
|
||||||
# track = client.get_current_track()
|
# try:
|
||||||
# if track:
|
# for _ in range(10): # Poll for a few seconds
|
||||||
# print(json.dumps(track, indent=2))
|
# track = client.get_current_track()
|
||||||
# else:
|
# if track:
|
||||||
# print("No track currently playing or error fetching.")
|
# print(json.dumps(track, indent=2))
|
||||||
# else:
|
# else:
|
||||||
# print(f"YTM Server not available at {client.base_url}. Is YTMD running with companion server enabled?")
|
# print("No track currently playing or error fetching (Socket.IO).")
|
||||||
|
# time.sleep(2)
|
||||||
|
# finally:
|
||||||
|
# client.disconnect_client()
|
||||||
|
# else:
|
||||||
|
# print(f"YTM Server not available at {client.base_url} (Socket.IO). Is YTMD running with companion server enabled?")
|
||||||
|
|
||||||
|
# It's important to note that a long-running application would typically
|
||||||
|
# keep the socketio client running in a background thread if sio.wait() is used,
|
||||||
|
# or integrate with an asyncio event loop. The above __main__ is simplified.
|
||||||
|
# The MusicManager's polling thread will interact with this client.
|
||||||
Reference in New Issue
Block a user