This commit is contained in:
David Rice
2026-05-26 17:33:02 +02:00
parent 423766f7a3
commit 0f7b0e1ac5
9 changed files with 1448 additions and 94 deletions

View File

@@ -1,18 +1,21 @@
import argparse
import gi
import os
import socket
import struct
import threading
import os
import sys
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
SWITCH_UDP_PORT = 5001
class KioskManager:
def __init__(self, pipeline):
self.pipeline = pipeline
self.videos = [
"file:///root/vid.mp4",
"file:///root/vid2.mp4"
"file:///root/python/vid.mp4",
"file:///root/python/vid2.mp4"
]
self.current_video_index = 0
@@ -117,7 +120,32 @@ def handle_button(source, condition, manager):
return True
def play_kiosk():
def handle_udp_switch(sock, condition, manager):
"""Receives 'switch' datagrams from device_server.py and cycles the video."""
try:
data, _ = sock.recvfrom(64)
except BlockingIOError:
return True
if data.strip() == b"switch":
print("UDP Trigger: switch")
manager.switch_video()
return True
def _resolve_start_index(start_name: str, videos: list) -> int:
"""Map a basename like 'vid.mp4' to its index in the videos list."""
target = os.path.basename(start_name).lower()
for i, uri in enumerate(videos):
if os.path.basename(uri).lower() == target:
return i
raise SystemExit(
f"--start {start_name!r} not in kiosk video list: "
+ ", ".join(os.path.basename(u) for u in videos)
)
def play_kiosk(start_index: int = 0):
Gst.init(None)
pipeline = Gst.ElementFactory.make("playbin", "player")
@@ -129,17 +157,19 @@ def play_kiosk():
pipeline.set_property("audio-sink", Gst.ElementFactory.make("fakesink"))
manager = KioskManager(pipeline)
pipeline.set_property("uri", manager.videos[0])
manager.current_video_index = start_index
pipeline.set_property("uri", manager.videos[start_index])
print(f"Starting on: {manager.videos[start_index]}")
# UDP trigger → switch video (device_server sends a packet to port 5001)
def _udp_listener():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', 5001))
while True:
sock.recv(64)
GLib.idle_add(manager.switch_video)
threading.Thread(target=_udp_listener, daemon=True).start()
# --- UDP SWITCH LISTENER ---
# device_server.py sends b'switch' to 127.0.0.1:5001 to cycle videos remotely.
try:
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.setblocking(False)
udp_sock.bind(("127.0.0.1", SWITCH_UDP_PORT))
GLib.io_add_watch(udp_sock, GLib.IO_IN, handle_udp_switch, manager)
except Exception as e:
print(f"UDP Listener Error: {e}")
# --- INPUT MONITORING ---
try:
@@ -159,16 +189,13 @@ def play_kiosk():
def on_message(bus, msg, manager_instance):
if msg.type == Gst.MessageType.EOS:
# Video ended. Cycle LED and advance to the next video in the list.
manager_instance.change_led_colour()
pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0)
manager_instance.switch_video()
elif msg.type == Gst.MessageType.ERROR:
err, debug = msg.parse_error()
print(f"GStreamer Error: {err}\nDebug: {debug}", flush=True)
loop.quit()
elif msg.type == Gst.MessageType.STATE_CHANGED:
if msg.src == pipeline:
old, new, _ = msg.parse_state_changed()
print(f"Pipeline: {old.value_nick} -> {new.value_nick}", flush=True)
print(f"GStreamer Error: {err}")
loop.quit
bus.connect("message", on_message, manager)
@@ -180,73 +207,18 @@ def play_kiosk():
except KeyboardInterrupt:
pipeline.set_state(Gst.State.NULL)
def play_static_color(r: int, g: int, b: int):
"""Display a solid colour using GStreamer videotestsrc (no video file required).
Uses videotestsrc pattern=solid-color so every DSI line carries the same
repeating RGB triplet — any deviation in the proto_decoder output is a DSI fault.
Listens on UDP port 5001 for a trigger packet (same as play_kiosk), which
briefly cycles the pipeline through READY→PLAYING to generate the LP→HS
startup sequence that the scope captures on Pass 1.
"""
Gst.init(None)
argb = (0xFF << 24) | (r << 16) | (g << 8) | b
SINK_STR = ("videoconvert ! video/x-raw,format=BGRx ! "
"kmssink driver-name=mxsfb-drm connector-id=37 plane-id=31 can-scale=false")
pipeline_str = (
f"videotestsrc pattern=solid-color foreground-color={argb} ! "
f"video/x-raw,width=1280,height=800,framerate=60/1 ! "
f"{SINK_STR}"
)
pipeline = Gst.parse_launch(pipeline_str)
bus = pipeline.get_bus()
bus.add_signal_watch()
loop = GLib.MainLoop()
def on_message(bus, msg):
if msg.type == Gst.MessageType.ERROR:
err, debug = msg.parse_error()
print(f"GStreamer Error: {err}\nDebug: {debug}", flush=True)
loop.quit()
elif msg.type == Gst.MessageType.STATE_CHANGED:
if msg.src == pipeline:
old, new, _ = msg.parse_state_changed()
print(f"Pipeline: {old.value_nick} -> {new.value_nick}", flush=True)
def _restart_pipeline():
"""Cycle READY→PLAYING to produce the LP→HS startup the scope triggers on."""
print("UDP trigger: restarting pipeline (READY → PLAYING)", flush=True)
pipeline.set_state(Gst.State.READY)
pipeline.set_state(Gst.State.PLAYING)
return False # GLib.idle_add one-shot
def _udp_listener():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', 5001))
while True:
sock.recv(64)
GLib.idle_add(_restart_pipeline)
threading.Thread(target=_udp_listener, daemon=True).start()
bus.connect("message", on_message)
pipeline.set_state(Gst.State.PLAYING)
print(f"Static colour R:{r} G:{g} B:{b} (0x{argb:08X}) — running", flush=True)
try:
loop.run()
except KeyboardInterrupt:
pipeline.set_state(Gst.State.NULL)
if __name__ == "__main__":
import sys
if "--static-pink" in sys.argv:
play_static_color(255, 51, 187) # R:255 G:51 B:187
else:
play_kiosk()
p = argparse.ArgumentParser(description="Kiosk video player")
p.add_argument("--start", default="vid.mp4",
help="Initial video filename (basename match against kiosk list)")
# parse_known_args so legacy flags like --static-pink don't crash the kiosk
args, _unknown = p.parse_known_args()
# We need the video list to resolve --start, so we recreate it here (must
# stay in sync with KioskManager.videos).
_videos = [
"file:///root/python/vid.mp4",
"file:///root/python/vid2.mp4",
]
start_index = _resolve_start_index(args.start, _videos)
play_kiosk(start_index=start_index)