I have ExoPlayerWrapper class which is wrapper for ExoPlayer and is singleton injected by Dagger. Init block in ExoPlayerWrapper class looks in the following way:
#Singleton
class ExoPlayerWrapper #Inject constructor(
context: Context,
userAgentInfo: UserAgentInfo
) {
private val exoPlayer: ExoPlayer
private val httpDataSource: HttpDataSource
private val mediaSourceFactory: ExtractorMediaSource.Factory
override val exoPlayerInstance: ExoPlayer
get() = exoPlayer
init {
...
exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector)
httpDataSource = CustomHttpDataSource(userAgentInfo.userAgent, null)
mediaSourceFactory = ExtractorMediaSource.Factory { httpDataSource }
state = Player.STATE_IDLE
}
override fun playFromUrl(uri: Uri, headers: Map<String, String>) {
reset()
...
val mediaSource = mediaSourceFactory.createMediaSource(uri)
exoPlayer.prepare(mediaSource)
exoPlayer.playWhenReady = true
}
override fun pause() {
exoPlayer.playWhenReady = false
}
override fun reset() {
stop()
state = Player.STATE_IDLE
}
override fun stop() {
exoPlayer.stop()
exoPlayer.seekTo(0)
}
override fun seekTo(position: Long) {
exoPlayer.seekTo(position)
}
...
override fun release() {
exoPlayer.release()
state = Player.STATE_RELEASED
}
And in onCreate() method of Activity I have the following code:
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_player)
...
if (savedInstanceState != null) {
initialized = savedInstanceState.getBoolean(INITIALIZED)
}
if (!initialized) {
val uri = Uri.parse(attachedVideo?.uri)
player.playFromUrl(uri, sessionStore.sessionHeaders)
videoView.requestFocus()
initialized = true
}
videoView.player = player.exoPlayerInstance
backBtn.setOnClickListener{ _ ->
player.release()
finish()
}
}
But in this case I can play only one video after click on backBtn button, other videos after that aren't played until I close app and reopen app again. And if I change backBtn's OnClickListener in the following way:
backBtn.setOnClickListener{ _ ->
player.pause()
finish()
}
all works fine, other videos are played after click on backBtn button. So it seems to be problem with reinitializing ExoPlayer after release() method invocation. And how correctly to reinitialize ExoPlayer after releasing?
UPD
And after release() invocation and after attempt to open video again I see the following error in Logcat:
java.lang.IllegalStateException: Handler (android.os.Handler) {81fa8ef} sending message to a Handler on a dead thread
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:545)
at android.os.Handler.enqueueMessage(Handler.java:661)
at android.os.Handler.sendMessageAtTime(Handler.java:630)
at android.os.Handler.sendMessageDelayed(Handler.java:600)
at android.os.Handler.sendMessage(Handler.java:537)
at android.os.Message.sendToTarget(Message.java:418)
at com.google.android.exoplayer2.ExoPlayerImplInternal.stop(ExoPlayerImplInternal.java:207)
at com.google.android.exoplayer2.ExoPlayerImpl.stop(ExoPlayerImpl.java:357)
at com.google.android.exoplayer2.SimpleExoPlayer.stop(SimpleExoPlayer.java:777)
at com.google.android.exoplayer2.SimpleExoPlayer.stop(SimpleExoPlayer.java:772)
at package.ExoPlayerWrapper.stop(ExoPlayerWrapper.kt:xx)
at package.ExoPlayerWrapper.reset(ExoPlayerWrapper.kt:xx)
at package.ExoPlayerWrapper.playFromUrl(ExoPlayerWrapper.kt:xx)
at package.VideoPlayerActivity.onCreate(VideoPlayerActivity.kt:xx)
The issue arises from trying to use a player whose resources have already been released. Because:
exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector)
only gets called once, during the initialization of ExoPlayerWrapper. If your activity is not restarted, then that instance of ExoPlayerWrapper sticks around with the released player.
Either create a new instance of ExoPlayerWrapper whenever you go to play your video, or create a player initialization method inside ExoPlayerWrapper, which you can call just before playing the video.
For your requirement no need to use release method. If you are using this method you should reinitialize exoplayer again.
You can use stop() method to stop the exoplayer.
Related
I am working on a native music player app for android using ExoPlayer and MediaSessionService from Media3. Now I want to make playback more energy efficient while the screen is off by using experimentalSetOffloadSchedulingEnabled, but it seems like I’m not able to get the offloading to work.
From the main activity of the app I send ACTION_START_AUDIO_OFFLOAD in the onStop() method to my service (the relevant parts of the service are show below), and ACTION_STOP_AUDIO_OFFLOAD in the onStart() method. In this way I have been able to get correct true/false responses from the onExperimentalOffloadSchedulingEnabledChanged listener, but I do not get any responses from the onExperimentalOffloadedPlayback or onExperimentalSleepingForOffloadChanged listeners, so it seems like the player never enters power saving mode.
My tests were made with Media3 version 1.0.0-beta03 on Android 13 (emulator) and Android 10 (phone) using MP3 files. I am aware that Media3 is in beta and that the offload scheduling method is experimental, but I'm not sure if that is the limitation or if I have done something wrong. Any ideas what could be the issue?
#androidx.media3.common.util.UnstableApi
class PlaybackService: MediaSessionService(), MediaSession.Callback {
private val listener = object : ExoPlayer.AudioOffloadListener {
override fun onExperimentalOffloadSchedulingEnabledChanged(offloadSchedulingEnabled: Boolean) {
Log.d("PlaybackService","offloadSchedulingEnabled: $offloadSchedulingEnabled")
super.onExperimentalOffloadSchedulingEnabledChanged(offloadSchedulingEnabled)
}
override fun onExperimentalOffloadedPlayback(offloadedPlayback: Boolean) {
Log.d("PlaybackService","offloadedPlayback: $offloadedPlayback")
super.onExperimentalOffloadedPlayback(offloadedPlayback)
}
override fun onExperimentalSleepingForOffloadChanged(sleepingForOffload: Boolean) {
Log.d("PlaybackService","sleepingForOffload: $sleepingForOffload")
super.onExperimentalSleepingForOffloadChanged(sleepingForOffload)
}
}
private lateinit var player: ExoPlayer
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
player = ExoPlayer.Builder(
this,
DefaultRenderersFactory(this)
.setEnableAudioOffload(true)
)
.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus = */ true)
.setHandleAudioBecomingNoisy(true)
.setSeekBackIncrementMs(10_000)
.setSeekForwardIncrementMs(10_000)
.setWakeMode(C.WAKE_MODE_LOCAL)
.build()
player.addAudioOffloadListener(listener)
mediaSession = MediaSession
.Builder(this, player)
.setCallback(this)
.build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
mediaSession
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when(intent?.action) {
ACTION_START_AUDIO_OFFLOAD -> startAudioOffload()
ACTION_STOP_AUDIO_OFFLOAD -> stopAudioOffload()
}
return super.onStartCommand(intent, flags, startId)
}
private fun startAudioOffload() {
player.experimentalSetOffloadSchedulingEnabled(true)
}
private fun stopAudioOffload() {
player.experimentalSetOffloadSchedulingEnabled(false)
}
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
mediaSession = null
}
super.onDestroy()
}
companion object {
const val ACTION_START_AUDIO_OFFLOAD = "ACTION_START_AUDIO_OFFLOAD"
const val ACTION_STOP_AUDIO_OFFLOAD = "ACTION_STOP_AUDIO_OFFLOAD"
}
}
package com.oyly.gpsdemo
import android.content.Context
import android.media.MediaPlayer
class SoundGenerator(context: Context) : MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
private var mediaPlayer: MediaPlayer = MediaPlayer()
private var nextSound: Int = R.raw.coast
init {
try { mediaPlayer = MediaPlayer.create(context, R.raw.coast) }
catch (ex : Exception) { println("lyseoy11: $ex") }
mediaPlayer.setOnPreparedListener(this)
mediaPlayer.setOnCompletionListener (this)
}
override fun onPrepared(p0: MediaPlayer) {mediaPlayer.start()}
override fun onCompletion(p0: MediaPlayer?) { playSound() }
fun queueSound(resid: Int) {
nextSound = resid
if (!mediaPlayer.isPlaying) playSound()
}
fun playSound() {
mediaPlayer.reset()
mediaPlayer.setDataSource(resources.openRawResourceFd(nextSound))
mediaPlayer.prepareAsync()
}
}
The "mediaPlayer = MediaPlayer.create(context, R.raw.coast" line throws the following exception:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
I have moved this code from a standalone app, where it worked, into this class of another app. Now it doesn't work and I can't understand why. The MediaPlayer.Create() method takes as arguments the context (which I pass on from the MainActivity class that calls this class), and an uri to the audio file to be played. Neither of them are null.
Why do I get this exception?
Edit:
The only place SoundGenerator is created is in the MainActivity Initializer:
class MainActivity : AppCompatActivity() {
private var soundGenerator : SoundGenerator = SoundGenerator(this)
...
}
Often it is best to wait to use the activity as a context until later in the lifecycle when things have been set up (e.g. in onCreate) to avoid initialization order issues. For exampe:
class MainActivity : AppCompatActivity() {
private lateinit var soundGenerator : SoundGenerator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
soundGenerator = SoundGenerator(this)
}
}
Alternately, sometimes you can use applicationContext instead of the current activity as an appropriate context.
I am using Exoplayer in my dialog. I want the video to play automatically when dialog opens. When
simpleExoPlayer.prepare() snippet is active I am able to do autoplay but when I close the dialog audio keeps playing. Before activating simpleExoPlayer.prepare() audio stops when I dismiss dialog. Is there another method to autoplay exoplayer or stop the audio when dialog dismiss?
class VideoViewDialog (context: Context) : BaseDialog<LayoutDialogVideoViewBinding>(context) {
private var videoUrl : String = ""
private lateinit var simpleExoPlayer: ExoPlayer
override fun populateUi() {
setCanceledOnTouchOutside(true)
mBinding?.apply {
initializePlayer()
}
}
private fun initializePlayer() {
val mediaDataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(context)
val mediaSource = ProgressiveMediaSource.Factory(mediaDataSourceFactory).createMediaSource(
MediaItem.fromUri(videoUrl))
val mediaSourceFactory: MediaSource.Factory = DefaultMediaSourceFactory(mediaDataSourceFactory)
simpleExoPlayer = ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory)
.build()
simpleExoPlayer.addMediaSource(mediaSource)
simpleExoPlayer.playWhenReady = true
simpleExoPlayer.prepare()
mBinding?.apply {
playerView.player = simpleExoPlayer
playerView.requestFocus()
}
simpleExoPlayer.play()
}
private fun releasePlayer() {
simpleExoPlayer.release()
}
public override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) initializePlayer()
}
public override fun onStop() {
super.onStop()
if (Util.SDK_INT > 23) releasePlayer()
}
override fun getLayoutRes(): Int {
return R.layout.layout_dialog_video_view
}
companion object{
fun newInstance(
context: Context,
videoUrl : String,
) : VideoViewDialog{
val dialog = VideoViewDialog(context)
dialog.also {
it.videoUrl = videoUrl
}
return dialog
}
}
}
I tried .stop, clearVideoSurface(), playerView.player = null before .release(). Didn't work
Seems like you called initializePlayer() twice. resulting in two Exoplayer instances playing; you're only able to release the one the simpleExoPlayer variable holds a reference to.
I was wondering how to make the music play automatically when starting the app and how to make it stop playing in the background when pressing the home button. Right now, it starts and stops by pressing the toggle button. I was also wondering if its possible to automatically switch to other music when going to another activity?
MainActivity.kt
private lateinit var player: MediaPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val secondActivity = findViewById<Button>(R.id.secondActivity)
secondActivity.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
val toggle: ToggleButton = findViewById(R.id.toggleButton)
toggle.setOnCheckedChangeListener { _, isChecked ->
val svc = Intent(this, MusicService::class.java)
if (isChecked) {
startService(svc)
} else {
stopService(svc)
}
}
}
MusicService.kt
class MusicService : Service() {
private lateinit var player: MediaPlayer
override fun onBind(intent: Intent?): IBinder? {
TODO("Return the communication channel to the service.")
return null
}
override fun onCreate() {
super.onCreate()
player = MediaPlayer.create(this, R.raw.music)
player.setLooping(true)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
player.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
player.stop()
}
}
Depending on your specific requirements, you should or shouldn't use a MediaService as part of your solution.
To be more clear, a Service should only be used if you want the audio to keep going whenever you're outside the app. This solution will usually be accompanied by a media Notification which you should populate with controls, image assets, etc. (Think of Spotify or SoundCloud) If this is the solution you're looking for, take a look at this doc page from Google and follow it through. Beware that this is a longer and tougher process to maintain.
On the other hand, if all you want to do is play music/sounds while your user is inside your app, then a simple
private lateinit var localMedia: MediaPlayer
override fun onCreate() {
...
localMedia = MediaPlayer.create(context, R.raw.your_audio_file)
}
override fun onResume() {
...
localMedia.start()
}
override fun onPause() {
...
localMedia.release()
}
Furthermore, if you want different audio files to be played on different Activities/Fragments, you might want to abstract the code I provided above into it's own Manager class or so and access it the same but changing the specific .mp3 file (or whatever format) as you see fit.
EDIT:
For a Manager class, you'll have to create your own functions and handle the MediaPlayer inside of it
private class MediaPlayerManager(private val context: Context) {
private lateinit var mediaPlayer: MediaPlayer
fun setupPlayer() {
mediaPlayer = MediaPlayer.create(context, R.raw.your_audio_file)
}
fun play() {
mediaPlayer.start()
}
fun stop() {
mediaPlayer.stop()
}
}
And call these functions from their respective lifecycle method inside your Activity/Fragment, depending on your specific needs
class YourActivity {
val mediaPlayerManager = MediaPlayerManager(context)
override onCreate() {
...
mediaPlayerManager.setupPlayer()
}
override fun onResume() {
...
mediaPlayerManager.play()
}
override fun onPause() {
...
mediaPlayerManager.stop()
}
}
I should add that I'm not necessarily providing a fully-fledged answer here, but a starting point for you to massage to your own needs. The Manager class is nothing but an abstraction of the concept I'm trying to communicate. Lastly, if you want to use a different audio resource file in another Activity/Fragment, you would have to create a method to re-assign the MediaPlayer object inside it with the appropriate file.
E.g.
fun setupPlayer(audioRes: Int) {
mediaPlayer = MediaPlayer.create(context, audioRes)
}
I have done this before
create a Class and named it (for example I named it C) and extend that from Application like following (don't forget put android:name=".C" in <application> tag in manifest.xml):
class C:Application() {
fun onCreate() {
super.onCreate()
context = getApplicationContext()
app = this
}
companion object {
private val context:Context
var currentActivity:Activity
var currentActivities:ArrayList<Activity> = ArrayList()
var handler:Handler
var app:C
fun get():C {
return app
}
fun getContext():Context {
if (currentActivity != null)
{
return currentActivity
}
return context
}
}
}
I created a class for parent of AppCompatActivity and (named UAppCompactActivity)extend it form AppCompatActivity then extend all activities from UAppCompactActivity:
abstract class UAppCompatActivity:AppCompatActivity() {
fun onCreate(#Nullable savedInstanceState:Bundle, #Nullable persistentState:PersistableBundle) {
super.onCreate(savedInstanceState, persistentState)
}
protected fun onResume() {
super.onResume()
C.setCurrentActivity(this)
C.currentActivities.add(this)
/* you can play your music here or do any action you desired */
}
protected fun onPause() {
super.onPause()
C.currentActivities.remove(this)
/*
you can stop your music here or do any action you desired
if (UBase.currentActivities.size() === 0)
G.backgroundMusics.get(G.app.musicNumberBackAndNowPlay).pause()
else
play music or switch to new music
*/
}
}
I'm on creating a simple music player with Exoplayer in Android Kotlin. (Playing local MP3 in storage)
The problem is that the playback music is stopped outside the app and if the mobile turns on sleep mode.
So, I tried to implement the Foreground service, but it didn't work.
Below is my code without the part I tried to implement the Foreground service.
Please, let me know how to resolve this issue or how to correctly implement foreground service.
class AudioviewActivity : AppCompatActivity() {
private var player: SimpleExoPlayer? = null
private var playbackPosition = 0L
private var currentWindow = 0
private var playWhenReady = false
override fun onBackPressed() {
super.onBackPressed()
player!!.stop()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_audioview)
}
private fun initializePlayer() {
if (player == null) {
player = ExoPlayerFactory.newSimpleInstance(this)
val userAgent = Util.getUserAgent(this, "Mymusicplayer")
val mediaSource = ExtractorMediaSource(
Uri.parse("asset:///trackone.mp3"),
DefaultDataSourceFactory(this, userAgent), DefaultExtractorsFactory(),
null, null)
player!!.prepare(mediaSource)
player!!.seekTo(currentWindow, playbackPosition)
player!!.playWhenReady = playWhenReady
player!!.repeatMode = SimpleExoPlayer.REPEAT_MODE_ONE
}
}
private fun releasePlayer() {
player?.let {
playbackPosition = it.currentPosition
currentWindow = it.currentWindowIndex
playWhenReady = it.playWhenReady
it.release()
player = null
}
}
override fun onResume() {
super.onResume()
initializePlayer()
}
override fun onRestart() {
super.onRestart()
initializePlayer()
}
override fun onStop() {
super.onStop()
releasePlayer()
}
}
Since you are releasing player in Onstop , So when your app goes in background then Onstop is being called and you are releasing player , So dont release player in OnStop(), remove that part of the code.