Update Saqel Platform: 2026-09-09 00:24:41
This commit is contained in:
@@ -48,6 +48,11 @@ class VideoService
|
||||
Database::query("ALTER TABLE lessons ADD COLUMN local_path VARCHAR(500) NULL AFTER bunny_video_id");
|
||||
}
|
||||
|
||||
$colsR2 = Database::select("SHOW COLUMNS FROM lessons LIKE 'r2_url'");
|
||||
if (empty($colsR2)) {
|
||||
Database::query("ALTER TABLE lessons ADD COLUMN r2_url VARCHAR(500) NULL AFTER local_path");
|
||||
}
|
||||
|
||||
$colsHls = Database::select("SHOW COLUMNS FROM lessons LIKE 'hls_url'");
|
||||
if (empty($colsHls)) {
|
||||
Database::query("ALTER TABLE lessons ADD COLUMN hls_url VARCHAR(500) NULL AFTER local_path");
|
||||
@@ -121,7 +126,7 @@ class VideoService
|
||||
* @param string $title
|
||||
* @return array
|
||||
*/
|
||||
public static function handleDirectUpload(array $file, int $courseId, string $title): array
|
||||
public static function handleDirectUpload(array $file, int $courseId, string $title, bool $syncR2Immediately = false): array
|
||||
{
|
||||
self::ensureSchema();
|
||||
|
||||
@@ -183,16 +188,14 @@ class VideoService
|
||||
// Attempt automated Server-Side HLS Transcoding
|
||||
$hlsResult = self::transcodeToHls($targetPath, $courseId, $videoUuid);
|
||||
|
||||
// Attempt automated sync to Cloudflare R2
|
||||
// Automated sync to Cloudflare R2
|
||||
$r2Config = self::getR2Config();
|
||||
$r2VideoUrl = null;
|
||||
if (!empty($r2Config['access_key']) && !empty($r2Config['secret_key']) && !empty($r2Config['bucket'])) {
|
||||
if ($syncR2Immediately && !empty($r2Config['access_key']) && !empty($r2Config['secret_key']) && !empty($r2Config['bucket'])) {
|
||||
$r2Key = "videos/{$courseId}/{$targetFileName}";
|
||||
$r2VideoUrl = self::uploadToR2($targetPath, $r2Key, $mime);
|
||||
|
||||
// The player must receive a complete HLS package, not only the source MP4.
|
||||
// Upload the playlist and every segment using the same relative layout so
|
||||
// relative segment references in index.m3u8 continue to resolve on R2.
|
||||
if (!empty($hlsResult['hls_url'])) {
|
||||
$hlsDir = dirname(__DIR__, 2) . '/storage/hls/' . $courseId . '/' . $videoUuid;
|
||||
$r2HlsPlaylist = self::uploadHlsDirectoryToR2($hlsDir, $courseId, $videoUuid);
|
||||
@@ -214,6 +217,8 @@ class VideoService
|
||||
return [
|
||||
'video_uuid' => $videoUuid,
|
||||
'local_path' => $relativePath,
|
||||
'target_path' => $targetPath,
|
||||
'target_file_name'=> $targetFileName,
|
||||
'r2_url' => $r2VideoUrl,
|
||||
'hls_url' => $hlsResult['hls_url'] ?? null,
|
||||
'thumbnail_url' => $hlsResult['thumbnail_url'] ?? null,
|
||||
@@ -222,11 +227,60 @@ class VideoService
|
||||
'mime_type' => $mime,
|
||||
'extension' => $ext,
|
||||
'storage_type' => 'api_upload',
|
||||
'encoding_status' => 'ready',
|
||||
'encoding_status' => $syncR2Immediately ? 'ready' : 'processing',
|
||||
'stream_url' => '/api/videos/stream/' . $videoUuid
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Pipeline: Asynchronously sync local video & HLS chunks to Cloudflare R2
|
||||
*/
|
||||
public static function syncLessonToR2(int $lessonId, int $courseId, string $targetPath, string $targetFileName, string $videoUuid, string $mime): array
|
||||
{
|
||||
$r2Config = self::getR2Config();
|
||||
if (empty($r2Config['access_key']) || empty($r2Config['secret_key']) || empty($r2Config['bucket'])) {
|
||||
return ['r2_url' => null, 'hls_url' => null];
|
||||
}
|
||||
|
||||
$r2Key = "videos/{$courseId}/{$targetFileName}";
|
||||
$r2VideoUrl = self::uploadToR2($targetPath, $r2Key, $mime);
|
||||
|
||||
$hlsDir = dirname(__DIR__, 2) . '/storage/hls/' . $courseId . '/' . $videoUuid;
|
||||
$r2HlsPlaylist = self::uploadHlsDirectoryToR2($hlsDir, $courseId, $videoUuid);
|
||||
|
||||
$thumbnailPath = dirname(__DIR__, 2) . '/storage/hls/' . $courseId . '/' . $videoUuid . '/thumbnail.jpg';
|
||||
$r2ThumbUrl = null;
|
||||
if (file_exists($thumbnailPath)) {
|
||||
$r2ThumbKey = "hls/{$courseId}/{$videoUuid}/thumbnail.jpg";
|
||||
$r2ThumbUrl = self::uploadToR2($thumbnailPath, $r2ThumbKey, 'image/jpeg');
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
$params = [];
|
||||
if ($r2VideoUrl) {
|
||||
$updates[] = 'r2_url = ?';
|
||||
$params[] = $r2VideoUrl;
|
||||
}
|
||||
if ($r2HlsPlaylist) {
|
||||
$updates[] = 'hls_url = ?';
|
||||
$params[] = $r2HlsPlaylist;
|
||||
}
|
||||
if ($r2ThumbUrl) {
|
||||
$updates[] = 'thumbnail_url = ?';
|
||||
$params[] = $r2ThumbUrl;
|
||||
}
|
||||
if (!empty($updates)) {
|
||||
$params[] = $lessonId;
|
||||
Database::query('UPDATE lessons SET ' . implode(', ', $updates) . ' WHERE id = ?', $params);
|
||||
}
|
||||
|
||||
return [
|
||||
'r2_url' => $r2VideoUrl,
|
||||
'hls_url' => $r2HlsPlaylist,
|
||||
'thumbnail_url' => $r2ThumbUrl,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcode MP4 to HLS Chunks (.m3u8 and .ts segments) using Server FFmpeg
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user