composition is ok
This commit is contained in:
parent
56c421ed63
commit
442f02ff37
@ -162,5 +162,12 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
showToast(l.js_common_copied);
|
||||
});
|
||||
|
||||
$(window).on('beforeunload', function(event) {
|
||||
$('.modal').each(function() {
|
||||
$(this).find('button[type=submit]').removeClass('hidden');
|
||||
$(this).find('.btn-loading').addClass('hidden');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ jQuery(document).ready(function ($) {
|
||||
let currentElement = null;
|
||||
let elementCounter = 0;
|
||||
|
||||
$('.screen').css({
|
||||
width: $('.screen').width(),
|
||||
height: $('.screen').height(),
|
||||
$('#screen').css({
|
||||
width: $('#screen').width(),
|
||||
height: $('#screen').height(),
|
||||
position: 'relative',
|
||||
}).parents('.screen-holder:eq(0)').css({
|
||||
width: 'auto',
|
||||
@ -72,7 +72,7 @@ jQuery(document).ready(function ($) {
|
||||
focusElement($(this));
|
||||
});
|
||||
|
||||
$('#screen').append(element);
|
||||
screen.append(element);
|
||||
addElementToList(elementId);
|
||||
|
||||
if (config !== null && config.contentId !== null) {
|
||||
@ -291,8 +291,8 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
$(document).on('submit', 'form.form', function (e) {
|
||||
unfocusElements();
|
||||
const composites = getCompositesPayload();
|
||||
$('#content-edit-location').val(JSON.stringify(composites));
|
||||
const layers = getLayersPayload();
|
||||
$('#content-edit-location').val(JSON.stringify(layers));
|
||||
});
|
||||
|
||||
function updateZIndexes() {
|
||||
@ -319,11 +319,11 @@ jQuery(document).ready(function ($) {
|
||||
applyElementsFromContent();
|
||||
});
|
||||
|
||||
const getCompositesPayload = function() {
|
||||
const getLayersPayload = function() {
|
||||
const screen = $('#screen');
|
||||
const screenWidth = screen.width();
|
||||
const screenHeight = screen.height();
|
||||
const composites = [];
|
||||
const layers = [];
|
||||
|
||||
$('.element').each(function () {
|
||||
const offset = $(this).position();
|
||||
@ -340,7 +340,7 @@ const getCompositesPayload = function() {
|
||||
const contentName = $(this).attr('data-content-name');
|
||||
const contentType = $(this).attr('data-content-type');
|
||||
|
||||
composites.push({
|
||||
layers.push({
|
||||
xPercent: xPercent,
|
||||
yPercent: yPercent,
|
||||
widthPercent: widthPercent,
|
||||
@ -352,9 +352,9 @@ const getCompositesPayload = function() {
|
||||
});
|
||||
});
|
||||
|
||||
composites.sort(function(a, b) {
|
||||
layers.sort(function(a, b) {
|
||||
return parseInt(b.zIndex) - parseInt(a.zIndex);
|
||||
});
|
||||
|
||||
return composites;
|
||||
return layers;
|
||||
};
|
||||
@ -28,28 +28,31 @@ class PlayerController(ObController):
|
||||
self._app.add_url_rule('/player/playlist', 'player_playlist', self.player_playlist, methods=['GET'])
|
||||
self._app.add_url_rule('/player/playlist/use/<playlist_slug_or_id>', 'player_playlist_use', self.player_playlist, methods=['GET'])
|
||||
self._app.add_url_rule('/serve/content/<content_type>/<content_id>/<content_location>', 'serve_content_file', self.serve_content_file, methods=['GET'])
|
||||
self._app.add_url_rule('/serve/content/composition/<content_id>', 'serve_content_composition', self.serve_content_composition, methods=['GET'])
|
||||
|
||||
def player(self, playlist_slug_or_id: str = ''):
|
||||
preview_playlist = request.args.get('preview_playlist')
|
||||
preview_content_id = request.args.get('preview_content_id')
|
||||
playlist_id = None
|
||||
playlist_slug_or_id = self._get_dynamic_playlist_id(playlist_slug_or_id)
|
||||
|
||||
query = " (slug = ? OR id = ?) "
|
||||
query_args = {
|
||||
"slug": playlist_slug_or_id,
|
||||
"id": playlist_slug_or_id,
|
||||
}
|
||||
if not preview_content_id:
|
||||
query = " (slug = ? OR id = ?) "
|
||||
query_args = {
|
||||
"slug": playlist_slug_or_id,
|
||||
"id": playlist_slug_or_id,
|
||||
}
|
||||
|
||||
if not preview_playlist:
|
||||
query = query + " AND enabled = ? "
|
||||
query_args["enabled"] = True
|
||||
if not preview_playlist:
|
||||
query = query + " AND enabled = ? "
|
||||
query_args["enabled"] = True
|
||||
|
||||
current_playlist = self._model_store.playlist().get_one_by(query, query_args)
|
||||
current_playlist = self._model_store.playlist().get_one_by(query, query_args)
|
||||
|
||||
if playlist_slug_or_id and not current_playlist:
|
||||
return abort(404)
|
||||
if playlist_slug_or_id and not current_playlist:
|
||||
return abort(404)
|
||||
|
||||
playlist_id = current_playlist.id if current_playlist else None
|
||||
playlist_id = current_playlist.id if current_playlist else None
|
||||
|
||||
try:
|
||||
items = self._get_playlist(playlist_id=playlist_id, preview_content_id=preview_content_id)
|
||||
@ -63,6 +66,8 @@ class PlayerController(ObController):
|
||||
slide_animation_entrance_effect = request.args.get('animation_effect', self._model_store.variable().get_one_by_name('slide_animation_entrance_effect').eval())
|
||||
slide_animation_exit_effect = request.args.get('slide_animation_exit_effect', self._model_store.variable().get_one_by_name('slide_animation_exit_effect').eval())
|
||||
|
||||
|
||||
|
||||
return render_template(
|
||||
'player/player.jinja.html',
|
||||
items=items,
|
||||
@ -122,7 +127,7 @@ class PlayerController(ObController):
|
||||
preview_content = self._model_store.content().get(preview_content_id) if preview_content_id else None
|
||||
preview_mode = preview_content is not None
|
||||
|
||||
if playlist_id == 0 or not playlist_id:
|
||||
if not preview_mode and (playlist_id == 0 or not playlist_id):
|
||||
playlist = self._model_store.playlist().get_one_by(query="fallback = 1")
|
||||
|
||||
if playlist:
|
||||
@ -132,8 +137,9 @@ class PlayerController(ObController):
|
||||
|
||||
enabled_slides = [Slide(content_id=preview_content.id, duration=1000000)] if preview_mode else self._model_store.slide().get_slides(enabled=True, playlist_id=playlist_id)
|
||||
slides = self._model_store.slide().to_dict(enabled_slides)
|
||||
contents = self._model_store.content().get_all_indexed()
|
||||
playlist = self._model_store.playlist().get(playlist_id)
|
||||
content_ids = [str(slide['content_id']) for slide in slides if slide['content_id'] is not None]
|
||||
contents = self._model_store.content().get_all_indexed(query="id IN ({})".format(','.join(content_ids)))
|
||||
playlist = self._model_store.playlist().get(playlist_id) if not preview_mode else None
|
||||
position = 9999
|
||||
|
||||
playlist_loop = []
|
||||
@ -248,3 +254,14 @@ class PlayerController(ObController):
|
||||
response.headers['ETag'] = etag
|
||||
|
||||
return response
|
||||
|
||||
def serve_content_composition(self, content_id):
|
||||
content = self._model_store.content().get(content_id)
|
||||
|
||||
if not content or content.type != ContentType.COMPOSITION:
|
||||
abort(404, 'Content not found')
|
||||
|
||||
return render_template(
|
||||
'player/content/composition.jinja.html',
|
||||
content=content,
|
||||
)
|
||||
|
||||
@ -73,10 +73,12 @@ class ContentManager(ModelManager):
|
||||
def get_all(self, sort: Optional[str] = 'created_at', ascending=False) -> List[Content]:
|
||||
return self.hydrate_list(self._db.get_all(table_name=self.TABLE_NAME, sort=sort, ascending=ascending))
|
||||
|
||||
def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, Content]:
|
||||
def get_all_indexed(self, attribute: str = 'id', multiple=False, query: str = None) -> Dict[str, Content]:
|
||||
index = {}
|
||||
|
||||
for item in self.get_contents():
|
||||
items = self.get_by(query) if query else self.get_contents()
|
||||
|
||||
for item in items:
|
||||
id = getattr(item, attribute)
|
||||
if multiple:
|
||||
if id not in index:
|
||||
@ -237,6 +239,14 @@ class ContentManager(ModelManager):
|
||||
|
||||
if content.type == ContentType.YOUTUBE:
|
||||
location = content.location
|
||||
elif content.type == ContentType.COMPOSITION:
|
||||
location = "{}/{}".format(
|
||||
var_external_url if len(var_external_url) > 0 else "",
|
||||
url_for(
|
||||
'serve_content_composition',
|
||||
content_id=content.id
|
||||
).strip('/')
|
||||
)
|
||||
elif content.has_file() or content.type == ContentType.EXTERNAL_STORAGE:
|
||||
location = "{}/{}".format(
|
||||
var_external_url if len(var_external_url) > 0 else "",
|
||||
|
||||
@ -50,7 +50,6 @@ class WebServer:
|
||||
host=self._model_store.config().map().get('bind'),
|
||||
port=self._model_store.config().map().get('port'),
|
||||
threads=100,
|
||||
max_request_body_size=self.get_max_upload_size(),
|
||||
)
|
||||
|
||||
def reload(self) -> None:
|
||||
@ -58,6 +57,7 @@ class WebServer:
|
||||
|
||||
def setup(self) -> None:
|
||||
self._setup_flask_app()
|
||||
self._setup_flask_headers()
|
||||
self._setup_web_globals()
|
||||
self._setup_web_errors()
|
||||
self._setup_web_controllers()
|
||||
@ -160,6 +160,19 @@ class WebServer:
|
||||
def inject_global_vars() -> dict:
|
||||
return self._template_renderer.get_view_globals()
|
||||
|
||||
def _setup_flask_headers(self) -> None:
|
||||
@self._app.after_request
|
||||
def modify_headers(response):
|
||||
# Supprimer les en-têtes de sécurité
|
||||
response.headers.pop('X-Frame-Options', None)
|
||||
response.headers.pop('X-Content-Type-Options', None)
|
||||
response.headers.pop('X-XSS-Protection', None)
|
||||
|
||||
# Modifier ou supprimer la politique CSP
|
||||
response.headers['Content-Security-Policy'] = "frame-ancestors *"
|
||||
|
||||
return response
|
||||
|
||||
def _setup_web_errors(self) -> None:
|
||||
def handle_error(error):
|
||||
if request.headers.get('Content-Type') == 'application/json' or request.headers.get('Accept') == 'application/json':
|
||||
@ -171,14 +184,16 @@ class WebServer:
|
||||
})
|
||||
return make_response(response, error.code)
|
||||
|
||||
if error.code == 404:
|
||||
return send_from_directory(self.get_template_dir(), 'core/error404.html'), 404
|
||||
|
||||
return error
|
||||
return self._template_renderer.render_view(
|
||||
'@core/http-error.html',
|
||||
error_code=error.code,
|
||||
error_message=error.description
|
||||
)
|
||||
|
||||
self._app.register_error_handler(400, handle_error)
|
||||
self._app.register_error_handler(404, handle_error)
|
||||
self._app.register_error_handler(409, handle_error)
|
||||
self._app.register_error_handler(413, handle_error)
|
||||
self._app.register_error_handler(HttpClientException, handle_error)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
78
views/player/content/composition.jinja.html
Executable file
78
views/player/content/composition.jinja.html
Executable file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body, #screen {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: black;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
iframe {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="screen"></div>
|
||||
<script src="{{ STATIC_PREFIX }}js/lib/jquery.min.js"></script>
|
||||
<script>
|
||||
const base_iframe_route = '{{ url_for('player', preview_content_id='!c!', autoplay=1) }}';
|
||||
|
||||
jQuery(function($) {
|
||||
function createElement(config = null) {
|
||||
const screen = $('#screen');
|
||||
const screenWidth = screen.width();
|
||||
const screenHeight = screen.height();
|
||||
|
||||
const elementWidth = (config.widthPercent / 100) * screenWidth
|
||||
const elementHeight = (config.heightPercent / 100) * screenHeight;
|
||||
let x = (config.xPercent / 100) * screenWidth;
|
||||
let y = (config.yPercent / 100) * screenHeight;
|
||||
const zIndex = config.zIndex;
|
||||
|
||||
x = Math.round(Math.max(0, Math.min(x, screenWidth - elementWidth)));
|
||||
y = Math.round(Math.max(0, Math.min(y, screenHeight - elementHeight)));
|
||||
|
||||
const element = $('<iframe class="element" id="element-' + zIndex + '" data-id="' + zIndex + '" src="'+base_iframe_route.replace('!c!', config.contentId)+'"></iframe>');
|
||||
|
||||
element.css({
|
||||
left: x,
|
||||
top: y,
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
zIndex: zIndex,
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
transform: `rotate(0deg)`
|
||||
});
|
||||
|
||||
screen.append(element);
|
||||
}
|
||||
|
||||
const applyElementsFromContent = function() {
|
||||
$('#screen').html('');
|
||||
|
||||
const contentData = JSON.parse({{ json_dumps(content.location) | safe }});
|
||||
for (let i = 0; i < contentData.length; i++) {
|
||||
if (contentData[i].contentId !== null) {
|
||||
createElement(contentData[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const main = function() {
|
||||
applyElementsFromContent();
|
||||
};
|
||||
|
||||
$(window).on('resize', function() {
|
||||
main();
|
||||
});
|
||||
|
||||
main();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -47,6 +47,7 @@
|
||||
// Frontend config
|
||||
const syncWithTime = items['time_sync'];
|
||||
const previewMode = items['preview_mode'];
|
||||
const disableAutoplay = {{ 'false' if request.args.get('autoplay') == '1' else 'previewMode' }};
|
||||
const tickRefreshResolutionMs = 100;
|
||||
|
||||
// Frontend flag updates
|
||||
@ -345,6 +346,9 @@
|
||||
case 'youtube':
|
||||
loadYoutube(element, callbackReady, item);
|
||||
break;
|
||||
case 'composition':
|
||||
loadComposition(element, callbackReady, item);
|
||||
break;
|
||||
default:
|
||||
loadUrl(element, callbackReady, item);
|
||||
break;
|
||||
@ -361,6 +365,26 @@
|
||||
callbackReady(function() {});
|
||||
};
|
||||
|
||||
const loadComposition = function(element, callbackReady, item) {
|
||||
element.innerHTML = `composition`;
|
||||
callbackReady(function() {});
|
||||
|
||||
const loadingDelayMs = 1000;
|
||||
let delayNoisyContentJIT = Math.max(100, (lookupCurrentItem().duration * 1000) - loadingDelayMs);
|
||||
delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0;
|
||||
|
||||
const autoplayLoader = function() {
|
||||
if (secondsBeforeNext * 1000 > loadingDelayMs) {
|
||||
return setTimeout(autoplayLoader, 500);
|
||||
}
|
||||
|
||||
if (element.innerHTML === 'composition') {
|
||||
element.innerHTML = `<iframe src="${item.location}" frameborder="0" allow="autoplay" allowfullscreen></iframe>`;
|
||||
}
|
||||
}
|
||||
setTimeout(autoplayLoader, delayNoisyContentJIT);
|
||||
};
|
||||
|
||||
const loadYoutube = function(element, callbackReady, item) {
|
||||
element.innerHTML = `youtube`;
|
||||
callbackReady(function() {});
|
||||
@ -375,7 +399,7 @@
|
||||
}
|
||||
|
||||
if (element.innerHTML === 'youtube') {
|
||||
const autoplay = previewMode ? '0' : '1';
|
||||
const autoplay = disableAutoplay ? '0' : '1';
|
||||
element.innerHTML = `<iframe src="https://www.youtube.com/embed/${item.location}?version=3&autoplay=${autoplay}&showinfo=0&controls=0&modestbranding=1&fs=1&rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>`;
|
||||
}
|
||||
}
|
||||
@ -383,7 +407,7 @@
|
||||
};
|
||||
|
||||
const loadVideo = function(element, callbackReady, item) {
|
||||
element.innerHTML = `<video ${previewMode ? 'controls' : ''}><source src=${item.location} type="video/mp4" /></video>`;
|
||||
element.innerHTML = `<video ${disableAutoplay ? 'controls' : ''}><source src=${item.location} type="video/mp4" /></video>`;
|
||||
const video = element.querySelector('video');
|
||||
callbackReady(function() {});
|
||||
|
||||
@ -407,7 +431,7 @@
|
||||
}
|
||||
|
||||
if (element.innerHTML.match('<video>')) {
|
||||
if (!previewMode) {
|
||||
if (!disableAutoplay) {
|
||||
setTimeout(function() {
|
||||
video.play();
|
||||
pausableContent = video;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user