Code Example

Kotlin Android Gain and Release Audio Focus Snippets

A step by step Android Gain and Release Audio Focus example.

1. TakuSemba/AudioFocusServiceApp

AudioFocusServiceApp allow you to control audio focus via Service.

Here is a full example:

Full Example

Awaiting below is a full android sample to demonstrate the concept:

Step 1. Add Permissions

We will need to add some permissions in our AndroidManifest.xml as shown below:

(a). AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="com.example.takusemba.audiofocusserviceapp"
  >

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

  <application
    android:name=".App"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    tools:ignore="GoogleAppIndexingWarning"
    >
    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

    <service
      android:name=".AudioFocusGainService"
      android:exported="true"
      />
    <service
      android:name=".AudioFocusGainTransientService"
      android:exported="true"
      />
    <service
      android:name=".AudioFocusGainTransientMayDuckService"
      android:exported="true"
      />
    <service
      android:name=".AudioFocusGainTransientExclusiveService"
      android:exported="true"
      />
  </application>

</manifest>

Step 2. Design Layouts

We need to design our XML layouts as follows:

(a). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity"
  >

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    />

</android.support.constraint.ConstraintLayout>

Step 3. Write Code

Finally we need to write our code as follows:

(a). Config.kt

package com.example.takusemba.audiofocusserviceapp

object Config {

  const val channelId = "test_id"
  const val channelName = "testName"
  const val notificationId = 1
  const val foregroundId = 1
}

Step 4. Write Code

Finally we need to write our code as follows:

(a). AudioFocusGainTransientService.kt

package com.example.takusemba.audiofocusserviceapp

import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import android.util.Log

class AudioFocusGainTransientService : Service() {

  companion object {
    const val TAG = "AudioFocusGainService"
  }

  override fun onBind(intent: Intent): IBinder? {
    return null
  }

  private val request: AudioFocusRequest = AudioFocusRequest
      .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
      .setAudioAttributes(AudioAttributes.Builder()
          .setUsage(AudioAttributes.USAGE_GAME)
          .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
          .build()
      )
      .setOnAudioFocusChangeListener {}
      .setAcceptsDelayedFocusGain(true)
      .build()

  override fun onCreate() {
    super.onCreate()
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.requestAudioFocus(request)
    val notification = NotificationCompat.Builder(this, "test")
        .setContentTitle("AUDIOFOCUS_GAIN_TRANSIENT")
        .build()
    startForeground(1, notification)
    Log.d(TAG, "request audio focus")
  }

  override fun onDestroy() {
    super.onDestroy()
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.abandonAudioFocusRequest(request)
    Log.d(TAG, "release audio focus")
  }
}

Step 5. Write Code

Finally we need to write our code as follows:

(a). AudioFocusGainTransientMayDuckService.kt

package com.example.takusemba.audiofocusserviceapp

import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import android.util.Log

class AudioFocusGainTransientMayDuckService : Service() {

  companion object {
    const val TAG = "AudioFocusGainService"
  }

  override fun onBind(intent: Intent): IBinder? {
    return null
  }

  private val request: AudioFocusRequest = AudioFocusRequest
      .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
      .setAudioAttributes(AudioAttributes.Builder()
          .setUsage(AudioAttributes.USAGE_GAME)
          .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
          .build()
      )
      .setOnAudioFocusChangeListener {}
      .setAcceptsDelayedFocusGain(true)
      .build()

  override fun onCreate() {
    super.onCreate()
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.requestAudioFocus(request)
    val notification = NotificationCompat.Builder(this, "test")
        .setContentTitle("AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK")
        .build()
    startForeground(1, notification)
    Log.d(TAG, "request audio focus")
  }

  override fun onDestroy() {
    super.onDestroy()
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.abandonAudioFocusRequest(request)
    Log.d(TAG, "release audio focus")
  }
}

Step 6. Write Code

Finally we need to write our code as follows:

(a). AudioFocusGainTransientExclusiveService.kt

package com.example.takusemba.audiofocusserviceapp

import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import android.util.Log

class AudioFocusGainTransientExclusiveService : Service() {

  companion object {
    const val TAG = "AudioFocusGainService"
  }

