Add optimized media delivery and HLS support
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
هذا الالتزام موجود في:
@@ -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;
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم