Merge pull request #29 from jr-k/feature/auto-refresh-player-if-changes

Refreshable player and some player customizations through settings
This commit is contained in:
JRK 2024-05-08 18:53:56 +02:00 committed by GitHub
commit 5f5f2d3743
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 131 additions and 26 deletions

View File

@ -189,6 +189,24 @@ button.purple:hover {
border: 1px solid #fff;
}
.alert {
margin: 10px 30px 20px 30px;
padding: 20px 50px;
flex: 1;
align-self: stretch;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.alert-success {
color: rgb(14, 239, 95);
border: 1px solid rgb(14, 239, 95);
background: rgba(14, 239, 95, .2);
border-radius: 4px;
}
.panel {
background: rgba(255, 255, 255, 0.15);
@ -548,7 +566,7 @@ form .actions button {
form .actions button.green:hover {
background: white;
color: rgb(14, 239, 95);
border-color: rgb(14, 239, 95)
border-color: rgb(14, 239, 95);
}
form .actions button.modal-close {

View File

@ -29,7 +29,8 @@ jQuery(document).ready(function ($) {
});
$('#variable-edit-value').replaceWith($select);
} else {
$('#variable-edit-value').replaceWith('<input type="text" name="value" id="variable-edit-value" required="required" />');
var type = variable.type === 'int' ? 'number' : 'text';
$('#variable-edit-value').replaceWith('<input type="'+type+'" name="value" id="variable-edit-value" required="required" />');
}
showModal('modal-variable-edit');

View File

@ -1,6 +1,8 @@
{
"slideshow_page_title": "Schedule Overview",
"slideshow_goto_player": "Go to player",
"slideshow_refresh_player": "Refresh player",
"slideshow_refresh_player_success": "A player refresh has been schedueld, it should happen soon enough (%time% seconds maximum)",
"slideshow_slide_button_add": "Add a slide",
"slideshow_slide_panel_active": "Active slides",
"slideshow_slide_panel_inactive": "Inactive slides",
@ -71,6 +73,8 @@
"settings_variable_desc_fleet_enabled": "Enable fleet screen management view",
"settings_variable_desc_external_url": "External url (i.e: https://screen-01.company.com or http://10.10.3.100)",
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in bytes, 32*1024*1024 for 32MB)",
"settings_variable_desc_default_slide_duration": "Intro slide duration (in seconds)",
"settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)",
"settings_variable_desc_slide_animation_enabled": "Enable animation effect between slides",
"settings_variable_desc_slide_animation_entrance_effect": "Slide animation entrance effect",
@ -79,6 +83,7 @@
"settings_variable_desc_ro_editable": "Last application reboot datetime",
"settings_variable_desc_ro_last_slide_update": "Last slide update datetime",
"settings_variable_desc_ro_refresh_player_request": "Last player refresh request datetime",
"sysinfo_page_title": "System infos",
"sysinfo_panel_button_restart": "Restart",
@ -120,5 +125,6 @@
"enum_animation_speed_fast": "Fast",
"enum_animation_speed_faster": "Faster",
"enum_variable_section_general": "General",
"enum_variable_section_animation": "Animation"
"enum_variable_section_player_animation": "Player animation",
"enum_variable_section_player_options": "Options du lecteur"
}

View File

@ -1,6 +1,8 @@
{
"slideshow_page_title": "Vue Planning",
"slideshow_goto_player": "Voir le lecteur",
"slideshow_refresh_player": "Rafraîchir le lecteur",
"slideshow_refresh_player_success": "Un rafraîchissement du lecteur a été programmé, il devrait avoir lieu sous peu (%time% secondes maximum)",
"slideshow_slide_button_add": "Ajouter une slide",
"slideshow_slide_panel_active": "Slides actives",
"slideshow_slide_panel_inactive": "Slides inactives",
@ -71,6 +73,8 @@
"settings_variable_desc_fleet_enabled": "Activer la gestion de flotte des écrans",
"settings_variable_desc_external_url": "URL externe (i.e: https://screen-01.company.com or http://10.10.3.100)",
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en octets, 32*1024*1024 pour 32Mo)",
"settings_variable_desc_default_slide_duration": "Durée de la slide d'introduction (en secondes)",
"settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)",
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
@ -79,6 +83,7 @@
"settings_variable_desc_ro_editable": "Date de dernier redémarrage de l'application",
"settings_variable_desc_ro_last_slide_update": "Date de dernière modification d'une slide",
"settings_variable_desc_ro_refresh_player_request": "Date de dernière demande de rafraîchissement du lecteur",
"sysinfo_page_title": "Système",
"sysinfo_panel_button_restart": "Redémarrer",
@ -120,5 +125,6 @@
"enum_animation_speed_fast": "Rapide",
"enum_animation_speed_faster": "Très rapide",
"enum_variable_section_general": "Général",
"enum_variable_section_animation": "Animation"
"enum_variable_section_player_animation": "Animation du lecteur",
"enum_variable_section_player_options": "Options du lecteur"
}

View File

@ -24,7 +24,8 @@ class PlayerController(ObController):
playlists = {
'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()
}
return playlists
@ -38,6 +39,8 @@ class PlayerController(ObController):
return render_template(
'player/player.jinja.html',
items=json.dumps(self._get_playlist()),
default_slide_duration=self._model_store.variable().get_one_by_name('default_slide_duration'),
polling_interval=self._model_store.variable().get_one_by_name('polling_interval'),
slide_animation_enabled=self._model_store.variable().get_one_by_name('slide_animation_enabled'),
slide_animation_entrance_effect=self._model_store.variable().get_one_by_name('slide_animation_entrance_effect'),
slide_animation_exit_effect=self._model_store.variable().get_one_by_name('slide_animation_exit_effect'),

View File

@ -1,3 +1,4 @@
import time
import json
from flask import Flask, render_template, redirect, request, url_for
@ -21,4 +22,12 @@ class SettingsController(ObController):
def settings_variable_edit(self):
self._model_store.variable().update_form(request.form['id'], request.form['value'])
return redirect(url_for('settings_variable_list'))
self._post_update(request.form['id'])
return redirect(url_for('settings_variable_list'))
def _post_update(self, id: str):
variable = self._model_store.variable().get(id)
if variable.refresh_player:
self._model_store.variable().update_by_name("refresh_player_request", time.time())

View File

@ -21,6 +21,7 @@ class SlideshowController(ObController):
self._app.add_url_rule('/slideshow/slide/toggle', 'slideshow_slide_toggle', self.slideshow_slide_toggle, methods=['POST'])
self._app.add_url_rule('/slideshow/slide/delete', 'slideshow_slide_delete', self.slideshow_slide_delete, methods=['DELETE'])
self._app.add_url_rule('/slideshow/slide/position', 'slideshow_slide_position', self.slideshow_slide_position, methods=['POST'])
self._app.add_url_rule('/slideshow/player-refresh', 'slideshow_player_refresh', self.slideshow_player_refresh, methods=['GET'])
def manage(self):
return redirect(url_for('slideshow_slide_list'))
@ -88,6 +89,15 @@ class SlideshowController(ObController):
self._post_update()
return jsonify({'status': 'ok'})
def slideshow_player_refresh(self):
self._model_store.variable().update_by_name("refresh_player_request", time.time())
return redirect(
url_for(
'slideshow_slide_list',
refresh_player=self._model_store.variable().get_one_by_name('polling_interval').as_int()
)
)
def _post_update(self):
self._model_store.variable().update_by_name("last_slide_update", time.time())

View File

@ -56,7 +56,7 @@ class ObPlugin(abc.ABC):
def get_plugin_variable_name(self, name: str) -> str:
return "{}_{}".format(self.get_plugin_variable_prefix(), name)
def add_variable(self, name: str, value='', section: str = '', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None) -> Variable:
def add_variable(self, name: str, value='', section: str = '', type: VariableType = VariableType.STRING, editable: bool = True, description: str = '', selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None, refresh_player: bool = False) -> Variable:
return self._model_store.variable().set_variable(
name=self.get_plugin_variable_name(name),
section=section,
@ -65,6 +65,7 @@ class ObPlugin(abc.ABC):
editable=editable,
description=description,
unit=unit,
refresh_player=refresh_player,
selectables=selectables if isinstance(selectables, dict) else None,
plugin=self.use_id(),
)

View File

@ -30,6 +30,7 @@ class VariableManager(ModelManager):
"selectables",
"type",
"unit",
"refresh_player",
"value"
]
@ -39,7 +40,7 @@ class VariableManager(ModelManager):
self._var_map = {}
self.reload()
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str, plugin: Optional[None] = None, selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None, section: str = '') -> Variable:
def set_variable(self, name: str, value, type: VariableType, editable: bool, description: str, plugin: Optional[None] = None, selectables: Optional[Dict[str, str]] = None, unit: Optional[VariableUnit] = None, section: str = '', refresh_player: bool = False) -> Variable:
if isinstance(value, bool) and value:
value = '1'
elif isinstance(value, bool) and not value:
@ -54,6 +55,7 @@ class VariableManager(ModelManager):
"value": value,
"type": type.value,
"editable": editable,
"refresh_player": refresh_player,
"description": description,
"plugin": plugin,
"unit": unit.value if unit else None,
@ -76,6 +78,9 @@ class VariableManager(ModelManager):
if variable.section != default_var['section']:
self._db.update_by_id(variable.id, {"section": default_var['section']})
if variable.refresh_player != default_var['refresh_player']:
self._db.update_by_id(variable.id, {"refresh_player": default_var['refresh_player']})
if not same_selectables:
self._db.update_by_id(variable.id, {"selectables": default_var['selectables']})
@ -87,18 +92,27 @@ class VariableManager(ModelManager):
def reload(self) -> None:
default_vars = [
# Editable (Customizable settings)
{"name": "lang", "section": self.t(VariableSection.GENERAL), "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_lang'), "selectables": {"en": "English", "fr": "French"}},
{"name": "fleet_enabled", "section": self.t(VariableSection.GENERAL), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_fleet_enabled')},
{"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": True, "description": self.t('settings_variable_desc_external_url')},
{"name": "slide_upload_limit", "section": self.t(VariableSection.ANIMATION), "value": 32 * 1024 * 1024, "unit": VariableUnit.BYTE, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_slide_upload_limit')},
{"name": "slide_animation_enabled", "section": self.t(VariableSection.ANIMATION), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_slide_animation_enabled')},
{"name": "slide_animation_entrance_effect", "section": self.t(VariableSection.ANIMATION), "value": AnimationEntranceEffect.FADE_IN.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_entrance_effect'), "selectables": enum_to_dict(AnimationEntranceEffect)},
{"name": "slide_animation_exit_effect", "section": self.t(VariableSection.ANIMATION), "value": AnimationExitEffect.NONE.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_exit_effect'), "selectables": enum_to_dict(AnimationExitEffect)},
{"name": "slide_animation_speed", "section": self.t(VariableSection.ANIMATION), "value": AnimationSpeed.NORMAL.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_speed'), "selectables": self.t(AnimationSpeed)},
### General
{"name": "lang", "section": self.t(VariableSection.GENERAL), "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_lang'), "selectables": {"en": "English", "fr": "French"}, "refresh_player": False},
{"name": "fleet_enabled", "section": self.t(VariableSection.GENERAL), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_fleet_enabled'), "refresh_player": False},
{"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": True, "description": self.t('settings_variable_desc_external_url'), "refresh_player": False},
{"name": "slide_upload_limit", "section": self.t(VariableSection.GENERAL), "value": 32 * 1024 * 1024, "unit": VariableUnit.BYTE, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_slide_upload_limit'), "refresh_player": False},
### Player Options
{"name": "default_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_default_slide_duration'), "refresh_player": False},
{"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True},
### Player Animation
{"name": "slide_animation_enabled", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_slide_animation_enabled'), "refresh_player": True},
{"name": "slide_animation_entrance_effect", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": AnimationEntranceEffect.FADE_IN.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_entrance_effect'), "selectables": enum_to_dict(AnimationEntranceEffect), "refresh_player": True},
{"name": "slide_animation_exit_effect", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": AnimationExitEffect.NONE.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_exit_effect'), "selectables": enum_to_dict(AnimationExitEffect), "refresh_player": True},
{"name": "slide_animation_speed", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": AnimationSpeed.NORMAL.value, "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_slide_animation_speed'), "selectables": self.t(AnimationSpeed), "refresh_player": True},
# Not editable (System information)
{"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')},
{"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_last_slide_update')},
{"name": "refresh_player_request", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_refresh_player_request')},
]
for default_var in default_vars:

View File

@ -12,7 +12,8 @@ class Variable:
def __init__(self, name: str = '', section: str = '', description: str = '', type: Union[VariableType, str] = VariableType.STRING,
value: Union[int, bool, str] = '', editable: bool = True, id: Optional[str] = None,
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None):
plugin: Optional[str] = None, selectables: Optional[List[Selectable]] = None, unit: Optional[VariableUnit] = None,
refresh_player: bool = False):
self._id = id if id else None
self._name = name
self._section = section
@ -22,6 +23,7 @@ class Variable:
self._value = value
self._editable = editable
self._plugin = plugin
self._refresh_player = refresh_player
self._selectables = selectables
@property
@ -87,6 +89,14 @@ class Variable:
def editable(self, value: bool):
self._editable = value
@property
def refresh_player(self) -> bool:
return self._refresh_player
@refresh_player.setter
def refresh_player(self, value: bool):
self._refresh_player = value
@property
def value(self) -> Union[int, bool, str]:
return self._value
@ -113,6 +123,7 @@ class Variable:
f"unit='{self.unit}',\n" \
f"description='{self.description}',\n" \
f"editable='{self.editable}',\n" \
f"refresh_player='{self.refresh_player}',\n" \
f"plugin='{self.plugin}',\n" \
f"selectables='{self.selectables}',\n" \
f")"
@ -130,6 +141,7 @@ class Variable:
"unit": self.unit.value if self.unit else None,
"description": self.description,
"editable": self.editable,
"refresh_player": self.refresh_player,
"plugin": self.plugin,
"selectables": [selectable.to_dict() for selectable in self.selectables] if isinstance(self._selectables, list) else None
}
@ -161,6 +173,8 @@ class Variable:
value / 1024 / 1024,
"MB"
)
elif self.unit == VariableUnit.SECOND:
value = "{}{}".format(value, "s")
return value

View File

@ -4,4 +4,5 @@ from enum import Enum
class VariableSection(Enum):
GENERAL = 'general'
ANIMATION = 'animation'
PLAYER_OPTIONS = 'player_options'
PLAYER_ANIMATION = 'player_animation'

View File

@ -4,3 +4,4 @@ from enum import Enum
class VariableUnit(Enum):
BYTE = 'byte'
SECOND = 'second'

View File

@ -19,16 +19,21 @@
</head>
<body>
<div id="FirstSlide" class="slide" style="z-index: 1000;">
<iframe src="/player/default"></iframe>
{% if default_slide_duration.eval() > 0 %}
<iframe src="/player/default"></iframe>
{% endif %}
</div>
<div id="SecondSlide" class="slide" style="z-index: 500;">
<iframe src="/player/default"></iframe>
{% if default_slide_duration.eval() > 0 %}
<iframe src="/player/default"></iframe>
{% endif %}
</div>
<script type="text/javascript">
var items = {{items | safe}};
var duration = 3000 / 1;
var playlistCheck = 10 * 1000; // 10 seconds check
var duration = {{ default_slide_duration.eval() * 1000 }};
var playlistCheck = {{ polling_interval.eval() * 1000 }};
var curItemIndex = 0;
var needHardRefresh = null;
var nextReady = true;
var itemCheck = setInterval(function () {
fetch('player/playlist').then(function(response) {
@ -37,6 +42,12 @@
}
}).then(function(data) {
items = data;
if (needHardRefresh === null) {
needHardRefresh = items.hard_refresh_request;
} else if (needHardRefresh != items.hard_refresh_request) {
document.location.reload();
}
}).catch(function(err) {
console.error(err);
});

View File

@ -28,15 +28,25 @@
{{ HOOK(H_FLEETMODE_SLIDESHOW_TOOLBAR_ACTIONS) }}
{% endif %}
<a href="/" target="_blank" class="btn">
<i class="fa fa-play icon-left"></i>
{{ l.slideshow_goto_player }}
<a href="/" target="_blank" class="btn" title="{{ l.slideshow_goto_player }}">
<i class="fa fa-play"></i>
</a>
<a href="{{ url_for('slideshow_player_refresh') }}" class="btn" title="{{ l.slideshow_refresh_player }}">
<i class="fa fa-refresh"></i>
</a>
<button class="purple slide-add item-add"><i class="fa fa-plus icon-left"></i>{{ l.slideshow_slide_button_add }}</button>
{{ HOOK(H_SLIDESHOW_TOOLBAR_ACTIONS_END) }}
</div>
</div>
{% if request.args.get('refresh_player') %}
<div class="alert alert-success">
<i class="fa fa-refresh icon-left"></i>
{{ l.slideshow_refresh_player_success|replace('%time%', request.args.get('refresh_player')) }}
</div>
{% endif %}
<div class="panel">
<div class="panel-body">
<h3>{{ l.slideshow_slide_panel_active }}</h3>