Налаштування сховища медіафайлів мобільного додатку (S3/Cloud Storage)

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Налаштування сховища медіафайлів мобільного додатку (S3/Cloud Storage)
Середній
від 1 дня до 3 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Налаштування хранилища медіафайлів мобільного додатку (S3/Cloud Storage)

Завантаження фото та відео з мобільного додатку — завдання, яке легко недооцінити. Один запит на 50 МБ відео через бекенд-проксі — це нагрузка на сервер, подвійний трафік та повільна завантаження для користувача. Правильна архітектура: presigned URL → пряма завантаження з клієнта у S3 або GCS, бекенд тільки видає токен та отримує підтвердження.

Вибір провайдера

Провайдер SDK Сильні сторони
AWS S3 aws-sdk-swift, aws-sdk-kotlin, Amplify Зрілість, багатство функцій, multipart з коробки
Google Cloud Storage google-cloud-storage (Java/Kotlin), GCS REST API Хороша інтеграція з Firebase, GCP
Cloudflare R2 S3-сумісний API Немає egress-трафіку, дешевше
Backblaze B2 S3-сумісний API Найдешевший об'єктний storage

R2 та B2 сумісні з S3 API — той самий клієнтський код, тільки інший endpoint.

Завантаження з прогресом

Користувач бачить progress bar — це не опціонально для відео. iOS через URLSession.uploadTask з делегатом:

class MediaUploader: NSObject, URLSessionTaskDelegate {
    private lazy var session: URLSession = {
        URLSession(configuration: .default, delegate: self, delegateQueue: nil)
    }()

    func upload(fileURL: URL, presignedURL: URL,
                progress: @escaping (Double) -> Void,
                completion: @escaping (Result<Void, Error>) -> Void) {
        var request = URLRequest(url: presignedURL)
        request.httpMethod = "PUT"
        request.setValue(fileURL.mimeType, forHTTPHeaderField: "Content-Type")

        let task = session.uploadTask(with: request, fromFile: fileURL)
        task.taskDescription = fileURL.lastPathComponent

        uploadCompletionHandlers[task.taskIdentifier] = completion
        uploadProgressHandlers[task.taskIdentifier] = progress
        task.resume()
    }

    func urlSession(_ session: URLSession, task: URLSessionTask,
                    didSendBodyData bytesSent: Int64,
                    totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
        let progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend)
        uploadProgressHandlers[task.taskIdentifier]?(progress)
    }
}

На Android через OkHttp з кастомним RequestBody:

class ProgressRequestBody(
    private val file: File,
    private val contentType: MediaType,
    private val onProgress: (Int) -> Unit
) : RequestBody() {
    override fun contentType() = contentType
    override fun contentLength() = file.length()

    override fun writeTo(sink: BufferedSink) {
        val buffer = ByteArray(8192)
        var uploaded = 0L
        file.inputStream().use { input ->
            var bytesRead: Int
            while (input.read(buffer).also { bytesRead = it } != -1) {
                sink.write(buffer, 0, bytesRead)
                uploaded += bytesRead
                val progress = (uploaded * 100 / file.length()).toInt()
                onProgress(progress)
            }
        }
    }
}

Multipart завантаження для відео

S3 вимагає multipart для файлів більше 5 МБ (рекомендується від 100 МБ). Переваги: паралельна завантаження частин, можливість возобновити після переривання.

class S3MultipartUploader(private val s3Client: S3Client) {
    suspend fun upload(bucketName: String, key: String, file: File): String {
        // 1. Ініціалізуємо multipart завантаження
        val createResponse = s3Client.createMultipartUpload {
            bucket = bucketName
            this.key = key
            contentType = file.detectMimeType()
        }
        val uploadId = createResponse.uploadId!!

        val partSize = 10 * 1024 * 1024L // 10 МБ на частину
        val parts = mutableListOf<CompletedPart>()

        try {
            file.inputStream().use { stream ->
                var partNumber = 1
                val buffer = ByteArray(partSize.toInt())
                var bytesRead: Int

                while (stream.read(buffer).also { bytesRead = it } != -1) {
                    val partData = buffer.copyOf(bytesRead)
                    val uploadPartResponse = s3Client.uploadPart {
                        bucket = bucketName
                        this.key = key
                        this.uploadId = uploadId
                        this.partNumber = partNumber
                        body = ByteStream.fromBytes(partData)
                    }
                    parts.add(CompletedPart {
                        this.partNumber = partNumber
                        eTag = uploadPartResponse.eTag
                    })
                    partNumber++
                }
            }

            // 3. Завершуємо multipart завантаження
            s3Client.completeMultipartUpload {
                bucket = bucketName
                this.key = key
                this.uploadId = uploadId
                multipartUpload = CompletedMultipartUpload { this.parts = parts }
            }

            return "https://$bucketName.s3.amazonaws.com/$key"
        } catch (e: Exception) {
            // Очищуємо незавершене завантаження — інакше тарифікується
            s3Client.abortMultipartUpload {
                bucket = bucketName
                this.key = key
                this.uploadId = uploadId
            }
            throw e
        }
    }
}

abortMultipartUpload при помилці — важливо. Незавершені multipart завантаження продовжують тарифікуватися у AWS. Додайте Lifecycle Rule на видалення незавершених завантажень через 7 днів як страховку.

Обробка медіафайлів перед завантаженням

Відео 4K 200 МБ прямо у S3 — рідко потрібно. На клієнті перед завантаженням:

  • Зображення: стиснення через UIGraphicsImageRenderer (iOS) або BitmapFactory.Options.inSampleSize (Android). WebP замість JPEG — краще співвідношення якості/розміру.
  • Відео: трансодування через AVAssetExportSession (iOS) або MediaCodec/Transformer (Android) до 1080p/720p. Для React Native — react-native-video-processing.
// iOS: стиснення відео перед завантаженням
let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset1280x720)!
exporter.outputURL = outputURL
exporter.outputFileType = .mp4
exporter.shouldOptimizeForNetworkUse = true

await exporter.export()
// Після експорту — завантажуємо outputURL у S3

Клієнтське стиснення — компроміс: менше трафіку, швидша завантаження, але нагрузка на CPU пристрою.

CDN для розповсюдження

S3 напрямки — тільки для приватних файлів. Публічні медіа (аватари, контент) — через CloudFront або Cloudflare CDN. Це кеширування на edge-нодах по всьому світу та HTTPS без додаткової налаштування.

Налаштування хранилища медіафайлів з presigned завантаженням, multipart для відео, CDN та стисненням: 1–2 тижні. Вартість рассчитывается індивідуально.