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
Related
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.
Currently, I need a bound (Music)Service, because I need to interact with it. But I also want it to not be stopped, even when all components have unbound themselves.
My service code:
class ServicePlayer : LifecycleService() {
var mediaPlayer: MediaPlayer? = null
var notificationManager: NotificationManager? = null
var notificationBuilder: NotificationCompat.Builder? = null
private val mBinder: IBinder = PlayerBinder()
private val NOTIFICATION_ID = 1111
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_REDELIVER_INTENT
}
inner class PlayerBinder : Binder() {
val service: ServicePlayer
get() = this#ServicePlayer
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mBinder
}
override fun onCreate() {
super.onCreate()
mediaPlayer = MediaPlayer()
mediaPlayer!!.setOnCompletionListener(this)
mediaPlayer!!.setOnBufferingUpdateListener(this)
mediaPlayer!!.setOnErrorListener(this)
val filter = IntentFilter()
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED)
filter.addAction(Intent.ACTION_SCREEN_ON)
registerReceiver(receiver, filter)
}
override fun onDestroy() {
super.onDestroy()
mediaPlayer!!.reset()
mediaPlayer!!.release()
Log.i("DESTROY SERVICE", "destroy")
unregisterReceiver(receiver)
}
fun play(trackIndex: Int, tracks: ArrayList<Track>?) {
...
val intent = Intent(BUFFERING)
this#ServicePlayer.sendBroadcast(intent)
}
fun pause() {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.pause()
PlayerLiveData.isPlaying.value = false
val intent = Intent(UPDATE_UI)
this#ServicePlayer.sendBroadcast(intent)
//Show notification
CoroutineScope(Dispatchers.Default).launch {
showNotification()
}
}
}
private fun hideNotification() {
notificationManager!!.cancel(NOTIFICATION_ID)
stopForeground(true)
}
private fun showNotification() {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val CHANNEL_ID = "controls_channel_id"
val CHANNEL_NAME = "Play tracks"
val channel = NotificationChannel(CHANNEL_ID,CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)
...
val mMediaSession = MediaSessionCompat(applicationContext, getString(R.string.app_name))
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
notificationManager!!.createNotificationChannel(channel)
notificationBuilder = NotificationCompat.Builder(applicationContext)
.setChannelId(CHANNEL_ID)
.setContentText(artistText)
.setContentTitle(track.title)
...
} else {
notificationBuilder = NotificationCompat.Builder(applicationContext)
...
notificationBuilder!!
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(contentIntent)
.setCustomContentView(remoteSmallViews)
.setCustomBigContentView(remoteViews)
}
CoroutineScope(Dispatchers.Default).launch {
val notification = notificationBuilder!!.build()
startForeground(NOTIFICATION_ID, notification)
val notificationTarget = NotificationTarget(
applicationContext
, R.id.imgThumb, remoteViews
, notification, NOTIFICATION_ID
)
...
lifecycleScope.launch {
val request = ImageRequest.Builder(applicationContext)
.data(thumb)
.error(R.drawable.placeholder_song)
.placeholder(R.drawable.placeholder_song)
.build()
val drawable = imageLoader.execute(request).drawable
val bitmap = (drawable as BitmapDrawable).bitmap
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder!!.setLargeIcon(bitmap)
val notification = notificationBuilder!!.build()
notificationManager!!.notify(NOTIFICATION_ID,notification)
//Start Foreground service
startForeground(NOTIFICATION_ID, notification)
}
}
}
}
}
Manifest file declaration:
<service android:name=".services.ServicePlayer" android:enabled="true" android:exported="true"/>
Using service in activity
class MainActivity : AppCompatActivity() {
lateinit var binding: MainActivityBinding
private lateinit var audioPlayerService: ServicePlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, ServicePlayer::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
binding.lifecycleOwner = this
binding.viewmodel = mainViewModel
}
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
// audioPlayerService = null;
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
audioPlayerService = (service as ServicePlayer.PlayerBinder).service
if (audioPlayerService.trackIndex !== -1) {
//updatePlaybackUI()
}
}
}
}
How can I keep my service running in background even after activity destroyed. I refer few threads of StackOverflow but they are not helpful.
Use Service instead LifecycleService as parent class.
Add partial wake lock start and stop calls to onCreate and onDestroy methods respectively.
private val powerManager
get() = (this.getSystemService(Context.POWER_SERVICE) as PowerManager)
private var wakeLock: PowerManager.WakeLock? = null
private fun startWakeLock() {
if (wakeLock == null) {
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"${packageName}:wakeLock"
)
wakeLock?.acquire()
}
}
private fun stopWakeLock() {
if (wakeLock?.isHeld == true) {
wakeLock?.release()
wakeLock = null
}
}
Add the following tag to service at mainfest
android:foregroundServiceType="mediaPlayback"
You should start service as foreground from the activity
A bound service stops once every client unbinds from it, and that happens automatically when the client (your Activity) is destroyed
If your client is still bound to a service when your app destroys the client, destruction causes the client to unbind. It is better practice to unbind the client as soon as it is done interacting with the service. Doing so allows the idle service to shut down.
If you want the service to just keep running, a Started Service will do that. You can still bind to it, but it won't stop until you explicitly tell it to stop and there are no clients bound.
Honestly though, if you're making some kind of media player, you'll probably want to use the MediaBrowserServiceCompat framework. It allows you to create a service that plays nice with MediaBrowser (which does the binding, among other things) and lets you use a MediaSession to get a media notification with controls and all that.
A few links about that stuff:
MediaBrowserServiceCompat and the modern media playback app by Ian Lake from the Android team
Background Audio in Android With MediaSessionCompat - Java but gets into a lot of the nonsense you'll have to wrangle
Developer docs about building media apps - a few sections here and it's all kinda spread out, I feel like the other links give a better overview
If you don't care about any of that then startService/startForegroundService (or ContextCompat#startForegroundService) will get you a service that just runs, but those links might give you some pointers about stuff
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
*/
}
}
Recording audio is a long time operation, so I launch mRecorder?.start() in a coroutine within a service, you can see RecordService.kt.
I invoke suspend fun startRecord(){...} in AndroidViewModel with viewModelScope.launch { } to start record audio.
I only invoke a normal fun stopRecord(){...} in AndroidViewModel to stop record audio, you can see HomeViewModel.kt, will it cause error with the object var mRecorder: MediaRecorder? ?
HomeViewModel.kt
class HomeViewModel(val mApplication: Application, private val mDBVoiceRepository: DBVoiceRepository) : AndroidViewModel(mApplication) {
private var mService: RecordService? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, iBinder: IBinder) {
val binder = iBinder as RecordService.MyBinder
mService = binder.service
}
...
}
fun bindService() {
Intent(mApplication , RecordService::class.java).also { intent ->
mApplication.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
}
fun unbindService() {
Intent(mApplication, RecordService::class.java).also { intent ->
mApplication.unbindService(serviceConnection)
}
}
fun startRecord(){
viewModelScope.launch {
mService?.startRecord()
}
}
fun stopRecord(){
mService?.stopRecord()
}
}
RecordService.kt
class RecordService : Service() {
private var mRecorder: MediaRecorder? = null
suspend fun startRecord(){
mRecorder = MediaRecorder()
withContext(Dispatchers.IO) {
mRecorder?.setOutputFile(filename);
mRecorder?.setMaxDuration(1000*60*20); //20 Mins
mRecorder?.setAudioChannels(1);
mRecorder?.setAudioSamplingRate(44100);
mRecorder?.setAudioEncodingBitRate(192000);
mRecorder?.prepare()
mRecorder?.start()
}
}
fun stopRecord(){
mRecorder?.stop()
mRecorder=null
}
}
No, it doesn't cause error but if you face runtime error while calling this method it may be caused by the recorder didn't receive any valid sound or video to record. Check below documentation link for more information.
https://developer.android.com/reference/android/media/MediaRecorder#stop()
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.