Quản lý quyền phát âm thanh

Hai hoặc nhiều ứng dụng Android có thể phát đồng thời âm thanh đến cùng một luồng đầu ra, sau đó hệ thống sẽ kết hợp mọi thứ với nhau. Mặc dù về mặt kỹ thuật thì điều này thật sự ấn tượng, nhưng nó có thể gây khó chịu cho người dùng. Để tránh mọi ứng dụng âm nhạc phát cùng một lúc, Android đưa ra ý tưởng tập trung âm thanh. Tại một thời điểm, chỉ một ứng dụng có thể lưu giữ quyền phát âm thanh.

Khi cần xuất âm thanh, ứng dụng của bạn phải yêu cầu quyền phát âm thanh. Khi có tâm điểm, điện thoại có thể phát âm thanh. Tuy nhiên, sau khi có được quyền phát âm thanh, có thể bạn sẽ không giữ lại được chế độ này cho đến khi phát xong. Một ứng dụng khác có thể yêu cầu quyền phát âm thanh, và quyền này sẽ được ưu tiên áp dụng đối với lệnh giữ quyền phát âm thanh. Nếu điều đó xảy ra, ứng dụng của bạn nên tạm dừng phát hoặc giảm âm lượng để người dùng nghe được nguồn âm thanh mới dễ dàng hơn.

Trước Android 12 (API cấp 31), hệ thống không quản lý quyền phát âm thanh. Vì vậy, mặc dù các nhà phát triển ứng dụng nên tuân thủ nguyên tắc về quyền phát âm thanh, nhưng nếu một ứng dụng tiếp tục phát to ngay cả sau khi mất quyền phát âm thanh trên thiết bị chạy Android 11 (API cấp 30) trở xuống, thì hệ thống sẽ không thể ngăn chặn việc đó. Tuy nhiên, hành vi ứng dụng này dẫn đến trải nghiệm người dùng kém và thường có thể khiến người dùng gỡ cài đặt ứng dụng bị lỗi.

Một ứng dụng âm thanh được thiết kế tốt nên quản lý quyền phát âm thanh theo các nguyên tắc chung sau đây:

  • Hãy gọi requestAudioFocus() ngay trước khi bắt đầu phát và xác minh rằng lệnh gọi trả về AUDIOFOCUS_REQUEST_GRANTED. Thực hiện lệnh gọi đến requestAudioFocus() trong lệnh gọi lại onPlay() của phiên phát nội dung đa phương tiện.

  • Khi một ứng dụng khác nhận quyền phát âm thanh, hãy dừng/tạm dừng phát hoặc giảm âm lượng (tức là giảm) âm lượng.

  • Khi dừng phát (ví dụ: khi ứng dụng không còn nội dung nào để phát), hãy bỏ quyền phát âm thanh. Ứng dụng của bạn không phải từ bỏ quyền phát âm thanh nếu người dùng tạm dừng phát nhưng có thể tiếp tục phát sau.

  • Sử dụng AudioAttributes để mô tả loại âm thanh mà ứng dụng của bạn đang phát. Ví dụ: đối với các ứng dụng phát giọng nói, hãy chỉ định CONTENT_TYPE_SPEECH.

Quyền phát âm thanh được xử lý theo cách khác nhau tuỳ thuộc vào phiên bản Android đang chạy:

Android 12 (API cấp 31) trở lên
Hệ thống quản lý quyền phát âm thanh. Hệ thống sẽ buộc tắt dần âm thanh phát trên một ứng dụng khi một ứng dụng khác yêu cầu quyền phát âm thanh. Hệ thống cũng tắt tiếng phát âm thanh khi nhận được cuộc gọi đến.
Android 8.0 (API cấp 26) đến Android 11 (API cấp 30)
Hệ thống không quản lý quyền phát âm thanh mà chỉ có một số thay đổi mới ra mắt trong Android 8.0 (API cấp 26).
Android 7.1 (API cấp 25) trở xuống
Hệ thống không quản lý quyền phát âm thanh. Ngoài ra, các ứng dụng quản lý quyền phát âm thanh bằng requestAudioFocus()abandonAudioFocus().

Quyền phát âm thanh trong Android 12 trở lên

