Skip to content

Commit

Permalink
fix: incorrect startup time after enable, disable, and videoChange (#89
Browse files Browse the repository at this point in the history
…) (#90)

* Lots of logging

* test case for enable/disable

* Ok so we can enable/disable or do videoChange now

* Ok now we can control that

* Accidental extra delay

* this works

* fix videoChange

* Ok now i think its ready to clean up

* nicer log tag

* some notes

* Slightly better

* Separate example

* that's kinda better

* more cleanup

* yet more cleanup

* ok last cleanup

* use enable and disable by default
  • Loading branch information
daytime-em authored Aug 21, 2024
1 parent 86b79ad commit 5928c53
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 4 deletions.
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Expand Down Expand Up @@ -31,6 +32,8 @@
<activity
android:name=".examples.background.BackgroundPlayActivity"
android:exported="false" />
<activity android:name=".examples.basic.PlayerReuseActivity"
android:exported="false" />

<service
android:name=".examples.background.BackgroundPlayService"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.mux.stats.muxdatasdkformedia3.databinding.ActivityMainBinding
import com.mux.stats.muxdatasdkformedia3.databinding.ListitemExampleBinding
import com.mux.stats.muxdatasdkformedia3.examples.basic.BasicPlayerActivity
import com.mux.stats.muxdatasdkformedia3.examples.basic.ComposeUiExampleActivity
import com.mux.stats.muxdatasdkformedia3.examples.basic.PlayerReuseActivity
import com.mux.stats.muxdatasdkformedia3.examples.ima.ImaClientAdsActivity
import com.mux.stats.muxdatasdkformedia3.examples.ima.ImaServerAdsActivity

Expand Down Expand Up @@ -53,7 +54,11 @@ class MainActivity : AppCompatActivity() {
Example(
title = "Compose UI With Shared Player",
destination = Intent(this, ComposeUiExampleActivity::class.java)
)
),
Example(
title = "Reusing a Player for multiple MediaItems",
destination = Intent(this, PlayerReuseActivity::class.java),
),
// TODO: post-beta, add APIs for talking to a `MediaSessionService`
// Example(
// title = "Background playback",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.Player.PositionInfo
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView.KEEP_SCREEN_ON
Expand All @@ -21,6 +20,7 @@ import com.mux.stats.sdk.core.model.CustomerData
import com.mux.stats.sdk.core.model.CustomerPlayerData
import com.mux.stats.sdk.core.model.CustomerVideoData
import com.mux.stats.sdk.core.model.CustomerViewData
import com.mux.stats.sdk.muxstats.MuxDataSdk
import com.mux.stats.sdk.muxstats.MuxStatsSdkMedia3
import com.mux.stats.sdk.muxstats.monitorWithMuxData

Expand Down Expand Up @@ -59,6 +59,7 @@ class BasicPlayerActivity : AppCompatActivity() {

player = createPlayer().also { newPlayer ->
muxStats = monitorPlayer(newPlayer)

view.playerView.player = newPlayer
newPlayer.setMediaItem(createMediaItem(mediaUrl))
newPlayer.prepare()
Expand Down Expand Up @@ -91,6 +92,7 @@ class BasicPlayerActivity : AppCompatActivity() {
CustomerPlayerData().apply { },
CustomerVideoData().apply {
videoId = "A Custom ID"
videoTitle = "Sintel (First)"
},
CustomerViewData().apply { }
)
Expand All @@ -99,7 +101,8 @@ class BasicPlayerActivity : AppCompatActivity() {
context = this,
envKey = Constants.MUX_DATA_ENV_KEY,
customerData = customerData,
playerView = view.playerView
playerView = view.playerView,
logLevel = MuxDataSdk.LogcatLevel.DEBUG,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package com.mux.stats.muxdatasdkformedia3.examples.basic

import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView.KEEP_SCREEN_ON
import androidx.media3.ui.PlayerView.SHOW_BUFFERING_WHEN_PLAYING
import com.mux.stats.muxdatasdkformedia3.Constants
import com.mux.stats.muxdatasdkformedia3.databinding.ActivityPlayerBinding
import com.mux.stats.muxdatasdkformedia3.toMediaItem
import com.mux.stats.sdk.core.model.CustomerData
import com.mux.stats.sdk.core.model.CustomerPlayerData
import com.mux.stats.sdk.core.model.CustomerVideoData
import com.mux.stats.sdk.core.model.CustomerViewData
import com.mux.stats.sdk.muxstats.MuxDataSdk
import com.mux.stats.sdk.muxstats.MuxStatsSdkMedia3
import com.mux.stats.sdk.muxstats.monitorWithMuxData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class PlayerReuseActivity : AppCompatActivity() {

private lateinit var view: ActivityPlayerBinding
private var player: Player? = null
private var muxStats: MuxStatsSdkMedia3<ExoPlayer>? = null

@OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
view = ActivityPlayerBinding.inflate(layoutInflater)
setContentView(view.root)

view.playerView.apply {
setShowBuffering(SHOW_BUFFERING_WHEN_PLAYING)
}
window.addFlags(KEEP_SCREEN_ON)
}

override fun onResume() {
super.onResume()
startPlaying(
intent.getStringExtra(EXTRA_URL) ?: Constants.VOD_TEST_URL_DRAGON_WARRIOR_LADY
)
}

override fun onPause() {
stopPlaying()
super.onPause()
}

private fun startPlaying(mediaUrl: String) {
stopPlaying()

player = createPlayer().also { newPlayer ->
muxStats = monitorPlayer(newPlayer)

view.playerView.player = newPlayer
newPlayer.setMediaItem(createMediaItem(mediaUrl))
newPlayer.prepare()
newPlayer.playWhenReady = true

// now change the video a couple of times
lifecycleScope.launch(Dispatchers.Main) {
val usingEnableAndDisable = true // If false, will use videoChange instead

delay(10_000)
Log.d(TAG, "disabling")
if (usingEnableAndDisable) {
muxStats?.disable()
}

Log.d(TAG, "playing Steve video")
newPlayer.setMediaItem(MediaItem.fromUri(Uri.parse(Constants.VOD_TEST_URL_STEVE)))
if (!usingEnableAndDisable) {
Log.d(TAG, "calling videoChange() 1")
muxStats?.videoChange(CustomerVideoData().apply {
videoTitle = "Old Keynote (Second)"
})
}
newPlayer.prepare()
newPlayer.play()
delay(10_000)

Log.d(TAG, "playing Big Buck Bunny")
// stop() is optional
//newPlayer.stop()

newPlayer.setMediaItem(MediaItem.fromUri(Uri.parse(Constants.VOD_TEST_URL_BIG_BUCK_BUNNY)))
if (!usingEnableAndDisable) {
Log.d(TAG, "calling videoChange() 2")
muxStats?.videoChange(CustomerVideoData().apply {
videoTitle = "Big Buck Bunny (Third)"
})
} else {
Log.d(TAG, "calling enable()")
muxStats?.enable(CustomerData().apply {
customerVideoData = CustomerVideoData().apply {
videoTitle = "Big Buck Bunny (Third)"
}
})
}
Log.d(TAG, "About to prepare the player. The current state is ${newPlayer.playbackState}")
newPlayer.prepare()
Log.d(TAG, "Just called prepare on the player. The current state is ${newPlayer.playbackState}")
newPlayer.play()
// debugging: Maybe try calling enable() _after_

delay(10_000)
Log.w(TAG, "test over")
muxStats?.disable()
}
}
}

private fun createMediaItem(mediaUrl: String): MediaItem {
return mediaUrl.toMediaItem().buildUpon()
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle("Sample app, BasicPlayerActivity")
.setDescription("A Basic test video")
.build()
).build()
}

private fun stopPlaying() {
player?.let { oldPlayer ->
oldPlayer.stop()
oldPlayer.release()
}
// Make sure to release() your muxStats whenever the user is done with the player
muxStats?.release()
}

private fun monitorPlayer(player: ExoPlayer): MuxStatsSdkMedia3<ExoPlayer> {
// You can add your own data to a View, which will override any data we collect
val customerData = CustomerData(
CustomerPlayerData().apply { },
CustomerVideoData().apply {
videoId = "A Custom ID"
videoTitle = "Sintel (First)"
},
CustomerViewData().apply { }
)

return player.monitorWithMuxData(
context = this,
envKey = Constants.MUX_DATA_ENV_KEY,
customerData = customerData,
playerView = view.playerView,
logLevel = MuxDataSdk.LogcatLevel.DEBUG,
)
}

private fun createPlayer(): ExoPlayer {
return ExoPlayer.Builder(this)
.build().apply {
addListener(object : Player.Listener {
override fun onPlayerError(error: PlaybackException) {
Log.e(javaClass.simpleName, "player error!", error)
Toast.makeText(this@PlayerReuseActivity, error.localizedMessage, Toast.LENGTH_SHORT)
.show()
}
})
}
}

companion object {
const val EXTRA_URL: String = "com.mux.video.url"
const val TAG = "PlayerReuseActivity"
}
}
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {

allprojects {
project.ext {
coreVersion = '1.3.0'
coreVersion = '1.3.1'
}

tasks.withType(DokkaTaskPartial.class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.mux.stats.sdk.core.CustomOptions
import com.mux.stats.sdk.core.events.EventBus
import com.mux.stats.sdk.core.events.playback.AdEvent
import com.mux.stats.sdk.core.model.CustomerData
import com.mux.stats.sdk.core.model.CustomerVideoData
import com.mux.stats.sdk.muxstats.media3.BuildConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -77,6 +78,12 @@ class MuxStatsSdkMedia3<P : Player> @JvmOverloads constructor(
catchUpPlayState(player, collector)
catchUpStreamData(player, collector)
}

override fun videoChange(videoData: CustomerVideoData) {
super.videoChange(videoData)
catchUpPlayState(player, collector)
catchUpStreamData(player, collector)
}
}

/**
Expand Down
10 changes: 10 additions & 0 deletions library/src/main/java/com/mux/stats/sdk/muxstats/PlayerUtils.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.mux.stats.sdk.muxstats

import android.net.Uri
import androidx.annotation.OptIn
import androidx.media3.common.Format
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.common.Timeline
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import com.mux.android.util.oneOf
import com.mux.stats.sdk.core.model.VideoData
import com.mux.stats.sdk.core.util.MuxLogger
Expand All @@ -17,6 +19,7 @@ private const val LOG_TAG = "PlayerUtils"
/**
* Returns true if any media track in the given [Tracks] object had a video MIME type
*/
@OptIn(UnstableApi::class)
fun Tracks.hasAtLeastOneVideoTrack(): Boolean {
return groups.map { it.mediaTrackGroup }
.filter { trackGroup -> trackGroup.length > 0 }
Expand All @@ -40,7 +43,9 @@ fun <R> Tracks.Group.mapFormats(block: (Format) -> R): List<R> {
@JvmSynthetic
fun catchUpPlayState(player: Player, collector: MuxStateCollector) {
MuxLogger.d("PlayerUtils", "catchUpPlayState: Called. pwr is ${player.playWhenReady}")
MuxLogger.d("PlayerUtils", "catchUpPlayState: Called. state is ${player.playbackState}")
if (player.playWhenReady) {
MuxLogger.d("PlayerUtils", "catchUpPlayState: dispatching play")
// Captures auto-play & late-registration, setting state and sending 'viewstart'
collector.play()
}
Expand Down Expand Up @@ -88,9 +93,12 @@ fun MuxStateCollector.handlePlayWhenReady(
playWhenReady: Boolean,
@Player.State playbackState: Int
) {
MuxLogger.d("PlayerUtils", "handlePlayWhenReady: Called. pwr is $playWhenReady")
if (playWhenReady) {
MuxLogger.d("PlayerUtils", "handlePlayWhenReady: dispatching play")
play()
if (playbackState == Player.STATE_READY) {
MuxLogger.d("PlayerUtils", "handlePlayWhenReady: dispatching playing")
// If we were already READY when playWhenReady is set, then we are definitely also playing
playing()
}
Expand Down Expand Up @@ -129,6 +137,7 @@ fun MuxStateCollector.handleExoPlaybackState(
when (playbackState) {
Player.STATE_BUFFERING -> {
MuxLogger.d(LOG_TAG, "entering BUFFERING")
MuxLogger.d(LOG_TAG, "muxPlayerState is $muxPlayerState")
buffering()
}

Expand All @@ -143,6 +152,7 @@ fun MuxStateCollector.handleExoPlaybackState(

// If playWhenReady && READY, we're playing or else we're paused
if (playWhenReady) {
MuxLogger.d(LOG_TAG, "entered READY && pwr is true, dispatching playing()")
playing()
} else if (muxPlayerState != MuxPlayerState.PAUSED) {
pause()
Expand Down

0 comments on commit 5928c53

Please sign in to comment.