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.
Related
How can MediaPlayer be stopped when the App is backgrounded? I see that the MediaPlayer stops when screen rotates. But the MediaPlayed keeps playing while it is backgrounded and a different App is run.
val current = LocalContext.current
val player1: MediaPlayer = remember {MediaPlayer.create(current, R.raw.music1)}
DisposableEffect(Unit) {
player1.isLooping = true
player1.start()
onDispose {
player1.stop()
}
}
I tried adding MediaPlayer in a class which was life cycle aware. This can be controlled from compose. But the functions "onPause" and "onStop" do not stop the MediaPlayer.
class ExampleClass(application: Application) : AndroidViewModel(application),
DefaultLifecycleObserver {
private var player: MediaPlayer? = null
fun toggleOn(application: Application) {
if (player == null) {
player = MediaPlayer.create(application, R.raw.music1)
}
player!!.start()
}
fun toggleOff() {
player!!.pause()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
toggleOff()
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
toggleOff()
}
}
Use LifecycleOwner and LauchedEffect
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
player.start()
} else if (event == Lifecycle.Event.ON_STOP) {
player.stop()
}
}
// Add the observer to the lifecycle
lifecycleOwner.lifecycle.addObserver(observer)
// When the effect leaves the Composition, remove the observer
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
A similar sample: here
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 am using MediaPlayer as a Service in my app. I have implemented a mute and unmute feature but I have a problem with the volume when I switch between the two states:
Say the music is playing at max volume and you reduce the volume to halfway while in the unmuted state. Then you mute the sound and then unmute it again. The audio that I hear after unmuting it is noticeably quieter than it was just before I muted it, even though the phone's media volume showed that volume was the same both times.
The opposite is true when playing at a low volume and you increase the volume while in unmuted state. In this case the volume after you unmute sounds louder.
Lastly, when the volume is set to 0 and then unmuted, any change to the volume produces no change to the loudness of the audio. The audio remains mute in this case until I press mute then unmute.
This makes me believe that the loudness of the volume when the music is unmuted has some effect on the audio when you change the volume but I'm not sure how.
The way that I have the volume set when the user unmutes is by using AudioManager and getStreamVolume for the Stream Music.
Here's the code:
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
var mService: BackgroundSoundService? = null
var mIsBound: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//button to switch between muted and unmuted
binding.fab.setOnClickListener {
if (mService?.mute == true) {
val currentVolume = mService!!.getVolume()
mService?.mp?.setVolume(currentVolume, currentVolume)
mService?.setMuted(false)
} else if (mService?.mute == false) {
mService?.mp?.setVolume(0f, 0f)
mService?.setMuted(true)
}
}
}
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, iBinder: IBinder) {
val binder = iBinder as MyBinder
mService = binder.service
mIsBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mIsBound = false
}
}
private fun bindService() {
Intent(this, BackgroundSoundService::class.java).also { intent ->
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
}
private fun unbindService() {
Intent(this, BackgroundSoundService::class.java).also {
unbindService(serviceConnection)
}
}
override fun onStart() {
super.onStart()
bindService()
}
override fun onStop() {
super.onStop()
if (mIsBound == true) {
unbindService()
}
}
}
MediaPlayer Service
class BackgroundSoundService : Service() {
var mute = false
private val mBinder: IBinder = MyBinder()
inner class MyBinder : Binder() {
val service: BackgroundSoundService
get() = this#BackgroundSoundService
}
var mp: MediaPlayer? = null
override fun onBind(intent: Intent): IBinder {
return mBinder
}
override fun onUnbind(intent: Intent?): Boolean {
mp?.stop()
mp?.release()
return false
}
override fun onCreate() {
super.onCreate()
val currentVolume = getVolume()
mp = MediaPlayer.create(this, R.raw.song)
mp?.isLooping = true
mp?.setVolume(currentVolume, currentVolume)
mp?.start()
}
fun setMuted(boolean: Boolean) {
mute = boolean
}
fun getVolume(): Float {
val audio = getSystemService(Context.AUDIO_SERVICE) as AudioManager
return audio.getStreamVolume(AudioManager.STREAM_MUSIC) / 15f
}
}
Any help appreciated,
Thanks
I ended up getting it to work by changing the code in my getVolume function in my Service:
fun getVolume(): Float {
val audio = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC)/ 15f
//just added the following line
return (ln(15 - currentVolume) / ln(15.0)).toFloat()
}
Honestly, I don't understand why this works so if you know a way that makes more sense or can explain this to me that would be appreciated.
Thanks
I need to catch errors thrown by exoplayer when there is no input or bad stream. If there is no input activity opening showing player with controls nothing is displaying. when there is no input hls source I need to show the alert dialogue that Streaming is offline. How to achieve this. Please help.
class playlive : AppCompatActivity() {
private var player: SimpleExoPlayer? = null
private var playerView: PlayerView? = null
private var playWhenReady = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playlive)
playerView = findViewById(R.id.player_view)
play()
}
private fun play(){
val trackSelector = DefaultTrackSelector()
trackSelector.setParameters(trackSelector.buildUponParameters().setMaxVideoSizeSd())
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector)
playerView!!.player = player
val dataSourceFactory = DefaultHttpDataSourceFactory(Util.getUserAgent(this, "app-name"))
val uri = Uri.parse("http://localhost:1935/live/mystream/index.m3u8")
val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
player!!.playWhenReady = playWhenReady
player!!.prepare(hlsMediaSource)
}
override fun onStop() {
super.onStop()
releasePlayer()
}
override fun onResume() {
super.onResume()
releasePlayer()
resumePlayer()
}
override fun onDestroy() {
super.onDestroy()
releasePlayer()
}
private fun releasePlayer(){
player?.release()
}
private fun resumePlayer(){
play()
}
}```
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.