  override fun onBind(intent: Intent): IBinder? {
    return null
  }

  private val request: AudioFocusRequest = AudioFocusRequest
      .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
      .setAudioAttributes(AudioAttributes.Builder()
          .setUsage(AudioAttributes.USAGE_GAME)
          .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
          .build()
      )
      .setOnAudioFocusChangeListener {}
      .setAcceptsDelayedFocusGain(true)
      .build()

  override fun onCreate() {
    super.onCreate()
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.requestAudioFocus(request)
    val notification = NotificationCompat.Builder(this, "test")
        .setContentTitle("AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE")
        .build()
    startForeground(1, notification)
    Log.d(TAG, "request audio focus")
  }

  override fun onDestroy() {
    super.onDestroy()
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.abandonAudioFocusRequest(request)
    Log.d(TAG, "release audio focus")
  }
}

Step 7. Write Code

Finally we need to write our code as follows:

(a). AudioFocusGainService.kt

package com.example.takusemba.audiofocusserviceapp

import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.util.Log

class AudioFocusGainService : Service() {

  companion object {
    const val TAG = "AudioFocusGainService"

    const val AUDIO_REQUEST_TYPE = "AUDIO_REQUEST_TYPE"
  }

  override fun onBind(intent: Intent): IBinder? {
    return null
  }

  enum class AudioRequestType(val id: Int, val audioFocusId: Int, val title: String) {
    GAIN(
        id = 1,
        audioFocusId = AudioManager.AUDIOFOCUS_GAIN,
        title = "AUDIOFOCUS_GAIN"
    ),

    GAIN_TRANSIENT(
        id = 2,
        audioFocusId = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
        title = "AUDIOFOCUS_GAIN_TRANSIENT"
    ),

    GAIN_TRANSIENT_MAY_DUCK(
        id = 3,
        audioFocusId = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
        title = "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK"
    ),

    GAIN_TRANSIENT_EXCLUSIVE(
        id = 4,
        audioFocusId = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
        title = "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE"
    )
  }

  private lateinit var request: AudioFocusRequest

  override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {

    if (intent.hasExtra(AUDIO_REQUEST_TYPE)) {
      val audioRequest = when (intent.getIntExtra(AUDIO_REQUEST_TYPE, -1)) {
        AudioRequestType.GAIN.id -> AudioRequestType.GAIN
        AudioRequestType.GAIN_TRANSIENT.id -> AudioRequestType.GAIN_TRANSIENT
        AudioRequestType.GAIN_TRANSIENT_MAY_DUCK.id -> AudioRequestType.GAIN_TRANSIENT_MAY_DUCK
        AudioRequestType.GAIN_TRANSIENT_EXCLUSIVE.id -> AudioRequestType.GAIN_TRANSIENT_EXCLUSIVE
        else -> throw IllegalStateException("invalid request type")
      }

      val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
      request = AudioFocusRequest
          .Builder(audioRequest.audioFocusId)
          .setAudioAttributes(AudioAttributes.Builder()
              .setUsage(AudioAttributes.USAGE_GAME)
              .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
              .build()
          )
          .setOnAudioFocusChangeListener {}
          .setAcceptsDelayedFocusGain(true)
          .build()
      audioManager.requestAudioFocus(request)

      val notification = NotificationCompat.Builder(this, Config.channelId)
          .setContentTitle(audioRequest.title)
          .setSmallIcon(R.mipmap.ic_launcher)
          .build()

      val notificationManager = NotificationManagerCompat.from(this)
      notificationManager.notify(Config.notificationId, notification)
      startForeground(Config.foregroundId, notification)
      Log.d(TAG, "request audio focus")
    } else {
      stopSelf()
    }

    return Service.START_NOT_STICKY
  }

  override fun onDestroy() {
    super.onDestroy()
    if (::request.isInitialized) {
      val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
      audioManager.abandonAudioFocusRequest(request)
      Log.d(TAG, "release audio focus")
    }
  }
}

Step 8. Write Code

Finally we need to write our code as follows:

(a). App.kt

package com.example.takusemba.audiofocusserviceapp

import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context

class App : Application() {

