feat: Implement Dual Video Pipeline (Direct Server API Upload & Bunny.net Stream Cloud CDN + DRM) with Socratic interactive player
This commit is contained in:
@@ -859,7 +859,8 @@ class StudentPortal
|
||||
video.addEventListener('timeupdate', () => {
|
||||
const cur = Math.floor(video.currentTime);
|
||||
const dur = Math.floor(video.duration || 596);
|
||||
document.getElementById('video_time_display').textContent = `${formatTime(cur)} / ${formatTime(dur)}`;
|
||||
const timeEl = document.getElementById('video_time_display');
|
||||
if (timeEl) timeEl.textContent = `${formatTime(cur)} / ${formatTime(dur)}`;
|
||||
|
||||
// Trigger checkpoint at second 15 automatically once
|
||||
if (cur === 15 && !checkpointTriggered) {
|
||||
@@ -877,6 +878,84 @@ class StudentPortal
|
||||
return `${m}:${s}`;
|
||||
}
|
||||
|
||||
function triggerCheckpointDemo() {
|
||||
const video = document.getElementById('lesson_video_player');
|
||||
if (video) {
|
||||
video.currentTime = 14;
|
||||
video.play();
|
||||
checkpointTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCheckpointAnswer(btn, isCorrect) {
|
||||
const feedback = document.getElementById('checkpoint_feedback');
|
||||
const video = document.getElementById('lesson_video_player');
|
||||
const btns = document.querySelectorAll('#socratic_quiz_modal .quiz-option-btn');
|
||||
|
||||
if (isCorrect) {
|
||||
btn.style.background = 'rgba(16, 185, 129, 0.25)';
|
||||
btn.style.borderColor = '#10B981';
|
||||
feedback.style.color = '#34D399';
|
||||
feedback.style.display = 'block';
|
||||
feedback.innerHTML = '✓ إجابة ممتازة وصحيحة 100%! سيتم استئناف الشرح فوراً...';
|
||||
|
||||
playChimeNotification();
|
||||
setTimeout(() => {
|
||||
document.getElementById('socratic_quiz_modal').style.display = 'none';
|
||||
feedback.style.display = 'none';
|
||||
btns.forEach(b => {
|
||||
b.style.background = '';
|
||||
b.style.borderColor = '';
|
||||
});
|
||||
if (video) video.play();
|
||||
}, 1500);
|
||||
} else {
|
||||
btn.style.background = 'rgba(239, 68, 68, 0.25)';
|
||||
btn.style.borderColor = '#EF4444';
|
||||
feedback.style.color = '#F87171';
|
||||
feedback.style.display = 'block';
|
||||
feedback.innerHTML = '⚠️ إجابة غير دقيقة. سيتم إرجاعك 45 ثانية لمراجعة الفكرة وتثبيت الفهم!';
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('socratic_quiz_modal').style.display = 'none';
|
||||
feedback.style.display = 'none';
|
||||
btns.forEach(b => {
|
||||
b.style.background = '';
|
||||
b.style.borderColor = '';
|
||||
});
|
||||
if (video) {
|
||||
video.currentTime = Math.max(0, video.currentTime - 45);
|
||||
video.play();
|
||||
checkpointTriggered = false;
|
||||
}
|
||||
}, 2200);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStudentLessonPlayback(lessonId = 1) {
|
||||
const token = localStorage.getItem('saqel_student_jwt');
|
||||
if (!token) return;
|
||||
try {
|
||||
const res = await fetch(`/api/lessons/${lessonId}/playback`, {
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success' && data.data?.playback) {
|
||||
const pb = data.data.playback;
|
||||
const video = document.getElementById('lesson_video_player');
|
||||
if (video) {
|
||||
if (pb.storage_type === 'api_upload' && pb.stream_url) {
|
||||
video.src = pb.stream_url;
|
||||
} else if (pb.hls_url) {
|
||||
video.src = pb.hls_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Load lesson playback notice:', e);
|
||||
}
|
||||
}
|
||||
|
||||
let wsPingInterval = null;
|
||||
|
||||
// 1. Initialize Real-time WebSocket (Workerman)
|
||||
|
||||
Reference in New Issue
Block a user