Ứng dụng đa phương tiện hoặc trò chơi sử dụng quyền phát âm thanh không được phát âm thanh sau khi mất tiêu điểm. Trong Android 12 (API cấp 31) trở lên, hệ thống sẽ thực thi hành vi này. Khi một ứng dụng yêu cầu quyền phát âm thanh trong khi một ứng dụng khác đang có tâm điểm và đang phát, hệ thống sẽ buộc ứng dụng phát đó mờ đi. Việc thêm hiệu ứng làm mờ sẽ giúp quá trình chuyển đổi mượt mà hơn khi chuyển từ ứng dụng này sang ứng dụng khác.

Hành vi mờ dần này xảy ra khi các điều kiện sau được đáp ứng:

  1. Ứng dụng đầu tiên, đang phát đáp ứng tất cả các tiêu chí sau:

  2. Ứng dụng thứ hai yêu cầu quyền phát âm thanh bằng AudioManager.AUDIOFOCUS_GAIN.

Khi đáp ứng những điều kiện này, hệ thống âm thanh sẽ mờ dần ứng dụng đầu tiên. Khi chế độ mờ dần, hệ thống sẽ thông báo cho ứng dụng đầu tiên về việc mất tiêu điểm. Trình phát của ứng dụng vẫn bị tắt tiếng cho đến khi ứng dụng yêu cầu lại quyền phát âm thanh.

Các hành vi hiện có về quyền phát âm thanh

Bạn cũng nên lưu ý các trường hợp khác có liên quan đến việc chuyển đổi quyền phát âm thanh.

Tự động giảm tốc độ

Tính năng tự động giảm mức âm thanh (tạm thời giảm mức âm thanh của một ứng dụng để có thể nghe rõ ứng dụng khác) đã được ra mắt trong Android 8.0 (API cấp 26).

Bằng cách để hệ thống triển khai tính năng giảm âm thanh, bạn không phải triển khai tính năng giảm âm thanh trong ứng dụng của mình.

Tình trạng tự động giảm tốc độ cũng xảy ra khi có thông báo âm thanh lấy tiêu điểm từ một ứng dụng đang phát. Thời điểm bắt đầu phát thông báo sẽ được đồng bộ hoá với thời điểm kết thúc đoạn đường giảm dần.

Tính năng giảm âm thanh tự động xảy ra khi đáp ứng các điều kiện sau:

  1. Ứng dụng đầu tiên, đang phát đáp ứng tất cả các tiêu chí sau:

  2. Ứng dụng thứ hai yêu cầu quyền phát âm thanh bằng AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

Khi các điều kiện này được đáp ứng, hệ thống âm thanh sẽ loại bỏ tất cả trình phát đang hoạt động của ứng dụng đầu tiên trong khi ứng dụng thứ hai được lấy làm tâm điểm. Khi ứng dụng thứ hai bỏ tiêu điểm, ứng dụng sẽ loại bỏ các tiêu điểm đó. Ứng dụng đầu tiên không được thông báo khi mất tiêu điểm, do đó, ứng dụng không phải làm gì cả.

Lưu ý rằng tính năng tự động giảm tốc độ không được thực hiện khi người dùng đang nghe nội dung lời nói, vì người dùng có thể bỏ lỡ một số chương trình. Ví dụ: hướng dẫn bằng giọng nói cho thông tin chỉ đường lái xe không bị giảm đi.

Tắt tiếng chế độ phát âm thanh hiện tại khi có cuộc gọi điện thoại đến

Một số ứng dụng hoạt động không đúng cách và tiếp tục phát âm thanh trong khi gọi điện thoại. Tình huống này buộc người dùng phải tìm và tắt tiếng hoặc thoát khỏi ứng dụng vi phạm để nghe lệnh gọi của họ. Để ngăn điều này, hệ thống có thể tắt âm thanh từ các ứng dụng khác khi có cuộc gọi đến. Hệ thống sẽ gọi tính năng này khi nhận được cuộc gọi điện thoại đến và ứng dụng đáp ứng các điều kiện sau:

  • Ứng dụng có thuộc tính sử dụng AudioAttributes.USAGE_MEDIA hoặc AudioAttributes.USAGE_GAME.
  • Ứng dụng đã yêu cầu thành công quyền phát âm thanh (mọi mức tăng quyền phát âm thanh) và đang phát âm thanh.

