Added some functionality so we can modify the size of the playing video
This commit is contained in:
@@ -47,8 +47,14 @@ DEFAULT_SETTINGS = {
|
||||
'card_style': 'flat', # 'flat' | 'elevated' | 'bordered'
|
||||
'corner_radius': 'rounded', # 'sharp' | 'rounded' | 'pill'
|
||||
'library_path': '', # '' = use COURSES_LIBRARY_PATH/--library-path default
|
||||
'video_width': '', # '' = responsive full-width; else last dragged size, in px
|
||||
'video_height': '',
|
||||
}
|
||||
|
||||
# Bounds for the persisted video player size, to reject garbage values
|
||||
# without capping how big/small someone can reasonably drag it.
|
||||
VIDEO_SIZE_BOUNDS = {'video_width': (200, 4000), 'video_height': (120, 3000)}
|
||||
|
||||
# Allowed values for every setting except accent_color (validated separately
|
||||
# as a hex string). Anything outside these sets is rejected/ignored on save.
|
||||
SETTINGS_CHOICES = {
|
||||
@@ -91,6 +97,9 @@ def load_settings() -> Dict[str, Any]:
|
||||
# Lenient on load (directory may be transiently
|
||||
# unavailable at startup) - only validated on save.
|
||||
settings[key] = value
|
||||
elif key in VIDEO_SIZE_BOUNDS:
|
||||
if value == '' or (isinstance(value, int) and not isinstance(value, bool)):
|
||||
settings[key] = value
|
||||
elif key in SETTINGS_CHOICES and value in SETTINGS_CHOICES[key]:
|
||||
settings[key] = value
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
@@ -109,6 +118,18 @@ def save_settings(new_settings: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if value and not os.path.isdir(value):
|
||||
raise ValueError(f"Directory not found: {value}")
|
||||
current[key] = value
|
||||
elif key in VIDEO_SIZE_BOUNDS:
|
||||
if value in (None, ''):
|
||||
current[key] = ''
|
||||
continue
|
||||
try:
|
||||
num = int(value)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError(f"{key} must be a number")
|
||||
lo, hi = VIDEO_SIZE_BOUNDS[key]
|
||||
if not (lo <= num <= hi):
|
||||
raise ValueError(f"{key} must be between {lo} and {hi}")
|
||||
current[key] = num
|
||||
elif key in SETTINGS_CHOICES and value in SETTINGS_CHOICES[key]:
|
||||
current[key] = value
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
|
||||
@@ -130,11 +130,25 @@
|
||||
}
|
||||
video, audio {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
max-width: 100%;
|
||||
border-radius: var(--radius);
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
video {
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
object-fit: contain;
|
||||
background: #000;
|
||||
min-width: 320px;
|
||||
min-height: 200px;
|
||||
}
|
||||
.resize-hint {
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
color: var(--text-muted);
|
||||
margin-top: 6px;
|
||||
}
|
||||
.content {
|
||||
margin: 20px 0;
|
||||
}
|
||||
@@ -246,6 +260,7 @@
|
||||
{% endif %}
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<p class="resize-hint">↘ Drag the bottom-right corner to resize the player</p>
|
||||
{% endif %}
|
||||
|
||||
{% if lesson.audio_file %}
|
||||
@@ -372,6 +387,44 @@
|
||||
const activeMedia = video || audio;
|
||||
let isCompleted = false;
|
||||
|
||||
// Restore last-used video player size, and persist future resizes
|
||||
// (dragging the native corner handle) so it sticks next time.
|
||||
if (video) {
|
||||
let applyingStoredSize = true;
|
||||
let resizeSaveTimeout = null;
|
||||
|
||||
fetch('/api/settings')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const s = data.settings || {};
|
||||
if (s.video_width && s.video_height) {
|
||||
video.style.width = s.video_width + 'px';
|
||||
video.style.height = s.video_height + 'px';
|
||||
}
|
||||
setTimeout(() => { applyingStoredSize = false; }, 300);
|
||||
})
|
||||
.catch(() => { applyingStoredSize = false; });
|
||||
|
||||
if (window.ResizeObserver) {
|
||||
const observer = new ResizeObserver(() => {
|
||||
if (applyingStoredSize) return;
|
||||
clearTimeout(resizeSaveTimeout);
|
||||
resizeSaveTimeout = setTimeout(() => {
|
||||
const rect = video.getBoundingClientRect();
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
video_width: Math.round(rect.width),
|
||||
video_height: Math.round(rect.height)
|
||||
})
|
||||
}).catch(() => {});
|
||||
}, 500);
|
||||
});
|
||||
observer.observe(video);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeMedia) {
|
||||
let lastSaveTime = 0;
|
||||
|
||||
|
||||
@@ -331,6 +331,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Video Player</h2>
|
||||
<div class="setting-row">
|
||||
<label>Player size
|
||||
<span class="setting-desc">
|
||||
{% if settings.video_width and settings.video_height %}
|
||||
Remembered at {{ settings.video_width }}×{{ settings.video_height }}px from your last resize
|
||||
{% else %}
|
||||
Using default responsive full-width sizing
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
<button class="btn btn-secondary" onclick="resetVideoSize()">Reset size</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" onclick="resetSettings()">Reset to Defaults</button>
|
||||
<span id="save-status">✓ Saved</span>
|
||||
@@ -425,6 +441,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
function resetVideoSize() {
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ video_width: '', video_height: '' })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) location.reload();
|
||||
})
|
||||
.catch(err => console.error('Failed to reset video size:', err));
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
fetch('/api/settings/reset', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
|
||||
Reference in New Issue
Block a user