Update Saqel Platform: 2026-09-08 13:43:36
This commit is contained in:
@@ -95,9 +95,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
try {
|
||||
var playback = await _repo.getLessonPlayback(lesson.markdownFilePath ?? lesson.id, subjectId: subject?.id);
|
||||
if (playback.videoUrl.isEmpty) {
|
||||
playback = playback.copyWith(
|
||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
||||
);
|
||||
throw StateError('لم يربط الخادم فيديو R2 بهذا الدرس بعد.');
|
||||
}
|
||||
|
||||
// Load saved resume position strictly per video
|
||||
@@ -120,118 +118,11 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
isPlaying: true,
|
||||
));
|
||||
} catch (e) {
|
||||
AppLogger.log('Playback API unavailable (${lesson.title}): $e — Switching to offline Socratic fallback', tag: 'VIDEO_CUBIT');
|
||||
final fallback = _buildResilientLessonPlayback(lesson, subject: subject);
|
||||
|
||||
// Load saved resume position strictly per video
|
||||
int resumePos = fallback.lastPositionSeconds ?? 0;
|
||||
Set<int> passedIds = {};
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0;
|
||||
if (localPos > resumePos) resumePos = localPos;
|
||||
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
|
||||
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
|
||||
} catch (_) {}
|
||||
|
||||
emit(VideoPlaybackReady(
|
||||
playbackData: fallback,
|
||||
lessonItem: lesson,
|
||||
subject: subject,
|
||||
currentPositionSeconds: resumePos,
|
||||
passedCheckpointIds: passedIds,
|
||||
isPlaying: true,
|
||||
));
|
||||
AppLogger.error('Playback API unavailable (${lesson.title})', error: e, tag: 'VIDEO_CUBIT');
|
||||
emit(VideoPlaybackError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
LessonPlaybackData _buildResilientLessonPlayback(CurriculumLessonItemModel lesson, {SubjectModel? subject}) {
|
||||
final title = lesson.title.toLowerCase();
|
||||
final isEng = (subject?.id ?? '').contains('english') || title.contains('english') || title.contains('unit 01');
|
||||
final isPhys = (subject?.id ?? '').contains('physic') || title.contains('فيزياء') || title.contains('متجه');
|
||||
|
||||
List<SocraticCheckpointModel> points = [];
|
||||
|
||||
if (isEng) {
|
||||
points = [
|
||||
const SocraticCheckpointModel(
|
||||
id: 101,
|
||||
questionText: 'According to the reading passage, in how many seconds do humans make subconscious judgments?',
|
||||
timestampSeconds: 20,
|
||||
hint: 'Remember the rule of first impressions in psychological studies.',
|
||||
pedagogicalExplanation: 'Behavioral research confirms that people form initial impressions within the first 7 seconds.',
|
||||
options: [
|
||||
SocraticOptionModel(id: 1, text: 'Within 7 seconds', isCorrect: true),
|
||||
SocraticOptionModel(id: 2, text: 'Within 5 minutes', isCorrect: false),
|
||||
SocraticOptionModel(id: 3, text: 'After prolonged conversation', isCorrect: false),
|
||||
],
|
||||
),
|
||||
const SocraticCheckpointModel(
|
||||
id: 102,
|
||||
questionText: 'Which article should precede the singular noun "university"?',
|
||||
timestampSeconds: 50,
|
||||
hint: 'Consider the initial phonetic sound rather than the written letter.',
|
||||
pedagogicalExplanation: 'Although "university" starts with the vowel letter "u", it begins with the consonant sound /juː/, so we use "a".',
|
||||
options: [
|
||||
SocraticOptionModel(id: 4, text: 'a (e.g. a university)', isCorrect: true),
|
||||
SocraticOptionModel(id: 5, text: 'an (e.g. an university)', isCorrect: false),
|
||||
],
|
||||
),
|
||||
];
|
||||
} else if (isPhys) {
|
||||
points = [
|
||||
const SocraticCheckpointModel(
|
||||
id: 201,
|
||||
questionText: 'ما هي النتيجة الصحيحة للضرب القياسي لمتجهين متعامدين (θ = 90°)؟',
|
||||
timestampSeconds: 20,
|
||||
hint: 'تذكر أن الضرب النقطي يعتمد على جيب التمام cos(θ).',
|
||||
pedagogicalExplanation: 'بما أن cos(90°) = 0، فإن الضرب القياسي لمتجهين متعامدين ينعدم تماماً ويساوي صفراً.',
|
||||
options: [
|
||||
SocraticOptionModel(id: 1, text: 'ينعدم الناتج (يساوي صفراً)', isCorrect: true),
|
||||
SocraticOptionModel(id: 2, text: 'يساوي حاصل ضرب مقداريهما', isCorrect: false),
|
||||
SocraticOptionModel(id: 3, text: 'يساوي متجهاً رأسياً جديداً', isCorrect: false),
|
||||
],
|
||||
),
|
||||
];
|
||||
} else {
|
||||
points = [
|
||||
const SocraticCheckpointModel(
|
||||
id: 301,
|
||||
questionText: 'قبل تحليل المعادلة x³ + 4x² = 5x، ما هي الخطوة الجبرية الإلزامية الأولى؟',
|
||||
timestampSeconds: 20,
|
||||
hint: 'احذر من قسمة طرفي المعادلة على المتغير x فتفقد أحد الجذور.',
|
||||
pedagogicalExplanation: 'يجب نقل الحد 5x إلى الطرف الأيسر ليصبح الطرف الأيمن صفراً، ثم إخراج العامل المشترك x.',
|
||||
options: [
|
||||
SocraticOptionModel(id: 1, text: 'نقل 5x للطرف الأيسر وجعل الطرف الأيمن صفراً', isCorrect: true),
|
||||
SocraticOptionModel(id: 2, text: 'القسمة المباشرة على x في الطرفين', isCorrect: false),
|
||||
SocraticOptionModel(id: 3, text: 'أخذ الجذر التكعيبي لكافة الحدود', isCorrect: false),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return LessonPlaybackData(
|
||||
lessonId: int.tryParse(lesson.id.replaceAll(RegExp(r'[^0-9]'), '')) ?? 101,
|
||||
title: lesson.title,
|
||||
durationSeconds: lesson.durationSeconds > 0 ? lesson.durationSeconds : 1200,
|
||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
||||
storageType: 'hls_stream',
|
||||
checkpoints: points,
|
||||
lastPositionSeconds: 0,
|
||||
availableVersions: const [
|
||||
LessonVersionModel(
|
||||
lessonId: 101,
|
||||
isAi: true,
|
||||
teacherName: 'منصة صَقِل التعليمية المعتمدة',
|
||||
schoolName: 'المركز الرقمي المعتمد',
|
||||
label: 'الشرح الرقمي الرسمي المعتمد',
|
||||
isRecommended: true,
|
||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void updatePosition(int seconds) {
|
||||
final currentState = state;
|
||||
if (currentState is VideoPlaybackReady && currentState.activeCheckpoint == null) {
|
||||
|
||||
Reference in New Issue
Block a user