Nếu một ứng dụng tiếp tục phát trong cuộc gọi, thì nội dung phát của ứng dụng đó sẽ bị tắt tiếng cho đến khi cuộc gọi kết thúc. Tuy nhiên, nếu một ứng dụng bắt đầu phát trong cuộc gọi, thì trình phát đó sẽ không bị tắt tiếng với giả định rằng người dùng chủ ý bắt đầu phát.

Quyền phát âm thanh trong Android 8.0 đến Android 11

Kể từ Android 8.0 (API cấp 26), khi gọi requestAudioFocus(), bạn phải cung cấp một tham số AudioFocusRequest. AudioFocusRequest chứa thông tin về các chức năng và ngữ cảnh âm thanh của ứng dụng. Hệ thống sẽ dùng thông tin này để tự động quản lý việc tự động thu và mất quyền phát âm thanh. Để giải phóng quyền phát âm thanh, hãy gọi phương thức abandonAudioFocusRequest(). Phương thức này cũng lấy AudioFocusRequest làm đối số. Sử dụng cùng một thực thể AudioFocusRequest cả khi bạn yêu cầu và bỏ qua tiêu điểm.

Để tạo AudioFocusRequest, hãy dùng AudioFocusRequest.Builder. Vì yêu cầu tâm điểm phải luôn chỉ định loại yêu cầu, nên loại này sẽ được đưa vào hàm khởi tạo cho trình tạo. Sử dụng các phương thức của trình tạo để đặt các trường khác của yêu cầu.

Trường FocusGain là bắt buộc; tất cả các trường khác là không bắt buộc.

Phương thứcGhi chú
setFocusGain() Trường này là bắt buộc trong mọi yêu cầu. Phương thức này sẽ nhận các giá trị giống với durationHint dùng trong lệnh gọi trước Android 8.0 đến requestAudioFocus(): AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK hoặc AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
setAudioAttributes() AudioAttributes mô tả trường hợp sử dụng ứng dụng. Hệ thống sẽ xem xét trường hợp này khi ứng dụng nhận và mất quyền phát âm thanh. Thuộc tính thay thế khái niệm về loại luồng. Trong Android 8.0 (API cấp 26) trở lên, các loại luồng cho bất kỳ thao tác nào khác ngoài các tuỳ chọn điều khiển âm lượng sẽ không được dùng nữa. Sử dụng các thuộc tính giống nhau trong yêu cầu lấy tiêu điểm mà bạn dùng trong trình phát âm thanh (như trong ví dụ theo bảng này).

Sử dụng AudioAttributes.Builder để chỉ định các thuộc tính trước, sau đó sử dụng phương thức này để gán các thuộc tính cho yêu cầu.

Nếu không được chỉ định, AudioAttributes sẽ đặt mặc định thành AudioAttributes.USAGE_MEDIA.

setWillPauseWhenDucked() Khi một ứng dụng khác yêu cầu lấy tiêu điểm bằng AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, ứng dụng có tâm điểm thường không nhận được lệnh gọi lại onAudioFocusChange() vì hệ thống có thể tự thực hiện quá trình vị trí. Khi bạn cần tạm dừng phát thay vì giảm âm lượng, hãy gọi setWillPauseWhenDucked(true), tạo và đặt OnAudioFocusChangeListener, như mô tả trong phần ghi vị trí tự động.
setAcceptsDelayedFocusGain() Yêu cầu lấy quyền phát âm thanh có thể không thành công khi tiêu điểm bị khoá bởi một ứng dụng khác. Phương thức này bật độ trễ lấy nét: khả năng thu thập không đồng bộ tiêu điểm khi có tiêu điểm đó.

Xin lưu ý rằng chế độ lấy nét bị trễ chỉ hoạt động nếu bạn cũng chỉ định một AudioManager.OnAudioFocusChangeListener trong yêu cầu âm thanh, vì ứng dụng của bạn cần nhận lệnh gọi lại để biết rằng tâm điểm đã được cấp.

