sync playlists
This commit is contained in:
parent
235d5df5e9
commit
28d9269e36
@ -64,6 +64,7 @@ jQuery(document).ready(function ($) {
|
|||||||
showModal('modal-playlist-edit');
|
showModal('modal-playlist-edit');
|
||||||
$('.modal-playlist-edit input:visible:eq(0)').focus().select();
|
$('.modal-playlist-edit input:visible:eq(0)').focus().select();
|
||||||
$('#playlist-edit-name').val(playlist.name);
|
$('#playlist-edit-name').val(playlist.name);
|
||||||
|
$('#playlist-edit-time-sync').val(playlist.time_sync ? '1' : '0');
|
||||||
$('#playlist-edit-id').val(playlist.id);
|
$('#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.
|
#### 🔵 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
|
## 📡 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.
|
#### 🔴 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
|
## 🎛️ 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)`
|
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`)
|
2. Log into your RaspberryPi locally or via ssh (by default it's `ssh pi@raspberrypi.local`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📺 Run the player
|
## 📺 Run the player
|
||||||
Install player autorun by executing following script
|
Install player autorun by executing following script
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
"playlist_form_edit_title": "Edit Slide",
|
"playlist_form_edit_title": "Edit Slide",
|
||||||
"playlist_form_edit_submit": "Save",
|
"playlist_form_edit_submit": "Save",
|
||||||
"playlist_form_label_name": "Name",
|
"playlist_form_label_name": "Name",
|
||||||
|
"playlist_form_label_time_sync": "Sync slides across players",
|
||||||
"playlist_form_button_cancel": "Cancel",
|
"playlist_form_button_cancel": "Cancel",
|
||||||
"js_playlist_delete_confirmation": "Are you sure?",
|
"js_playlist_delete_confirmation": "Are you sure?",
|
||||||
"playlist_delete_has_slides": "Playlist has slides, please remove them before and retry",
|
"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_title": "Modification d'une liste de lecture",
|
||||||
"playlist_form_edit_submit": "Enregistrer",
|
"playlist_form_edit_submit": "Enregistrer",
|
||||||
"playlist_form_label_name": "Nom",
|
"playlist_form_label_name": "Nom",
|
||||||
|
"playlist_form_label_time_sync": "Synchroniser les slides des lecteurs",
|
||||||
"playlist_form_button_cancel": "Annuler",
|
"playlist_form_button_cancel": "Annuler",
|
||||||
"js_playlist_delete_confirmation": "Êtes-vous sûr ?",
|
"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",
|
"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:
|
def _get_playlist(self, playlist_id: Optional[int] = 0) -> dict:
|
||||||
enabled_slides = self._model_store.slide().get_slides(enabled=True, playlist_id=playlist_id)
|
enabled_slides = self._model_store.slide().get_slides(enabled=True, playlist_id=playlist_id)
|
||||||
slides = self._model_store.slide().to_dict(enabled_slides)
|
slides = self._model_store.slide().to_dict(enabled_slides)
|
||||||
|
playlist = self._model_store.playlist().get(playlist_id)
|
||||||
|
|
||||||
playlist_loop = []
|
playlist_loop = []
|
||||||
playlist_cron = []
|
playlist_cron = []
|
||||||
@ -26,6 +27,8 @@ class PlayerController(ObController):
|
|||||||
playlist_loop.append(slide)
|
playlist_loop.append(slide)
|
||||||
|
|
||||||
playlists = {
|
playlists = {
|
||||||
|
'playlist_id': playlist.id if playlist else None,
|
||||||
|
'time_sync': playlist.time_sync if playlist else None,
|
||||||
'loop': playlist_loop,
|
'loop': playlist_loop,
|
||||||
'cron': playlist_cron,
|
'cron': playlist_cron,
|
||||||
'hard_refresh_request': self._model_store.variable().get_one_by_name("refresh_player_request").as_int()
|
'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):
|
def playlist_add(self):
|
||||||
playlist = Playlist(
|
playlist = Playlist(
|
||||||
name=request.form['name'],
|
name=request.form['name'],
|
||||||
|
time_sync=request.form['time_sync'],
|
||||||
)
|
)
|
||||||
|
|
||||||
self._model_store.playlist().add_form(playlist)
|
self._model_store.playlist().add_form(playlist)
|
||||||
@ -51,6 +52,7 @@ class PlaylistController(ObController):
|
|||||||
self._model_store.playlist().update_form(
|
self._model_store.playlist().update_form(
|
||||||
id=request.form['id'],
|
id=request.form['id'],
|
||||||
name=request.form['name'],
|
name=request.form['name'],
|
||||||
|
time_sync=request.form['time_sync'],
|
||||||
)
|
)
|
||||||
return redirect(url_for('playlist_list'))
|
return redirect(url_for('playlist_list'))
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ class PlaylistManager(ModelManager):
|
|||||||
"name CHAR(255)",
|
"name CHAR(255)",
|
||||||
"slug CHAR(255)",
|
"slug CHAR(255)",
|
||||||
"enabled INTEGER DEFAULT 0",
|
"enabled INTEGER DEFAULT 0",
|
||||||
|
"time_sync INTEGER DEFAULT 1",
|
||||||
"created_by CHAR(255)",
|
"created_by CHAR(255)",
|
||||||
"updated_by CHAR(255)",
|
"updated_by CHAR(255)",
|
||||||
"created_at INTEGER",
|
"created_at INTEGER",
|
||||||
@ -75,7 +76,7 @@ class PlaylistManager(ModelManager):
|
|||||||
if not with_default:
|
if not with_default:
|
||||||
return playlists
|
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]:
|
def get_disabled_playlists(self) -> List[Playlist]:
|
||||||
return self.get_by(query="enabled = 0")
|
return self.get_by(query="enabled = 0")
|
||||||
@ -116,14 +117,15 @@ class PlaylistManager(ModelManager):
|
|||||||
def post_delete(self, playlist_id: str) -> str:
|
def post_delete(self, playlist_id: str) -> str:
|
||||||
return playlist_id
|
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)
|
playlist = self.get(id)
|
||||||
|
|
||||||
if not playlist:
|
if not playlist:
|
||||||
return
|
return
|
||||||
|
|
||||||
form = {
|
form = {
|
||||||
"name": name
|
"name": name,
|
||||||
|
"time_sync": time_sync
|
||||||
}
|
}
|
||||||
|
|
||||||
self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))
|
self._db.update_by_id(self.TABLE_NAME, id, self.pre_update(form))
|
||||||
|
|||||||
@ -6,11 +6,12 @@ from typing import Optional, Union
|
|||||||
|
|
||||||
class Playlist:
|
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._id = id if id else None
|
||||||
self._name = name
|
self._name = name
|
||||||
self._slug = slug
|
self._slug = slug
|
||||||
self._enabled = enabled
|
self._enabled = enabled
|
||||||
|
self._time_sync = time_sync
|
||||||
self._created_by = created_by if created_by else None
|
self._created_by = created_by if created_by else None
|
||||||
self._updated_by = updated_by if updated_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())
|
self._created_at = int(created_at if created_at else time.time())
|
||||||
@ -28,6 +29,14 @@ class Playlist:
|
|||||||
def enabled(self, value: bool):
|
def enabled(self, value: bool):
|
||||||
self._enabled = bool(value)
|
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
|
@property
|
||||||
def created_by(self) -> str:
|
def created_by(self) -> str:
|
||||||
return self._created_by
|
return self._created_by
|
||||||
@ -82,6 +91,7 @@ class Playlist:
|
|||||||
f"name='{self.name}',\n" \
|
f"name='{self.name}',\n" \
|
||||||
f"nameslug='{self.slug}',\n" \
|
f"nameslug='{self.slug}',\n" \
|
||||||
f"enabled='{self.enabled}',\n" \
|
f"enabled='{self.enabled}',\n" \
|
||||||
|
f"time_sync='{self.time_sync}',\n" \
|
||||||
f"created_by='{self.created_by}',\n" \
|
f"created_by='{self.created_by}',\n" \
|
||||||
f"updated_by='{self.updated_by}',\n" \
|
f"updated_by='{self.updated_by}',\n" \
|
||||||
f"created_at='{self.created_at}',\n" \
|
f"created_at='{self.created_at}',\n" \
|
||||||
@ -102,6 +112,7 @@ class Playlist:
|
|||||||
"name": self.name,
|
"name": self.name,
|
||||||
"slug": self.slug,
|
"slug": self.slug,
|
||||||
"enabled": self.enabled,
|
"enabled": self.enabled,
|
||||||
|
"time_sync": self.time_sync,
|
||||||
"created_by": self.created_by,
|
"created_by": self.created_by,
|
||||||
"updated_by": self.updated_by,
|
"updated_by": self.updated_by,
|
||||||
"created_at": self.created_at,
|
"created_at": self.created_at,
|
||||||
|
|||||||
@ -211,6 +211,8 @@ def slugify(value):
|
|||||||
|
|
||||||
|
|
||||||
def seconds_to_hhmmss(seconds):
|
def seconds_to_hhmmss(seconds):
|
||||||
|
if not seconds:
|
||||||
|
return ""
|
||||||
hours = seconds // 3600
|
hours = seconds // 3600
|
||||||
minutes = (seconds % 3600) // 60
|
minutes = (seconds % 3600) // 60
|
||||||
secs = seconds % 60
|
secs = seconds % 60
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
var needHardRefresh = null;
|
var needHardRefresh = null;
|
||||||
|
|
||||||
// Frontend config
|
// Frontend config
|
||||||
var syncedWithTime = false;
|
var syncWithTime = items['time_sync'];
|
||||||
var tickRefreshResolutionMs = 100;
|
var tickRefreshResolutionMs = 100;
|
||||||
|
|
||||||
// Frontend flag updates
|
// Frontend flag updates
|
||||||
@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
var itemCheck = setInterval(function () {
|
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) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncedWithTime) {
|
if (syncWithTime) {
|
||||||
return console.warn('You can\'t seek with synced playlists');
|
return console.warn('You can\'t seek with synced playlists');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +203,7 @@
|
|||||||
if (isPaused()) {
|
if (isPaused()) {
|
||||||
return pauseClockValue;
|
return pauseClockValue;
|
||||||
}
|
}
|
||||||
if (syncedWithTime) {
|
if (syncWithTime) {
|
||||||
clockValue = Date.now();
|
clockValue = Date.now();
|
||||||
} else {
|
} else {
|
||||||
clockValue += tickRefreshResolutionMs;
|
clockValue += tickRefreshResolutionMs;
|
||||||
@ -235,9 +235,9 @@
|
|||||||
var slide = emptySlide ? emptySlide : nextSlide;
|
var slide = emptySlide ? emptySlide : nextSlide;
|
||||||
preloadSlide(slide.attributes['id'].value, item);
|
preloadSlide(slide.attributes['id'].value, item);
|
||||||
|
|
||||||
if (!hasMoveOnce && syncedWithTime) {
|
if (!hasMoveOnce && syncWithTime) {
|
||||||
if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) {
|
if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) {
|
||||||
// Prevent glitch when syncedWithTime for first init
|
// Prevent glitch when syncWithTime for first init
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +266,7 @@
|
|||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPaused() && !syncedWithTime) {
|
if (isPaused() && !syncWithTime) {
|
||||||
return setTimeout(loadingNextSlide, 500);
|
return setTimeout(loadingNextSlide, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ l.playlist_panel_th_name }}</th>
|
<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_enabled }}</th>
|
||||||
<th class="tac">{{ l.playlist_panel_th_duration }}</th>
|
<th class="tac">{{ l.playlist_panel_th_duration }}</th>
|
||||||
<th class="tac">{{ l.playlist_panel_th_activity }}</th>
|
<th class="tac">{{ l.playlist_panel_th_activity }}</th>
|
||||||
@ -30,6 +31,13 @@
|
|||||||
{{ playlist.name }}
|
{{ playlist.name }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="tac">
|
||||||
|
{% if playlist.time_sync %}
|
||||||
|
✅
|
||||||
|
{% else %}
|
||||||
|
❌
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td class="tac">
|
<td class="tac">
|
||||||
{% if playlist.id %}
|
{% if playlist.id %}
|
||||||
<label class="pure-material-switch">
|
<label class="pure-material-switch">
|
||||||
@ -38,7 +46,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="tac">
|
<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>
|
||||||
<td class="actions tac">
|
<td class="actions tac">
|
||||||
{% if playlist.id %}
|
{% if playlist.id %}
|
||||||
|
|||||||
@ -12,6 +12,16 @@
|
|||||||
<input name="name" type="text" id="playlist-add-name" required="required" />
|
<input name="name" type="text" id="playlist-add-name" required="required" />
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="actions">
|
||||||
<button type="button" class="btn-normal modal-close">
|
<button type="button" class="btn-normal modal-close">
|
||||||
|
|||||||
@ -14,6 +14,16 @@
|
|||||||
<input type="text" name="name" id="playlist-edit-name" required="required" />
|
<input type="text" name="name" id="playlist-edit-name" required="required" />
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="actions">
|
||||||
<button type="button" class="btn-normal modal-close">
|
<button type="button" class="btn-normal modal-close">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user