This commit is contained in:
jr-k 2024-08-12 14:19:55 +02:00
commit 4712047015
21 changed files with 260 additions and 77 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -37,8 +37,6 @@ jQuery(document).ready(function ($) {
'padding-top': '0px' 'padding-top': '0px'
}); });
function createElement() { function createElement() {
let screen = $('#screen'); let screen = $('#screen');
let screenWidth = screen.width(); let screenWidth = screen.width();
@ -57,7 +55,7 @@ jQuery(document).ready(function ($) {
y = Math.round(Math.max(0, Math.min(y, screenHeight - elementHeight))); y = Math.round(Math.max(0, Math.min(y, screenHeight - elementHeight)));
let elementId = elementCounter++; let elementId = elementCounter++;
let element = $('<div class="element" id="element-' + elementId + '" data-id="' + elementId + '"><button>Button</button></div>'); let element = $('<div class="element" id="element-' + elementId + '" data-id="' + elementId + '"><i class="fa fa-cog"></i></div>');
// let element = $('<div class="element" id="' + elementId + '"><button>Button</button><div class="rotate-handle"></div></div>'); // let element = $('<div class="element" id="' + elementId + '"><button>Button</button><div class="rotate-handle"></div></div>');
element.css({ element.css({
@ -106,6 +104,8 @@ jQuery(document).ready(function ($) {
setTimeout(function() { setTimeout(function() {
focusElement(element); focusElement(element);
}, 10); }, 10);
return element;
} }
$(document).on('click', '.element-list-item', function(){ $(document).on('click', '.element-list-item', function(){
@ -113,7 +113,9 @@ jQuery(document).ready(function ($) {
}) })
$(document).on('click', '.remove-element', function(){ $(document).on('click', '.remove-element', function(){
removeElementById($(this).attr('data-id')); if (confirm(l.js_common_are_you_sure)) {
removeElementById($(this).attr('data-id'));
}
}) })
function removeElementById(elementId) { function removeElementById(elementId) {
@ -122,8 +124,16 @@ jQuery(document).ready(function ($) {
} }
function addElementToList(elementId) { function addElementToList(elementId) {
let listItem = $('<div class="element-list-item" data-id="' + elementId + '">Element ' + elementId + ' <button type="button" class="remove-element" data-id="' + elementId + '">remove</button></div>'); const listItem = `<div class="element-list-item" data-id="__ID__">
$('#elementList').append(listItem); Element __ID__
<button type="button" class="btn btn-neutral configure-element content-explr-picker" data-id="__ID__">
<i class="fa fa-cog"></i>
</button>
<button type="button" class="btn btn-naked remove-element" data-id="__ID__">
<i class="fa fa-trash"></i>
</button>
</div>`;
$('#elementList').append($(listItem.replace(/__ID__/g, elementId)));
updateZIndexes(); updateZIndexes();
} }
@ -145,9 +155,11 @@ jQuery(document).ready(function ($) {
function updateForm(element) { function updateForm(element) {
if (!element) { if (!element) {
$('form#elementForm input').val('').prop('disabled', true); $('form#elementForm input').val('').prop('disabled', true);
$('.form-element-properties').addClass('hidden');
return; return;
} }
$('.form-element-properties').removeClass('hidden');
$('form#elementForm input').prop('disabled', false); $('form#elementForm input').prop('disabled', false);
let offset = element.position(); let offset = element.position();
@ -158,6 +170,8 @@ jQuery(document).ready(function ($) {
$('#elem-width').val(element.width()); $('#elem-width').val(element.width());
$('#elem-height').val(element.height()); $('#elem-height').val(element.height());
} }
$(element).find('i').css('font-size', Math.min(element.width(), element.height()) / 3);
/* /*
let rotation = element.css('transform'); let rotation = element.css('transform');
let values = rotation.split('(')[1].split(')')[0].split(','); let values = rotation.split('(')[1].split(')')[0].split(',');
@ -201,13 +215,19 @@ jQuery(document).ready(function ($) {
} }
}); });
$(document).on('click', '#addElement', function () { // $(document).on('click', '#addElement', function () {
createElement(); // createElement();
}); // });
$(document).on('click', '#removeAllElements', function () { $(document).on('click', '#removeAllElements', function () {
$('.element, .element-list-item').remove(); if (confirm(l.js_common_are_you_sure)) {
updateZIndexes(); $('.element, .element-list-item').remove();
updateZIndexes();
}
});
$(document).on('dblclick', '.element', function (e) {
$('.content-explr-picker[data-id='+$(this).attr('data-id')+']').click();
}); });
$(document).on('mousedown', function (e) { $(document).on('mousedown', function (e) {
@ -220,7 +240,7 @@ jQuery(document).ready(function ($) {
if (!keepFocusedElement) { if (!keepFocusedElement) {
unfocusElements(); unfocusElements();
} }
}); })
$(document).on('click', '#presetGrid2x2', function () { $(document).on('click', '#presetGrid2x2', function () {
let screenWidth = $('#screen').width(); let screenWidth = $('#screen').width();
@ -252,6 +272,22 @@ jQuery(document).ready(function ($) {
}); });
}); });
$(document).on('click', '.content-explr-picker', function () {
const elementId = $(this).attr('data-id');
const isNew = !elementId;
const $element = isNew ? $(createElement()) : $('#element-'+elementId);
showPickers('modal-content-explr-picker', function (content) {
console.log(content);
$element.attr('data-content-id', content.id);
$element.attr('data-content-name', content.name);
$element.attr('data-content-type', content.type);
console.log($element)
$element.find('i').get(0).classList = ['fa', content.classIcon, content.classColor].join(' ');
});
});
function updateZIndexes() { function updateZIndexes() {
const zindex = $('.element-list-item').length + 1; const zindex = $('.element-list-item').length + 1;
$('.element-list-item').each(function(index) { $('.element-list-item').each(function(index) {
@ -266,6 +302,5 @@ jQuery(document).ready(function ($) {
} }
}); });
createElement(); $('#presetGrid2x2').click();
updateForm(null);
}); });

View File

@ -155,8 +155,6 @@ form {
color: $gscale5; color: $gscale5;
background: none; background: none;
box-shadow: none; box-shadow: none;
border: none;
border-bottom: 1px solid $gscale3;
border-radius: 0; border-radius: 0;
} }
@ -167,11 +165,8 @@ form {
&.disabled, &.disabled,
&[disabled] { &[disabled] {
border: none;
background: $gscale0; background: $gscale0;
border-radius: $baseRadius; border-radius: $baseRadius;
padding-left: 10px;
padding-right: 10px;
} }
} }
} }

View File

@ -45,14 +45,18 @@
.element { .element {
position: absolute !important; position: absolute !important;
background-color: #f0f0f0; background-color: $gkscaleE;
outline: 1px solid rgba($black, .5); outline: 1px solid $gkscaleC;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&.focused { &.focused {
border: none; border: none;
outline: 2px solid blue; outline: 2px solid $seaBlue;
z-index: 89 !important; z-index: 89 !important;
.ui-resizable-handle { .ui-resizable-handle {
@ -60,6 +64,14 @@
} }
} }
i {
font-size: 20px;
color: $gkscaleC;
&.fa-cog {
text-shadow: 0 -2px $gkscaleB, 0 0px 2px $gkscaleB;
}
}
.rotate-handle { .rotate-handle {
width: 10px; width: 10px;
height: 10px; height: 10px;
@ -72,8 +84,8 @@
} }
.ui-resizable-handle { .ui-resizable-handle {
background: black; background: $gkscaleA;
border: 1px solid #000; border: 1px solid $gkscale5;
width: 10px; width: 10px;
height: 10px; height: 10px;
z-index: 90; z-index: 90;
@ -145,25 +157,61 @@
.form-element-properties { .form-element-properties {
margin-left: 20px; flex: 1;
padding: 10px; align-self: stretch;
background-color: #f9f9f9;
border: 1px solid #ccc;
form { form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
label,
input {
margin-bottom: 10px;
}
}
#elementList {
h3 { h3 {
margin: 0 0 10px 0; font-size: 16px;
font-weight: 500;
color: $gscaleD;
text-decoration: none;
border-bottom: 1px solid $gscale2;
margin-bottom: 20px;
padding-bottom: 10px;
align-self: stretch;
} }
.form-group {
flex-direction: row;
justify-content: flex-start;
align-items: center;
display: flex;
label {
flex-grow: 1;
flex-direction: row;
justify-content: flex-start;
align-items: center;
display: flex;
font-weight: bold;
margin-right: 10px;
}
.widget {
flex-grow: 1;
flex-direction: row;
justify-content: flex-start;
align-items: center;
display: flex;
margin: 0;
input {
flex: 1;
margin: 0;
&[disabled] {
padding: 8px 0 5px 8px;
border: 1px solid rgba(255,255,255,.05);
}
}
}
}
} }
} }

View File

@ -110,7 +110,23 @@ When you run the browser yourself, don't forget to use these flags for chromium
```bash ```bash
# chromium or chromium-browser or even chrome # chromium or chromium-browser or even chrome
# replace http://localhost:5000 with your obscreen-studio instance url # replace http://localhost:5000 with your obscreen-studio instance 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 http://localhost:5000 chromium \
--disk-cache-size=2147483648 \
--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 \
http://localhost:5000
``` ```
--- ---

View File

@ -118,7 +118,23 @@ When you run the browser yourself, don't forget to use these flags for chromium
```bash ```bash
# chromium or chromium-browser or even chrome # chromium or chromium-browser or even chrome
# replace http://localhost:5000 with your obscreen-studio instance url # replace http://localhost:5000 with your obscreen-studio instance 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 http://localhost:5000 chromium \
--disk-cache-size=2147483648 \
--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 \
http://localhost:5000
``` ```
--- ---

View File

@ -110,11 +110,14 @@ class ContentController(ObController):
if not content: if not content:
return abort(404) return abort(404)
vargs = {}
working_folder_path, working_folder = self.get_folder_context() working_folder_path, working_folder = self.get_folder_context()
edit_view = 'slideshow/contents/edit.jinja.html' edit_view = 'slideshow/contents/edit.jinja.html'
if content.type == ContentType.COMPOSITION: if content.type == ContentType.COMPOSITION:
edit_view = 'slideshow/contents/edit-composition.jinja.html' edit_view = 'slideshow/contents/edit-composition.jinja.html'
vargs['folders_tree'] = self._model_store.folder().get_folder_tree(FolderEntity.CONTENT)
vargs['foldered_contents'] = self._model_store.content().get_all_indexed('folder_id', multiple=True)
return render_template( return render_template(
edit_view, edit_view,
@ -122,7 +125,8 @@ class ContentController(ObController):
working_folder_path=working_folder_path, working_folder_path=working_folder_path,
working_folder=working_folder, working_folder=working_folder,
enum_content_type=ContentType, enum_content_type=ContentType,
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint') external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint'),
**vargs
) )
def slideshow_content_save(self, content_id: int = 0): def slideshow_content_save(self, content_id: int = 0):

View File

@ -156,7 +156,7 @@ class PlayerController(ObController):
slide = dict(slide) slide = dict(slide)
slide['id'] = hashlib.md5(str(file).encode('utf-8')).hexdigest() slide['id'] = hashlib.md5(str(file).encode('utf-8')).hexdigest()
slide['position'] = position slide['position'] = position
slide['delegate_duration'] = 1 if slide['type'] == ContentType.VIDEO.value else 0 slide['delegate_duration'] = 1 if virtual_content.type == ContentType.VIDEO else 0
slide['name'] = file.name slide['name'] = file.name
slide['type'] = virtual_content.type.value slide['type'] = virtual_content.type.value
slide['location'] = self._model_store.content().resolve_content_location(virtual_content) slide['location'] = self._model_store.content().resolve_content_location(virtual_content)

View File

@ -28,7 +28,7 @@ class ContentManager(ModelManager):
"name CHAR(255)", "name CHAR(255)",
"type CHAR(30)", "type CHAR(30)",
"location TEXT", "location TEXT",
"duration INTEGER", "duration FLOAT",
"folder_id INTEGER", "folder_id INTEGER",
"created_by CHAR(255)", "created_by CHAR(255)",
"updated_by CHAR(255)", "updated_by CHAR(255)",

View File

@ -3,6 +3,7 @@ import os
from typing import Dict, Optional, List, Tuple, Union from typing import Dict, Optional, List, Tuple, Union
from src.model.entity.Playlist import Playlist from src.model.entity.Playlist import Playlist
from src.model.enum.ContentType import ContentType
from src.util.utils import get_optional_string, get_yt_video_id, slugify, slugify_next from src.util.utils import get_optional_string, get_yt_video_id, slugify, slugify_next
from src.manager.DatabaseManager import DatabaseManager from src.manager.DatabaseManager import DatabaseManager
from src.manager.SlideManager import SlideManager from src.manager.SlideManager import SlideManager
@ -69,15 +70,17 @@ class PlaylistManager(ModelManager):
durations = self._db.execute_read_query(""" durations = self._db.execute_read_query("""
SELECT SELECT
playlist_id, playlist_id,
SUM(CASE ROUND(SUM(CASE
WHEN s.delegate_duration = 1 THEN c.duration WHEN s.delegate_duration = 1 THEN c.duration
WHEN c.type = '{}' THEN s.duration
ELSE s.duration ELSE s.duration
END) AS total_duration END)) AS total_duration
FROM {} s FROM {} s
LEFT JOIN {} c ON c.id = s.content_id LEFT JOIN {} c ON c.id = s.content_id
WHERE cron_schedule IS NULL {} WHERE cron_schedule IS NULL {} AND s.enabled is TRUE
GROUP BY playlist_id; GROUP BY playlist_id;
""".format( """.format(
ContentType.EXTERNAL_STORAGE.value,
SlideManager.TABLE_NAME, SlideManager.TABLE_NAME,
ContentManager.TABLE_NAME, ContentManager.TABLE_NAME,
"{}".format( "{}".format(

View File

@ -9,7 +9,7 @@ from src.util.utils import str_to_enum
class Content: class Content:
def __init__(self, uuid: str = '', location: str = '', type: Union[ContentType, str] = ContentType.URL, name: str = 'Untitled', id: Optional[int] = None, duration: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None, folder_id: Optional[int] = None): def __init__(self, uuid: str = '', location: str = '', type: Union[ContentType, str] = ContentType.URL, name: str = 'Untitled', id: Optional[int] = None, duration: Optional[float] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None, folder_id: Optional[int] = None):
self._uuid = uuid if uuid else self.generate_and_set_uuid() self._uuid = uuid if uuid else self.generate_and_set_uuid()
self._id = id if id else None self._id = id if id else None
self._location = location self._location = location
@ -88,11 +88,11 @@ class Content:
self._folder_id = value self._folder_id = value
@property @property
def duration(self) -> Optional[int]: def duration(self) -> Optional[float]:
return self._duration return self._duration
@duration.setter @duration.setter
def duration(self, value: Optional[int]): def duration(self, value: Optional[float]):
self._duration = value self._duration = value
@property @property

View File

@ -10,9 +10,9 @@ def mp4_duration_with_ffprobe(filename):
fields = json.loads(result)['streams'][0] fields = json.loads(result)['streams'][0]
if 'tags' in fields and 'DURATION' in fields['tags']: if 'tags' in fields and 'DURATION' in fields['tags']:
return int(float(fields['tags']['DURATION'])) return round(float(fields['tags']['DURATION']), 2)
if 'duration' in fields: if 'duration' in fields:
return int(float(fields['duration'])) return round(float(fields['duration']), 2)
return 0 return 0

View File

@ -251,6 +251,7 @@ def slugify(value):
def seconds_to_hhmmss(seconds): def seconds_to_hhmmss(seconds):
seconds = int(seconds)
if not seconds: if not seconds:
return "" return ""
hours = seconds // 3600 hours = seconds // 3600

View File

@ -16,4 +16,20 @@ WIDTH=$(echo $RESOLUTION | cut -d 'x' -f 1)
HEIGHT=$(echo $RESOLUTION | cut -d 'x' -f 2) HEIGHT=$(echo $RESOLUTION | cut -d 'x' -f 2)
# Start Chromium in kiosk mode # Start Chromium in kiosk mode
chromium-browser --disk-cache-size=2147483648 --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=${WIDTH},${HEIGHT} --display=:0 http://localhost:5000 chromium-browser \
--disk-cache-size=2147483648 \
--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=${WIDTH},${HEIGHT} \
--display=:0 \
http://localhost:5000

View File

@ -84,6 +84,7 @@ systemctl set-default graphical.target
mkdir -p "$WORKING_DIR/obscreen/var/run" mkdir -p "$WORKING_DIR/obscreen/var/run"
curl https://raw.githubusercontent.com/jr-k/obscreen/master/system/autostart-browser-x11.sh | sed "s#/home/pi#$WORKING_DIR#g" | sed "s#=pi#=$OWNER#g" | sed "s#http://localhost:5000#$obscreen_studio_url#g" | tee "$WORKING_DIR/obscreen/var/run/play" curl https://raw.githubusercontent.com/jr-k/obscreen/master/system/autostart-browser-x11.sh | sed "s#/home/pi#$WORKING_DIR#g" | sed "s#=pi#=$OWNER#g" | sed "s#http://localhost:5000#$obscreen_studio_url#g" | tee "$WORKING_DIR/obscreen/var/run/play"
chmod +x "$WORKING_DIR/obscreen/var/run/play" chmod +x "$WORKING_DIR/obscreen/var/run/play"
chown -R $OWNER:$OWNER "$WORKING_DIR/obscreen"
# ============================================================ # ============================================================
# Start # Start

View File

@ -25,7 +25,6 @@ apt-get install -y git python3-pip python3-venv libsqlite3-dev ntfs-3g ffmpeg
cd $WORKING_DIR cd $WORKING_DIR
git clone https://github.com/jr-k/obscreen.git git clone https://github.com/jr-k/obscreen.git
cd obscreen cd obscreen
chown -R $USER:$USER ./
# Install application dependencies # Install application dependencies
python3 -m venv venv python3 -m venv venv
@ -38,6 +37,9 @@ cp .env.dist .env
# Add user to needed group # Add user to needed group
usermod -aG plugdev $OWNER usermod -aG plugdev $OWNER
# Fix permissions
chown -R $OWNER:$OWNER ./
# ============================================================ # ============================================================
# Automount script for external storage # Automount script for external storage
# ============================================================ # ============================================================

View File

@ -2,11 +2,11 @@
<h2> <h2>
{{ l.common_pick_element }} {{ l.common_pick_element }}
</h2> </h2>
{% with use_href=False %} {% with use_href=False %}
{% include 'fleet/node-players/component/explr-sidebar.jinja.html' %} {% include 'fleet/node-players/component/explr-sidebar.jinja.html' %}
{% endwith %} {% endwith %}
<div class="actions"> <div class="actions">
<button type="button" class="btn btn-naked picker-close"> <button type="button" class="btn btn-naked picker-close">
<i class="fa fa-close icon-left"></i>{{ l.common_close }} <i class="fa fa-close icon-left"></i>{{ l.common_close }}

View File

@ -14,6 +14,7 @@
.slide, iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding-top: 0; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; } .slide, iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding-top: 0; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
.slide iframe { background: white; } .slide iframe { background: white; }
.slide img, .slide video { height: 100%; } .slide img, .slide video { height: 100%; }
.slide video { width: 100%; height: 100%; }
</style> </style>
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/utils.js"></script> <script type="application/javascript" src="{{ STATIC_PREFIX }}js/utils.js"></script>
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/lib/is-cron-now.js"></script> <script type="application/javascript" src="{{ STATIC_PREFIX }}js/lib/is-cron-now.js"></script>
@ -82,6 +83,7 @@
let curSlide = secondSlide; let curSlide = secondSlide;
let nextSlide = firstSlide; let nextSlide = firstSlide;
let notificationItemIndex = null; let notificationItemIndex = null;
let pausableContent = null;
// Functions // Functions
const itemsLoadedProcess = function() { const itemsLoadedProcess = function() {
@ -126,6 +128,10 @@
const resume = function() { const resume = function() {
playState = PLAY_STATE_PLAYING; playState = PLAY_STATE_PLAYING;
if (pausableContent) {
pausableContent.play();
}
}; };
const play = function() { const play = function() {
@ -135,6 +141,10 @@
const pause = function() { const pause = function() {
pauseClockValue = clockValue; pauseClockValue = clockValue;
playState = PLAY_STATE_PAUSE; playState = PLAY_STATE_PAUSE;
if (pausableContent) {
pausableContent.pause();
}
}; };
const stop = function() { const stop = function() {
@ -203,7 +213,7 @@
let duration = item.duration; let duration = item.duration;
if (durationsOverride[item.id]) { if (durationsOverride[item.id] !== undefined) {
duration = durationsOverride[item.id]; duration = durationsOverride[item.id];
} }
@ -250,17 +260,15 @@
if (i === curItemIndex) { if (i === curItemIndex) {
secondsBeforeNext = accumulatedTime + safe_duration(item) - timeInCurrentLoop; secondsBeforeNext = accumulatedTime + safe_duration(item) - timeInCurrentLoop;
//console.log("remaining:", secondsBeforeNext, "clock:",clockValue, curItemIndex); //console.log("id", item.id, "secondsBeforeNext:", secondsBeforeNext, "clock:", clockValue, "clockLoopDration", timeInCurrentLoop, "<", accumulatedTime , '+', safe_duration(item));
} }
if (timeInCurrentLoop < accumulatedTime + safe_duration(item)) { if (timeInCurrentLoop < accumulatedTime + safe_duration(item)) {
if (curItemIndex !== i) { if (curItemIndex !== i) {
//console.log('change to ', i , item.name)
curItemIndex = i; curItemIndex = i;
const emptySlide = getEmptySlide(); const emptySlide = getEmptySlide();
if ((emptySlide && !hasMoveOnce) || forcePreload) { if ((emptySlide && !hasMoveOnce) || forcePreload) {
//console.log('init preload');
if (!hasMoveOnce && syncWithTime) { if (!hasMoveOnce && syncWithTime) {
if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) { if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) {
// Prevent glitch when syncWithTime for first init // Prevent glitch when syncWithTime for first init
@ -386,10 +394,13 @@
delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0; delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0;
video.addEventListener('loadedmetadata', function() { video.addEventListener('loadedmetadata', function() {
if (item.duration !== video.duration && item.delegate_duration) { if (item.duration !== video.duration && !item.delegate_duration) {
durationsOverride[item.id] = video.duration;
console.warn('Given duration ' + item.duration + 's is different from video file ' + Math.ceil(video.duration) + 's'); console.warn('Given duration ' + item.duration + 's is different from video file ' + Math.ceil(video.duration) + 's');
} }
if (item.delegate_duration) {
durationsOverride[item.id] = Math.ceil(video.duration);
}
}); });
const autoplayLoader = function() { const autoplayLoader = function() {
@ -400,12 +411,14 @@
if (element.innerHTML.match('<video>')) { if (element.innerHTML.match('<video>')) {
if (!previewMode) { if (!previewMode) {
setTimeout(function() { setTimeout(function() {
video.play(); video.play();
pausableContent = video;
}, 1000); }, 1000);
} }
} }
} }
setTimeout(autoplayLoader, delayNoisyContentJIT);
setTimeout(autoplayLoader, delayNoisyContentJIT);
} }
const checkAndMoveNotifications = function() { const checkAndMoveNotifications = function() {

View File

@ -14,10 +14,10 @@
{{ render_folder(child) }} {{ render_folder(child) }}
{% endfor %} {% endfor %}
{% for content in content_children %} {% for content in content_children %}
{% set slides = slides_with_content[content.id]|default([]) %} {% set slides = slides_with_content[content.id]|default([]) if slides_with_content else [] %}
{% set icon = enum_content_type.get_fa_icon(content.type) %} {% set icon = enum_content_type.get_fa_icon(content.type) %}
{% set color = enum_content_type.get_color_icon(content.type) %} {% set color = enum_content_type.get_color_icon(content.type) %}
<li class="explr-item" data-entity-json="{{ content.to_json() }}"> <li class="explr-item" data-entity-json="{{ content.to_json({'classIcon': icon, 'classColor': color}) }}">
<i class="fa {{ icon }} {{ color }}"></i> <i class="fa {{ icon }} {{ color }}"></i>
{% if slides|length > 0 %} {% if slides|length > 0 %}
<sub> <sub>

View File

@ -6,8 +6,8 @@
{% endblock %} {% endblock %}
{% block add_css %} {% block add_css %}
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css"/>
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/> <link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/>
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css"/>
{{ HOOK(H_SLIDESHOW_CONTENT_CSS) }} {{ HOOK(H_SLIDESHOW_CONTENT_CSS) }}
{% endblock %} {% endblock %}
@ -17,6 +17,7 @@
<script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script> <script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script>
{# <script src="{{ STATIC_PREFIX }} js/lib/jquery-ui-rotatable.min.js"></script> #} {# <script src="{{ STATIC_PREFIX }} js/lib/jquery-ui-rotatable.min.js"></script> #}
<script src="{{ STATIC_PREFIX }}js/slideshow/content-composition.js"></script> <script src="{{ STATIC_PREFIX }}js/slideshow/content-composition.js"></script>
<script src="{{ STATIC_PREFIX }}js/explorer.js"></script>
{{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }} {{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }}
{% endblock %} {% endblock %}
@ -109,7 +110,7 @@
<div class="inner"> <div class="inner">
<div class="toolbar"> <div class="toolbar">
<button id="presetGrid2x2">Grid 2x2</button> <button id="presetGrid2x2">Grid 2x2</button>
<button id="addElement">Add Element</button> <button id="addElement" class="content-explr-picker">Add Element</button>
<button id="removeAllElements">Remove All Elements</button> <button id="removeAllElements">Remove All Elements</button>
</div> </div>
@ -123,23 +124,55 @@
<div class="page-panel right-panel"> <div class="page-panel right-panel">
<div class="form-element-properties"> <div class="form-element-properties hidden">
<form id="elementForm"> <form id="elementForm">
<h3>Element Properties</h3> <h3>Element Properties</h3>
<label for="elem-x">X:</label>
<input type="number" id="elem-x" name="elem-x"><br> <div class="form-group">
<label for="elem-y">Y:</label> <label for="elem-x">Position X</label>
<input type="number" id="elem-y" name="elem-y"><br> <div class="widget">
<label for="elem-width">Width:</label> <input type="number" id="elem-x" name="elem-x">
<input type="number" id="elem-width" name="elem-width"><br> </div>
<label for="elem-height">Height:</label> </div>
<input type="number" id="elem-height" name="elem-height"><br>
<!--<label for="elem-rotate">Rotate (deg):</label>--> <div class="form-group">
<!--<input type="number" id="elem-rotate" name="elem-rotate"><br>--> <label for="elem-y">Position Y</label>
<div class="widget">
<input type="number" id="elem-y" name="elem-y">
</div>
</div>
<div class="form-group">
<label for="elem-width">Width</label>
<div class="widget">
<input type="number" id="elem-width" name="elem-width">
</div>
</div>
<div class="form-group">
<label for="elem-height">Height</label>
<div class="widget">
<input type="number" id="elem-height" name="elem-height">
</div>
</div>
{# <div class="form-group">#}
{# <label for="elem-rotate">Rotate (deg)</label>#}
{# <div class="widget">#}
{# <input type="number" id="elem-rotate" name="elem-rotate">#}
{# </div>#}
{# </div>#}
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div class="pickers hidden">
<div class="modals-outer">
<div class="modals-inner">
{% include 'slideshow/contents/modal/explr-picker.jinja.html' %}
</div>
</div>
</div>
{% endblock %} {% endblock %}