setOnAudioFocusChangeListener() Bạn chỉ bắt buộc phải có OnAudioFocusChangeListener nếu cũng chỉ định willPauseWhenDucked(true) hoặc setAcceptsDelayedFocusGain(true) trong yêu cầu.

Có 2 phương thức để thiết lập trình nghe: một phương thức có và một phương thức không có đối số trình xử lý. Trình xử lý là luồng mà trình nghe chạy trên đó. Nếu bạn không chỉ định một trình xử lý, thì trình xử lý liên kết với Looper chính sẽ được sử dụng.

Ví dụ sau đây cho biết cách sử dụng AudioFocusRequest.Builder để tạo AudioFocusRequest cũng như yêu cầu và bỏ quyền phát âm thanh:

Kotlin

// initializing variables for audio focus and playback management
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
    setAudioAttributes(AudioAttributes.Builder().run {
        setUsage(AudioAttributes.USAGE_GAME)
        setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        build()
    })
    setAcceptsDelayedFocusGain(true)
    setOnAudioFocusChangeListener(afChangeListener, handler)
    build()
}
val focusLock = Any()

var playbackDelayed = false
var playbackNowAuthorized = false

// requesting audio focus and processing the response
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
    playbackNowAuthorized = when (res) {
        AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false
        AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
            playbackNow()
            true
        }
        AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
            playbackDelayed = true
            false
        }
        else -> false
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
override fun onAudioFocusChange(focusChange: Int) {
    when (focusChange) {
        AudioManager.AUDIOFOCUS_GAIN ->
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false
                    resumeOnFocusGain = false
                }
                playbackNow()
            }
        AudioManager.AUDIOFOCUS_LOSS -> {
            synchronized(focusLock) {
                resumeOnFocusGain = false
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying()
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // ... pausing or ducking depends on your app
        }
    }
}

Java

