Reproductor de video para Raspberry Pi 5: VLC con cuepoints configurables, timer de sesión y control por GPIO. Arranque automático con systemd. Pensado para kiosk con HDMI. Incluye portal admin opcional (hotspot WiFi temporal).
Repositorio: github.com/sector7gp/video-player
| Elemento | Valor |
|---|---|
| Plataforma | Raspberry Pi 5, lgpio (chip 0) |
| Proyecto en la Pi | /home/video1/video-player |
| Video | /media/video1.mp4 |
| Arranque | Reproduce CUE1 → CUE2 y queda pausado en CUE2 |
| Botón1 (GPIO23) | Desde presentación pausada: play + salta a CUE3 (outro) y continúa a CUE4; luego loop sesión A CUE4 → CUE5 |
| Botón1 (2.ª pulsación) | En sesión A: loop sesión B CUE6 → CUE7 |
| Timer | Al vencer → CUE8; al llegar a CUE9 → CUE1 → pausa en CUE2 |
| Configuración | config.json (cuepoints + timer) |
| Audio | En config.json (audio.salida, audio.alsa_*) con override por env vars |
| Reproductor | video-control.service → multi-user.target |
| Portal admin (opcional) | video-admin.service — hotspot + web 10 min post-boot |
stateDiagram-v2
direction LR
Presentacion: Presentación\nCUE1 → pausa CUE2
Outro: OutroPresentacion\nCUE3 → CUE4
SesionA: Sesión A\nloop CUE4-CUE5
SesionB: Sesión B\nloop CUE6-CUE7
Finale: Finale\nCUE8 → CUE9
Presentacion --> Outro: Botón1 play + CUE3
Outro --> SesionA: Llega a CUE4
SesionA --> SesionB: Botón1 entra loop B
SesionB --> SesionA: Botón1 vuelve a CUE4
Outro --> Finale: Timer vence
SesionA --> Finale: Timer vence
SesionB --> Finale: Timer vence
Finale --> Presentacion: CUE9 → CUE1 → pausa CUE2
- Reproducción en bucle del MP4 (
--input-repeat=-1en instancia y medio). - Presentación al arrancar: reproduce CUE1 → CUE2 y queda pausada en
cue2_ms. - Botón1 (GPIO23): desde presentación pausada inicia timer, sale de pausa y salta a
CUE3(outro). Al pasarCUE4entra a loop de sesión A (CUE4–CUE5). - Sesión B por botón1: en sesión A, segunda pulsación (timer activo) → loop
CUE6–CUE7; tercera pulsación sale del loop y vuelve aCUE4. - Timer: al vencer salta a
CUE8; al llegar aCUE9vuelve a pausa enCUE2. - Antirebote hardware (50 ms) y software (400 ms entre pulsaciones).
- Log en
control.log; metadatos del video vía ffprobe al arrancar. - Audio: salida por HDMI o placa externa (USB/HAT) vía ALSA, configurable.
- Raspberry Pi 5
- Raspberry Pi OS con VLC y Python 3
- HDMI conectado; MP4 en
/media/video1.mp4 - Un botón entre GPIO23 y GND (pull-up interno en firmware)
sudo apt update
sudo apt install -y vlc python3-vlc python3-lgpio ffmpeg git
sudo usermod -aG video,render,input,gpio video1(Reiniciar sesión o la Pi tras usermod.)
git clone https://github.com/sector7gp/video-player.git /home/video1/video-player
# Copiar o enlazar el video:
# sudo cp /ruta/origen.mp4 /media/video1.mp4
cd /home/video1/video-player
cp config.json.example config.json # solo la primera vez; editar cuepoints locales
# Editar config.json con los tiempos de tu video (no se sobreescribe con git pull)
sudo bash deploy/install-service.sh video1
sudo reboot| GPIO | Función |
|---|---|
| 23 | Botón1 — inicia sesión / escala loop |
Conexión: un lado del botón al GPIO, el otro a GND.
cd /home/video1/video-player
python3 video_control.py| Botón | Acción |
|---|---|
| GPIO23 (botón1) | En presentación pausada: play + timer + salto a CUE3. En sesión A: entra loop CUE6–CUE7. En sesión B: sale del loop y vuelve a CUE4 |
sudo systemctl status video-control.service
journalctl -u video-control.service -f
tail -f /home/video1/video-player/control.logEl unit (deploy/video-control.service) arranca sin escritorio: espera /dev/dri/card0, WorkingDirectory y script en video-player/.
Servicio independiente del reproductor: durante 10 minutos tras cada boot expone un hotspot WiFi y un portal web para editar config.json y subir el MP4.
cd /home/video1/video-player
cp admin/portal.json.example admin/portal.json # primera vez
nano admin/portal.json # cambiar hotspot.clave
sudo bash deploy/install-admin.sh video1
sudo rebootActualizar en la Pi: git fetch --tags && git checkout v2.3.0 (o git pull en main).
| Campo | Descripción |
|---|---|
hotspot.ssid |
Nombre de la red WiFi |
hotspot.usuario |
Usuario del portal web |
hotspot.clave |
Clave WPA (≥8 chars) y contraseña del portal web |
ventana_minutos |
Duración de la ventana (default 10) |
puerto |
Puerto HTTP del portal (default 8080) |
config_path |
Ruta a config.json del reproductor |
- Tras el boot, conectate al WiFi
ssidconclave. - Abrí
http://192.168.4.1:8080(o la IP que muestre el log). - Login con
usuario+clave. - Editá
config.jsono subí un MP4 → el reproductor se reinicia automáticamente. - A los 10 minutos el hotspot y el portal se apagan;
video-controlsigue corriendo.
journalctl -u video-admin.service -fNota: con un solo chip WiFi, durante la ventana admin la Pi no estará conectada como cliente a otra red.
config.json no está en el repositorio (configuración local por instalación). Copiá la plantilla y editá los cuepoints:
cp config.json.example config.jsonPlantilla (config.json.example):
| Campo | Descripción |
|---|---|
video.path |
Archivo de video |
cuepoints.cue1_ms |
Inicio presentación (reproduce desde aquí hacia CUE2) |
cuepoints.cue2_ms |
Fin presentación / punto de pausa al arrancar y al volver desde CUE9 |
cuepoints.cue3_ms |
Inicio de outro de presentación (salto por botón1) |
cuepoints.cue4_ms |
Fin outro / inicio loop sesión A |
cuepoints.cue5_ms |
Fin loop sesión A / inicio loop sesión B |
cuepoints.cue6_ms |
Inicio loop sesión B |
cuepoints.cue7_ms |
Fin loop sesión B |
cuepoints.cue8_ms |
Salto al vencer el timer |
cuepoints.cue9_ms |
Al llegar aquí (post-timer) vuelve a pausa en CUE2 |
timer_minutos |
Duración del timer tras botón1 |
boton1_largo.segundos |
Umbral de pulsación larga para botón1 |
boton1_largo.salir_app_segundos |
Umbral de pulsación muy larga para cerrar la app |
boton1_largo.comando |
Comando a ejecutar en pulsación larga (recuperación) |
boton1_largo.overlay.texto |
Texto del overlay de pulsación larga |
boton1_largo.overlay.tamano |
Tamaño de fuente del overlay |
boton1_largo.overlay.centrado |
Centrado del overlay (true/false) |
boton1_largo.overlay.color_hex |
Color del texto (hex RGB) |
boton1_largo.overlay.opacidad |
Opacidad del texto (0-255) |
boton1_largo.overlay.sombra_roja |
Flag de intención visual (marquee no soporta sombra real) |
audio.salida |
hdmi o externa |
audio.alsa_hdmi |
Dispositivo ALSA HDMI |
audio.alsa_externa |
Dispositivo ALSA externo (USB/HAT) |
Los cuepoints deben ser estrictamente crecientes: CUE1 < CUE2 < … < CUE9.
Para pulsación larga, boton1_largo.salir_app_segundos debe ser mayor que boton1_largo.segundos.
git pull no modifica tu config.json local. El instalador crea config.json desde la plantilla si no existe.
Ruta alternativa del archivo: variable de entorno CONFIG_PATH.
Tras cambios en config.json: sudo systemctl restart video-control.service.
Al iniciar el servicio, control.log y journalctl muestran la config cargada y las especificaciones del video:
INFO - Config cargada: video=/media/video1.mp4 | CUE1=20 CUE2=12000 ... | timer=5 min
INFO - Iniciando reproducción (CUE1 → pausa en CUE2)...
INFO - Video: /media/video1.mp4
INFO - Duración: 22356 ms (0:22) [ffprobe]
Por defecto, el script toma audio desde config.json:
audio.salidaaudio.alsa_hdmiaudio.alsa_externa
Si definís variables de entorno en systemd, tienen prioridad sobre config:
| Variable | Default | Descripción |
|---|---|---|
AUDIO_SALIDA |
hdmi |
hdmi o externa |
ALSA_HDMI |
plughw:CARD=vc4hdmi0,DEV=0 |
Dispositivo ALSA para HDMI |
ALSA_EXTERNA |
plughw:CARD=Device,DEV=0 |
Dispositivo ALSA para placa externa |
Por defecto el sonido sale por HDMI (AUDIO_SALIDA = "hdmi").
- Conectá la placa (USB o HAT) y listá dispositivos:
aplay -l- Probá el dispositivo (ej. tarjeta 1):
speaker-test -D plughw:1,0 -c2 -t wav- En
video_control.py:
AUDIO_SALIDA = "externa"
ALSA_EXTERNA = "plughw:1,0" # ajustar según aplay -lO con variables de entorno (sin editar el script), en el servicio systemd:
Environment=AUDIO_SALIDA=externa
Environment=ALSA_EXTERNA=plughw:1,0- Reiniciá el servicio:
sudo systemctl restart video-control.serviceEn control.log debe aparecer: Audio: placa externa (plughw:1,0).
Si HDMI no suena en Pi 5, probá otro nombre de tarjeta, p. ej. plughw:CARD=vc4hdmi1,DEV=0 en ALSA_HDMI.
En algunas instalaciones con placa USB (por ejemplo, VENTION CDKHB), puede pasar que el video siga reproduciendo pero el audio se corte. Reiniciar video-control.service suele recuperar el sonido temporalmente.
Síntomas típicos
- El audio desaparece de forma aleatoria, sin detener el video.
- En logs de VLC/ALSA aparece:
cannot recover playback stream: No such device. - Reiniciar la app devuelve audio, pero el problema vuelve con el tiempo.
Evidencia de causa raíz (dmesg)
En dmesg se observan eventos de reconexión física/lógica del USB, por ejemplo:
usb usb3-port2: disabled by hub (EMI?), re-enabling...USB disconnect, device number ...
Esto apunta a que el dispositivo USB de audio se está deshabilitando/reinicializando por gestión de energía, ruido eléctrico (EMI) o estabilidad de alimentación/bus USB.
Mitigación software 1: desactivar autosuspend global USB
- Editar
/boot/firmware/cmdline.txt(una sola línea) y agregar:
usbcore.autosuspend=-1- Reiniciar:
sudo rebootMitigación software 2: forzar power/control=on por udev
- Obtener vendor y product del dispositivo USB de audio:
lsusb- Crear la regla udev (ejemplo para
0d8c:0014):
sudo tee /etc/udev/rules.d/99-usb-audio-power.rules >/dev/null <<'EOF'
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0d8c", ATTR{idProduct}=="0014", TEST=="power/control", ATTR{power/control}="on"
EOF- Recargar reglas y aplicar:
sudo udevadm control --reload-rules
sudo udevadm trigger- Verificar:
cat /sys/bus/usb/devices/*/power/controlNota sobre snd_usb_audio
En este kernel de Raspberry Pi puede no existir el parámetro power_save para snd_usb_audio (por eso no se puede resolver desde /sys/module/snd_usb_audio/parameters/power_save). En este caso, usar las mitigaciones anteriores (usbcore.autosuspend=-1 + regla udev) es el camino recomendado.
- Presentación: al arrancar, reproduce
CUE1 → CUE2y queda pausada enCUE2hasta botón1. - Outro presentación: botón1 en presentación pausada → play + seek a
CUE3; el video continúa libre hastaCUE4. - Sesión A: desde
CUE4, loopCUE4 → CUE5mientras el timer siga activo. - Sesión B: segunda pulsación de botón1 (con timer activo) → loop
CUE6 → CUE7. Tercera pulsación → vuelve aCUE4. - Finale: timer vencido → seek a
CUE8; al llegar aCUE9→ pausa enCUE2. - Pulsación larga de botón1: si se mantiene más de
boton1_largo.segundos, ejecutaboton1_largo.comando. - Pulsación muy larga de botón1: si se mantiene más de
boton1_largo.salir_app_segundos, cierra la app. - Overlay de confirmación: al superar
boton1_largo.segundosse muestra el texto deboton1_largo.overlay.texto, centrado y con tamaño configurable; al soltar se oculta.
video-player/
├── video_control.py # Reproductor
├── version.py # Lee VERSION (fuente única de versión)
├── admin/
│ ├── portal_main.py # Portal + hotspot temporal
│ ├── portal.json.example
│ └── static/ # UI web
├── config.json.example # Plantilla de cuepoints + timer
├── config.json # Local (gitignore)
├── VERSION # 2.3.0
├── README.md
└── deploy/
├── video-control.service
├── video-admin.service
├── install-service.sh
└── install-admin.sh
- MP4 H.264 o H.265; 4K exige buena refrigeración y alimentación en Pi 5.
- Si usás X11 (
DISPLAY=:0), adaptá el.servicelocalmente; la v1.0 por defecto es headless/DRM.
- Portal admin (nuevo): servicio
video-admincon hotspot WiFi + portal web temporal (10 min post-boot); edición deconfig.json, upload de MP4, reinicio devideo-control. - Versión unificada: un solo SemVer en
VERSION/version.pypara reproductor y admin. - Reproductor: sin cambios funcionales respecto a v2.2.5 (presentación CUE1→pausa CUE2, botón1, sesión, timer, finale).
Instalación/actualización en la Pi:
cd /home/video1/video-player
git fetch --tags
git checkout v2.3.0 # o: git pull en main
sudo bash deploy/install-service.sh video1
sudo bash deploy/install-admin.sh video1 # opcional
sudo rebootTag legacy
admin-v1.0.0: obsoleto; usar solo tagsv*(p. ej.v2.3.0).
Release estable del flujo de presentación pausada en CUE2 (sin botón2):
- Arranque y cierre: reproduce
CUE1 → CUE2y pausa (decodifica frames; evita pantalla negra por seek directo a CUE2). - Botón1 (GPIO23): desde presentación pausada inicia timer, reanuda VLC, seek a
CUE3y continúa el flujo outro/sesión/finale. - Fin de sesión: al llegar a
CUE9vuelve aCUE1 → pausa en CUE2. - Sin botón2: GPIO24 eliminado del flujo operativo (v2.2.1).
- Estabilidad VLC/Pi: tolerancia al pausar en cue, timeout de seek, flag
presentacion_en_reposo, unpause antes de seek al salir de presentación.
Instalación/actualización del reproductor (v2.2.5):
cd /home/video1/video-player
git fetch --tags
git checkout v2.2.5
sudo systemctl restart video-control.service- Elimina bucle de logs: ya no reinicia CUE1 tras pausar en CUE2 (VLC tarda en reportar Paused).
- Presentación: reproduce CUE1 y pausa al llegar a CUE2 (evita pantalla negra por seek directo).
- Corrige pausa en CUE2: tolerancia al pasar el cue, timeout de seek y espera inicial de VLC.
- Presentación sin loop: arranque pausado en CUE2.
- Botón1 en presentación pausada: play + salto a CUE3.
- Al llegar a CUE9 vuelve a pausa en CUE2 (no reinicia en CUE1).
- Eliminado botón2 (GPIO24) del flujo operativo.
- Sección A y sección B ahora usan extremos independientes (sin compartir cuepoints).
- Renumeración: sesión A
CUE4–CUE5, sesión BCUE6–CUE7, timerCUE8, reinicio enCUE9.
- Nuevo cuepoint de outro de presentación (
CUE3) entre loop de presentación y sesión. - Botón1 en presentación ahora salta a
CUE3y el video continúa hastaCUE4. - Renumeración completa de cuepoints: sesión A
CUE4–CUE5, sesión BCUE5–CUE6, timerCUE7, reinicio enCUE8.
- Pulsación larga de botón1 muestra overlay en pantalla (
SOLTAR PARA / REINICIAR) al cumplir el umbral y lo oculta al soltar.
- Fix de compatibilidad marquee: si
Position/Color/Opacityno están soportados por la build de VLC, el overlay sigue mostrando texto (degradación elegante).
- Overlay configurable desde
config.json: texto, tamaño, centrado, color y opacidad. - El overlay se muestra centrado (
boton1_largo.overlay.centrado=true) por defecto. - Nota: VLC marquee no soporta fondo/sombra real;
sombra_rojase registra como intención visual.
- Botón1 con pulsación larga configurable: ejecuta
boton1_largo.comandoal superarboton1_largo.segundos.
- Audio centralizado en
config.json(audio.salida,audio.alsa_hdmi,audio.alsa_externa). - Variables de entorno siguen disponibles como override (
AUDIO_SALIDA,ALSA_HDMI,ALSA_EXTERNA).
- Fix: loop CUE3–CUE4 (y CUE4–CUE5) con timer activo; seek ya no bloquea el loop al restaurar posición.
- Timer vencido: salta a CUE6; al llegar a CUE7 reinicia en CUE1 (nuevo
cue7_ms).
- Botón1 en loop CUE4–CUE5: toggle off restaura la posición guardada (como el loop corto anterior).
config.jsonignorado por git; plantilla enconfig.json.examplepara configuración local.
- Sistema de cuepoints CUE1–CUE6 con presentación, sesión con timer, loops escalonados y finale.
- Botón1 (GPIO23): inicia sesión / escala a loop CUE4–CUE5.
- Botón2 (GPIO24): siempre vuelve a CUE1.
config.json: reemplazaloop_cortoyloop_principalporcuepoints+timer_minutos.
- Fix: eliminado parse/tracks VLC que causaba segmentation fault en Pi; metadatos vía ffprobe o reproductor.
- Fix: lectura de pistas VLC compatible con python-vlc de Raspberry Pi (
track.videosin.u).
- Fix: metadatos en hilo aparte; ya no se parsea el media activo (evita que el video no arranque).
- Fix: metadatos del video al arranque (parse
fetch_local, lectura trasplay(), fallback ffprobe).
- Configuración en
config.json: ruta del video, loop corto y loop principal. - Al arrancar, log de duración y especificaciones del video (codec, resolución, fps, audio).
- Variable de entorno
CONFIG_PATHpara ruta alternativa del JSON.
- Audio configurable:
AUDIO_SALIDA=hdmioexterna(ALSA en VLC). - Variables
ALSA_HDMI/ALSA_EXTERNAy ejemplos en el servicio systemd.
- Eliminado
player.py(referencia obsoleta; solovideo_control.py). - Loop principal anticipado:
REINICIO_LOOP_MSreinicia enRESTART_MSantes del final del MP4 (evita pantalla negra del buffer). - Modo automático:
REINICIO_LOOP_MS = 0usaduración − MARGEN_ANTES_FIN_MS. - Respaldo si VLC llega igual a
Ended(umbral mal configurado).
Primera versión estable.
- GPIO23: loop corto por toggle con posición guardada.
- GPIO24: reinicio rápido a 20 ms.
- Reinicio automático al fin del video (estado
Ended). - Rutas: proyecto en
/home/video1/video-player, video en/media/video1.mp4. - Servicio systemd e instalador
deploy/install-service.sh. - Logging a
control.log.
Uso libre para el proyecto del autor; sin garantía.