Added channel update functionality
This commit is contained in:
parent
e8fae1a14b
commit
2300fd8685
83
channel_updater.py
Normal file
83
channel_updater.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python
|
||||
"""Channel update functionality for IPMPV.
|
||||
|
||||
This module provides functionality to periodically update the channel list
|
||||
without requiring application restart."""
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
class ChannelUpdater:
|
||||
"""Class to handle periodic channel list updates."""
|
||||
|
||||
def __init__(self, server, update_interval=3600):
|
||||
"""
|
||||
Initialize the channel updater.
|
||||
|
||||
Args:
|
||||
server: The IPMPVServer instance
|
||||
update_interval: Update interval in seconds (default: 1 hour)
|
||||
"""
|
||||
self.server = server
|
||||
self.update_interval = update_interval
|
||||
self.running = False
|
||||
self.update_thread = None
|
||||
|
||||
def start(self):
|
||||
"""Start the periodic update thread."""
|
||||
if self.update_thread is not None and self.update_thread.is_alive():
|
||||
return # Already running
|
||||
|
||||
self.running = True
|
||||
self.update_thread = threading.Thread(
|
||||
target=self._update_loop,
|
||||
daemon=True
|
||||
)
|
||||
self.update_thread.start()
|
||||
print(f"Channel updater started with {self.update_interval}s interval")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the periodic update thread."""
|
||||
self.running = False
|
||||
if self.update_thread:
|
||||
self.update_thread.join(timeout=1)
|
||||
self.update_thread = None
|
||||
|
||||
def _update_loop(self):
|
||||
"""Background loop to update channels periodically."""
|
||||
while self.running:
|
||||
# Sleep first to avoid immediate update after startup
|
||||
for _ in range(self.update_interval):
|
||||
if not self.running:
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
# Perform the update
|
||||
try:
|
||||
self._update_channels()
|
||||
except Exception as e:
|
||||
print(f"Error updating channels: {e}")
|
||||
|
||||
def _update_channels(self):
|
||||
"""Update the channel list."""
|
||||
from channels import get_channels
|
||||
|
||||
print("Updating channel list...")
|
||||
new_channels = get_channels()
|
||||
|
||||
# Only update if we got valid channels
|
||||
if new_channels:
|
||||
# Update the server's channel list
|
||||
self.server.channels = new_channels
|
||||
print(f"Channel list updated: {len(new_channels)} channels")
|
||||
else:
|
||||
print("Channel update failed or returned empty list")
|
||||
|
||||
def force_update(self):
|
||||
"""Force an immediate channel list update."""
|
||||
try:
|
||||
self._update_channels()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error during forced channel update: {e}")
|
||||
return False
|
||||
@ -6,6 +6,7 @@
|
||||
"stop_retroarch": "Stop RetroArch",
|
||||
"deinterlacing": "Deinterlacing",
|
||||
"resolution": "Resolution",
|
||||
"refresh_channels": "Refresh",
|
||||
"volume": "Volume",
|
||||
"mute": "mute",
|
||||
"toggle_osd": "Toggle OSD",
|
||||
@ -27,5 +28,8 @@
|
||||
"latency_high": "Hi",
|
||||
"latency_low": "Lo",
|
||||
"other": "Other",
|
||||
"invalid_url": "Invalid or unsupported URL"
|
||||
"invalid_url": "Invalid or unsupported URL",
|
||||
"channels_updated_p1": "Channels updated! Found",
|
||||
"channels_updated_p2": "channels.",
|
||||
"channel_update_failed": "Channel update failed."
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
"stop_retroarch": "Detener RetroArch",
|
||||
"deinterlacing": "Desentrelazado",
|
||||
"resolution": "Resolución",
|
||||
"refresh_channels": "Refrescar",
|
||||
"volume": "Volumen",
|
||||
"mute": "mute",
|
||||
"toggle_osd": "Alternar OSD",
|
||||
@ -27,5 +28,8 @@
|
||||
"latency_high": "Hi",
|
||||
"latency_low": "Lo",
|
||||
"other": "Otro",
|
||||
"invalid_url": "URL inválida o no soportada"
|
||||
"invalid_url": "URL inválida o no soportada",
|
||||
"channels_updated_p1": "Canales actualizados.",
|
||||
"channels_updated_p2": "canales encontrados.",
|
||||
"channel_update_failed": "Error al actualizar canales."
|
||||
}
|
||||
|
||||
6
main.py
6
main.py
@ -6,7 +6,7 @@ from multiprocessing import Queue
|
||||
import sys
|
||||
|
||||
# Set up utils first
|
||||
from utils import setup_environment, get_current_resolution, ipmpv_retroarch_cmd
|
||||
from utils import setup_environment, get_current_resolution, ipmpv_retroarch_cmd, update_interval
|
||||
|
||||
# Initialize environment
|
||||
setup_environment()
|
||||
@ -52,7 +52,9 @@ def main():
|
||||
from_qt_queue=from_qt_queue,
|
||||
resolution=resolution,
|
||||
ipmpv_retroarch_cmd=ipmpv_retroarch_cmd,
|
||||
volume_control=volume_control
|
||||
volume_control=volume_control,
|
||||
# Mmm, hacky! TODO: fix this.
|
||||
update_interval=update_interval if update_interval is not None else 3600
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
26
server.py
26
server.py
@ -9,11 +9,12 @@ import flask
|
||||
from flask import request, jsonify, send_from_directory, redirect, url_for, make_response
|
||||
from localization import localization, _
|
||||
from utils import is_valid_url, change_resolution, get_current_resolution, is_wayland, get_or_create_secret_key
|
||||
from channel_updater import ChannelUpdater
|
||||
|
||||
class IPMPVServer:
|
||||
"""Flask server for IPMPV web interface."""
|
||||
|
||||
def __init__(self, channels, player, to_qt_queue, from_qt_queue, resolution, ipmpv_retroarch_cmd, volume_control=None):
|
||||
def __init__(self, channels, player, to_qt_queue, from_qt_queue, resolution, ipmpv_retroarch_cmd, volume_control=None, update_interval=3600):
|
||||
"""Initialize the server."""
|
||||
self.app = flask.Flask(__name__,
|
||||
static_folder='static',
|
||||
@ -26,6 +27,7 @@ class IPMPVServer:
|
||||
self.ipmpv_retroarch_cmd = ipmpv_retroarch_cmd
|
||||
self.retroarch_p = None
|
||||
self.volume_control = volume_control
|
||||
self.channel_updater = ChannelUpdater(self, update_interval=update_interval)
|
||||
|
||||
# Register routes
|
||||
self._register_routes()
|
||||
@ -33,7 +35,12 @@ class IPMPVServer:
|
||||
|
||||
def run(self, host="0.0.0.0", port=5000):
|
||||
"""Run the Flask server."""
|
||||
self.app.run(host=host, port=port)
|
||||
self.channel_updater.start()
|
||||
|
||||
try:
|
||||
self.app.run(host=host, port=port)
|
||||
finally:
|
||||
self.channel_updater.stop()
|
||||
def _register_routes(self):
|
||||
"""Register Flask routes."""
|
||||
|
||||
@ -103,6 +110,10 @@ class IPMPVServer:
|
||||
def channel_down():
|
||||
return self._handle_channel_down()
|
||||
|
||||
@self.app.route('/update_channels')
|
||||
def update_channels():
|
||||
return self._handle_update_channels()
|
||||
|
||||
@self.app.route('/manifest.json')
|
||||
def serve_manifest():
|
||||
return send_from_directory("static", 'manifest.json',
|
||||
@ -127,6 +138,7 @@ class IPMPVServer:
|
||||
def serve_favicon():
|
||||
return send_from_directory("static", 'favicon.ico',
|
||||
mimetype='image/vnd.microsoft.icon')
|
||||
|
||||
def _handle_index(self):
|
||||
"""Handle the index route."""
|
||||
from channels import group_channels
|
||||
@ -178,6 +190,7 @@ class IPMPVServer:
|
||||
html = html.replace("%DEINTERLACE_STATE%", _("on") if self.player.deinterlace else _("off"))
|
||||
html = html.replace("%RESOLUTION_LABEL%", _("resolution"))
|
||||
html = html.replace("%RESOLUTION%", self.resolution)
|
||||
html = html.replace("%REFRESH_CH_LABEL%", _("refresh_channels"))
|
||||
html = html.replace("%LATENCY_STATE%", "ON" if self.player.low_latency else "OFF")
|
||||
html = html.replace("%LATENCY_LABEL%", _("latency_low") if self.player.low_latency else _("latency_high"))
|
||||
html = html.replace("%CHANNEL_GROUPS%", channel_groups_html)
|
||||
@ -208,6 +221,9 @@ class IPMPVServer:
|
||||
html = html.replace("%JS_START_RETROARCH%", _("start_retroarch"))
|
||||
html = html.replace("%JS_ON_LABEL%", _("on"))
|
||||
html = html.replace("%JS_OFF_LABEL%", _("off"))
|
||||
html = html.replace("%JS_CH_UPDATE_P1%", _("channels_updated_p1"))
|
||||
html = html.replace("%JS_CH_UPDATE_P2%", _("channels_updated_p2"))
|
||||
html = html.replace("%JS_CH_UPDATE_FAIL%", _("channel_update_failed"))
|
||||
|
||||
return html
|
||||
|
||||
@ -359,3 +375,9 @@ class IPMPVServer:
|
||||
is_muted = self.volume_control.is_muted()
|
||||
return jsonify(volume=volume, muted=is_muted)
|
||||
return jsonify(error="Volume control not available"), 404
|
||||
|
||||
def _handle_update_channels(self):
|
||||
"""Handle the update_channels route."""
|
||||
success = self.channel_updater.force_update()
|
||||
return jsonify(success=success,
|
||||
channel_count=len(self.channels) if success else None)
|
||||
|
||||
@ -471,6 +471,9 @@
|
||||
<button id="resolution-btn" class="control-button" onclick="toggleResolution()">
|
||||
%RESOLUTION_LABEL%: <span id="resolution-state">%RESOLUTION%</span>
|
||||
</button>
|
||||
<button id="refresh-ch-btn" class="control-button" onclick="refreshChannels()">
|
||||
%REFRESH_CH_LABEL%
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@ -672,6 +675,34 @@
|
||||
.then(() => window.location.reload())
|
||||
}
|
||||
|
||||
function refreshChannels() {
|
||||
const refreshBtn = document.getElementById('refresh-ch-btn');
|
||||
const originalText = refreshBtn.textContent;
|
||||
|
||||
refreshBtn.textContent = "Updating...";
|
||||
refreshBtn.disabled = true;
|
||||
|
||||
fetch('/update_channels')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
refreshBtn.textContent = originalText;
|
||||
refreshBtn.disabled = false;
|
||||
|
||||
if (data.success) {
|
||||
showToast(`%JS_CH_UPDATE_P1% ${data.channel_count} %JS_CH_UPDATE_P2%`);
|
||||
// Reload the page to show updated channel list
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} else {
|
||||
showToast("%JS_CH_UPDATE_FAIL%");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
refreshBtn.textContent = originalText;
|
||||
refreshBtn.disabled = false;
|
||||
showToast("Error updating channels. Please try again.");
|
||||
});
|
||||
}
|
||||
|
||||
// Mobile-friendly toast notification
|
||||
function showToast(message) {
|
||||
const toast = document.createElement('div');
|
||||
|
||||
1
utils.py
1
utils.py
@ -13,6 +13,7 @@ ipmpv_retroarch_cmd = os.environ.get("IPMPV_RETROARCH_CMD")
|
||||
m3u_url = os.environ.get('IPMPV_M3U_URL')
|
||||
hwdec = os.environ.get('IPMPV_HWDEC')
|
||||
ao = os.environ.get('IPMPV_AO')
|
||||
update_interval = os.environ.get('IPMPV_UPDATE_INTERVAL')
|
||||
|
||||
def setup_environment():
|
||||
"""Set up environment variables."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user