// initializing variables for audio focus and playback management
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(playbackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(afChangeListener, handler)
        .build();
final Object focusLock = new Object();

boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;

// requesting audio focus and processing the response
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        playbackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        playbackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
        playbackDelayed = true;
        playbackNowAuthorized = false;
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false;
                    resumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(focusLock) {
                resumeOnFocusGain = false;
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying();
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}

Tự động giảm tốc độ

Trong Android 8.0 (API cấp 26), khi một ứng dụng khác yêu cầu tiêu điểm bằng AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, hệ thống có thể giảm số lượng và khôi phục âm lượng mà không cần gọi lệnh gọi lại onAudioFocusChange() của ứng dụng.

Mặc dù tính năng tự động giảm tốc độ là hành vi chấp nhận được đối với các ứng dụng phát nhạc và video, nhưng tính năng này không hữu ích khi phát nội dung đọc, chẳng hạn như trong ứng dụng sách nói. Trong trường hợp này, ứng dụng nên tạm dừng.

Nếu bạn muốn ứng dụng của mình tạm dừng khi được yêu cầu giảm âm lượng thay vì giảm âm lượng, hãy tạo OnAudioFocusChangeListener bằng phương thức gọi lại onAudioFocusChange(). Phương thức này sẽ triển khai hành vi tạm dừng/tiếp tục mong muốn. Hãy gọi setOnAudioFocusChangeListener() để đăng ký trình nghe và gọi setWillPauseWhenDucked(true) để yêu cầu hệ thống sử dụng lệnh gọi lại của bạn thay vì tự động giảm tốc độ.

Tăng tiêu điểm bị trễ

Đôi khi, hệ thống không thể yêu cầu quyền phát âm thanh vì tiêu điểm bị một ứng dụng khác "khoá", chẳng hạn như trong một cuộc gọi điện thoại. Trong trường hợp này, requestAudioFocus() sẽ trả về AUDIOFOCUS_REQUEST_FAILED. Khi điều này xảy ra, ứng dụng của bạn không nên tiếp tục phát âm thanh vì ứng dụng không lấy được tiêu điểm.

Phương thức setAcceptsDelayedFocusGain(true) cho phép ứng dụng của bạn xử lý yêu cầu lấy tiêu điểm một cách không đồng bộ. Khi thiết lập cờ này, yêu cầu được đưa ra khi tiêu điểm bị khoá sẽ trả về AUDIOFOCUS_REQUEST_DELAYED. Khi điều kiện khoá tiêu điểm âm thanh không còn tồn tại, chẳng hạn như khi cuộc gọi điện thoại kết thúc, hệ thống sẽ cấp yêu cầu lấy tiêu điểm đang chờ xử lý và gọi onAudioFocusChange() để thông báo cho ứng dụng của bạn.

Để xử lý việc lấy tiêu điểm bị trễ, bạn phải tạo một OnAudioFocusChangeListener với phương thức gọi lại onAudioFocusChange() triển khai hành vi mong muốn và đăng ký trình nghe bằng cách gọi setOnAudioFocusChangeListener().

Quyền phát âm thanh trong Android 7.1 trở xuống

Khi gọi requestAudioFocus(), bạn phải chỉ định gợi ý thời lượng. Gợi ý này có thể được một ứng dụng khác hiện đang lấy tiêu điểm và phát:

  • Yêu cầu quyền phát âm thanh vĩnh viễn (AUDIOFOCUS_GAIN) khi bạn dự định phát âm thanh trong một tương lai gần (ví dụ: khi phát nhạc) và muốn người giữ quyền phát âm thanh trước đó ngừng phát.
  • Yêu cầu tâm điểm tạm thời (AUDIOFOCUS_GAIN_TRANSIENT) khi bạn muốn phát âm thanh chỉ trong một thời gian ngắn và muốn chủ thể trước tạm dừng phát.
  • Yêu cầu tiêu điểm tạm thời bằng tính năng ghi vịt (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) để cho biết rằng bạn chỉ muốn phát âm thanh trong một thời gian ngắn và chủ sở hữu tiêu điểm trước có thể tiếp tục phát nếu đã "vịt" (giảm) đầu ra âm thanh. Cả hai đầu ra âm thanh sẽ được kết hợp vào luồng âm thanh. Hiệu ứng giảm âm thanh đặc biệt phù hợp với các ứng dụng sử dụng luồng âm thanh không liên tục, chẳng hạn như để phát âm thanh chỉ đường lái xe.

Phương thức requestAudioFocus() cũng yêu cầu phải có AudioManager.OnAudioFocusChangeListener. Bạn nên tạo trình nghe này trong cùng một hoạt động hoặc dịch vụ sở hữu phiên phát nội dung đa phương tiện của bạn. Phương thức này triển khai lệnh gọi lại onAudioFocusChange() mà ứng dụng của bạn nhận được khi một số ứng dụng khác thu nạp hoặc từ bỏ quyền phát âm thanh.

Đoạn mã sau đây yêu cầu quyền phát âm thanh vĩnh viễn trên luồng STREAM_MUSIC và đăng ký một OnAudioFocusChangeListener để xử lý các thay đổi tiếp theo đối với quyền phát âm thanh. (Trình nghe thay đổi được thảo luận trong phần Phản hồi thay đổi về quyền phát âm thanh.)

Kotlin

audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener

...
// Request audio focus for playback
val result: Int = audioManager.requestAudioFocus(
        afChangeListener,
        // Use the music stream.
        AudioManager.STREAM_MUSIC,
        // Request permanent focus.
        AudioManager.AUDIOFOCUS_GAIN
)

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

Java

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// Request audio focus for playback
int result = audioManager.requestAudioFocus(afChangeListener,
                             // Use the music stream.
                             AudioManager.STREAM_MUSIC,
                             // Request permanent focus.
                             AudioManager.AUDIOFOCUS_GAIN);

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

Khi bạn phát xong, hãy gọi abandonAudioFocus().

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

Java

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

Thao tác này sẽ thông báo cho hệ thống rằng bạn không còn yêu cầu lấy tiêu điểm nữa và huỷ đăng ký OnAudioFocusChangeListener được liên kết. Nếu bạn yêu cầu lấy tiêu điểm tạm thời, thì hệ thống sẽ thông báo cho một ứng dụng đã tạm dừng hoặc giảm tần suất phát về việc ứng dụng đó có thể tiếp tục phát hoặc khôi phục âm lượng.

Phản hồi khi thay đổi quyền phát âm thanh

Khi nhận được quyền phát âm thanh, một ứng dụng phải có khả năng giải phóng quyền phát âm thanh khi một ứng dụng khác yêu cầu quyền phát âm thanh cho chính ứng dụng đó. Khi điều này xảy ra, ứng dụng sẽ nhận được lệnh gọi đến phương thức onAudioFocusChange() trong AudioFocusChangeListener mà bạn đã chỉ định khi ứng dụng gọi requestAudioFocus().

Tham số focusChange được truyền đến onAudioFocusChange() cho biết loại thay đổi đang diễn ra. Giá trị này tương ứng với gợi ý về thời lượng mà ứng dụng thu nhận tiêu điểm sử dụng. Ứng dụng của bạn phải phản hồi đúng cách.

Mất tập trung tạm thời
Nếu sự thay đổi tiêu điểm là tạm thời (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK hoặc AUDIOFOCUS_LOSS_TRANSIENT), thì ứng dụng của bạn sẽ giảm tốc độ (nếu bạn không dựa vào tính năng tự động giảm tốc độ) hoặc tạm dừng phát, nếu không thì vẫn giữ nguyên trạng thái.

Trong trường hợp tạm thời mất quyền phát âm thanh, bạn nên tiếp tục theo dõi các thay đổi đối với quyền phát âm thanh và chuẩn bị tiếp tục phát bình thường khi bạn lấy lại tiêu điểm. Khi ứng dụng chặn bỏ qua tiêu điểm, bạn sẽ nhận được một lệnh gọi lại (AUDIOFOCUS_GAIN). Khi đó, bạn có thể khôi phục âm lượng về mức bình thường hoặc bắt đầu phát lại.

Mất khả năng tập trung vĩnh viễn
Nếu trạng thái mất quyền phát âm thanh vĩnh viễn (AUDIOFOCUS_LOSS), thì tức là một ứng dụng khác đang phát âm thanh. Ứng dụng của bạn cần tạm dừng phát ngay lập tức vì ứng dụng sẽ không bao giờ nhận được lệnh gọi lại AUDIOFOCUS_GAIN. Để bắt đầu phát lại, người dùng phải thực hiện một hành động rõ ràng, chẳng hạn như nhấn vào nút điều khiển truyền tải phát trong một thông báo hoặc giao diện người dùng của ứng dụng.

Đoạn mã sau đây minh hoạ cách triển khai OnAudioFocusChangeListener và lệnh gọi lại onAudioFocusChange(). Lưu ý việc sử dụng Handler để trì hoãn lệnh gọi lại dừng khi mất quyền phát âm thanh vĩnh viễn.

Kotlin

private val handler = Handler()
private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS -> {
            // Permanent loss of audio focus
            // Pause playback immediately
            mediaController.transportControls.pause()
            // Wait 30 seconds before stopping playback
            handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30))
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            // Pause playback
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // Lower the volume, keep playing
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            // Your app has been granted audio focus again
            // Raise volume to normal, restart playback if necessary
        }
    }
}

