Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 6814

Troubleshooting • Performance Issue: Flask App with Animations on Raspberry Pi 5 - Stuttering/Laggy

$
0
0
Hello everyone,

I'm facing a performance issue with a Flask web application on a **Raspberry Pi 5** and hoping for your help.

## Hardware & Setup
- **Raspberry Pi 5** (8GB RAM)
- **Raspberry Pi OS Lite** (fresh installation, no desktop)
- **Monitor via HDMI** connected
- **Kiosk mode** - should permanently display a web app
- **Auto-start on boot** configured

## The Application
- **Python Flask app** (Prayer Time display for mosque)
- **Image slideshow** with fade transitions (multiple sponsor images)
- **Text slider** (marquee-like, runs from right to left)
- **Live updates** via Server-Sent Events (SSE)

## The Problem
The **animations are not smooth**:
- Image transitions **stutter heavily**
- Text slider **jerks** while scrolling
- **CPU usage** at 40-50% during animations

## What I've Already Tried

### 1. Browser Alternatives Tested
- **Chromium**: Very choppy, high CPU usage
- **Firefox ESR**: Slightly better, but still stuttering
- **WebKit2GTK with Python**: Same performance issues

### 2. GPU Optimizations
```bash
# /boot/firmware/config.txt
gpu_mem=128
dtoverlay=vc4-kms-v3d
gpu_freq=500
```

### 3. CSS Optimizations
- **Marquee element** replaced with CSS animation
- **GPU acceleration** added (`transform: translate3d()`, `will-change`)
- **Hardware acceleration** enabled in WebKit
- **requestAnimationFrame** for slideshow transitions

### 4. JavaScript Optimizations
- **jQuery removed**, using vanilla JavaScript
- **DOM access reduced** (elements cached)
- **Debouncing** for SSE updates
- **Unnecessary libraries removed** (AOS, Portfolio-JS, etc.)

### 5. System Optimizations
- **Duplicate services removed** (were running in parallel)
- **Only one browser instance** ensured
- **WebKit with performance flags** configured

### 6. Auto-start Configuration
```bash
# /etc/systemd/system/prayer-kiosk.service
[Unit]
Description=Prayer Time Kiosk Application
After=graphical.target network-online.target
Wants=network-online.target
Requires=graphical.target

[Service]
Type=simple
User=hanau
Group=hanau
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/hanau/.Xauthority"
Environment="XDG_RUNTIME_DIR=/run/user/1000"
WorkingDirectory=/home/hanau
ExecStart=/usr/bin/python3 /home/hanau/prayer_webkit_kiosk.py
Restart=always
RestartSec=10
KillMode=mixed
TimeoutStopSec=30

[Install]
WantedBy=graphical.target
```

## Current Code

### Complete WebKit Kiosk Application
```python
#!/usr/bin/env python3
import gi
import os
import subprocess
import threading
import time
import sys
import psutil

# Set DISPLAY automatically if not set
if not os.environ.get('DISPLAY'):
os.environ['DISPLAY'] = ':0'

# Initialize GTK
gi.require_version('Gtk', '3.0')
gi.require_version('WebKit2', '4.0')

try:
from gi.repository import Gtk, WebKit2, GLib, Gdk
if not Gtk.init_check():
print("Error: GTK could not be initialized")
sys.exit(1)
except Exception as e:
print(f"Import error: {e}")
sys.exit(1)

class AutoPrayerKiosk:
def __init__(self):
print("Starting automatic Prayer Time Kiosk...")
self.start_flask_server()
self.create_fullscreen_window()

def start_flask_server(self):
"""Start Flask server automatically"""
def run_flask():
subprocess.run([
"bash", "-c",
"cd /home/hanau/prayertime/prayertimeV3 && source venv/bin/activate && python app.py"
])

flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
time.sleep(8)
print("Flask server started automatically")

def create_fullscreen_window(self):
"""Create automatic fullscreen window"""
self.window = Gtk.Window()
self.window.set_title("Prayer Time Kiosk")
self.window.connect("destroy", Gtk.main_quit)

# AUTOMATIC FULLSCREEN - maximum size
self.window.set_decorated(False) # No title bar
self.window.set_resizable(False) # Not resizable
self.window.fullscreen() # Auto fullscreen
self.window.maximize() # Additionally maximize

# Auto-detect monitor size and adapt
screen = Gdk.Screen.get_default()
monitor_geometry = screen.get_monitor_geometry(0)
self.window.set_size_request(monitor_geometry.width, monitor_geometry.height)

# WebKit settings for best performance and fullscreen
settings = WebKit2.Settings()
settings.set_enable_javascript(True)
settings.set_enable_webgl(True)
settings.set_hardware_acceleration_policy(WebKit2.HardwareAccelerationPolicy.ALWAYS)
settings.set_enable_smooth_scrolling(True)
settings.set_enable_accelerated_2d_canvas(True)
settings.set_enable_developer_extras(False)
settings.set_zoom_text_only(False)

# Create WebView and configure fullscreen
self.webview = WebKit2.WebView()
self.webview.set_settings(settings)

# Adapt WebView completely to window
self.webview.set_size_request(monitor_geometry.width, monitor_geometry.height)

# Add WebView to window
self.window.add(self.webview)

# Load Prayer Time App
self.webview.load_uri("http://localhost:5000/prayertime")

# Show window - automatically fullscreen
self.window.show_all()

# Bring window to foreground and focus
self.window.present()
self.window.grab_focus()

print("Automatic fullscreen kiosk started")

# Hide cursor immediately (real kiosk mode)
GLib.timeout_add_seconds(1, self.hide_cursor)

# Disable screensaver
GLib.timeout_add_seconds(2, self.disable_screensaver)

def hide_cursor(self):
"""Hide mouse cursor automatically"""
display = Gdk.Display.get_default()
cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.BLANK_CURSOR)
if self.window.get_window():
self.window.get_window().set_cursor(cursor)
return False

def disable_screensaver(self):
"""Disable screensaver automatically"""
subprocess.run(["xset", "s", "off"], stderr=subprocess.DEVNULL)
subprocess.run(["xset", "-dpms"], stderr=subprocess.DEVNULL)
subprocess.run(["xset", "s", "noblank"], stderr=subprocess.DEVNULL)
return False

if __name__ == "__main__":
print("Starting automatic fullscreen kiosk...")
try:
kiosk = AutoPrayerKiosk()
Gtk.main()
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
```

