feat: resolve chromecast discovery conflict and add CI/CD workflow
Build and Push Docker Image / build-and-push (push) Failing after 10m6s

- Update Zeroconf to unicast mode to resolve port 5353 conflict with avahi-daemon
- Make API and Streamlit ports configurable via environment variables (defaults: 8055, 8505)
- Add Gitea Actions workflow for automated Docker builds and registry pushes
- Refactor Chromecast discovery to use modern CastBrowser API
This commit is contained in:
pie
2026-05-03 17:19:26 +01:00
parent d0dba8183b
commit 067cc51cf2
8 changed files with 95 additions and 21 deletions
+33
View File
@@ -0,0 +1,33 @@
name: Build and Push Docker Image
on:
push:
branches:
- main
env:
REGISTRY: git.home.piesweb.co.uk
IMAGE_NAME: pie/boys_streaming
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Push Docker image
run: |
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} --all-tags
+6 -2
View File
@@ -20,8 +20,12 @@ RUN chmod +x start.sh
# Create necessary directories # Create necessary directories
RUN mkdir -p config/thumbnails videos RUN mkdir -p config/thumbnails videos
# Expose ports (FastAPI: 8003, Streamlit: 8503) # Default ports
EXPOSE 8003 8503 ENV API_PORT=8055
ENV STREAMLIT_PORT=8505
# Expose ports
EXPOSE 8055 8505
# Set Environment Variables # Set Environment Variables
ENV CONFIG_DIR=/app/config ENV CONFIG_DIR=/app/config
+2 -3
View File
@@ -5,7 +5,7 @@ from typing import List
import os import os
from .library import scan_library from .library import scan_library
from .cast_logic import cast_manager from .cast_logic import cast_manager
from .utils import get_logger, VIDEOS_DIR, THUMBNAILS_DIR, get_host_ip from .utils import get_logger, VIDEOS_DIR, THUMBNAILS_DIR, get_host_ip, API_PORT
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -25,8 +25,7 @@ async def get_library():
@app.post("/cast") @app.post("/cast")
async def start_casting(request: CastRequest, background_tasks: BackgroundTasks): async def start_casting(request: CastRequest, background_tasks: BackgroundTasks):
host_ip = get_host_ip() host_ip = get_host_ip()
# Port is 8000 by default for uvicorn in our setup urls = [f"http://{host_ip}:{API_PORT}/media/{path}" for path in request.video_paths]
urls = [f"http://{host_ip}:8000/media/{path}" for path in request.video_paths]
logger.info(f"Received cast request for {len(urls)} videos") logger.info(f"Received cast request for {len(urls)} videos")
cast_manager.play_queue(urls) cast_manager.play_queue(urls)
+40 -8
View File
@@ -17,16 +17,48 @@ class CastManager:
self._lock = threading.Lock() self._lock = threading.Lock()
self._discover_thread = None self._discover_thread = None
self.browser = None self.browser = None
self.zconf = None
self.found_devices = {}
def discover(self, timeout=5): def discover(self, timeout=5):
logger.info("Discovering Chromecasts...") logger.info("Starting Chromecast discovery...")
chromecasts, browser = pychromecast.get_chromecasts(timeout=timeout) import zeroconf
self.browser = browser from pychromecast.discovery import CastBrowser, SimpleCastListener
if chromecasts: from .utils import get_host_ip
# For simplicity, pick the first one found or allow selection later
# In a real scenario, we might want to target a specific name if not self.browser:
self.chromecast = chromecasts[0] try:
logger.info(f"Found Chromecast: {self.chromecast.name}") ip = get_host_ip()
logger.info(f"Binding zeroconf to IP: {ip} (unicast mode)")
# unicast=True prevents binding to port 5353, avoiding conflict with Avahi
self.zconf = zeroconf.Zeroconf(interfaces=[ip], unicast=True)
except Exception as e:
logger.error(f"Zeroconf init failed: {e}")
self.zconf = zeroconf.Zeroconf(unicast=True)
self.found_devices = {}
def add_callback(uuid, service):
cast_info = self.browser.devices[uuid]
cast = pychromecast.get_chromecast_from_cast_info(cast_info, self.zconf)
self.found_devices[cast.name] = cast
logger.info(f"Discovered Chromecast: {cast.name}")
listener = SimpleCastListener(add_callback=add_callback)
self.browser = CastBrowser(listener, zeroconf_instance=self.zconf)
self.browser.start_discovery()
# Wait up to timeout seconds to find at least one
start_time = time.time()
while time.time() - start_time < timeout:
if self.found_devices:
break
time.sleep(0.1)
if self.found_devices:
# Pick the first one
self.chromecast = list(self.found_devices.values())[0]
logger.info(f"Selected Chromecast: {self.chromecast.name}")
self.chromecast.wait() self.chromecast.wait()
self.mc = self.chromecast.media_controller self.mc = self.chromecast.media_controller
self.mc.register_status_listener(self) self.mc.register_status_listener(self)
+2 -2
View File
@@ -2,13 +2,13 @@ import streamlit as st
import httpx import httpx
import time import time
from pathlib import Path from pathlib import Path
from utils import get_logger, get_host_ip from utils import get_logger, get_host_ip, API_PORT
logger = get_logger(__name__) logger = get_logger(__name__)
# Config - Use host IP so the browser can find the API # Config - Use host IP so the browser can find the API
HOST_IP = get_host_ip() HOST_IP = get_host_ip()
API_URL = f"http://{HOST_IP}:8000" API_URL = f"http://{HOST_IP}:{API_PORT}"
st.set_page_config( st.set_page_config(
page_title="Boys Streaming 📺", page_title="Boys Streaming 📺",
+4
View File
@@ -16,6 +16,10 @@ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
THUMBNAILS_DIR.mkdir(parents=True, exist_ok=True) THUMBNAILS_DIR.mkdir(parents=True, exist_ok=True)
VIDEOS_DIR.mkdir(parents=True, exist_ok=True) VIDEOS_DIR.mkdir(parents=True, exist_ok=True)
# Port configuration
API_PORT = int(os.getenv("API_PORT", "8055"))
STREAMLIT_PORT = int(os.getenv("STREAMLIT_PORT", "8505"))
# Logging configuration # Logging configuration
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
+3 -1
View File
@@ -12,8 +12,10 @@ services:
environment: environment:
- VIDEOS_DIR=/app/videos - VIDEOS_DIR=/app/videos
- CONFIG_DIR=/app/config - CONFIG_DIR=/app/config
- API_PORT=8055
- STREAMLIT_PORT=8505
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8501/_stcore/health"] test: ["CMD", "curl", "-f", "http://localhost:8505/_stcore/health"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
+4 -4
View File
@@ -1,12 +1,12 @@
#!/bin/bash #!/bin/bash
# Start FastAPI backend in the background # Start FastAPI backend in the background
echo "Starting FastAPI backend..." echo "Starting FastAPI backend on port ${API_PORT:-8055}..."
uvicorn app.api:app --host 0.0.0.0 --port 8003 & uvicorn app.api:app --host 0.0.0.0 --port ${API_PORT:-8055} &
# Start Streamlit frontend # Start Streamlit frontend
echo "Starting Streamlit frontend..." echo "Starting Streamlit frontend on port ${STREAMLIT_PORT:-8505}..."
streamlit run app/main.py --server.port 8503 --server.address 0.0.0.0 streamlit run app/main.py --server.port ${STREAMLIT_PORT:-8505} --server.address 0.0.0.0
# Wait for any process to exit # Wait for any process to exit
wait -n wait -n