  override fun onCreate() {
    super.onCreate()

    val channel = NotificationChannel(
        Config.channelId,
        Config.channelName,
        NotificationManager.IMPORTANCE_NONE
    )
    val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    service.createNotificationChannel(channel)
  }
}

Step 9. Write Code

Finally we need to write our code as follows:

(a). MainActivity.kt

package com.example.takusemba.audiofocusserviceapp

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
  }
}

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.

2. TakuSemba/AudioThief

Let you gain and release AudioFocus..

  • Features
    AudioThief Example Tutorial

Gradle

//app/build.gradle
dependencies {
    implementation 'com.github.takusemba:audiothief:x.x.x'
}

AudioThief Example Tutorial

Features

Since multiple apps can play audio to the sample ouptut stream simultaneously, it is requred to handle AudioFocus correctly.
However you do not want to call or set an alerm just to see if your impletemtation is working as you expected.
AudioThief gain AudioFocus from your app and release it back.

Usage

1. Add AudioThief dependency

dependencies {
    implementation 'com.github.takusemba:audiothief:x.x.x'
}

2. Edit your manifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <!--this permission is required if your app targets Android 9.0 (API level 28)-->
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

  <application>
  <!--...-->
    <service
       android:name="com.takusemba.audiothief.AudioFocusGainService"
       android:exported="true"
       />
  </application>

</manifest>

3. Gain and Release AudioFocus

You can start a foreground service that gains AudioFocus via adb command, or simply start the srevice inside your app.

adb shell am startservice --ei AUDIO_REQUEST_KEY [audioRequestKey] your.package.name/com.takusemba.audiothief.AudioFocusGainService

It holds AudioFocus until the foreground service stops.

adb shell am stopservice --ei AUDIO_REQUEST_KEY [audioRequestKey] your.package.name/com.takusemba.audiothief.AudioFocusGainService

AudioRequestKey

AudioRequestKey defines how you gain AudioFocus. That would be Int of 1, 2, 3, or 4.

AudioRequestKey defines how you gain AudioFocus. That would be Int of 1, 2, 3, or 4.

AudioRequestKey FocusGain Usage Content Type
1 AUDIOFOCUS_GAIN USAGE_MEDIA CONTENT_TYPE_MOVIE
2 AUDIOFOCUS_GAIN_TRANSIENT USAGE_ALARM CONTENT_TYPE_SONIFICATION
3 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK USAGE_NOTIFICATION CONTENT_TYPE_SONIFICATION
4 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE

Full Example

Let us look at a full Example below.

Step 1. Write Code

Finally we need to write our code as follows:

(a). MainActivity.kt

package com.takusemba.audiothiefsample

import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ui.PlayerView

class MainActivity : AppCompatActivity() {

  companion object {
    private const val URL = "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8"
    private val URI: Uri = Uri.parse(URL)
  }

  private var player: SimpleExoPlayer? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
  }

  override fun onStart() {
    super.onStart()
    player = SimpleExoPlayer.Builder(this).build()
    val playerView = findViewById<PlayerView>(R.id.player_view)
    playerView.player = player
    val mediaItem = MediaItem.fromUri(URI)
    val audioAttributes = AudioAttributes.Builder()
        .setUsage(C.USAGE_MEDIA)
        .setContentType(C.CONTENT_TYPE_MOVIE)
        .build()
    player?.setAudioAttributes(audioAttributes, true)
    player?.setMediaItem(mediaItem)
    player?.prepare()
    player?.playWhenReady = true
  }

  override fun onStop() {
    super.onStop()
    player?.release()
    player = null
  }
}

Step 2. Design Layouts

We need to design our XML layouts as follows:

(a). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.takusemba.audiothiefsample.MainActivity"
  >

  <com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/player_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3. Add Permissions

We will need to add some permissions in our AndroidManifest.xml as shown below:

(a). AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  package="com.takusemba.audiothiefsample"
  >

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

  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    tools:ignore="GoogleAppIndexingWarning"
    >
    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

    <service
      android:name="com.takusemba.audiothief.AudioFocusGainService"
      android:exported="true"
      />
  </application>

</manifest>

Reference

You can DOWNLOAD FULL CODE.
You can also browse code or read more here.
Follow code author here.


Read More.

Related Posts