From 0cbc7052f048951c3ff40f5dcd244f204ecb457c Mon Sep 17 00:00:00 2001 From: Emily Dixon Date: Fri, 17 Nov 2023 16:13:11 -0800 Subject: [PATCH] Releases/v0.3.0 (#16) ## New * new: Add max and min playback resolution (#17) ## Updates * update: Improve example app appearance + misc updates (#14) Co-authored-by: Emily Dixon Co-authored-by: Tom Kordic --- app/build.gradle.kts | 9 +- app/src/main/AndroidManifest.xml | 11 +- .../com/mux/player/media3/MainActivity.kt | 14 +- .../media3/examples/BasicPlayerActivity.kt | 6 +- .../player/media3/examples/MaxResActivity.kt | 122 ++++++++++++++++++ app/src/main/res/drawable/mux_logo.xml | 19 +++ app/src/main/res/layout/activity_main.xml | 25 +++- app/src/main/res/layout/listitem_example.xml | 8 +- app/src/main/res/values-night/themes.xml | 35 +++-- app/src/main/res/values/colors.xml | 15 +++ app/src/main/res/values/themes.xml | 34 +++-- build.gradle.kts | 4 +- library/build.gradle | 3 +- .../src/main/java/com/mux/player/MuxPlayer.kt | 10 +- .../java/com/mux/player/internal/Logger.kt | 4 +- .../java/com/mux/player/media/MediaItems.kt | 48 +++++-- 16 files changed, 314 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/com/mux/player/media3/examples/MaxResActivity.kt create mode 100644 app/src/main/res/drawable/mux_logo.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 92f4a5f1..9fe33d2e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,13 +4,12 @@ plugins { } android { - namespace = "com.mux.video.media3" + namespace = "com.mux.player.media3" compileSdk = 34 defaultConfig { - applicationId = "com.mux.video.media3" minSdk = 21 - targetSdk = 33 + targetSdk = 34 versionCode = 1 versionName = "1.0" @@ -38,9 +37,9 @@ android { dependencies { implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.9.0") + implementation("com.google.android.material:material:1.10.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.recyclerview:recyclerview:1.3.1") + implementation("androidx.recyclerview:recyclerview:1.3.2") implementation(project(":library")) testImplementation("junit:junit:4.13.2") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa79be7a..bfd7f572 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,10 +14,17 @@ tools:targetApi="31"> + android:exported="false" + android:label="Basic Foreground Player" /> + + android:exported="true" + android:label="Examples" + android:theme="@style/Theme.MuxVideoMedia3.NoActionBar"> diff --git a/app/src/main/java/com/mux/player/media3/MainActivity.kt b/app/src/main/java/com/mux/player/media3/MainActivity.kt index eb9e80fa..c701676e 100644 --- a/app/src/main/java/com/mux/player/media3/MainActivity.kt +++ b/app/src/main/java/com/mux/player/media3/MainActivity.kt @@ -3,15 +3,17 @@ package com.mux.player.media3 import android.content.Context import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.mux.player.media3.databinding.ActivityMainBinding +import com.mux.player.media3.databinding.ListitemExampleBinding import com.mux.player.media3.examples.BasicPlayerActivity -import com.mux.video.media3.databinding.ActivityMainBinding -import com.mux.video.media3.databinding.ListitemExampleBinding +import com.mux.player.media3.examples.MaxResActivity class MainActivity : AppCompatActivity() { @@ -24,6 +26,10 @@ class MainActivity : AppCompatActivity() { setContentView(binding.root) examplesView.layoutManager = LinearLayoutManager(this) + binding.mainExampleTb.apply { + setLogo(R.drawable.mux_logo) + } + setUpExampleList() } @@ -36,6 +42,10 @@ class MainActivity : AppCompatActivity() { Example( title = "Basic Foreground Player", destination = Intent(this@MainActivity, BasicPlayerActivity::class.java) + ), + Example( + title = "Max-Resolution Modifier", + destination = Intent(this@MainActivity, MaxResActivity::class.java) ) ) } diff --git a/app/src/main/java/com/mux/player/media3/examples/BasicPlayerActivity.kt b/app/src/main/java/com/mux/player/media3/examples/BasicPlayerActivity.kt index 0f6bed53..e06c032d 100644 --- a/app/src/main/java/com/mux/player/media3/examples/BasicPlayerActivity.kt +++ b/app/src/main/java/com/mux/player/media3/examples/BasicPlayerActivity.kt @@ -17,9 +17,9 @@ import com.mux.stats.sdk.core.model.CustomerViewData import com.mux.stats.sdk.core.util.UUID import com.mux.player.MuxPlayer import com.mux.player.media.MediaItems -import com.mux.player.media.PlaybackMaxResolution +import com.mux.player.media.PlaybackResolution import com.mux.player.media3.PlaybackIds -import com.mux.video.media3.databinding.ActivityBasicPlayerBinding +import com.mux.player.media3.databinding.ActivityBasicPlayerBinding /** * A simple example that uses the normal media3 player UI to play a video in the foreground from @@ -59,8 +59,6 @@ class BasicPlayerActivity : AppCompatActivity() { val player = createPlayer(this) val mediaItem = MediaItems.builderFromMuxPlaybackId( PlaybackIds.TEARS_OF_STEEL, -// PlaybackResolution.FHD_1080, - PlaybackMaxResolution.HD_720, ) .setMediaMetadata( MediaMetadata.Builder() diff --git a/app/src/main/java/com/mux/player/media3/examples/MaxResActivity.kt b/app/src/main/java/com/mux/player/media3/examples/MaxResActivity.kt new file mode 100644 index 00000000..5820ce08 --- /dev/null +++ b/app/src/main/java/com/mux/player/media3/examples/MaxResActivity.kt @@ -0,0 +1,122 @@ +package com.mux.player.media3.examples + +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.annotation.OptIn +import androidx.appcompat.app.AppCompatActivity +import androidx.media3.common.MediaMetadata +import androidx.media3.common.PlaybackException +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import com.mux.stats.sdk.core.model.CustomData +import com.mux.stats.sdk.core.model.CustomerData +import com.mux.stats.sdk.core.model.CustomerVideoData +import com.mux.stats.sdk.core.model.CustomerViewData +import com.mux.stats.sdk.core.util.UUID +import com.mux.player.MuxPlayer +import com.mux.player.media.MediaItems +import com.mux.player.media.PlaybackResolution +import com.mux.player.media3.PlaybackIds +import com.mux.player.media3.databinding.ActivityBasicPlayerBinding + +/** + * A simple example that uses the normal media3 player UI to play a video in the foreground from + * Mux Video, using a Playback ID + */ +class MaxResActivity : AppCompatActivity() { + + private lateinit var binding: ActivityBasicPlayerBinding + private val playerView get() = binding.player + + private var player: MuxPlayer? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityBasicPlayerBinding.inflate(layoutInflater) + setContentView(binding.root) + } + + override fun onStart() { + super.onStart() + + playSomething() + } + + override fun onStop() { + tearDownPlayer() + + super.onStop() + } + + private fun tearDownPlayer() { + playerView.player = null + player?.release() + } + + private fun playSomething() { + val player = createPlayer(this) + val mediaItem = MediaItems.builderFromMuxPlaybackId( + PlaybackIds.TEARS_OF_STEEL, + maxResolution = PlaybackResolution.HD_720, + ) + .setMediaMetadata( + MediaMetadata.Builder() + .setTitle("Basic MuxExoPlayer Example") + .build() + ) + .build() + player.setMediaItem(mediaItem) + player.prepare() + player.playWhenReady = true + + this.playerView.player = player + this.player = player + } + + @OptIn(UnstableApi::class) + private fun createPlayer(context: Context): MuxPlayer { + val out: MuxPlayer = MuxPlayer.Builder(context) + .addMonitoringData( + CustomerData().apply { + customerViewData = CustomerViewData().apply { + viewSessionId = UUID.generateUUID() + } + customerVideoData = CustomerVideoData().apply { + videoSeries = "My Series" + videoId = "abc1234zyxw" + } + customData = CustomData().apply { + customData1 = "my custom metadata field" + customData2 = "another custom metadata field" + customData10 = "up to 10 custom fields" + } + } + ) + .applyExoConfig { + // Call ExoPlayer.Builder methods here + setHandleAudioBecomingNoisy(true) + setSeekBackIncrementMs(10_000) + setSeekForwardIncrementMs(30_000) + } + .build() + + out.addListener(object : Player.Listener { + override fun onPlayerError(error: PlaybackException) { + Log.e(TAG, "player error!", error) + Toast.makeText( + this@MaxResActivity, + "Playback error! ${error.localizedMessage}", + Toast.LENGTH_LONG + ).show() + } + }) + + return out + } + + companion object { + val TAG = MaxResActivity::class.simpleName + } +} diff --git a/app/src/main/res/drawable/mux_logo.xml b/app/src/main/res/drawable/mux_logo.xml new file mode 100644 index 00000000..a931f7cb --- /dev/null +++ b/app/src/main/res/drawable/mux_logo.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index adf2e57b..d373461e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,10 +6,33 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + + diff --git a/app/src/main/res/layout/listitem_example.xml b/app/src/main/res/layout/listitem_example.xml index 50176dec..2c9cfb1b 100644 --- a/app/src/main/res/layout/listitem_example.xml +++ b/app/src/main/res/layout/listitem_example.xml @@ -1,7 +1,8 @@ \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index f1c26087..eb71b2c4 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -2,15 +2,32 @@ - \ No newline at end of file + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127d..54b2cfaf 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,19 @@ #FF018786 #FF000000 #FFFFFFFF + + + #FF00AA3C + #FF00802D + #FF005C20 + #FFB2BAC2 + #FF808C99 + #FF565E67 + #FF3E4247 + #FF242628 + #FF0A0A0B + #FFFB65D5 + #FFFFFFFF + + #FF1E1E1E \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index e922c55d..bd4d9f94 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -2,15 +2,33 @@ + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7cf1462f..0932b142 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.1.0" apply false + id("com.android.application") version "8.1.3" apply false id("org.jetbrains.kotlin.android") version "1.8.0" apply false - id("com.android.library") version "8.1.0" apply false + id("com.android.library") version "8.1.3" apply false id("com.mux.gradle.android.mux-android-distribution") version "1.1.2" apply false } \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index ab34241f..f5d9add9 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -6,11 +6,12 @@ plugins { } android { - namespace 'com.mux.video' + namespace 'com.mux.player' compileSdk 34 defaultConfig { minSdk 21 + targetSdk 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/library/src/main/java/com/mux/player/MuxPlayer.kt b/library/src/main/java/com/mux/player/MuxPlayer.kt index f356426c..0719b26e 100644 --- a/library/src/main/java/com/mux/player/MuxPlayer.kt +++ b/library/src/main/java/com/mux/player/MuxPlayer.kt @@ -7,9 +7,9 @@ import androidx.media3.exoplayer.ExoPlayer import com.mux.stats.sdk.core.model.CustomerData import com.mux.stats.sdk.muxstats.MuxStatsSdkMedia3 import com.mux.stats.sdk.muxstats.monitorWithMuxData -import com.mux.player.internal.LogcatLogger +import com.mux.player.internal.createLogcatLogger import com.mux.player.internal.Logger -import com.mux.player.internal.NoLogger +import com.mux.player.internal.createNoLogger import com.mux.player.media.MuxMediaSourceFactory /** @@ -93,9 +93,9 @@ class MuxPlayer private constructor( @Suppress("unused") fun enableLogcat(enableLogcat: Boolean): Builder { logger = if (enableLogcat) { - LogcatLogger() + createLogcatLogger() } else { - NoLogger() + createNoLogger() } return this } @@ -146,7 +146,7 @@ class MuxPlayer private constructor( context = context, exoPlayer = this.playerBuilder.build(), muxDataKey = this.dataEnvKey, - logger = logger ?: NoLogger(), + logger = logger ?: createNoLogger(), initialCustomerData = customerData, ) } diff --git a/library/src/main/java/com/mux/player/internal/Logger.kt b/library/src/main/java/com/mux/player/internal/Logger.kt index c0935026..1f88f005 100644 --- a/library/src/main/java/com/mux/player/internal/Logger.kt +++ b/library/src/main/java/com/mux/player/internal/Logger.kt @@ -18,13 +18,13 @@ internal interface Logger { * Creates a new [Logger] that logs to Logcat */ @JvmSynthetic -internal fun LogcatLogger(): Logger = DeviceLogger() +internal fun createLogcatLogger(): Logger = DeviceLogger() /** * Creates a new [Logger] that doesn't log anything at all */ @JvmSynthetic -internal fun NoLogger(): Logger = SilentLogger() +internal fun createNoLogger(): Logger = SilentLogger() private class SilentLogger: Logger { override fun e(tag: String, message: String, exception: Exception?) { } diff --git a/library/src/main/java/com/mux/player/media/MediaItems.kt b/library/src/main/java/com/mux/player/media/MediaItems.kt index 01542515..776ef40d 100644 --- a/library/src/main/java/com/mux/player/media/MediaItems.kt +++ b/library/src/main/java/com/mux/player/media/MediaItems.kt @@ -33,12 +33,16 @@ object MediaItems { @JvmOverloads fun fromMuxPlaybackId( playbackId: String, - maxResolution: PlaybackMaxResolution? = null, + maxResolution: PlaybackResolution? = null, + minResolution: PlaybackResolution? = null, + renditionOrder: RenditionOrder? = null, domain: String = MUX_VIDEO_DEFAULT_DOMAIN, playbackToken: String? = null, ): MediaItem = builderFromMuxPlaybackId( playbackId, maxResolution, + minResolution, + renditionOrder, domain, playbackToken, ).build() @@ -56,7 +60,9 @@ object MediaItems { @JvmOverloads fun builderFromMuxPlaybackId( playbackId: String, - maxResolution: PlaybackMaxResolution? = null, + maxResolution: PlaybackResolution? = null, + minResolution: PlaybackResolution? = null, + renditionOrder: RenditionOrder? = null, domain: String = MUX_VIDEO_DEFAULT_DOMAIN, playbackToken: String? = null, ): MediaItem.Builder { @@ -66,6 +72,8 @@ object MediaItems { playbackId = playbackId, domain = domain, maxResolution = maxResolution, + minResolution = minResolution, + renditionOrder = renditionOrder, playbackToken = playbackToken, ) ) @@ -79,23 +87,36 @@ object MediaItems { playbackId: String, domain: String = MUX_VIDEO_DEFAULT_DOMAIN, subdomain: String = MUX_VIDEO_SUBDOMAIN, - maxResolution: PlaybackMaxResolution? = null, + maxResolution: PlaybackResolution? = null, + minResolution: PlaybackResolution? = null, + renditionOrder: RenditionOrder? = null, playbackToken: String? = null, ): String { val base = Uri.parse("https://$subdomain.$domain/$playbackId.m3u8").buildUpon() + minResolution?.let { base.appendQueryParameter("min_resolution", resolutionValue(it)) } maxResolution?.let { base.appendQueryParameter("max_resolution", resolutionValue(it)) } + renditionOrder?.let { base.appendQueryParameter("rendition_order", resolutionValue(it)) } playbackToken?.let { base.appendQueryParameter("token", it) } return base.build().toString() } - private fun resolutionValue(playbackMaxResolution: PlaybackMaxResolution): String { - return when (playbackMaxResolution) { - PlaybackMaxResolution.HD_720 -> "720p" - PlaybackMaxResolution.FHD_1080 -> "1080p" - PlaybackMaxResolution.QHD_1440 -> "1440p" - PlaybackMaxResolution.FOUR_K_2160 -> "2160p" + private fun resolutionValue(renditionOrder: RenditionOrder): String { + return when (renditionOrder) { + RenditionOrder.Ascending -> "asc" + RenditionOrder.Descending -> "desc" + } + } + + private fun resolutionValue(playbackResolution: PlaybackResolution): String { + return when (playbackResolution) { + PlaybackResolution.LD_480 -> "480p" + PlaybackResolution.LD_540 -> "540p" + PlaybackResolution.HD_720 -> "720p" + PlaybackResolution.FHD_1080 -> "1080p" + PlaybackResolution.QHD_1440 -> "1440p" + PlaybackResolution.FOUR_K_2160 -> "2160p" } } } @@ -104,9 +125,16 @@ object MediaItems { * A resolution for playing back Mux assets. If specified in [MediaItems.fromMuxPlaybackId], or * similar methods, the video's resolution will be limited to the given value */ -enum class PlaybackMaxResolution { +enum class PlaybackResolution { + LD_480, + LD_540, HD_720, FHD_1080, QHD_1440, FOUR_K_2160, } + +enum class RenditionOrder { + Ascending, + Descending, +} \ No newline at end of file