sync playlists
This commit is contained in:
parent
235d5df5e9
commit
28d9269e36
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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'))
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ l.playlist_panel_th_name }}</th>
|
||||
<th class="tac"><i class="fa fa-compass"></i></th>
|
||||
<th class="tac">{{ l.playlist_panel_th_enabled }}</th>
|
||||
<th class="tac">{{ l.playlist_panel_th_duration }}</th>
|
||||
<th class="tac">{{ l.playlist_panel_th_activity }}</th>
|
||||
@ -30,6 +31,13 @@
|
||||
{{ playlist.name }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="tac">
|
||||
{% if playlist.time_sync %}
|
||||
✅
|
||||
{% else %}
|
||||
❌
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="tac">
|
||||
{% if playlist.id %}
|
||||
<label class="pure-material-switch">
|
||||
@ -38,7 +46,12 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="tac">
|
||||
{{ seconds_to_hhmmss(durations[playlist.id]) }}
|
||||
{% set total_duration = seconds_to_hhmmss(durations[playlist.id]) %}
|
||||
{% if total_duration %}
|
||||
{{ total_duration }}
|
||||
{% else %}
|
||||
{{ l.common_empty }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="actions tac">
|
||||
{% if playlist.id %}
|
||||
|
||||
@ -12,6 +12,16 @@
|
||||
<input name="name" type="text" id="playlist-add-name" required="required" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="playlist-add-time-sync">{{ l.playlist_form_label_time_sync }}</label>
|
||||
<div class="widget">
|
||||
<select name="time_sync" type="text" id="playlist-add-time-sync" required="required">
|
||||
<option value="1">✅</option>
|
||||
<option value="0">❌</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-normal modal-close">
|
||||
|
||||
@ -14,6 +14,16 @@
|
||||
<input type="text" name="name" id="playlist-edit-name" required="required" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="playlist-edit-time-sync">{{ l.playlist_form_label_time_sync }}</label>
|
||||
<div class="widget">
|
||||
<select name="time_sync" type="text" id="playlist-edit-time-sync" required="required">
|
||||
<option value="1">✅</option>
|
||||
<option value="0">❌</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-normal modal-close">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user