feat: Implement Server-Side FFmpeg HLS Transcoding (.m3u8), HLS.js streaming, and dynamic Socratic In-Video Checkpoint Engine

This commit is contained in:
Hamza-Ayed
2026-08-28 01:30:56 +03:00
parent 8044f86798
commit 54ceab1aae
5 changed files with 420 additions and 85 deletions
+104 -13
View File
@@ -546,21 +546,22 @@ class TeacherPortal
<!-- IN-VIDEO SOCRATIC CHECKPOINTS -->
<div style="background: rgba(15, 23, 42, 0.8); border: 1px solid var(--border); border-radius: 18px; padding: 24px;">
<h4 style="font-size: 15px; font-weight: 800; color: var(--accent-cyan); margin-bottom: 12px;">+ تثبيت نقطة فحص معرفي سقراطي (Socratic Checkpoint) في الفيديو</h4>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; flex-wrap: wrap; gap: 8px;">
<h4 style="font-size: 15px; font-weight: 800; color: var(--accent-cyan);">+ تثبيت نقطة فحص معرفي سقراطي (Socratic Checkpoint) في الفيديو</h4>
<span style="font-size: 11px; background: rgba(56,189,248,0.1); color: var(--accent-cyan); padding: 3px 10px; border-radius: 6px;">محرك التثبيت الصدمي والعلاجي 🧠</span>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px;">
<div class="form-group">
<label class="form-label">الدرس المستهدف</label>
<select id="checkpoint_lesson_select" class="input-text">
<option value="1">الرياضيات العلمي — الدرس 1: قواعد الاشتقاق الأساسية</option>
<option value="1">الدرس 1: قواعد الاشتقاق الأساسية</option>
<option value="2">الدرس 2: مشتقات الاقترانات المثلثية</option>
</select>
</div>
<div class="form-group">
<label class="form-label">توقيت ظهور السؤال (دقيقة:ثانية)</label>
<input type="text" id="checkpoint_timestamp" value="00:15" class="input-text" style="color: var(--accent-cyan); font-family: monospace;">
</div>
<div class="form-group">
<label class="form-label">نص السؤال السقراطي</label>
<input type="text" id="checkpoint_question_input" value="إذا كان f(x) = sin(3x)، فما هي قيمة المشتقة f'(x)؟" class="input-text">
<input type="text" id="checkpoint_timestamp" value="00:15" placeholder="00:15" class="input-text" style="color: var(--accent-cyan); font-family: monospace;">
</div>
<div class="form-group">
<label class="form-label">عقوبة الإرجاع عند الخطأ</label>
@@ -571,7 +572,43 @@ class TeacherPortal
</select>
</div>
</div>
<button type="button" onclick="handleSaveCheckpoint()" class="btn-primary" style="width: auto; padding: 10px 28px; font-size: 13px; margin-top: 8px;">تثبيت النقطة في محرك التثبيت المعرفي ✓</button>
<div class="form-group" style="margin-top: 8px;">
<label class="form-label">نص السؤال السقراطي الفوري</label>
<input type="text" id="checkpoint_question_input" value="إذا كان f(x) = sin(3x)، فما هي قيمة المشتقة f'(x)؟" placeholder="اكتب نص السؤال لفحص فهم الفكرة..." class="input-text">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;">
<div class="form-group">
<label class="form-label">الخيار (أ)</label>
<input type="text" id="cp_opt_0" value="3 cos(3x)" class="input-text">
</div>
<div class="form-group">
<label class="form-label">الخيار (ب)</label>
<input type="text" id="cp_opt_1" value="cos(3x)" class="input-text">
</div>
<div class="form-group">
<label class="form-label">الخيار (ج)</label>
<input type="text" id="cp_opt_2" value="-3 cos(3x)" class="input-text">
</div>
<div class="form-group">
<label class="form-label">الخيار (د)</label>
<input type="text" id="cp_opt_3" value="3 sin(3x)" class="input-text">
</div>
</div>
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 14px; flex-wrap: wrap; gap: 12px;">
<div class="form-group" style="margin: 0; min-width: 220px;">
<label class="form-label">الإجابة الصحيحة المعتمدة</label>
<select id="cp_correct_idx" class="input-text" style="color: var(--accent-gold); font-weight: 700;">
<option value="0">الخيار (أ) هو الصحيح ✓</option>
<option value="1">الخيار (ب) هو الصحيح ✓</option>
<option value="2">الخيار (ج) هو الصحيح ✓</option>
<option value="3">الخيار (د) هو الصحيح ✓</option>
</select>
</div>
<button type="button" id="btn_save_checkpoint" onclick="handleSaveCheckpoint()" class="btn-primary" style="width: auto; padding: 12px 32px; font-size: 13px;">تثبيت النقطة في محرك التثبيت المعرفي ✓</button>
</div>
</div>
</div>
@@ -1467,12 +1504,66 @@ class TeacherPortal
}
}
function handleSaveCheckpoint() {
async function handleSaveCheckpoint() {
const token = getAuthToken();
const lessonId = document.getElementById('checkpoint_lesson_select').value;
const ts = document.getElementById('checkpoint_timestamp').value;
const question = document.getElementById('checkpoint_question_input').value;
const rewind = document.getElementById('checkpoint_rewind_select').value;
alert(`✅ تم تثبيت نقطة الفحص السقراطي بنجاح في الدرس (${lessonId}) عند التوقيت (${ts}) بعقوبة إرجاع (${rewind} ثانية) وحفظ السؤال في محرك التثبيت المعرفي!`);
const tsStr = document.getElementById('checkpoint_timestamp').value.trim();
const question = document.getElementById('checkpoint_question_input').value.trim();
const rewind = parseInt(document.getElementById('checkpoint_rewind_select').value) || 45;
const correctIdx = parseInt(document.getElementById('cp_correct_idx').value) || 0;
// Parse MM:SS to seconds
let seconds = 15;
if (tsStr.includes(':')) {
const parts = tsStr.split(':');
seconds = (parseInt(parts[0]) || 0) * 60 + (parseInt(parts[1]) || 0);
} else {
seconds = parseInt(tsStr) || 15;
}
const options = [
document.getElementById('cp_opt_0').value.trim() || 'الخيار الأول',
document.getElementById('cp_opt_1').value.trim() || 'الخيار الثاني',
document.getElementById('cp_opt_2').value.trim() || 'الخيار الثالث',
document.getElementById('cp_opt_3').value.trim() || 'الخيار الرابع'
];
const btn = document.getElementById('btn_save_checkpoint');
btn.disabled = true;
btn.textContent = 'جارٍ الحفظ والتثبيت... ⏳';
try {
const res = await fetch('/api/teacher/lessons/checkpoints', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({
lesson_id: lessonId,
timestamp_seconds: seconds,
rewind_seconds: rewind,
question_text: question,
options: options,
correct_index: correctIdx
})
});
const data = await res.json();
btn.disabled = false;
btn.textContent = 'تثبيت النقطة في محرك التثبيت المعرفي ✓';
if (res.ok && data.status === 'success') {
showLuxuryToast('تم تثبيت النقطة السقراطية 🧠', `الدرس (${lessonId}) عند التوقيت (${tsStr})`);
alert('✅ ' + data.message + `\nالتوقيت: ${tsStr} (ثانية: ${seconds})\nعقوبة الإرجاع: ${rewind} ثانية`);
} else {
alert('❌ خطأ: ' + (data.message || 'فشل حفظ نقطة الفحص'));
}
} catch (err) {
btn.disabled = false;
btn.textContent = 'تثبيت النقطة في محرك التثبيت المعرفي ✓';
alert('❌ حدث خطأ في الاتصال بالخادم');
}
}
function handleLogout() {