Giter Site home page Giter Site logo

Comments (3)

marcbaechinger avatar marcbaechinger commented on September 21, 2024

Thanks for your report.

We are using media3 library to send voice commands to the media outlet.

Can you clarify a bit what Media3 API you are using to send a voice command from your app to another app? I don't know how this works I'm afraid.

Best would probably be to extend the the repro steps to make them tell what API is called to pause/play another app with Media3.

from media.

google-oss-bot avatar google-oss-bot commented on September 21, 2024

Hey @akrulec. We need more information to resolve this issue but there hasn't been an update in 14 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

from media.

akrulec avatar akrulec commented on September 21, 2024

We are using ExoPlayer from media3 to play voice commands to the user while they are going through a routine. Each time we speak a voice command, we request focus, play our mp4, and then give away focus when the audio is done (usually short 1-8sec). If the user connects to the phone via bluetooth, and they are listening to anything in the background, the background music ducks or stops, but after a while, there is a bluetooth crash, and the background music never resumes.

import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlaybackException
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService


AudioPlayerService : MediaSessionService()
    private var exoPlayer: ExoPlayer? = null
    private var mediaSession: MediaSession? = null

    private var currentItem: AudioQueueItem? = null

    private val audioAttributes =
        AudioAttributes.Builder()
            // Cannot use C.USAGE_ASSISTANT here because it connects to Bixby on Samsung phones, and
            // it makes it impossible to adjust when the media is playing in the background. I
            // suspect it connects to AudioSystem.STREAM_ASSISTANT which is not publicly available.
            .setUsage(C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .setContentType(C.AUDIO_CONTENT_TYPE_SPEECH)
            .build()

    // Lock for all adding and removing media operations. This seems to have fixed the issue when
    // sometimes the media is not ducking.
    private val focusLock = Any()
    // Check whether the playback is delayed, because audio focus was not granted.
    private var playbackDelayed = false
    // Variable to keep track on whether the playback should be continued when the focus is
    // regained.
    private var resumeOnFocusGain = false
    // Audio focus listener with the help from here:
    // https://developer.android.com/reference/android/media/AudioFocusRequest
    private val onAudioFocusChangeListener =
        AudioManager.OnAudioFocusChangeListener { focusChange ->
            when (focusChange) {
                AudioManager.AUDIOFOCUS_GAIN -> {
                    if (playbackDelayed || resumeOnFocusGain) {
                        synchronized(focusLock) {
                            playbackDelayed = false
                            resumeOnFocusGain = false
                        }
                        playbackNow()
                    }
                }
                AudioManager.AUDIOFOCUS_LOSS -> {
                    synchronized(focusLock) {
                        // This is not a transient loss, we shouldn't automatically resume for now.
                        resumeOnFocusGain = false
                        playbackDelayed = false
                    }
                    duckPlayback()
                }
                AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,
                AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
                    // Handle all transient losses the same way because we never duck.
                    synchronized(focusLock) {
                        // We should only resume if playback was interrupted.
                        resumeOnFocusGain = exoPlayer?.isPlaying ?: false
                        playbackDelayed = false
                    }
                    duckPlayback()
                }
            }
        }
        
        @OptIn(UnstableApi::class)
    override fun onCreate() {
        super.onCreate()

        exoPlayer =
            ExoPlayer.Builder(this)
                .setAudioAttributes(audioAttributes, false)
                .setMediaSourceFactory(
                    DefaultMediaSourceFactory(coPilotDownloadManager.cachedMediaSourceFactory))
                .build()
        exoPlayer?.also {
            it.addListener(playerListener)
            it.playWhenReady = true
            it.setWakeMode(PowerManager.PARTIAL_WAKE_LOCK)
            it.prepare()
            setVolume()
        }

        audioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
        val audioAttributes =
            android.media.AudioAttributes.Builder()
                .setUsage(android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                .setContentType(android.media.AudioAttributes.CONTENT_TYPE_SPEECH)
                .build()
        focusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
                .setAcceptsDelayedFocusGain(false)
                .setAudioAttributes(audioAttributes)
                .setForceDucking(true)
                .setOnAudioFocusChangeListener(onAudioFocusChangeListener)
                .build()
        exoPlayer?.let { mediaSession = MediaSession.Builder(applicationContext, it).build() }

        acquireWifiLock()
    }
    ....
                currentItem?.let { item ->
                if (audioManager != null && focusRequest != null) {
                    // Requesting audio focus.
                    val res = audioManager!!.requestAudioFocus(focusRequest!!)
                    synchronized(focusLock) {
                        focusRequested = true

                        when (res) {
                            AudioManager.AUDIOFOCUS_REQUEST_FAILED -> {
                                playbackDelayed = false
                            }
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
                                playbackDelayed = false
                                playbackNow(item.url)
                            }
                            AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
                                playbackDelayed = true
                            }
                        }
                    }
                }
            }
...
    /** Clears media from the player and abandons focus. */
    fun clearMedia() {
        synchronized(focusLock) {
            // First abandon focus.
            if (audioManager != null && focusRequest != null && focusRequested) {
                audioManager!!.abandonAudioFocusRequest(focusRequest!!)
                synchronized(focusLock) { focusRequested = false }
            }
            // Then clear the media.
            resumeOnFocusGain = false
            exoPlayer?.clearMediaItems()
        }
    }

    /** Plays the given URL or resumes the exo player if there is anything to play. */
    @OptIn(UnstableApi::class)
    private fun playbackNow(url: String? = null) {
        // Play new item or continue playing.
        url?.let {
            exoPlayer?.addMediaSource(coPilotDownloadManager.buildMediaSourceFromUrl(it))
        }
        setVolume()
        exoPlayer?.play()
    }
    
    /** Pauses the audio. Used when other apps request audio duck. */
    private fun duckPlayback() {
        val volume = SharedPreferencesUtil.getVoiceInstructionsVolume(applicationContext)
        // Volume comes in 1-100, and ExoPlayer accepts 0-1. Multiply with duck to talk in the back.
        exoPlayer?.volume = (volume / MAX_VOLUME) * DUCK_VOLUME
    }

    private fun acquireWifiLock() {
        if (wifiLock == null) {
            val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
            wifiLock =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    wifiManager.createWifiLock(
                        WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "coPilot_lock")
                } else {
                    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "coPilot_lock")
                }
        }

        wifiLock?.acquire()
    }
    private fun releaseWifiLock() {
        wifiLock?.release()
    }

    private inner class PlayerEventListener : Player.Listener {
        override fun onPlaybackStateChanged(playbackState: Int) {
            when (playbackState) {
                Player.STATE_ENDED -> {
                    // Only clear focus when no new items to play.
                    if (audioManager != null &&
                        focusRequest != null &&
                        focusRequested &&
                        (listener?.isQueueEmpty() == true)) {
                        audioManager!!.abandonAudioFocusRequest(focusRequest!!)
                        synchronized(focusLock) { focusRequested = false }
                    }
                    // Clear the media items, and reset the player.
                    // Unsure if this needs to happen here or in the STATE_READY.
                    exoPlayer?.clearMediaItems()
                    exoPlayer?.seekToDefaultPosition()
                    mediaReady()
                }
                Player.STATE_BUFFERING,
                Player.STATE_IDLE,
                Player.STATE_READY -> {
                    // Don't do anything for now.
                }
            }
        }

Dependencies

    version_media = "1.2.0"
    implementation "androidx.media3:media3-exoplayer:$version_media"
    implementation "androidx.media3:media3-session:$version_media"

from media.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.