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/UploadNotificationService.kt b/app/src/main/java/com/mux/video/vod/demo/UploadNotificationService.kt
deleted file mode 100644
index 2fcf5bbf..00000000
--- a/app/src/main/java/com/mux/video/vod/demo/UploadNotificationService.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.mux.video.vod.demo
-
-import android.annotation.SuppressLint
-import android.annotation.TargetApi
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ServiceInfo
-import android.os.Binder
-import android.os.Build
-import android.os.IBinder
-import android.util.Log
-import androidx.core.app.NotificationCompat
-import androidx.core.app.ServiceCompat
-import com.mux.video.upload.api.MuxUpload
-import com.mux.video.upload.api.MuxUploadManager
-import com.mux.video.upload.api.UploadEventListener
-import com.mux.video.upload.api.UploadStatus
-
-/**
- * Service that monitors ongoing [MuxUpload]s, showing progress notifications for them. This
- * service will enter the foreground whenever there are uploads in progress and will exit foreground
- * and stop itself when there are no more uploads in progress (ie, all have completed, paused, or
- * failed)
- */
-class UploadNotificationService : Service() {
-
- companion object {
- private const val TAG = "BackgroundUploadService"
-
- const val ACTION_START = "start"
- const val NOTIFICATION_FG = 200002
- const val CHANNEL_UPLOAD_PROGRESS = "upload_progress"
-
- fun startCompat(context: Context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startImplApiO(context)
- } else {
- startImplLegacy(context)
- }
- }
-
- @TargetApi(Build.VERSION_CODES.O)
- private fun startImplApiO(context: Context) {
- val startIntent = Intent(context, UploadNotificationService::class.java)
- startIntent.action = ACTION_START
- context.startForegroundService(startIntent)
- }
-
- private fun startImplLegacy(context: Context) {
- val startIntent = Intent(context, UploadNotificationService::class.java)
- startIntent.action = ACTION_START
- context.startService(startIntent)
- }
- }
-
- private var uploadListListener: UploadListListener? = null
- // uploads tracked by this Service, regardless of state. cleared when the service is destroyed
- private val uploadsByFile = mutableMapOf()
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- val action = intent?.action
- if (action != ACTION_START) {
- throw RuntimeException("Unknown action")
- }
-
- // can be commanded to start arbitrary number of times
- if (uploadListListener == null) {
- notify(MuxUploadManager.allUploadJobs())
-
- val lis = UploadListListener()
- this.uploadListListener = lis
- MuxUploadManager.addUploadsUpdatedListener(lis)
- }
-
- return super.onStartCommand(intent, flags, startId)
- }
-
- override fun onBind(intent: Intent?): IBinder? {
- return MyBinder()
- }
-
- override fun onDestroy() {
- uploadListListener?.let { MuxUploadManager.removeUploadsUpdatedListener(it) }
- }
-
- private fun notifyWithCurrentUploads() = notify(this.uploadsByFile.values)
-
- @SuppressLint("InlinedApi", "MissingPermission") // inline use of FOREGROUND_SERVICE
- private fun notify(uploads: Collection) {
- if (uploads.isEmpty()) {
- // only notify if there are uploads being tracked (in-progress or finished)
- return
- }
-
- val uploadsInProgress = uploads.filter { it.isRunning }
- val uploadsCompleted = uploads.filter { it.isSuccessful }
- val uploadsFailed = uploads.filter { it.error != null }
-
- Log.v(TAG, "notify: uploadsInProgress: ${uploadsInProgress.size}")
- Log.v(TAG, "notify: uploadsCompleted: ${uploadsCompleted.size}")
- Log.v(TAG, "notify: uploadsFailed: ${uploadsFailed.size}")
-
- val builder = NotificationCompat.Builder(this, CHANNEL_UPLOAD_PROGRESS)
- builder.setSmallIcon(R.drawable.ic_launcher)
- builder.setAutoCancel(false)
- builder.setOngoing(true)
-
- if (uploadsInProgress.isNotEmpty()) {
- Log.d(TAG, "notifying progress")
- if (uploadsInProgress.size == 1 && this.uploadsByFile.size == 1) {
- // Special case: A single upload in progress, with a single upload requested
- val upload = uploadsInProgress.first()
- val kbUploaded = (upload.currentProgress.bytesUploaded / 1024).toInt()
- val kbTotal = (upload.currentProgress.totalBytes / 1024).toInt()
-
- Log.d(TAG, "upload state: ${upload.uploadStatus}")
-
- builder.setProgress(kbTotal, kbUploaded, false)
- builder.setContentTitle(
- resources.getQuantityString(
- R.plurals.notif_txt_uploading, 1, 1, 1
- )
- )
- } else {
- // Multiple uploads requested simultaneously so we batch them into one
- val totalKbUploaded = uploadsInProgress.sumOf { it.currentProgress.bytesUploaded / 1024 }
- val totalKb = uploadsInProgress.sumOf { it.currentProgress.totalBytes / 1024 }
-
- builder.setProgress(totalKb.toInt(),totalKbUploaded.toInt(), false)
- builder.setContentTitle(
- resources.getQuantityString(
- R.plurals.notif_txt_uploading,
- uploads.size,
- uploads.size,
- )
- )
- }
- } else if (uploadsFailed.isNotEmpty()) {
- Log.i(TAG, "notifying Fail")
- builder.setContentTitle(
- resources.getQuantityString(
- R.plurals.notif_txt_failed,
- uploadsFailed.size,
- uploadsFailed.size
- )
- )
- } else if (uploadsCompleted.isNotEmpty()) {
- Log.i(TAG, "notifying Complete")
- builder.setContentTitle(
- resources.getQuantityString(
- R.plurals.notif_txt_success,
- uploadsCompleted.size,
- uploadsCompleted.size,
- )
- )
- }
-
- // always startForeground even if we're about to detach (to update the notification)
- ServiceCompat.startForeground(
- this,
- NOTIFICATION_FG,
- builder.build(),
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
- )
-
- if (uploadsInProgress.isEmpty()) {
- // we only need foreground/to even be running while uploads are actually running
- ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
- stopSelf()
- }
- }
-
- private fun updateCurrentUploads(incomingUploads: List) {
- // listen to status of new uploads
- incomingUploads
- .filter { !this.uploadsByFile.containsKey(it.videoFile.path) }
- .forEach {
- this.uploadsByFile[it.videoFile.path] = it
- it.setStatusListener(UploadStatusListener())
- }
- }
-
- private inner class UploadListListener : UploadEventListener> {
- override fun onEvent(event: List) {
- val service = this@UploadNotificationService
- service.updateCurrentUploads(event)
- service.notifyWithCurrentUploads()
- }
- }
-
- private inner class UploadStatusListener : UploadEventListener {
- override fun onEvent(event: UploadStatus) {
- val service = this@UploadNotificationService
- service.notifyWithCurrentUploads()
- }
- }
-
- private inner class MyBinder : Binder() {
- fun getService(): UploadNotificationService = this@UploadNotificationService
- }
-}
\ No newline at end of file
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