commit
e801485ec1
@ -30,13 +30,21 @@ class PlayerController(ObController):
|
||||
self._app.add_url_rule('/serve/content/<content_type>/<content_id>/<content_location>', 'serve_content_file', self.serve_content_file, 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_slug_or_id = self._get_dynamic_playlist_id(playlist_slug_or_id)
|
||||
|
||||
current_playlist = self._model_store.playlist().get_one_by("slug = ? OR id = ?", {
|
||||
query = " (slug = ? OR id = ?) "
|
||||
query_args = {
|
||||
"slug": playlist_slug_or_id,
|
||||
"id": playlist_slug_or_id
|
||||
})
|
||||
"id": playlist_slug_or_id,
|
||||
}
|
||||
|
||||
if not preview_playlist:
|
||||
query = query + " AND enabled = ? "
|
||||
query_args["enabled"] = True
|
||||
|
||||
current_playlist = self._model_store.playlist().get_one_by(query, query_args)
|
||||
|
||||
if playlist_slug_or_id and not current_playlist:
|
||||
return abort(404)
|
||||
|
||||
@ -28,7 +28,7 @@ class ContentManager(ModelManager):
|
||||
"name CHAR(255)",
|
||||
"type CHAR(30)",
|
||||
"location TEXT",
|
||||
"duration INTEGER",
|
||||
"duration FLOAT",
|
||||
"folder_id INTEGER",
|
||||
"created_by CHAR(255)",
|
||||
"updated_by CHAR(255)",
|
||||
|
||||
@ -3,6 +3,7 @@ import os
|
||||
from typing import Dict, Optional, List, Tuple, Union
|
||||
|
||||
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.manager.DatabaseManager import DatabaseManager
|
||||
from src.manager.SlideManager import SlideManager
|
||||
@ -69,15 +70,17 @@ class PlaylistManager(ModelManager):
|
||||
durations = self._db.execute_read_query("""
|
||||
SELECT
|
||||
playlist_id,
|
||||
SUM(CASE
|
||||
ROUND(SUM(CASE
|
||||
WHEN s.delegate_duration = 1 THEN c.duration
|
||||
WHEN c.type = '{}' THEN s.duration
|
||||
ELSE s.duration
|
||||
END) AS total_duration
|
||||
END)) AS total_duration
|
||||
FROM {} s
|
||||
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;
|
||||
""".format(
|
||||
ContentType.EXTERNAL_STORAGE.value,
|
||||
SlideManager.TABLE_NAME,
|
||||
ContentManager.TABLE_NAME,
|
||||
"{}".format(
|
||||
|
||||
@ -9,7 +9,7 @@ from src.util.utils import str_to_enum
|
||||
|
||||
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._id = id if id else None
|
||||
self._location = location
|
||||
@ -88,11 +88,11 @@ class Content:
|
||||
self._folder_id = value
|
||||
|
||||
@property
|
||||
def duration(self) -> Optional[int]:
|
||||
def duration(self) -> Optional[float]:
|
||||
return self._duration
|
||||
|
||||
@duration.setter
|
||||
def duration(self, value: Optional[int]):
|
||||
def duration(self, value: Optional[float]):
|
||||
self._duration = value
|
||||
|
||||
@property
|
||||
|
||||
@ -10,9 +10,9 @@ def mp4_duration_with_ffprobe(filename):
|
||||
fields = json.loads(result)['streams'][0]
|
||||
|
||||
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:
|
||||
return int(float(fields['duration']))
|
||||
return round(float(fields['duration']), 2)
|
||||
|
||||
return 0
|
||||
|
||||
@ -251,6 +251,7 @@ def slugify(value):
|
||||
|
||||
|
||||
def seconds_to_hhmmss(seconds):
|
||||
seconds = int(seconds)
|
||||
if not seconds:
|
||||
return ""
|
||||
hours = seconds // 3600
|
||||
|
||||
@ -1 +1 @@
|
||||
2.4.0
|
||||
2.4.1
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -65,7 +65,7 @@
|
||||
{% if current_player_group.playlist_id %}
|
||||
<div class="preview-holder">
|
||||
{% set base_url = external_url if external_url else request.scheme ~ '://' ~ request.headers.get('host') %}
|
||||
{% set preview_url = base_url ~ url_for('player_use', playlist_slug_or_id=current_player_group.playlist_id) %}
|
||||
{% set preview_url_iframe = base_url ~ url_for('player_use', playlist_slug_or_id=current_player_group.playlist_id, preview_playlist=1, intro=0, animation=0) %}
|
||||
|
||||
<h4 class="divide">
|
||||
Iframe
|
||||
@ -77,7 +77,7 @@
|
||||
<div class="preview">
|
||||
<button type="button" class="btn btn-pixel node-player-group-preview"
|
||||
title="{{ l.playlist_panel_preview_action }}"
|
||||
data-url="{{ preview_url }}?intro=0&animation=0">
|
||||
data-url="{{ preview_url_iframe }}">
|
||||
<i class="fa fa-play"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -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 { background: white; }
|
||||
.slide img, .slide video { height: 100%; }
|
||||
.slide video { width: 100%; height: 100%; }
|
||||
</style>
|
||||
<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>
|
||||
@ -82,6 +83,7 @@
|
||||
let curSlide = secondSlide;
|
||||
let nextSlide = firstSlide;
|
||||
let notificationItemIndex = null;
|
||||
let pausableContent = null;
|
||||
|
||||
// Functions
|
||||
const itemsLoadedProcess = function() {
|
||||
@ -126,6 +128,10 @@
|
||||
|
||||
const resume = function() {
|
||||
playState = PLAY_STATE_PLAYING;
|
||||
|
||||
if (pausableContent) {
|
||||
pausableContent.play();
|
||||
}
|
||||
};
|
||||
|
||||
const play = function() {
|
||||
@ -135,6 +141,10 @@
|
||||
const pause = function() {
|
||||
pauseClockValue = clockValue;
|
||||
playState = PLAY_STATE_PAUSE;
|
||||
|
||||
if (pausableContent) {
|
||||
pausableContent.pause();
|
||||
}
|
||||
};
|
||||
|
||||
const stop = function() {
|
||||
@ -203,7 +213,7 @@
|
||||
|
||||
let duration = item.duration;
|
||||
|
||||
if (durationsOverride[item.id]) {
|
||||
if (durationsOverride[item.id] !== undefined) {
|
||||
duration = durationsOverride[item.id];
|
||||
}
|
||||
|
||||
@ -250,17 +260,15 @@
|
||||
|
||||
if (i === curItemIndex) {
|
||||
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 (curItemIndex !== i) {
|
||||
//console.log('change to ', i , item.name)
|
||||
curItemIndex = i;
|
||||
|
||||
const emptySlide = getEmptySlide();
|
||||
if ((emptySlide && !hasMoveOnce) || forcePreload) {
|
||||
//console.log('init preload');
|
||||
if (!hasMoveOnce && syncWithTime) {
|
||||
if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) {
|
||||
// Prevent glitch when syncWithTime for first init
|
||||
@ -386,10 +394,13 @@
|
||||
delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0;
|
||||
|
||||
video.addEventListener('loadedmetadata', function() {
|
||||
if (item.duration !== video.duration && item.delegate_duration) {
|
||||
durationsOverride[item.id] = video.duration;
|
||||
if (item.duration !== video.duration && !item.delegate_duration) {
|
||||
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() {
|
||||
@ -400,12 +411,14 @@
|
||||
if (element.innerHTML.match('<video>')) {
|
||||
if (!previewMode) {
|
||||
setTimeout(function() {
|
||||
video.play();
|
||||
video.play();
|
||||
pausableContent = video;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(autoplayLoader, delayNoisyContentJIT);
|
||||
|
||||
setTimeout(autoplayLoader, delayNoisyContentJIT);
|
||||
}
|
||||
|
||||
const checkAndMoveNotifications = function() {
|
||||
|
||||
@ -84,6 +84,7 @@
|
||||
<div class="preview-holder">
|
||||
{% set base_url = external_url if external_url else request.scheme ~ '://' ~ request.headers.get('host') %}
|
||||
{% set preview_url = base_url ~ url_for('player_use', playlist_slug_or_id=current_playlist.slug) %}
|
||||
{% set preview_url_iframe = base_url ~ url_for('player_use', playlist_slug_or_id=current_playlist.slug, preview_playlist=1, intro=0, animation=0) %}
|
||||
|
||||
<h4 class="divide">
|
||||
URL
|
||||
@ -123,7 +124,7 @@
|
||||
<div class="preview">
|
||||
<button type="button" class="btn btn-pixel playlist-preview"
|
||||
title="{{ l.playlist_panel_preview_action }}"
|
||||
data-url="{{ preview_url }}?intro=0&animation=0">
|
||||
data-url="{{ preview_url_iframe }}">
|
||||
<i class="fa fa-play"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user