From 6bcae65bca7b4775690d76e7266b613c6ce3112f Mon Sep 17 00:00:00 2001 From: Emily Dixon Date: Wed, 12 Jun 2024 16:59:40 -0700 Subject: [PATCH] Remove release-branch stuff from main --- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 16 +-- .../mux/video/vod/demo/UploadExampleApp.kt | 23 ---- .../vod/demo/backend/ImaginaryBackend.kt | 4 +- .../upload/viewmodel/CreateUploadViewModel.kt | 5 +- .../upload/viewmodel/UploadListViewModel.kt | 14 +-- app/src/main/res/values/strings.xml | 20 +--- build.gradle | 6 +- gradle/wrapper/gradle-wrapper.properties | 2 +- library/build.gradle | 2 +- .../com/mux/video/upload/api/MuxUpload.kt | 108 +++++++----------- .../com/mux/video/upload/api/UploadResult.kt | 1 - .../com/mux/video/upload/api/UploadStatus.kt | 6 +- .../mux/video/upload/internal/UploadInfo.kt | 5 +- .../video/upload/internal/UploadMetrics.kt | 26 +++-- 15 files changed, 84 insertions(+), 163 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 89bab66a..0c7be826 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.5.14' + kotlinCompilerExtensionVersion '1.5.9' } packagingOptions { resources { @@ -52,7 +52,7 @@ dependencies { implementation project(':library') implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0' + implementation 'com.google.android.material:material:1.10.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.activity:activity-ktx:1.8.2' implementation "androidx.fragment:fragment-ktx:1.6.2" @@ -60,7 +60,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'net.danlew:android.joda:2.12.7' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' - implementation 'androidx.activity:activity-compose:1.8.2' + implementation 'androidx.activity:activity-compose:1.8.0' implementation "androidx.compose.ui:ui:$compose_ui_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version" @@ -68,12 +68,11 @@ dependencies { implementation "androidx.compose.material:material-icons-extended:$compose_ui_version" implementation "com.google.accompanist:accompanist-systemuicontroller:0.34.0" - implementation 'androidx.compose.material:material:1.6.2' + implementation 'androidx.compose.material:material:1.6.1' implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation 'com.squareup.retrofit2:retrofit:2.9.0' - implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 72052409..f0e072e7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,12 +2,6 @@ - - - - - + android:maxSdkVersion="29" /> - - \ No newline at end of file diff --git a/app/src/main/java/com/mux/video/vod/demo/UploadExampleApp.kt b/app/src/main/java/com/mux/video/vod/demo/UploadExampleApp.kt index b603c1d4..828c7ea6 100644 --- a/app/src/main/java/com/mux/video/vod/demo/UploadExampleApp.kt +++ b/app/src/main/java/com/mux/video/vod/demo/UploadExampleApp.kt @@ -1,34 +1,11 @@ package com.mux.video.vod.demo -import android.annotation.TargetApi import android.app.Application -import android.app.NotificationChannel -import android.app.NotificationManager -import android.os.Build import com.mux.video.upload.MuxUploadSdk -import com.mux.video.upload.api.MuxUploadManager class UploadExampleApp : Application() { - override fun onCreate() { super.onCreate() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel() - } MuxUploadSdk.initialize(this) - if (MuxUploadManager.allUploadJobs().isNotEmpty()) { - UploadNotificationService.startCompat(this) - } - } - - @TargetApi(Build.VERSION_CODES.O) - private fun createNotificationChannel() { - val channel = NotificationChannel( - UploadNotificationService.CHANNEL_UPLOAD_PROGRESS, - getString(R.string.notif_channel_name), - NotificationManager.IMPORTANCE_LOW - ) - channel.description = getString(R.string.notif_channel_desc) - getSystemService(NotificationManager::class.java).createNotificationChannel(channel) } } diff --git a/app/src/main/java/com/mux/video/vod/demo/backend/ImaginaryBackend.kt b/app/src/main/java/com/mux/video/vod/demo/backend/ImaginaryBackend.kt index 266d5468..c8570760 100644 --- a/app/src/main/java/com/mux/video/vod/demo/backend/ImaginaryBackend.kt +++ b/app/src/main/java/com/mux/video/vod/demo/backend/ImaginaryBackend.kt @@ -56,8 +56,8 @@ object ImaginaryBackend { // note: You shouldn't do basic auth with hard-coded keys in a real app private fun basicCredential(): String = Credentials.basic(ACCESS_TOKEN_ID, ACCESS_TOKEN_SECRET) - private const val ACCESS_TOKEN_ID = "d354b6f0-c753-40de-86a0-f92d0d08d699" - private const val ACCESS_TOKEN_SECRET = "HPcVJBvQL+PTRn3NXX40UqJaV94XM1kuj5AdeG/WR92RDY4w9TapQlFJnk1l3FmIpF2SrGJPuXj" + private const val ACCESS_TOKEN_ID = "YOUR TOKEN ID HERE" + private const val ACCESS_TOKEN_SECRET = "YOUR TOKEN SECRET HERE" } private interface ImaginaryWebapp { diff --git a/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/CreateUploadViewModel.kt b/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/CreateUploadViewModel.kt index ae710302..a0588743 100644 --- a/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/CreateUploadViewModel.kt +++ b/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/CreateUploadViewModel.kt @@ -3,6 +3,7 @@ package com.mux.video.vod.demo.upload.viewmodel import android.app.Application import android.database.Cursor import android.graphics.Bitmap +import android.media.MediaMetadataRetriever import android.net.Uri import android.os.Build import android.provider.MediaStore @@ -12,7 +13,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.mux.video.upload.api.MuxUpload -import com.mux.video.vod.demo.UploadNotificationService import com.mux.video.vod.demo.backend.ImaginaryBackend import com.mux.video.vod.demo.upload.model.MediaStoreVideo import com.mux.video.vod.demo.upload.model.extractThumbnail @@ -67,8 +67,6 @@ class CreateUploadViewModel(private val app: Application) : AndroidViewModel(app ).build() // Force restart when creating brand new uploads (because we're making new Direct uploads) .start(forceRestart = true) - - UploadNotificationService.startCompat(app) } } @@ -76,6 +74,7 @@ class CreateUploadViewModel(private val app: Application) : AndroidViewModel(app * In order to upload a file from the device's media store, the file must be copied into the app's * temp directory. (Technically we could stream it from the source, but this prevents the other * app from modifying the file if we pause the upload for a long time or whatever) + * TODO Is this something that should go in the SDK? This is a common workflow */ @Throws private suspend fun copyIntoTempFile(contentUri: Uri): File { diff --git a/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/UploadListViewModel.kt b/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/UploadListViewModel.kt index 2a02e69e..89d45b22 100644 --- a/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/UploadListViewModel.kt +++ b/app/src/main/java/com/mux/video/vod/demo/upload/viewmodel/UploadListViewModel.kt @@ -1,7 +1,6 @@ package com.mux.video.vod.demo.upload.viewmodel import android.app.Application -import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -23,8 +22,7 @@ class UploadListViewModel(app: Application) : AndroidViewModel(app) { private val listUpdateListener: UploadEventListener> by lazy { UploadEventListener { newUploads -> - //uploadMap.forEach { entry -> entry.value.clearListeners() } - observeUploads(newUploads) + newUploads.forEach { uploadMap[it.videoFile] = it } updateUiData(uploadMap.values.toList()) } } @@ -50,13 +48,11 @@ class UploadListViewModel(app: Application) : AndroidViewModel(app) { } private fun observeUploads(recentUploads: List) { - recentUploads - .filter { !this.uploadMap.containsKey(it.videoFile) } - .forEach { upload -> - upload.setStatusListener { - updateUiData(uploadMap.values.toList()) - } + recentUploads.forEach { upload -> + upload.setProgressListener { uploadMap[upload.videoFile] = upload + updateUiData(uploadMap.values.toList()) + } } // recentUploads.forEach } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 793c13b3..edfc851e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,25 +1,9 @@ - Mux Upload Demo + Mux Video VOD Upload SDK Mux Upload Plain-View Mux Upload Example Settings Pause Button Create New Upload Upload! - - Upload status notifications - Progress Notifications for uploads moving in the background - - - Uploading video - Uploading %1$d videos - - - Failed to upload video. Tap to retry - %1$d uploads failed. Tap to retry - - - Upload succeeded - Successfully uploaded %1$d videos - - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 48f811bd..64bfd76e 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,8 @@ buildscript { } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.4.1' apply false - id 'com.android.library' version '8.4.1' apply false - id 'org.jetbrains.kotlin.android' version '1.9.24' apply false + id 'com.android.application' version '8.1.2' apply false + id 'com.android.library' version '8.1.2' apply false + id 'org.jetbrains.kotlin.android' version '1.9.22' apply false id 'com.mux.gradle.android.mux-android-distribution' version '1.1.2' apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3155ea18..d5e1fba2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Jan 19 16:04:49 PST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/library/build.gradle b/library/build.gradle index aefcfb06..127e217a 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -72,7 +72,7 @@ muxDistribution { dependencies { implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.squareup.okhttp3:okhttp:4.12.0" - implementation "io.github.crow-misia.libyuv:libyuv-android:0.31.1" + implementation "io.github.crow-misia.libyuv:libyuv-android:0.29.0" implementation 'androidx.core:core-ktx:1.12.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" diff --git a/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt b/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt index b2653048..371a8a82 100644 --- a/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt +++ b/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt @@ -1,14 +1,12 @@ package com.mux.video.upload.api import android.net.Uri -import android.util.Log import androidx.annotation.MainThread import com.mux.video.upload.MuxUploadSdk import com.mux.video.upload.api.MuxUpload.Builder import com.mux.video.upload.internal.UploadInfo import com.mux.video.upload.internal.update import kotlinx.coroutines.* -import kotlinx.coroutines.flow.distinctUntilChangedBy import java.io.File /** @@ -35,6 +33,7 @@ import java.io.File class MuxUpload private constructor( private var uploadInfo: UploadInfo, private val autoManage: Boolean = true, + initialStatus: UploadStatus = UploadStatus.Ready ) { /** @@ -57,18 +56,13 @@ class MuxUpload private constructor( * To be notified of status updates (including upload progress), use [setStatusListener] */ @Suppress("MemberVisibilityCanBePrivate") - val uploadStatus: UploadStatus get() = uploadInfo.statusFlow?.value ?: currentStatus + val uploadStatus: UploadStatus /** * True when the upload is running, false if it's paused, failed, or canceled */ val isRunning get() = uploadInfo.isRunning() - /** - * True when the upload is paused by [pause], false otherwise - */ - val isPaused get() = currentStatus is UploadStatus.UploadPaused - /** * If the upload has failed, gets the error associated with the failure */ @@ -78,25 +72,22 @@ class MuxUpload private constructor( /** * True if the upload was successful, false otherwise */ - val isSuccessful get() = uploadInfo.statusFlow?.value?.isSuccessful() ?: _successful + val isSuccessful get() = _successful private var _successful: Boolean = false private var resultListener: UploadEventListener>? = null private var progressListener: UploadEventListener? = null private var statusListener: UploadEventListener? = null private var observerJob: Job? = null - private val currentStatus: UploadStatus get() = - uploadInfo.statusFlow?.value ?: lastKnownStatus ?: UploadStatus.Ready - private var lastKnownStatus: UploadStatus? = null + private var currentStatus: UploadStatus = UploadStatus.Ready private val lastKnownProgress: Progress? get() = currentStatus.getProgress() private val callbackScope: CoroutineScope = MainScope() private val logger get() = MuxUploadSdk.logger init { - // Catch state if an upload was already in progress - // no need to observe: the Flow will have the most-recent values when queried - uploadInfo.statusFlow?.value?.let { status -> this.lastKnownStatus = status } + // Catch Events if an upload was already in progress + observeUpload(uploadInfo) } /** @@ -203,16 +194,8 @@ class MuxUpload private constructor( */ @MainThread fun setProgressListener(listener: UploadEventListener?) { - if (listener == null) { - observerJob?.cancel("clearing listeners") - observerJob = null - } else { - observeUpload(uploadInfo) - } - progressListener = listener lastKnownProgress?.let { listener?.onEvent(it) } - observeUpload(uploadInfo) } /** @@ -221,18 +204,11 @@ class MuxUpload private constructor( * @see setStatusListener */ @MainThread - fun setResultListener(listener: UploadEventListener>?) { - if (listener == null) { - observerJob?.cancel("clearing listeners") - observerJob = null - } else { - observeUpload(uploadInfo) - } - + fun setResultListener(listener: UploadEventListener>) { resultListener = listener lastKnownProgress?.let { if (it.bytesUploaded >= it.totalBytes) { - listener?.onEvent(Result.success(it)) + listener.onEvent(Result.success(it)) } } } @@ -244,13 +220,6 @@ class MuxUpload private constructor( */ @MainThread fun setStatusListener(listener: UploadEventListener?) { - if (listener == null) { - observerJob?.cancel("clearing listeners") - observerJob = null - } else { - observeUpload(uploadInfo) - } - statusListener = listener listener?.onEvent(currentStatus) } @@ -261,40 +230,39 @@ class MuxUpload private constructor( @Suppress("unused") @MainThread fun clearListeners() { - observerJob?.cancel("clearing listeners") resultListener = null progressListener = null statusListener = null } - private fun newObserveProgressJob(upload: UploadInfo): Job? { + private fun newObserveProgressJob(upload: UploadInfo): Job { // Job that collects and notifies state updates on the main thread (suspending on main is safe) - return upload.statusFlow?.let { flow -> - callbackScope.launch { - flow.collect { status -> - // Update the status of our upload - lastKnownStatus = status - - // Notify the old listeners - when (status) { - is UploadStatus.Uploading -> { progressListener?.onEvent(status.uploadProgress) } - is UploadStatus.UploadPaused -> { progressListener?.onEvent(status.uploadProgress) } - is UploadStatus.UploadSuccess -> { - _successful = true - progressListener?.onEvent(status.uploadProgress) - resultListener?.onEvent(Result.success(status.uploadProgress)) - } - is UploadStatus.UploadFailed -> { - progressListener?.onEvent(status.uploadProgress) // Make sure we're most up-to-date - if (status.exception !is CancellationException) { - _error = status.exception - resultListener?.onEvent(Result.failure(status.exception)) + return callbackScope.launch { + upload.statusFlow?.let { flow -> + launch { + flow.collect { status -> + // Update the status of our upload + currentStatus = status + statusListener?.onEvent(status) + + // Notify the old listeners + when (status) { + is UploadStatus.Uploading -> { progressListener?.onEvent(status.uploadProgress) } + is UploadStatus.UploadPaused -> { progressListener?.onEvent(status.uploadProgress) } + is UploadStatus.UploadSuccess -> { + progressListener?.onEvent(status.uploadProgress) + resultListener?.onEvent(Result.success(status.uploadProgress)) } + is UploadStatus.UploadFailed -> { + progressListener?.onEvent(status.uploadProgress) // Make sure we're most up-to-date + if (status.exception !is CancellationException) { + _error = status.exception + resultListener?.onEvent(Result.failure(status.exception)) + } + } + else -> { } // no relevant info } - else -> { } // no relevant info } - - statusListener?.onEvent(status) } } } @@ -305,6 +273,10 @@ class MuxUpload private constructor( observerJob = newObserveProgressJob(uploadInfo) } + init { + uploadStatus = initialStatus + } + /** * The current progress of an upload, in terms of time elapsed and data transmitted */ @@ -339,7 +311,7 @@ class MuxUpload private constructor( * @param videoFile a File that represents the video file you want to upload */ @Suppress("MemberVisibilityCanBePrivate") - class Builder(val uploadUri: Uri, val videoFile: File) { + class Builder constructor(val uploadUri: Uri, val videoFile: File) { /** * Create a new Builder with the specified input file and upload URL @@ -348,7 +320,8 @@ class MuxUpload private constructor( * @param videoFile a File that represents the video file you want to upload */ @Suppress("unused") - constructor(uploadUri: String, videoFile: File): this(Uri.parse(uploadUri), videoFile) + constructor(uploadUri: String, videoFile: File) + : this(Uri.parse(uploadUri), videoFile) private var manageTask: Boolean = true private var uploadInfo: UploadInfo = UploadInfo( @@ -430,6 +403,7 @@ class MuxUpload private constructor( * [MuxUploadManager] */ @JvmSynthetic - internal fun create(uploadInfo: UploadInfo) = MuxUpload(uploadInfo = uploadInfo) + internal fun create(uploadInfo: UploadInfo, initialStatus: UploadStatus = UploadStatus.Ready) + = MuxUpload(uploadInfo = uploadInfo, initialStatus = initialStatus) } } diff --git a/library/src/main/java/com/mux/video/upload/api/UploadResult.kt b/library/src/main/java/com/mux/video/upload/api/UploadResult.kt index 41ddb334..31e96440 100644 --- a/library/src/main/java/com/mux/video/upload/api/UploadResult.kt +++ b/library/src/main/java/com/mux/video/upload/api/UploadResult.kt @@ -5,7 +5,6 @@ package com.mux.video.upload.api * * Kotlin callers can use the [Result] API as normal */ -@Suppress("unused") class UploadResult { companion object { diff --git a/library/src/main/java/com/mux/video/upload/api/UploadStatus.kt b/library/src/main/java/com/mux/video/upload/api/UploadStatus.kt index d4df0a55..556150ad 100644 --- a/library/src/main/java/com/mux/video/upload/api/UploadStatus.kt +++ b/library/src/main/java/com/mux/video/upload/api/UploadStatus.kt @@ -35,19 +35,19 @@ sealed class UploadStatus { /** * This upload hos not been started. It is ready to start by calling [MuxUpload.start] */ - data object Ready: UploadStatus() + object Ready: UploadStatus() /** * This upload has been started via [MuxUpload.start] but has not yet started processing anything */ - data object Started: UploadStatus() + object Started: UploadStatus() /** * This upload is being prepared. If standardization is required, it is done during this step * * @see MuxUpload.Builder.standardizationRequested */ - data object Preparing: UploadStatus() + object Preparing: UploadStatus() /** * The upload is currently being sent to Mux Video. The progress is available diff --git a/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt b/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt index 7e6d70cf..a0d31406 100644 --- a/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt +++ b/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt @@ -28,10 +28,7 @@ internal data class UploadInfo( @JvmSynthetic internal val uploadJob: Deferred>?, @JvmSynthetic internal val statusFlow: StateFlow?, ) { - fun isRunning(): Boolean = /*uploadJob?.isActive*/ - statusFlow?.value?.let { - it is UploadStatus.Uploading || it is UploadStatus.Started || it is UploadStatus.Preparing - } ?: false + fun isRunning(): Boolean = uploadJob?.isActive ?: false } /** diff --git a/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt b/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt index 4b6c9324..f5ff1f32 100644 --- a/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt +++ b/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt @@ -24,6 +24,16 @@ internal class UploadMetrics private constructor() { private val logger get() = MuxUploadSdk.logger + + private fun formatMilliseconds(ms:Long):String { + return String.format("%02d:%02d:%02d", + TimeUnit.MILLISECONDS.toHours(ms), + TimeUnit.MILLISECONDS.toMinutes(ms) - + TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(ms)), // The change is in this line + TimeUnit.MILLISECONDS.toSeconds(ms) - + TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(ms))); + } + private suspend fun getEventInfo(startTimeMillis: Long, startTimeKey: String, endTimeMillis: Long, @@ -115,8 +125,8 @@ internal class UploadMetrics private constructor() { maximumResolution:String, sessionId: String, uploadInfo: UploadInfo - ) = runCatching { - val body = JSONObject() + ) { + var body = JSONObject() body.put("type", "upload_input_standardization_succeeded") body.put("session_id", sessionId) body.put("version", "1") @@ -139,8 +149,8 @@ internal class UploadMetrics private constructor() { maximumResolution:String, sessionId: String, uploadInfo: UploadInfo - ) = runCatching { - val body = JSONObject() + ) { + var body = JSONObject() body.put("type", "upload_input_standardization_failed") body.put("session_id", sessionId) body.put("version", "1") @@ -162,8 +172,8 @@ internal class UploadMetrics private constructor() { inputFileDurationMs: Long, sessionId: String, uploadInfo: UploadInfo - ) = runCatching { - val body = JSONObject() + ) { + var body = JSONObject() body.put("type", "upload_succeeded") body.put("session_id", sessionId) body.put("version", "1") @@ -183,8 +193,8 @@ internal class UploadMetrics private constructor() { errorDescription:String, sessionId: String, uploadInfo: UploadInfo - ) = runCatching { - val body = JSONObject() + ) { + var body = JSONObject() body.put("type", "uploadfailed") body.put("session_id", sessionId) body.put("version", "1")