From 28d9269e36696e56f4cb66af0f36973d7a4e5b31 Mon Sep 17 00:00:00 2001 From: jr-k Date: Sat, 25 May 2024 19:04:16 +0200 Subject: [PATCH] sync playlists --- data/www/js/playlist/playlists.js | 1 + docs/setup-run-headless.md | 9 +++++++++ docs/setup-run-on-rpi.md | 4 ++++ lang/en.json | 1 + lang/fr.json | 1 + src/controller/PlayerController.py | 3 +++ src/controller/PlaylistController.py | 2 ++ src/manager/PlaylistManager.py | 8 +++++--- src/model/entity/Playlist.py | 13 ++++++++++++- src/utils.py | 2 ++ views/player/player.jinja.html | 14 +++++++------- views/playlist/component/table.jinja.html | 15 ++++++++++++++- views/playlist/modal/add.jinja.html | 10 ++++++++++ views/playlist/modal/edit.jinja.html | 10 ++++++++++ 14 files changed, 81 insertions(+), 12 deletions(-) diff --git a/data/www/js/playlist/playlists.js b/data/www/js/playlist/playlists.js index 87c19c1..924f6b3 100644 --- a/data/www/js/playlist/playlists.js +++ b/data/www/js/playlist/playlists.js @@ -64,6 +64,7 @@ jQuery(document).ready(function ($) { showModal('modal-playlist-edit'); $('.modal-playlist-edit input:visible:eq(0)').focus().select(); $('#playlist-edit-name').val(playlist.name); + $('#playlist-edit-time-sync').val(playlist.time_sync ? '1' : '0'); $('#playlist-edit-id').val(playlist.id); }); diff --git a/docs/setup-run-headless.md b/docs/setup-run-headless.md index af0a5cf..fef61e9 100644 --- a/docs/setup-run-headless.md +++ b/docs/setup-run-headless.md @@ -4,6 +4,15 @@ #### 🔵 You just want a slideshow manager and you'll deal with screen and browser yourself ? You're in the right place. +--- +## 📺 Run the player +When you run the browser yourself don't forget to use these flags for chromium browser: +```bash +# chromium or chromium-browser +# replace https://duckduckgo.com with valid playlist url +chromium --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --noerrdialogs --kiosk --incognito --window-position=0,0 --window-size=1920,1080 --display=:0 https://duckduckgo.com +``` + --- ## 📡 Run the manager diff --git a/docs/setup-run-on-rpi.md b/docs/setup-run-on-rpi.md index fdb0bf6..be24813 100644 --- a/docs/setup-run-on-rpi.md +++ b/docs/setup-run-on-rpi.md @@ -4,11 +4,15 @@ #### 🔴 You want to power RaspberryPi and automatically see your slideshow on a screen connected to it and manage your slideshow ? You're in the right place. +--- + ## 🎛️ Hardware installation 1. Download RaspberryPi Imager and setup an sdcard with `Raspberry Pi OS Lite` (🚨without desktop, only `Lite` version!). You'll find it under category `Raspberry PI OS (other)` 2. Log into your RaspberryPi locally or via ssh (by default it's `ssh pi@raspberrypi.local`) +--- + ## 📺 Run the player Install player autorun by executing following script ```bash diff --git a/lang/en.json b/lang/en.json index f831705..9c0a0b5 100644 --- a/lang/en.json +++ b/lang/en.json @@ -55,6 +55,7 @@ "playlist_form_edit_title": "Edit Slide", "playlist_form_edit_submit": "Save", "playlist_form_label_name": "Name", + "playlist_form_label_time_sync": "Sync slides across players", "playlist_form_button_cancel": "Cancel", "js_playlist_delete_confirmation": "Are you sure?", "playlist_delete_has_slides": "Playlist has slides, please remove them before and retry", diff --git a/lang/fr.json b/lang/fr.json index 8c0534e..bdfafaf 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -55,6 +55,7 @@ "playlist_form_edit_title": "Modification d'une liste de lecture", "playlist_form_edit_submit": "Enregistrer", "playlist_form_label_name": "Nom", + "playlist_form_label_time_sync": "Synchroniser les slides des lecteurs", "playlist_form_button_cancel": "Annuler", "js_playlist_delete_confirmation": "Êtes-vous sûr ?", "playlist_delete_has_slides": "La liste de lecture contient des sldies, supprimez-les avant et réessayez", diff --git a/src/controller/PlayerController.py b/src/controller/PlayerController.py index 981e2fc..ed3daee 100644 --- a/src/controller/PlayerController.py +++ b/src/controller/PlayerController.py @@ -14,6 +14,7 @@ class PlayerController(ObController): def _get_playlist(self, playlist_id: Optional[int] = 0) -> dict: enabled_slides = self._model_store.slide().get_slides(enabled=True, playlist_id=playlist_id) slides = self._model_store.slide().to_dict(enabled_slides) + playlist = self._model_store.playlist().get(playlist_id) playlist_loop = [] playlist_cron = [] @@ -26,6 +27,8 @@ class PlayerController(ObController): playlist_loop.append(slide) playlists = { + 'playlist_id': playlist.id if playlist else None, + 'time_sync': playlist.time_sync if playlist else None, 'loop': playlist_loop, 'cron': playlist_cron, 'hard_refresh_request': self._model_store.variable().get_one_by_name("refresh_player_request").as_int() diff --git a/src/controller/PlaylistController.py b/src/controller/PlaylistController.py index be6bc6a..747ea2c 100644 --- a/src/controller/PlaylistController.py +++ b/src/controller/PlaylistController.py @@ -41,6 +41,7 @@ class PlaylistController(ObController): def playlist_add(self): playlist = Playlist( name=request.form['name'], + time_sync=request.form['time_sync'], ) self._model_store.playlist().add_form(playlist) @@ -51,6 +52,7 @@ class PlaylistController(ObController): self._model_store.playlist().update_form( id=request.form['id'], name=request.form['name'], + time_sync=request.form['time_sync'], ) return redirect(url_for('playlist_list')) diff --git a/src/manager/PlaylistManager.py b/src/manager/PlaylistManager.py index 23771c0..177b92f 100644 --- a/src/manager/PlaylistManager.py +++ b/src/manager/PlaylistManager.py @@ -17,6 +17,7 @@ class PlaylistManager(ModelManager): "name CHAR(255)", "slug CHAR(255)", "enabled INTEGER DEFAULT 0", + "time_sync INTEGER DEFAULT 1", "created_by CHAR(255)", "updated_by CHAR(255)", "created_at INTEGER", @@ -75,7 +76,7 @@ class PlaylistManager(ModelManager): if not with_default: return playlists - return [Playlist(id=None, name=self.t('slideshow_playlist_panel_item_default'))] + playlists + return [Playlist(id=None, time_sync=True, name=self.t('slideshow_playlist_panel_item_default'))] + playlists def get_disabled_playlists(self) -> List[Playlist]: return self.get_by(query="enabled = 0") @@ -116,14 +117,15 @@ class PlaylistManager(ModelManager): def post_delete(self, playlist_id: str) -> str: return playlist_id - def update_form(self, id: int, name: str) -> None: + def update_form(self, id: int, name: str, time_sync: bool) -> None: playlist = self.get(id) if not playlist: return form = { - "name": name + "name": name, + "time_sync": time_sync } self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form)) diff --git a/src/model/entity/Playlist.py b/src/model/entity/Playlist.py index 20d1088..ce18f5f 100644 --- a/src/model/entity/Playlist.py +++ b/src/model/entity/Playlist.py @@ -6,11 +6,12 @@ from typing import Optional, Union class Playlist: - def __init__(self, name: str = 'Untitled', slug: str = 'untitled', id: Optional[int] = None, enabled: bool = False, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): + def __init__(self, name: str = 'Untitled', slug: str = 'untitled', id: Optional[int] = None, enabled: bool = False, time_sync: bool = True, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): self._id = id if id else None self._name = name self._slug = slug self._enabled = enabled + self._time_sync = time_sync self._created_by = created_by if created_by else None self._updated_by = updated_by if updated_by else None self._created_at = int(created_at if created_at else time.time()) @@ -28,6 +29,14 @@ class Playlist: def enabled(self, value: bool): self._enabled = bool(value) + @property + def time_sync(self) -> bool: + return bool(self._time_sync) + + @time_sync.setter + def time_sync(self, value: bool): + self._time_sync = bool(value) + @property def created_by(self) -> str: return self._created_by @@ -82,6 +91,7 @@ class Playlist: f"name='{self.name}',\n" \ f"nameslug='{self.slug}',\n" \ f"enabled='{self.enabled}',\n" \ + f"time_sync='{self.time_sync}',\n" \ f"created_by='{self.created_by}',\n" \ f"updated_by='{self.updated_by}',\n" \ f"created_at='{self.created_at}',\n" \ @@ -102,6 +112,7 @@ class Playlist: "name": self.name, "slug": self.slug, "enabled": self.enabled, + "time_sync": self.time_sync, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": self.created_at, diff --git a/src/utils.py b/src/utils.py index b8731f9..4723f8e 100644 --- a/src/utils.py +++ b/src/utils.py @@ -211,6 +211,8 @@ def slugify(value): def seconds_to_hhmmss(seconds): + if not seconds: + return "" hours = seconds // 3600 minutes = (seconds % 3600) // 60 secs = seconds % 60 diff --git a/views/player/player.jinja.html b/views/player/player.jinja.html index 0654357..841eb67 100755 --- a/views/player/player.jinja.html +++ b/views/player/player.jinja.html @@ -43,7 +43,7 @@ var needHardRefresh = null; // Frontend config - var syncedWithTime = false; + var syncWithTime = items['time_sync']; var tickRefreshResolutionMs = 100; // Frontend flag updates @@ -83,7 +83,7 @@ // Functions var itemCheck = setInterval(function () { - fetch('player/playlist').then(function(response) { + fetch('/player/playlist' + (items['playlist_id'] ? '/use/'+items['playlist_id'] : '')).then(function(response) { if (response.ok) { return response.json(); } @@ -131,7 +131,7 @@ return; } - if (syncedWithTime) { + if (syncWithTime) { return console.warn('You can\'t seek with synced playlists'); } @@ -203,7 +203,7 @@ if (isPaused()) { return pauseClockValue; } - if (syncedWithTime) { + if (syncWithTime) { clockValue = Date.now(); } else { clockValue += tickRefreshResolutionMs; @@ -235,9 +235,9 @@ var slide = emptySlide ? emptySlide : nextSlide; preloadSlide(slide.attributes['id'].value, item); - if (!hasMoveOnce && syncedWithTime) { + if (!hasMoveOnce && syncWithTime) { if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) { - // Prevent glitch when syncedWithTime for first init + // Prevent glitch when syncWithTime for first init continue; } } @@ -266,7 +266,7 @@ play(); } - if (isPaused() && !syncedWithTime) { + if (isPaused() && !syncWithTime) { return setTimeout(loadingNextSlide, 500); } diff --git a/views/playlist/component/table.jinja.html b/views/playlist/component/table.jinja.html index a9e0ec1..aebf37a 100644 --- a/views/playlist/component/table.jinja.html +++ b/views/playlist/component/table.jinja.html @@ -2,6 +2,7 @@ {{ l.playlist_panel_th_name }} + {{ l.playlist_panel_th_enabled }} {{ l.playlist_panel_th_duration }} {{ l.playlist_panel_th_activity }} @@ -30,6 +31,13 @@ {{ playlist.name }} + + {% if playlist.time_sync %} + ✅ + {% else %} + ❌ + {% endif %} + {% if playlist.id %}