Add optimized media delivery and HLS support
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled

هذا الالتزام موجود في:
2026-05-18 23:57:12 +03:00
الأصل 4912a99b8d
التزام 87adaae04b
9 ملفات معدلة مع 464 إضافات و14 حذوفات

عرض الملف

@@ -50,6 +50,7 @@ type NormalizedPostMediaMetadata = {
type SavedVideoUpload = {
videoUrl: string;
hlsUrl: string;
thumbnailUrl: string;
};
@@ -100,6 +101,7 @@ export class PostsService {
const uploadedImageUrls = await this.saveImageFiles(imageFiles);
const savedVideoUpload = videoFile ? await this.saveVideoUpload(videoFile) : null;
const uploadedVideoUrl = savedVideoUpload?.videoUrl ?? '';
const uploadedHlsUrl = savedVideoUpload?.hlsUrl ?? '';
const uploadedThumbnailUrl = savedVideoUpload?.thumbnailUrl ?? '';
const uploadedAudioUrl = audioFile ? await this.saveMediaFile('audio', audioFile) : '';
const finalImageUrls = uploadedImageUrls.length ? uploadedImageUrls : inputImageUrls;
@@ -127,6 +129,7 @@ export class PostsService {
content: finalContent,
imageUrls: finalImageUrls,
videoUrl: finalVideoUrl,
hlsUrl: uploadedHlsUrl,
audioUrl: finalAudioUrl,
taggedUserIds,
mentionUsernames: mentionResolution.mentionUsernames,
@@ -142,6 +145,7 @@ export class PostsService {
await Promise.all([
...uploadedImageUrls.map((url) => this.deleteManagedPostMedia(url)),
uploadedVideoUrl ? this.deleteManagedPostMedia(uploadedVideoUrl) : Promise.resolve(),
uploadedHlsUrl ? this.storageService.deleteContainingDirectory(uploadedHlsUrl) : Promise.resolve(),
uploadedThumbnailUrl ? this.deleteManagedPostMedia(uploadedThumbnailUrl) : Promise.resolve(),
uploadedAudioUrl ? this.deleteManagedPostMedia(uploadedAudioUrl) : Promise.resolve(),
]);
@@ -198,6 +202,7 @@ export class PostsService {
const uploadedImageUrls = await this.saveImageFiles(imageFiles);
const savedVideoUpload = videoFile ? await this.saveVideoUpload(videoFile) : null;
const uploadedVideoUrl = savedVideoUpload?.videoUrl ?? '';
const uploadedHlsUrl = savedVideoUpload?.hlsUrl ?? '';
const uploadedThumbnailUrl = savedVideoUpload?.thumbnailUrl ?? '';
const uploadedAudioUrl = audioFile ? await this.saveMediaFile('audio', audioFile) : '';
const hasImageUpdate = typeof dto.imageUrls !== 'undefined' || imageFiles.length > 0;
@@ -215,6 +220,7 @@ export class PostsService {
? uploadedVideoUrl
: dto.videoUrl ?? ''
: post.videoUrl ?? '';
const nextHlsUrl = hasVideoUpdate ? (videoFile ? uploadedHlsUrl : '') : post.hlsUrl ?? '';
const nextAudioUrl = hasAudioUpdate
? audioFile
? uploadedAudioUrl
@@ -266,6 +272,7 @@ export class PostsService {
...dto,
content: nextContent,
imageUrls: nextImageUrls,
hlsUrl: nextHlsUrl,
taggedUserIds: nextTaggedUserIds,
mentionUsernames: mentionResolution.mentionUsernames,
location: nextLocation,
@@ -287,14 +294,17 @@ export class PostsService {
}
if (hasAudioUpdate && !hasVideoUpdate) {
payload.videoUrl = '';
payload.hlsUrl = '';
}
if (hasImageUpdate) {
payload.videoUrl = '';
payload.audioUrl = '';
payload.hlsUrl = '';
}
if (videoFile) {
payload.videoUrl = uploadedVideoUrl;
payload.hlsUrl = uploadedHlsUrl;
payload.imageUrls = [];
payload.audioUrl = '';
}
@@ -302,6 +312,7 @@ export class PostsService {
payload.audioUrl = uploadedAudioUrl;
payload.imageUrls = [];
payload.videoUrl = '';
payload.hlsUrl = '';
}
let updated: PostDocument | null;
@@ -311,6 +322,7 @@ export class PostsService {
await Promise.all([
...uploadedImageUrls.map((url) => this.deleteManagedPostMedia(url)),
uploadedVideoUrl ? this.deleteManagedPostMedia(uploadedVideoUrl) : Promise.resolve(),
uploadedHlsUrl ? this.storageService.deleteContainingDirectory(uploadedHlsUrl) : Promise.resolve(),
uploadedThumbnailUrl ? this.deleteManagedPostMedia(uploadedThumbnailUrl) : Promise.resolve(),
uploadedAudioUrl ? this.deleteManagedPostMedia(uploadedAudioUrl) : Promise.resolve(),
]);
@@ -320,6 +332,7 @@ export class PostsService {
await Promise.all([
...uploadedImageUrls.map((url) => this.deleteManagedPostMedia(url)),
uploadedVideoUrl ? this.deleteManagedPostMedia(uploadedVideoUrl) : Promise.resolve(),
uploadedHlsUrl ? this.storageService.deleteContainingDirectory(uploadedHlsUrl) : Promise.resolve(),
uploadedThumbnailUrl ? this.deleteManagedPostMedia(uploadedThumbnailUrl) : Promise.resolve(),
uploadedAudioUrl ? this.deleteManagedPostMedia(uploadedAudioUrl) : Promise.resolve(),
]);
@@ -329,6 +342,9 @@ export class PostsService {
if (hasVideoUpdate && (post.videoUrl ?? '') !== (updated.videoUrl ?? '')) {
await this.deleteManagedPostMedia(post.videoUrl ?? '');
}
if ((post.hlsUrl ?? '') !== (updated.hlsUrl ?? '')) {
await this.storageService.deleteContainingDirectory(post.hlsUrl ?? '');
}
if ((post.thumbnailUrl ?? '') !== (updated.thumbnailUrl ?? '')) {
await this.deleteManagedPostMedia(post.thumbnailUrl ?? '');
}
@@ -369,6 +385,7 @@ export class PostsService {
await Promise.all([
...(post.imageUrls ?? []).map((url) => this.deleteManagedPostMedia(url)),
this.deleteManagedPostMedia(post.videoUrl ?? ''),
this.storageService.deleteContainingDirectory(post.hlsUrl ?? ''),
this.deleteManagedPostMedia(post.audioUrl ?? ''),
]);
await this.usersRepository.incrementPostsCount(userId, -1);
@@ -610,6 +627,7 @@ export class PostsService {
await Promise.all([
...(post.imageUrls ?? []).map((url) => this.deleteManagedPostMedia(url)),
this.deleteManagedPostMedia(post.videoUrl ?? ''),
this.storageService.deleteContainingDirectory(post.hlsUrl ?? ''),
this.deleteManagedPostMedia(post.audioUrl ?? ''),
]);
const authorId = this.extractEntityId(post.authorId);
@@ -918,7 +936,9 @@ export class PostsService {
const extension = this.validateMediaFile('video', optimized.file);
let videoUrl = '';
let hlsUrl = '';
let thumbnailUrl = '';
const hlsFolderName = `stream-${new Types.ObjectId().toString()}`;
try {
videoUrl = await this.storageService.saveFile({
@@ -929,6 +949,14 @@ export class PostsService {
fileNamePrefix: 'video',
});
if (optimized.generatedHls?.files.length) {
const savedHlsFiles = await this.storageService.saveFiles({
folderSegments: ['posts', 'hls', hlsFolderName],
files: optimized.generatedHls.files,
});
hlsUrl = savedHlsFiles[optimized.generatedHls.playlistRelativePath] ?? '';
}
if (optimized.generatedThumbnail) {
thumbnailUrl = await this.storageService.saveFile({
folderSegments: ['posts', 'thumbnails'],
@@ -939,10 +967,11 @@ export class PostsService {
});
}
return { videoUrl, thumbnailUrl };
return { videoUrl, hlsUrl, thumbnailUrl };
} catch (error) {
await Promise.all([
videoUrl ? this.deleteManagedPostMedia(videoUrl) : Promise.resolve(),
hlsUrl ? this.storageService.deleteContainingDirectory(hlsUrl) : Promise.resolve(),
thumbnailUrl ? this.deleteManagedPostMedia(thumbnailUrl) : Promise.resolve(),
]);
throw error;

عرض الملف

@@ -22,6 +22,9 @@ export class Post {
@Prop({ default: '' })
videoUrl!: string;
@Prop({ default: '' })
hlsUrl!: string;
@Prop({ default: '' })
audioUrl!: string;
@@ -134,6 +137,7 @@ PostSchema.index({
const transformManagedPostFiles = (_doc: unknown, ret: any) => {
ret.imageUrls = resolveManagedFileUrls(ret.imageUrls);
ret.videoUrl = resolveManagedFileUrl(ret.videoUrl);
ret.hlsUrl = resolveManagedFileUrl(ret.hlsUrl);
ret.audioUrl = resolveManagedFileUrl(ret.audioUrl);
ret.thumbnailUrl = resolveManagedFileUrl(ret.thumbnailUrl);
return ret;