diff --git a/openvidu-server/pom.xml b/openvidu-server/pom.xml index bd2fbf0d..1f42077d 100644 --- a/openvidu-server/pom.xml +++ b/openvidu-server/pom.xml @@ -327,6 +327,18 @@ openvidu-java-client ${version.openvidu.java.client} + + com.google.cloud + libraries-bom + 16.1.0 + pom + + + com.google.cloud + google-cloud-storage + 1.113.4 + + diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java index cede2eb9..d0fa5e5d 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java +++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Semaphore; +import io.openvidu.server.recording.*; import org.bouncycastle.util.Arrays; import org.kurento.jsonrpc.internal.server.config.JsonRpcConfiguration; import org.kurento.jsonrpc.server.JsonRpcConfigurer; @@ -61,10 +62,6 @@ import io.openvidu.server.kurento.kms.DummyLoadManager; import io.openvidu.server.kurento.kms.FixedOneKmsManager; import io.openvidu.server.kurento.kms.KmsManager; import io.openvidu.server.kurento.kms.LoadManager; -import io.openvidu.server.recording.DummyRecordingDownloader; -import io.openvidu.server.recording.DummyRecordingUploader; -import io.openvidu.server.recording.RecordingDownloader; -import io.openvidu.server.recording.RecordingUploader; import io.openvidu.server.recording.service.RecordingManager; import io.openvidu.server.recording.service.RecordingManagerUtils; import io.openvidu.server.recording.service.RecordingManagerUtilsLocalStorage; @@ -203,7 +200,11 @@ public class OpenViduServer implements JsonRpcConfigurer { @Bean @ConditionalOnMissingBean - public RecordingUploader recordingUpload() { + @DependsOn("openviduConfig") + public RecordingUploader recordingUpload(OpenviduConfig openviduConfig) { + if (openviduConfig.isGcpRecordingStorageEnabled()) { + return new GoogleCloudStorageRecordingUploader(); + } return new DummyRecordingUploader(); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index 199350af..cf011735 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -190,6 +190,10 @@ public class OpenviduConfig { private String dotenvPath; + private boolean gcpRecordingStorageEnabled; + + private String gcpStorageBucketName; + // Derived properties public static String finalUrl; @@ -338,6 +342,14 @@ public class OpenviduConfig { return dotenvPath; } + public boolean isGcpRecordingStorageEnabled() { + return gcpRecordingStorageEnabled; + } + + public String getGcpStorageBucketName() { + return gcpStorageBucketName; + } + // Derived properties methods public String getSpringProfile() { @@ -533,6 +545,10 @@ public class OpenviduConfig { openviduForcedCodec = asEnumValue("OPENVIDU_STREAMS_FORCED_VIDEO_CODEC", VideoCodec.class); openviduAllowTranscoding = asBoolean("OPENVIDU_STREAMS_ALLOW_TRANSCODING"); + gcpRecordingStorageEnabled = asBoolean("OPENVIDU_GCP_RECORDING_STORAGE_ENABLED"); + gcpStorageBucketName = gcpRecordingStorageEnabled ? asNonEmptyString("OPENVIDU_GCP_STORAGE_BUCKET_NAME") + : null; + kmsUrisList = checkKmsUris(); checkCoturnIp(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/GoogleCloudStorageRecordingUploader.java b/openvidu-server/src/main/java/io/openvidu/server/recording/GoogleCloudStorageRecordingUploader.java new file mode 100644 index 00000000..4222e060 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/GoogleCloudStorageRecordingUploader.java @@ -0,0 +1,68 @@ +package io.openvidu.server.recording; + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageException; +import io.openvidu.server.config.OpenviduConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + + +public class GoogleCloudStorageRecordingUploader implements RecordingUploader { + + private static final Logger log = LoggerFactory.getLogger(GoogleCloudStorageRecordingUploader.class); + + @Autowired + private OpenviduConfig config; + + @Autowired + private Storage gcpStorage; + + private final Set recordingsBeingCurrentlyUploaded = ConcurrentHashMap.newKeySet(); + + @Override + public void uploadRecording(Recording recording, Runnable successCallback, Runnable errorCallback) { + recordingsBeingCurrentlyUploaded.add(recording.getId()); + + String bucketName = config.getGcpStorageBucketName(); + BlobInfo blobInfo = BlobInfo.newBuilder(BlobId.of(bucketName, recording.getName())).build(); + String filePath = getComposedRecordingLocalFilePath(recording); + + try { + gcpStorage.create(blobInfo, Files.readAllBytes(Paths.get(filePath))); + } catch (StorageException | IOException e) { + log.error(e.getMessage(), e); + errorCallback.run(); + } finally { + recordingsBeingCurrentlyUploaded.remove(recording.getId()); + } + successCallback.run(); + } + + private String getComposedRecordingLocalFilePath(Recording recording) { + // Audio-only recordings are in WEBM file format + String fileExt = recording.hasVideo() ? ".mp4" : ".webm"; + return config.getOpenViduRecordingPath() + "/" + recording.getId() + "/" + recording.getName() + fileExt; + } + + // Prevent uploading recordings from being retrieved from REST API with "ready" + // status. This will force their status back to "stopped" on GET until upload + // process has finished + @Override + public void storeAsUploadingRecording(String recordingId) { + recordingsBeingCurrentlyUploaded.add(recordingId); + } + + @Override + public boolean isBeingUploaded(String recordingId) { + return recordingsBeingCurrentlyUploaded.contains(recordingId); + } +}