Compare commits
5 Commits
f30f97683b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f7b889be11 | |||
| 39565c5761 | |||
| fb9f46a94f | |||
| 9d8eb0cf21 | |||
| 64e49886df |
@@ -1 +0,0 @@
|
||||
1782752485.750587
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#\!/bin/bash
|
||||
SCRIPT_DIR="/home/pi/kiwy-signage"
|
||||
LOG_FILE="/home/pi/kiwy-signage/logs/player-background.log"
|
||||
mkdir -p "."
|
||||
echo "[2026-07-17 10:54:53] Starting player in background..." >> ""
|
||||
cd "/home/pi/kiwy-signage" || exit 1
|
||||
nohup bash start.sh >> "" 2>&1 &
|
||||
PLAYER_PID=$\!
|
||||
echo "[2026-07-17 10:54:53] Player started with PID: $PLAYER_PID" >> ""
|
||||
echo "Player started in background (PID: $PLAYER_PID)"
|
||||
echo "Logs: $LOG_FILE"
|
||||
@@ -1,6 +1,3 @@
|
||||
#!/bin/bash
|
||||
# Wait for desktop environment to be ready
|
||||
#\!/bin/bash
|
||||
sleep 15
|
||||
|
||||
# Start the player
|
||||
cd "/home/pi/Desktop/Kiwy-Signage" && bash start.sh
|
||||
cd /home/pi/kiwy-signage && bash start.sh
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"server_ip": "192.168.0.159",
|
||||
"server_ip": "192.168.0.109",
|
||||
"port": "8080",
|
||||
"screen_name": "rpi-Receptie",
|
||||
"screen_name": "Birou_IT",
|
||||
"quickconnect_key": "8887779",
|
||||
"orientation": "Landscape",
|
||||
"touch": "True",
|
||||
@@ -9,4 +9,4 @@
|
||||
"edit_feature_enabled": true,
|
||||
"use_https": false,
|
||||
"verify_ssl": false
|
||||
}
|
||||
}
|
||||
Regular → Executable
+596
-568
File diff suppressed because it is too large
Load Diff
Executable
+1056
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
@@ -1,23 +1,37 @@
|
||||
{
|
||||
"count": 2,
|
||||
"count": 4,
|
||||
"player_id": 1,
|
||||
"player_name": "Receptie TV",
|
||||
"player_name": "Test_player1",
|
||||
"playlist": [
|
||||
{
|
||||
"file_name": "sean-oulashin-KMn4VEeEPR8-unsplash.jpg",
|
||||
"type": "image",
|
||||
"url": "media/sean-oulashin-KMn4VEeEPR8-unsplash.jpg",
|
||||
"duration": 25,
|
||||
"duration": 14,
|
||||
"edit_on_player": true
|
||||
},
|
||||
{
|
||||
"file_name": "weblink-1e4a4d6d885a",
|
||||
"type": "weblink",
|
||||
"url": "https://moto-adv.com/",
|
||||
"duration": 30,
|
||||
"edit_on_player": false
|
||||
},
|
||||
{
|
||||
"file_name": "robert-lukeman-zNN6ubHmruI-unsplash.jpg",
|
||||
"type": "image",
|
||||
"url": "media/robert-lukeman-zNN6ubHmruI-unsplash.jpg",
|
||||
"duration": 20,
|
||||
"file_name": "sample-30s.mp4",
|
||||
"type": "video",
|
||||
"url": "media/sample-30s.mp4",
|
||||
"duration": 31,
|
||||
"edit_on_player": false
|
||||
},
|
||||
{
|
||||
"file_name": "edited_media/5/eye_e_v2.jpg",
|
||||
"type": "image",
|
||||
"url": "media/edited_media/5/eye_e_v2.jpg",
|
||||
"duration": 50,
|
||||
"edit_on_player": true
|
||||
}
|
||||
],
|
||||
"playlist_id": 1,
|
||||
"playlist_version": 15
|
||||
"playlist_version": 14
|
||||
}
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+3
-4
@@ -1,7 +1,6 @@
|
||||
kivy>=2.3.0
|
||||
ffpyplayer
|
||||
requests==2.32.4
|
||||
bcrypt==4.2.1
|
||||
aiohttp==3.9.1
|
||||
asyncio==3.4.3
|
||||
requests>=2.32.0,<3.0.0
|
||||
bcrypt>=4.2.0,<5.0.0
|
||||
aiohttp>=3.9.0,<4.0.0
|
||||
evdev>=1.6.0
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start Kivy Signage Player
|
||||
cd "$(dirname "$0")" && source .venv/bin/activate
|
||||
cd "$(dirname "$0")/src"
|
||||
python3 main.py
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MARKER_FILE="/etc/kiwy-signage/pi_boot_config_patched.marker"
|
||||
|
||||
is_pi() {
|
||||
if [[ -f /proc/device-tree/model ]] && grep -qi "raspberry pi" /proc/device-tree/model; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -qi "raspberry" /proc/cpuinfo 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
find_boot_config() {
|
||||
if [[ -f /boot/firmware/config.txt ]]; then
|
||||
echo "/boot/firmware/config.txt"
|
||||
elif [[ -f /boot/config.txt ]]; then
|
||||
echo "/boot/config.txt"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
upsert_key() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="$3"
|
||||
|
||||
if grep -Eq "^[#[:space:]]*${key}=" "$file"; then
|
||||
sed -i -E "s|^[#[:space:]]*${key}=.*|${key}=${value}|" "$file"
|
||||
else
|
||||
echo "${key}=${value}" >> "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "[pi-boot-patch] Please run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! is_pi; then
|
||||
echo "[pi-boot-patch] Not a Raspberry Pi. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -f "$MARKER_FILE" ]]; then
|
||||
echo "[pi-boot-patch] Patch already applied. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BOOT_CONFIG="$(find_boot_config)"
|
||||
if [[ -z "$BOOT_CONFIG" ]]; then
|
||||
echo "[pi-boot-patch] Boot config not found. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[pi-boot-patch] Applying patch to ${BOOT_CONFIG}"
|
||||
|
||||
# Graphics and memory settings for smoother Chromium playback on Pi.
|
||||
upsert_key "dtoverlay" "vc4-kms-v3d" "$BOOT_CONFIG"
|
||||
upsert_key "gpu_mem" "256" "$BOOT_CONFIG"
|
||||
upsert_key "disable_overscan" "1" "$BOOT_CONFIG"
|
||||
|
||||
# Keep HDMI and display active for kiosk use.
|
||||
upsert_key "hdmi_blanking" "0" "$BOOT_CONFIG"
|
||||
upsert_key "hdmi_ignore_cec_init" "1" "$BOOT_CONFIG"
|
||||
upsert_key "hdmi_force_hotplug" "1" "$BOOT_CONFIG"
|
||||
|
||||
mkdir -p "$(dirname "$MARKER_FILE")"
|
||||
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$MARKER_FILE"
|
||||
|
||||
echo "[pi-boot-patch] Patch complete. Marker written to ${MARKER_FILE}"
|
||||
+75
-46
@@ -1307,60 +1307,59 @@ class SignagePlayer(Widget):
|
||||
|
||||
Logger.info(f"SignagePlayer: Playing item {self.current_index + 1}/{len(self.playlist)}: {file_name} ({duration}s)")
|
||||
|
||||
# ── Weblink → media transition (Wayland-safe) ──────────────────
|
||||
# On Wayland (Labwc) Window.raise_window() is a no-op, so we must
|
||||
# ensure Chromium's fullscreen window is fully gone BEFORE we try
|
||||
# to render Kivy content. We:
|
||||
# 1. Stop watchdog + preload immediately
|
||||
# 2. Terminate Chromium (non-blocking)
|
||||
# 3. Hide the Kivy content_area so nothing stale is visible
|
||||
# 4. Wait 200 ms for the compositor to remove Chromium's window
|
||||
# 5. Show content_area + re-call play_current_media to render
|
||||
# ── Weblink → media transition (desktop-flash safe) ──────────────
|
||||
# For web→media transitions, render the next Kivy widget first,
|
||||
# then close Chromium on the next frame. This prevents a brief
|
||||
# desktop exposure while the compositor removes Chromium.
|
||||
if not _after_weblink:
|
||||
proc = getattr(self, '_weblink_proc', None)
|
||||
if proc is not None:
|
||||
self._stop_inactivity_watchdog()
|
||||
self._kill_weblink_preload()
|
||||
self._weblink_proc = None
|
||||
next_is_weblink = media_item.get('type') == 'weblink'
|
||||
if next_is_weblink:
|
||||
# Weblink -> weblink: close current Chromium first.
|
||||
self._stop_inactivity_watchdog()
|
||||
self._kill_weblink_preload()
|
||||
self._weblink_proc = None
|
||||
|
||||
# Hide content while Chromium is still closing
|
||||
try:
|
||||
self.ids.content_area.opacity = 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Terminate Chromium
|
||||
if proc.poll() is None:
|
||||
try:
|
||||
proc.terminate()
|
||||
except Exception as exc:
|
||||
Logger.warning(f"SignagePlayer: weblink terminate: {exc}")
|
||||
|
||||
def _resume(dt):
|
||||
# Force-kill if still alive after the delay
|
||||
if proc.poll() is None:
|
||||
try:
|
||||
proc.kill()
|
||||
proc.terminate()
|
||||
except Exception as exc:
|
||||
Logger.warning(f"SignagePlayer: weblink terminate: {exc}")
|
||||
|
||||
def _resume(dt):
|
||||
if proc.poll() is None:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
Window.show()
|
||||
Window.raise_window()
|
||||
except Exception:
|
||||
pass
|
||||
# Restore content area and show Kivy window
|
||||
try:
|
||||
self.ids.content_area.opacity = 1
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
Window.show()
|
||||
Window.raise_window()
|
||||
except Exception:
|
||||
pass
|
||||
# Now render the actual media
|
||||
self.play_current_media(
|
||||
force_reload=force_reload, _after_weblink=True
|
||||
)
|
||||
self.play_current_media(
|
||||
force_reload=force_reload, _after_weblink=True
|
||||
)
|
||||
|
||||
# 200 ms gives Labwc time to remove the fullscreen surface
|
||||
Clock.schedule_once(_resume, 0.2)
|
||||
return
|
||||
Clock.schedule_once(_resume, 0.2)
|
||||
return
|
||||
|
||||
# Weblink -> non-weblink: keep Chromium visible while we
|
||||
# prepare next Kivy frame, then close Chromium deferred.
|
||||
self._stop_inactivity_watchdog()
|
||||
self._kill_weblink_preload()
|
||||
self._weblink_proc = proc
|
||||
try:
|
||||
self.ids.content_area.opacity = 1
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
Window.show()
|
||||
Window.raise_window()
|
||||
except Exception:
|
||||
pass
|
||||
Logger.debug("SignagePlayer: Deferred Chromium close after next frame render")
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
|
||||
# Handle web links before any file/path handling (no local file exists)
|
||||
@@ -1440,6 +1439,11 @@ class SignagePlayer(Widget):
|
||||
# Reset error counter on successful playback
|
||||
self.consecutive_errors = 0
|
||||
Logger.debug(f"SignagePlayer: Media started successfully")
|
||||
|
||||
# If we arrived here from a weblink item, close Chromium after the
|
||||
# next Kivy frame so the new widget is already visible underneath.
|
||||
if media_item.get('type') != 'weblink' and getattr(self, '_weblink_proc', None) is not None:
|
||||
self._kill_weblink_after_frame()
|
||||
|
||||
except Exception as e:
|
||||
Logger.error(f"SignagePlayer: Error playing media: {e}")
|
||||
@@ -1602,9 +1606,12 @@ class SignagePlayer(Widget):
|
||||
self._skip_to_next_media()
|
||||
return False
|
||||
|
||||
target_width, target_height = self._get_browser_target_size()
|
||||
|
||||
try:
|
||||
Logger.info(f"SignagePlayer: Opening weblink in kiosk browser: {url}")
|
||||
Logger.info(f"SignagePlayer: Inactivity timeout set to {duration}s (touch resets the countdown)")
|
||||
Logger.info(f"SignagePlayer: Chromium target launch size: {target_width}x{target_height}")
|
||||
# Kill the hidden pre-warm instance first; its work (binary in RAM,
|
||||
# page in disk cache) makes the kiosk relaunch below near-instant.
|
||||
self._kill_weblink_preload()
|
||||
@@ -1616,6 +1623,7 @@ class SignagePlayer(Widget):
|
||||
# --start-fullscreen is a normal maximised window that the
|
||||
# compositor can un-stack without issues.
|
||||
'--start-fullscreen',
|
||||
'--start-maximized',
|
||||
'--app=' + url,
|
||||
'--noerrdialogs',
|
||||
'--disable-infobars',
|
||||
@@ -1631,6 +1639,8 @@ class SignagePlayer(Widget):
|
||||
'--disable-background-networking',
|
||||
'--no-default-browser-check',
|
||||
'--window-position=0,0',
|
||||
f'--window-size={target_width},{target_height}',
|
||||
'--force-device-scale-factor=1',
|
||||
])
|
||||
|
||||
# Unschedule any previous fixed timer — the watchdog thread takes
|
||||
@@ -1653,6 +1663,23 @@ class SignagePlayer(Widget):
|
||||
self._skip_to_next_media()
|
||||
return False
|
||||
|
||||
def _get_browser_target_size(self):
|
||||
"""Return a stable browser launch size to avoid 800x600 first-paint flash."""
|
||||
default_width, default_height = 1920, 1080
|
||||
try:
|
||||
width, height = Window.size
|
||||
width = int(width)
|
||||
height = int(height)
|
||||
except Exception:
|
||||
return default_width, default_height
|
||||
|
||||
# Chromium sometimes first-paints at 800x600 before fullscreen;
|
||||
# force at least Full HD when reported dimensions are too small.
|
||||
if width < 1280 or height < 720:
|
||||
return default_width, default_height
|
||||
|
||||
return width, height
|
||||
|
||||
def _start_inactivity_watchdog(self, duration):
|
||||
"""Start a background thread that monitors /dev/input/* for touch/key
|
||||
activity. The thread resets the idle counter on every event; when the
|
||||
@@ -1893,6 +1920,8 @@ class SignagePlayer(Widget):
|
||||
if not browser:
|
||||
return
|
||||
|
||||
target_width, target_height = self._get_browser_target_size()
|
||||
|
||||
# Kill any stale preload first.
|
||||
self._kill_weblink_preload()
|
||||
|
||||
@@ -1903,7 +1932,7 @@ class SignagePlayer(Widget):
|
||||
'--app=' + url,
|
||||
# Place window completely outside the visible display area.
|
||||
'--window-position=-9999,-9999',
|
||||
'--window-size=1920,1080', # pre-render at full resolution
|
||||
f'--window-size={target_width},{target_height}', # pre-render at target resolution
|
||||
'--noerrdialogs',
|
||||
'--disable-infobars',
|
||||
'--incognito',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"hostname": "rpi-Receptie",
|
||||
"auth_code": "BrJrGzX_IT9oP_Lfke8Qgo4sDjMc49T1EhSnWmKKpDM",
|
||||
"hostname": "Birou_IT",
|
||||
"auth_code": "CZncd_2dlTZGieBEdqAbUTjf3qNyEUPDXr8jLVx7NLs",
|
||||
"player_id": 1,
|
||||
"player_name": "Receptie TV",
|
||||
"player_name": "Test_player1",
|
||||
"playlist_id": 1,
|
||||
"orientation": "Landscape",
|
||||
"authenticated": true,
|
||||
"server_url": "http://192.168.0.159:8080"
|
||||
"server_url": "http://192.168.0.109:8080"
|
||||
}
|
||||
@@ -218,19 +218,6 @@ rm -f "$STOP_FLAG_FILE"
|
||||
# Change to the project directory
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ -d ".venv" ]; then
|
||||
log_message "✓ Virtual environment found"
|
||||
source .venv/bin/activate
|
||||
else
|
||||
log_message "⚠️ Creating virtual environment..."
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
log_message "📦 Installing dependencies..."
|
||||
pip3 install -r requirements.txt
|
||||
log_message "✓ Virtual environment ready"
|
||||
fi
|
||||
|
||||
# Check if configuration exists
|
||||
if [ ! -f "config/app_config.json" ]; then
|
||||
log_message "⚠️ WARNING: Configuration file not found!"
|
||||
@@ -250,6 +237,11 @@ while true; do
|
||||
# Clean old heartbeat
|
||||
rm -f "$HEARTBEAT_FILE"
|
||||
|
||||
# Activate virtual environment if it exists
|
||||
if [ -f "$SCRIPT_DIR/.venv/bin/activate" ]; then
|
||||
source "$SCRIPT_DIR/.venv/bin/activate"
|
||||
fi
|
||||
|
||||
# Start the player
|
||||
cd "$SCRIPT_DIR/src"
|
||||
python3 main.py &
|
||||
@@ -292,8 +284,8 @@ while true; do
|
||||
log_message "⏳ Waiting ${RESTART_DELAY}s before restart..."
|
||||
sleep $RESTART_DELAY
|
||||
|
||||
# Cleanup any zombie processes
|
||||
pkill -9 -f "python3 main.py" 2>/dev/null
|
||||
# Ensure old player process is gone before restarting
|
||||
kill -9 $PLAYER_PID 2>/dev/null
|
||||
|
||||
done
|
||||
|
||||
@@ -301,6 +293,3 @@ log_message ""
|
||||
log_message "=========================================="
|
||||
log_message "Watchdog stopped"
|
||||
log_message "=========================================="
|
||||
|
||||
# Deactivate virtual environment (this line is never reached in watchdog mode)
|
||||
deactivate
|
||||
|
||||
Reference in New Issue
Block a user