From 37ed8003f57abda6561ba108e6f3280ab3452aba Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Thu, 29 May 2025 14:07:29 +0200 Subject: [PATCH] backend: implement getRecordingMedia method in StorageProvider and S3StorageProvider, refactor RecordingService to use it --- backend/src/services/recording.service.ts | 4 ++-- backend/src/services/s3.service.ts | 4 ++-- .../storage/providers/s3-storage.provider.ts | 17 ++++++++++++++++ .../src/services/storage/storage.interface.ts | 14 +++++++++++++ .../src/services/storage/storage.service.ts | 20 +++++++++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/backend/src/services/recording.service.ts b/backend/src/services/recording.service.ts index 3e29520..7307bc3 100644 --- a/backend/src/services/recording.service.ts +++ b/backend/src/services/recording.service.ts @@ -432,7 +432,7 @@ export class RecordingService { return this.getFullStreamResponse(recordingPath, fileSize); } - const fileStream = await this.s3Service.getObjectAsStream(recordingPath, MEET_S3_BUCKET, { + const fileStream = await this.storageService.getRecordingMedia(recordingPath, { start, end }); @@ -446,7 +446,7 @@ export class RecordingService { recordingPath: string, fileSize: number ): Promise<{ fileSize: number; fileStream: Readable }> { - const fileStream = await this.s3Service.getObjectAsStream(recordingPath, MEET_S3_BUCKET); + const fileStream = await this.storageService.getRecordingMedia(recordingPath); return { fileSize, fileStream }; } diff --git a/backend/src/services/s3.service.ts b/backend/src/services/s3.service.ts index f704ca3..4a8d023 100644 --- a/backend/src/services/s3.service.ts +++ b/backend/src/services/s3.service.ts @@ -187,8 +187,8 @@ export class S3Service { async getObjectAsStream( name: string, - bucket: string = MEET_S3_BUCKET, - range?: { start: number; end: number } + range?: { start: number; end: number }, + bucket: string = MEET_S3_BUCKET ): Promise { try { const obj = await this.getObject(name, bucket, range); diff --git a/backend/src/services/storage/providers/s3-storage.provider.ts b/backend/src/services/storage/providers/s3-storage.provider.ts index 5fa62eb..3d81186 100644 --- a/backend/src/services/storage/providers/s3-storage.provider.ts +++ b/backend/src/services/storage/providers/s3-storage.provider.ts @@ -5,6 +5,7 @@ import INTERNAL_CONFIG from '../../../config/internal-config.js'; import { errorRecordingNotFound, OpenViduMeetError, RedisKeyName } from '../../../models/index.js'; import { LoggerService, RedisService, S3Service, StorageProvider } from '../../index.js'; import { RecordingHelper } from '../../../helpers/recording.helper.js'; +import { Readable } from 'stream'; /** * Implementation of the StorageProvider interface using AWS S3 for persistent storage @@ -384,6 +385,22 @@ export class S3StorageProvider< } } + async getRecordingMedia( + recordingPath: string, + range?: { + end: number; + start: number; + } + ): Promise { + try { + this.logger.debug(`Retrieving recording media from S3 at path: ${recordingPath}`); + return await this.s3Service.getObjectAsStream(recordingPath, range); + } catch (error) { + this.handleError(error, `Error fetching recording media for path ${recordingPath}`); + throw error; + } + } + /** * Deletes multiple recording binary files from S3 storage using their file paths. * diff --git a/backend/src/services/storage/storage.interface.ts b/backend/src/services/storage/storage.interface.ts index 0e4c373..11e0109 100644 --- a/backend/src/services/storage/storage.interface.ts +++ b/backend/src/services/storage/storage.interface.ts @@ -1,4 +1,5 @@ import { GlobalPreferences, MeetRecordingInfo, MeetRoom } from '@typings-ce'; +import { Readable } from 'stream'; /** * An interface that defines the contract for storage providers in the OpenVidu Meet application. @@ -149,6 +150,19 @@ export interface StorageProvider< */ deleteRecordingMetadataByPaths(metadataPaths: string[]): Promise; + /** + * Retrieves the media content of a recording file. + * + * @param recordingPath - The path of the recording file to retrieve. + * @param range - An optional range object specifying the start and end byte positions to retrieve. + */ + getRecordingMedia( + recordingPath: string, + range?: { + end: number; + start: number; + } + ): Promise; /** * Deletes multiple recording binary files by their paths. * diff --git a/backend/src/services/storage/storage.service.ts b/backend/src/services/storage/storage.service.ts index 9c2c40e..97a43f0 100644 --- a/backend/src/services/storage/storage.service.ts +++ b/backend/src/services/storage/storage.service.ts @@ -5,6 +5,7 @@ import { MEET_NAME_ID, MEET_SECRET, MEET_USER, MEET_WEBHOOK_ENABLED, MEET_WEBHOO import { MeetLock, PasswordHelper } from '../../helpers/index.js'; import { errorRoomNotFound, internalError, OpenViduMeetError } from '../../models/error.model.js'; import { LoggerService, MutexService, StorageFactory, StorageProvider } from '../index.js'; +import { Readable } from 'stream'; /** * A service for managing storage operations related to OpenVidu Meet rooms and preferences. @@ -221,6 +222,25 @@ export class MeetStorageService< }>; } + /** + * Retrieves recording media as a readable stream from the storage provider. + * + * @param recordingPath - The path to the recording file in storage + * @param range - Optional byte range for partial content retrieval + * @param range.start - Starting byte position + * @param range.end - Ending byte position + * @returns A Promise that resolves to a Readable stream of the recording media + */ + async getRecordingMedia( + recordingPath: string, + range?: { + end: number; + start: number; + } + ): Promise { + return this.storageProvider.getRecordingMedia(recordingPath, range) as Promise; + } + /** * Deletes multiple recording metadata files by their paths. *