Add autoplay, progress rings, duplicate-lesson detection, title-card thumbnails
- Auto-play next lesson on end, with a cancelable countdown and a Settings toggle (default on). - Grid-view course cards show a small progress ring (checkmark at 100%) instead of a separate bar. - Duplicate Lesson Files: scans within each course/folder for media files that look like the same lesson downloaded twice, with per-file delete and a shared ignore list with Duplicate Courses. - Auto-generated cover art now samples a few early candidate frames and keeps the largest JPEG, favoring an intro title card over a blank fade-in or a plain presenter frame. - Settings -> "Regenerate Thumbnails" re-runs that logic for every course with an auto-generated thumbnail (never touches manual covers), so already-cached thumbnails can pick up the improvement. - Add .gitignore for __pycache__/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,17 @@
|
||||
color: var(--text-muted);
|
||||
margin-top: 6px;
|
||||
}
|
||||
.autoplay-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-top: 10px;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.speed-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -539,6 +550,13 @@
|
||||
<p class="resize-hint">↘ Drag the bottom-right corner to resize the player</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next_lesson %}
|
||||
<div class="autoplay-banner" id="autoplay-banner" style="display: none;">
|
||||
<span>Playing next lesson in <span id="autoplay-countdown">5</span>s…</span>
|
||||
<button type="button" class="btn btn-secondary" onclick="cancelAutoplay()">Cancel</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if lesson.audio_file %}
|
||||
<h3>Audio</h3>
|
||||
<audio controls preload="metadata" id="audio-player">
|
||||
@@ -800,13 +818,16 @@
|
||||
// Restore the persisted playback speed and highlight the active
|
||||
// button; clicking a button applies it immediately and persists
|
||||
// it via /api/settings, mirroring the video-size pattern above.
|
||||
let autoplayNextEnabled = true;
|
||||
if (activeMedia) {
|
||||
fetch('/api/settings')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const speed = (data.settings || {}).playback_speed || '1';
|
||||
const settings = data.settings || {};
|
||||
const speed = settings.playback_speed || '1';
|
||||
activeMedia.playbackRate = parseFloat(speed);
|
||||
highlightSpeedButton(speed);
|
||||
autoplayNextEnabled = settings.autoplay_next !== false;
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
@@ -827,6 +848,8 @@
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
const NEXT_LESSON_URL = {{ ('/lesson/' ~ next_lesson)|tojson if next_lesson else 'null' }};
|
||||
|
||||
// ---- Outline topic chooser + push-on-leave ----
|
||||
const LESSON_PATH = {{ lesson_path|tojson }};
|
||||
const LESSON_TITLE = {{ lesson.title|tojson }};
|
||||
@@ -1210,7 +1233,43 @@
|
||||
activeMedia.addEventListener('ended', function() {
|
||||
saveProgress(activeMedia.currentTime, true);
|
||||
markAsCompleted();
|
||||
if (NEXT_LESSON_URL && autoplayNextEnabled) startAutoplayCountdown();
|
||||
});
|
||||
|
||||
// Replaying the just-ended lesson (rather than letting it sit)
|
||||
// is a clear enough signal the user doesn't want to leave yet.
|
||||
activeMedia.addEventListener('play', cancelAutoplay);
|
||||
}
|
||||
|
||||
let autoplayTimer = null;
|
||||
let autoplaySecondsLeft = 5;
|
||||
|
||||
function startAutoplayCountdown() {
|
||||
const banner = document.getElementById('autoplay-banner');
|
||||
const countdownEl = document.getElementById('autoplay-countdown');
|
||||
if (!banner || !countdownEl) return;
|
||||
autoplaySecondsLeft = 5;
|
||||
countdownEl.textContent = autoplaySecondsLeft;
|
||||
banner.style.display = 'flex';
|
||||
autoplayTimer = setInterval(function() {
|
||||
autoplaySecondsLeft -= 1;
|
||||
if (autoplaySecondsLeft <= 0) {
|
||||
clearInterval(autoplayTimer);
|
||||
autoplayTimer = null;
|
||||
window.location.href = NEXT_LESSON_URL;
|
||||
return;
|
||||
}
|
||||
countdownEl.textContent = autoplaySecondsLeft;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function cancelAutoplay() {
|
||||
if (autoplayTimer) {
|
||||
clearInterval(autoplayTimer);
|
||||
autoplayTimer = null;
|
||||
}
|
||||
const banner = document.getElementById('autoplay-banner');
|
||||
if (banner) banner.style.display = 'none';
|
||||
}
|
||||
|
||||
function saveProgress(progressSeconds, completed = false) {
|
||||
|
||||
Reference in New Issue
Block a user