refactor: Remove Bunny Stream completely from UI and unify Teacher Studio to streamlined Cloudflare R2 & FFmpeg HLS uploader

This commit is contained in:
Hamza-Ayed
2026-08-28 02:40:17 +03:00
parent 7ddaff6b14
commit 0ce5616e22
+29 -156
View File
@@ -446,24 +446,24 @@ class TeacherPortal
</div> </div>
</div> </div>
<!-- TAB 2: Zero-Touch Autonomous Video & AI Studio --> <!-- TAB 2: Zero-Touch Autonomous Video & Cloudflare R2 Studio -->
<div id="tab_courses_content" class="studio-card" style="display: none;"> <div id="tab_courses_content" class="studio-card" style="display: none;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; gap: 12px;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; gap: 12px;">
<div> <div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="font-size: 11px; font-weight: 800; background: rgba(56,189,248,0.12); color: var(--accent-cyan); padding: 3px 10px; border-radius: 6px;">⚡ Cloudflare R2 + HLS Adaptive</span>
<span style="font-size: 11px; font-weight: 800; background: rgba(0,245,212,0.12); color: #00F5D4; padding: 3px 10px; border-radius: 6px;">🧠 Gemini Socratic Engine</span>
</div>
<h3 style="font-size: 18px; font-weight: 900;">استوديو رفع الحصص والتحليل الجنائي للذكاء الاصطناعي 🎬</h3> <h3 style="font-size: 18px; font-weight: 900;">استوديو رفع الحصص والتحليل الجنائي للذكاء الاصطناعي 🎬</h3>
<p style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">ارفع الفيديو واترك الباقي لمحرك صَقِل: تقطيع HLS، مطابقة المنهاج، وتوليد الأسئلة السقراطية آلياً.</p> <p style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">ارفع الفيديو بضغطة زر واحدة: يتولى السيرفر التقطيع إلى بث HLS، والتخزين على Cloudflare R2، وتوليد الفحص السقراطي آلياً.</p>
</div>
<div style="display: flex; gap: 10px;">
<button type="button" onclick="switchVideoUploadMode('api')" id="btn_mode_api" class="btn-primary" style="width: auto; padding: 8px 18px; font-size: 12px; background: linear-gradient(135deg, #0284C7, #0369A1);">1. الرفع المباشر عبر السيرفر ⚡</button>
<button type="button" onclick="switchVideoUploadMode('bunny')" id="btn_mode_bunny" class="btn-primary" style="width: auto; padding: 8px 18px; font-size: 12px; background: rgba(255,255,255,0.06); border: 1px solid var(--border);">2. ربط Bunny Stream CDN 🐰</button>
</div> </div>
</div> </div>
<!-- UPLOADER BOX 1: Direct API Upload --> <!-- UNIFIED UPLOADER BOX -->
<div id="uploader_box_api" style="background: rgba(11, 19, 43, 0.85); border: 1px solid rgba(56, 189, 248, 0.3); border-radius: 18px; padding: 24px; margin-bottom: 24px; box-shadow: 0 10px 30px rgba(0,0,0,0.4);"> <div style="background: rgba(11, 19, 43, 0.85); border: 1px solid rgba(56, 189, 248, 0.3); border-radius: 18px; padding: 26px; margin-bottom: 24px; box-shadow: 0 10px 30px rgba(0,0,0,0.4);">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; flex-wrap: wrap; gap: 8px;">
<h4 style="font-size: 15px; font-weight: 800; color: var(--accent-cyan);">⚡ رفع مباشر ومعالجة ذكية فورية (HLS + Gemini Socratic Engine)</h4> <h4 style="font-size: 15px; font-weight: 800; color: var(--accent-cyan);">⚡ رفع وحفظ الحصة ومعالجة البث الذكي</h4>
<span style="font-size: 11px; background: rgba(56,189,248,0.1); color: var(--accent-cyan); padding: 3px 10px; border-radius: 6px; font-weight: 700;">معالجة آلية 100% 🧠</span> <span style="font-size: 11px; background: rgba(56,189,248,0.15); color: var(--accent-cyan); padding: 4px 12px; border-radius: 980px; font-weight: 700;">معالجة تلقائية بالكامل 100% 🧠</span>
</div> </div>
<form id="form_direct_upload" onsubmit="handleDirectVideoUpload(event)"> <form id="form_direct_upload" onsubmit="handleDirectVideoUpload(event)">
@@ -486,10 +486,10 @@ class TeacherPortal
<div class="form-group" style="margin-top: 12px;"> <div class="form-group" style="margin-top: 12px;">
<label class="form-label">ملف الفيديو (MP4, WebM, MOV - حتى 2GB)</label> <label class="form-label">ملف الفيديو (MP4, WebM, MOV - حتى 2GB)</label>
<div style="border: 2px dashed rgba(56, 189, 248, 0.4); border-radius: 14px; padding: 28px; text-align: center; background: rgba(0,0,0,0.2); cursor: pointer;" onclick="document.getElementById('direct_video_file').click()"> <div style="border: 2px dashed rgba(56, 189, 248, 0.4); border-radius: 14px; padding: 32px 20px; text-align: center; background: rgba(0,0,0,0.25); cursor: pointer; transition: all 0.2s ease;" onclick="document.getElementById('direct_video_file').click()" onmouseover="this.style.borderColor='var(--accent-cyan)'" onmouseout="this.style.borderColor='rgba(56, 189, 248, 0.4)'">
<div style="font-size: 32px; margin-bottom: 8px;">🎬</div> <div style="font-size: 36px; margin-bottom: 8px;">🎬</div>
<div style="font-size: 14px; font-weight: 700; color: #FFFFFF;" id="file_selected_label">اضغط لاختيار ملف الفيديو أو اسحبه وأفلته هنا</div> <div style="font-size: 14px; font-weight: 700; color: #FFFFFF;" id="file_selected_label">اضغط لاختيار ملف الفيديو أو اسحبه وأفلته هنا</div>
<div style="font-size: 11.5px; color: var(--text-muted); margin-top: 6px;">يتولى السيرفر التقطيع إلى HLS والتحليل الجنائي وتوليد نقاط الفحص السقراطي فوراً في الخلفية</div> <div style="font-size: 11.5px; color: var(--text-muted); margin-top: 6px;">يتولى السيرفر التقطيع إلى HLS، المزامنة مع Cloudflare R2، وتوليد نقاط الفحص السقراطي فوراً في الخلفية</div>
<input type="file" id="direct_video_file" accept="video/mp4,video/webm,video/quicktime,video/x-matroska" style="display: none;" onchange="onVideoFileSelected(this)"> <input type="file" id="direct_video_file" accept="video/mp4,video/webm,video/quicktime,video/x-matroska" style="display: none;" onchange="onVideoFileSelected(this)">
</div> </div>
</div> </div>
@@ -505,52 +505,17 @@ class TeacherPortal
</div> </div>
</div> </div>
<button type="submit" id="btn_submit_direct_upload" class="btn-primary" style="margin-top: 18px; width: auto; padding: 12px 36px; font-size: 13.5px; font-weight: 800;">رفع وحفظ الحصة والتحليل الذكي الآن 🚀</button> <button type="submit" id="btn_submit_direct_upload" class="btn-primary" style="margin-top: 18px; width: auto; padding: 12px 40px; font-size: 14px; font-weight: 800;">رفع وبث الحصة وتوليد الفحص السقراطي آلياً 🚀</button>
</form> </form>
</div> </div>
<!-- UPLOADER BOX 2: Bunny Stream CDN --> <!-- AUTONOMOUS AI & R2 PIPELINE BANNER -->
<div id="uploader_box_bunny" style="display: none; background: rgba(11, 19, 43, 0.85); border: 1px solid rgba(245, 158, 11, 0.3); border-radius: 18px; padding: 24px; margin-bottom: 24px; box-shadow: 0 10px 30px rgba(0,0,0,0.4);">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;">
<h4 style="font-size: 15px; font-weight: 800; color: var(--accent-gold);">🐰 الربط مع شبكة Bunny.net Stream (Cloud CDN & Token DRM)</h4>
<span style="font-size: 11px; background: rgba(245,158,11,0.1); color: var(--accent-gold); padding: 3px 10px; border-radius: 6px; font-weight: 700;">HLS + SHA256 DRM</span>
</div>
<form id="form_bunny_link" onsubmit="handleBunnyVideoLink(event)">
<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="bunny_course_select" class="input-text" required>
<option value="1">الرياضيات العلمي — توجيهي 2008 (المستوى الثالث)</option>
</select>
</div>
<div class="form-group">
<label class="form-label">عنوان الحصة / الدرس</label>
<input type="text" id="bunny_lesson_title" placeholder="مثال: الدرس 2 — مشتقة ضرب اقترانين" class="input-text" required>
</div>
<div class="form-group">
<label class="form-label">معرف فيديو Bunny Stream (Video GUID)</label>
<div style="display: flex; gap: 8px;">
<input type="text" id="bunny_video_id_input" placeholder="e.g. 78a9c12b-34ef-..." class="input-text" style="font-family: monospace;" required>
<button type="button" onclick="handleBunnyCreateVideo()" class="btn-primary" style="width: auto; padding: 0 14px; font-size: 11px; background: #D97706; white-space: nowrap;">+ توليد GUID</button>
</div>
</div>
<div class="form-group">
<label class="form-label">مدة الفيديو المقدرة (بالثواني)</label>
<input type="number" id="bunny_duration_input" value="1800" class="input-text">
</div>
</div>
<button type="submit" id="btn_submit_bunny_link" class="btn-primary" style="margin-top: 18px; width: auto; padding: 12px 36px; font-size: 13.5px; font-weight: 800; background: linear-gradient(135deg, #F59E0B, #D97706);">حفظ وربط درس Bunny Stream ✓</button>
</form>
</div>
<!-- AUTONOMOUS AI BANNER -->
<div style="background: rgba(0, 245, 212, 0.05); border: 1px solid rgba(0, 245, 212, 0.2); border-radius: 18px; padding: 20px; display: flex; align-items: center; gap: 16px;"> <div style="background: rgba(0, 245, 212, 0.05); border: 1px solid rgba(0, 245, 212, 0.2); border-radius: 18px; padding: 20px; display: flex; align-items: center; gap: 16px;">
<div style="font-size: 32px;">🧠</div> <div style="font-size: 32px;">🧠</div>
<div> <div>
<h4 style="font-size: 14px; font-weight: 800; color: var(--accent-cyan); margin-bottom: 2px;">المعالجة السقراطية الذكية تعمل تلقائياً 100%</h4> <h4 style="font-size: 14px; font-weight: 800; color: var(--accent-cyan); margin-bottom: 2px;">المعالجة السقراطية والتخزين السحابي يعملان تلقائياً 100%</h4>
<p style="font-size: 12px; color: var(--text-muted); line-height: 1.6; margin: 0;"> <p style="font-size: 12px; color: var(--text-muted); line-height: 1.6; margin: 0;">
لا داعي لأي جهد يدوي من المعلم. فور رفعك للملف، يطابق النظام نصوص المنهاج الوزاري، ويولد الفهرس الزمني للأفكار ونقاط الفحص السقراطي اللحظي، وتظهر فوراً للطالب في المشغل التفاعلي. لا يوجد أي جهد إضافي على المعلم. فور انتهاء الرفع، يُجزأ الفيديو إلى مقاطع HLS مشفرة، وتُحفظ على شبكة Cloudflare R2 فائقة السرعة، ويولد الذكاء الاصطناعي الفهرس الزمني وأسئلة التثبيت الصدمي لتظهر فوراً للطالب.
</p> </p>
</div> </div>
</div> </div>
@@ -1248,29 +1213,6 @@ class TeacherPortal
// ========================================== // ==========================================
// Video Storage & Bunny Stream Studio Handlers // Video Storage & Bunny Stream Studio Handlers
// ========================================== // ==========================================
function switchVideoUploadMode(mode) {
const apiBox = document.getElementById('uploader_box_api');
const bunnyBox = document.getElementById('uploader_box_bunny');
const btnApi = document.getElementById('btn_mode_api');
const btnBunny = document.getElementById('btn_mode_bunny');
if (mode === 'api') {
apiBox.style.display = 'block';
bunnyBox.style.display = 'none';
btnApi.style.background = 'linear-gradient(135deg, #0284C7, #0369A1)';
btnApi.style.border = 'none';
btnBunny.style.background = 'rgba(255,255,255,0.06)';
btnBunny.style.border = '1px solid var(--border)';
} else {
apiBox.style.display = 'none';
bunnyBox.style.display = 'block';
btnBunny.style.background = 'linear-gradient(135deg, #F59E0B, #D97706)';
btnBunny.style.border = 'none';
btnApi.style.background = 'rgba(255,255,255,0.06)';
btnApi.style.border = '1px solid var(--border)';
}
}
function onVideoFileSelected(input) { function onVideoFileSelected(input) {
if (input.files && input.files[0]) { if (input.files && input.files[0]) {
const f = input.files[0]; const f = input.files[0];
@@ -1312,7 +1254,7 @@ class TeacherPortal
progressContainer.style.display = 'block'; progressContainer.style.display = 'block';
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.textContent = 'جارٍ رفع الفيديو... ⏳'; submitBtn.textContent = 'جارٍ رفع الفيديو والمعالجة الذكية... ⏳';
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/teacher/videos/upload-direct', true); xhr.open('POST', '/api/teacher/videos/upload-direct', true);
@@ -1331,12 +1273,12 @@ class TeacherPortal
xhr.onload = function() { xhr.onload = function() {
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.textContent = 'بدء رفع وحفظ الفيديو الآن 🚀'; submitBtn.textContent = 'رفع وبث الحصة وتوليد الفحص السقراطي آلياً 🚀';
try { try {
const data = JSON.parse(xhr.responseText); const data = JSON.parse(xhr.responseText);
if (xhr.status >= 200 && xhr.status < 300 && data.status === 'success') { if (xhr.status >= 200 && xhr.status < 300 && data.status === 'success') {
showLuxuryToast('تم حفظ الفيديو بنجاح 🎬', data.message || 'تم إنشاء الدرس وحفظ الفيديو'); showLuxuryToast('تم حفظ ومعالجة الفيديو بنجاح 🎬', data.message || 'تم إنشاء الدرس وبث HLS');
alert('✅ ' + data.message + '\nرابط البث المباشر: ' + data.data.stream_url); alert('✅ ' + data.message + '\n\nتم تقطيع الفيديو لـ HLS وحفظه وتوليد نقاط الفحص السقراطي آلياً!');
document.getElementById('form_direct_upload').reset(); document.getElementById('form_direct_upload').reset();
document.getElementById('file_selected_label').textContent = 'اضغط لاختيار ملف الفيديو أو اسحبه وأفلته هنا'; document.getElementById('file_selected_label').textContent = 'اضغط لاختيار ملف الفيديو أو اسحبه وأفلته هنا';
progressContainer.style.display = 'none'; progressContainer.style.display = 'none';
@@ -1351,77 +1293,13 @@ class TeacherPortal
xhr.onerror = function() { xhr.onerror = function() {
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.textContent = 'بدء رفع وحفظ الفيديو الآن 🚀'; submitBtn.textContent = 'رفع وبث الحصة وتوليد الفحص السقراطي آلياً 🚀';
alert('❌ حدث خطأ في الاتصال أثناء رفع الفيديو'); alert('❌ حدث خطأ في الاتصال أثناء رفع الفيديو');
}; };
xhr.send(formData); xhr.send(formData);
} }
async function handleBunnyCreateVideo() {
const token = getAuthToken();
const title = document.getElementById('bunny_lesson_title').value.trim() || 'درس جديد في الرياضيات';
const courseId = document.getElementById('bunny_course_select').value;
try {
const res = await fetch('/api/teacher/videos/bunny-create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({ title: title, course_id: courseId })
});
const data = await res.json();
if (res.ok && data.status === 'success') {
document.getElementById('bunny_video_id_input').value = data.data.video_id;
showLuxuryToast('تم توليد المعرف في Bunny Stream 🐰', `GUID: ${data.data.video_id}`);
} else {
alert('خطأ: ' + (data.message || 'فشل الاتصال بـ Bunny Stream'));
}
} catch (err) {
console.error(err);
alert('خطأ في الاتصال بالخادم');
}
}
async function handleBunnyVideoLink(e) {
e.preventDefault();
const token = getAuthToken();
const courseId = document.getElementById('bunny_course_select').value;
const title = document.getElementById('bunny_lesson_title').value.trim();
const bunnyVideoId = document.getElementById('bunny_video_id_input').value.trim();
const duration = document.getElementById('bunny_duration_input').value;
try {
const res = await fetch('/api/teacher/videos/bunny-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({
course_id: courseId,
title: title,
bunny_video_id: bunnyVideoId,
duration_seconds: duration
})
});
const data = await res.json();
if (res.ok && data.status === 'success') {
showLuxuryToast('تم ربط الدرس بنجاح 🐰', data.message);
alert('✅ تم ربط الحصة بـ Bunny Stream وحمايتها بروابط التشفير الرقمي DRM!');
document.getElementById('form_bunny_link').reset();
loadTeacherCourses();
} else {
alert('خطأ: ' + (data.message || 'فشل حفظ الدرس'));
}
} catch (err) {
console.error(err);
alert('خطأ في الاتصال بالخادم');
}
}
async function loadTeacherCourses() { async function loadTeacherCourses() {
const token = getAuthToken(); const token = getAuthToken();
if (!token) return; if (!token) return;
@@ -1431,17 +1309,12 @@ class TeacherPortal
}); });
const data = await res.json(); const data = await res.json();
if (res.ok && data.data && data.data.length > 0) { if (res.ok && data.data && data.data.length > 0) {
const selects = [ const sel = document.getElementById('direct_upload_course_select');
document.getElementById('direct_upload_course_select'), if (sel) {
document.getElementById('bunny_course_select') sel.innerHTML = data.data.map(c => `
]; <option value="${c.id}">${c.title} (${c.semester || 'فصل أول'})</option>
selects.forEach(sel => { `).join('');
if (sel) { }
sel.innerHTML = data.data.map(c => `
<option value="${c.id}">${c.title} (${c.semester || 'فصل أول'})</option>
`).join('');
}
});
} }
} catch (e) { } catch (e) {
console.error('Load courses note:', e); console.error('Load courses note:', e);