Java

private Handler handler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
  new AudioManager.OnAudioFocusChangeListener() {
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        // Permanent loss of audio focus
        // Pause playback immediately
        mediaController.getTransportControls().pause();
        // Wait 30 seconds before stopping playback
        handler.postDelayed(delayedStopRunnable,
          TimeUnit.SECONDS.toMillis(30));
      }
      else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
        // Pause playback
      } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume, keep playing
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Your app has been granted audio focus again
        // Raise volume to normal, restart playback if necessary
      }
    }
  };

Trình xử lý sử dụng Runnable có dạng như sau:

Kotlin

private var delayedStopRunnable = Runnable {
    mediaController.transportControls.stop()
}

Java

private Runnable delayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        getMediaController().getTransportControls().stop();
    }
};

Để đảm bảo rằng trạng thái dừng bị trì hoãn không hoạt động nếu người dùng bắt đầu phát lại, hãy gọi mHandler.removeCallbacks(mDelayedStopRunnable) để phản hồi mọi thay đổi về trạng thái. Ví dụ: gọi removeCallbacks() trong onPlay(), onSkipToNext(), v.v. của Lệnh gọi lại, v.v. Bạn cũng nên gọi phương thức này trong lệnh gọi lại onDestroy() của dịch vụ khi dọn dẹp các tài nguyên mà dịch vụ của bạn sử dụng.