### CSS Animation (Marquee Replacement)
```css
/* GPU-ACCELERATED TEXT SLIDER */
.slide-text .marquee-replacement p {
display: inline-block;
margin: 0;
/* GPU-accelerated animation */
animation: marqueeScroll 35s linear infinite;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
will-change: transform;
}

@keyframes marqueeScroll {
0% { transform: translate3d(100%, 0, 0); }
100% { transform: translate3d(-100%, 0, 0); }
}

/* GPU-ACCELERATED SLIDESHOW */
#slideshow {
transform: translateZ(0);
backface-visibility: hidden;
will-change: transform;
}

#slideshow > div {
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
will-change: transform, opacity;
}

#slideshow img {
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
image-rendering: optimizeSpeed;
will-change: transform;
}
```

### JavaScript Slideshow (Optimized)
```javascript
function initSlideshow(speed) {
let slideDiv = document.getElementById("slideshow");
if (!slideDiv) return;

let slides = slideDiv.querySelectorAll("div");
if (slides.length <= 1) {
slides.forEach((slide) => slide.style.display = "flex");
return;
}

slides.forEach((slide, index) => {
slide.style.display = index === 0 ? "flex" : "none";
slide.style.opacity = index === 0 ? "1" : "0";
});

let currentIndex = 0;
if (slideshowTimer) clearInterval(slideshowTimer);

slideshowTimer = setInterval(function () {
requestAnimationFrame(() => {
let slides = slideDiv.querySelectorAll("div");
let currentSlide = slides[currentIndex];
let nextIndex = (currentIndex + 1) % slides.length;
let nextSlide = slides[nextIndex];

// CSS transitions instead of jQuery for better performance
currentSlide.style.transition = "opacity 400ms ease-in-out";
currentSlide.style.opacity = "0";

setTimeout(() => {
currentSlide.style.display = "none";
nextSlide.style.display = "flex";
nextSlide.style.opacity = "0";

requestAnimationFrame(() => {
nextSlide.style.transition = "opacity 400ms ease-in-out";
nextSlide.style.opacity = "1";
});
}, 400);

currentIndex = nextIndex;
});
}, speed);
}
```

### Autostart Setup Commands
```bash
# Install minimal graphics environment
sudo apt install -y xserver-xorg-core xinit openbox lightdm
sudo apt install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0

# Configure autologin
sudo nano /etc/lightdm/lightdm.conf
# Add: autologin-user=hanau

# Set graphical boot
sudo systemctl set-default graphical.target

# Enable service
sudo systemctl enable prayer-kiosk.service
```

## System Information
```bash
# CPU usage during animations
~ 40-50% (WebKitWebProcess)

# GPU status
vcgencmd measure_temp
# temp=57.6'C

vcgencmd get_mem gpu
# gpu=128M

# No throttling
vcgencmd get_throttled
# throttled=0x0

# Memory
free -h
# total used free
# Mem: 7.8Gi 2.1Gi 5.4Gi

# Load average during animations
uptime
# load average: 1.70, 1.26, 0.60
```

## Questions to the Community

1. **Is Raspberry Pi 5 too weak** for such web animations in kiosk mode?

2. **Are there better browser alternatives** than WebKit/Chromium for animations?

3. **Should I switch to native application** (e.g., PyQt/Tkinter) instead of web browser?

4. **Additional GPU/system optimizations** I might have missed?

5. **Hardware-accelerated video overlay** possible for such applications?

6. **Different approach**: Use **Electron** or **Flutter** for better performance?

7. **Video-based solution**: Convert animations to **H.264 video** and use hardware decoder?

The app runs perfectly smooth on desktop PCs, only on the Pi it stutters. Any tips are welcome!

## Additional Information
- **Network**: Local (Flask server on same Pi)
- **Images**: ~500KB-1MB per sponsor image
- **Slideshow speed**: 5-8 seconds per image
- **Text slider**: ~35 seconds for complete run
- **Display resolution**: 1920x1080 @ 60Hz
- **Use case**: Mosque prayer time display (runs 24/7)

## What Works Well
- **Static content** displays perfectly
- **Clock updates** (every second) work smoothly
- **SSE updates** work without issues
- **Auto-boot and auto-start** work reliably

## What Doesn't Work Well
- **CSS transitions** (opacity, transform)
- **Scrolling text** (marquee-style animation)
- **Image slideshow** transitions
- **Any continuous animation** stutters

Has anyone successfully implemented smooth animations on Raspberry Pi 5 in kiosk mode? What's the best approach for such use cases?

Thank you for your help!

---

**Hardware**: Raspberry Pi 5 (8GB)
**OS**: Raspberry Pi OS Lite
**Browser**: WebKit2GTK, Chromium tested
**Issue**: Choppy CSS animations despite GPU optimizations
**Goal**: Smooth 24/7 kiosk display for mosque

Statistics: Posted by TheDaywalker — Thu May 22, 2025 7:53 pm — Replies 0 — Views 23



Viewing all articles
Browse latest Browse all 6814

Trending Articles