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

Hai ứng dụng Android trở lên có thể phát âm thanh đồng thời vào cùng một luồng đầu ra và hệ thống sẽ trộn mọi thứ lại với nhau. Mặc dù về mặt kỹ thuật, điều này rất ấn tượng nhưng có thể gây khó chịu cho người dùng. Để tránh việc mọi ứng dụng nhạc đều phát cùng lúc, Android đã đưa ra ý tưởng về quyền phát âm thanh focus. Mỗi lần, chỉ một ứng dụng có thể 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ó quyền phát âm thanh, ứng dụng có thể phát âm thanh. Tuy nhiên, sau khi có được quyền phát âm thanh, bạn có thể không giữ được quyền 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, điều này sẽ khiến bạn mất 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 dễ dàng nghe thấy nguồn âm thanh mới 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ù nhà phát triển ứng dụng được khuyến khích 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 một thiết bị chạy Android 11 (API cấp 30) trở xuống, thì hệ thống không thể ngăn chặn điều đó. Tuy nhiên, hành vi này của ứng dụng dẫn đến trải nghiệm người dùng kém và thường khiến người dùng gỡ cài đặt ứng dụng hoạt động không đúng cách.

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

  • 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 đa phương tiện.

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

  • Khi quá trình phát dừng (ví dụ: khi ứng dụng không còn nội dung nào để phát), hãy từ bỏ quyền phát âm thanh. Ứng dụng của bạn không cần 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, 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
Quyền phát âm thanh do hệ thống quản lý. Hệ thống buộc quá trình phát âm thanh từ một ứng dụng phải mờ dần 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 quá trình 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)
Quyền phát âm thanh không do hệ thống quản lý, nhưng bao gồm một số thay đổi được giới thiệu bắt đầu từ Android 8.0 (API cấp 26).
Android 7.1 (API cấp 25) trở xuống
Quyền phát âm thanh không do hệ thống quản lý và các ứng dụng quản lý quyền phát âm thanh bằng cách sử dụng requestAudioFocus()abandonAudioFocus().

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

Một ứng dụng trò chơi hoặc đa phương tiện sử dụng quyền phát âm thanh không được phát âm thanh sau khi mất quyền phát âm thanh. 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 có quyền phát âm thanh và đang phát, hệ thống sẽ buộc ứng dụng đang phát phải mờ dần. Việc thêm tính năng mờ dần sẽ giúp quá trình chuyển đổi từ ứng dụng này sang ứng dụng khác diễn ra mượt mà hơn.

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

  1. Ứng dụng đầu tiên hiệ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 các điều kiện này, hệ thống âm thanh sẽ mờ dần ứng dụng đầu tiên. Khi quá trình mờ dần kết thúc, hệ thống sẽ thông báo cho ứng dụng đầu tiên về việc mất quyền phát âm thanh. 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.

Hành vi hiện có của quyền phát âm thanh

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

Tự động điều chỉnh âm lượng

Tính năng tự động điều chỉnh âm lượng (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 giới thiệu trong Android 8.0 (API cấp 26).

Bằng cách yêu cầu hệ thống triển khai tính năng điều chỉnh âm lượng, bạn không phải triển khai tính năng điều chỉnh âm lượng trong ứng dụng của mình.

Tính năng tự động điều chỉnh âm lượng cũng xảy ra khi một thông báo âm thanh giành được quyền phát âm thanh từ một ứng dụng đang phát. Thời điểm bắt đầu phát thông báo được đồng bộ hoá với thời điểm kết thúc quá trình tăng dần âm lượng.

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

  1. Ứng dụng đầu tiên hiệ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 đáp ứng các điều kiện này, hệ thống âm thanh sẽ điều chỉnh âm lượng của 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ó quyền phát âm thanh. Khi ứng dụng thứ hai từ bỏ quyền phát âm thanh, hệ thống sẽ huỷ điều chỉnh âm lượng của các trình phát đó. Ứng dụng đầu tiên không được thông báo khi mất quyền phát âm thanh, vì vậy, ứng dụng này không cần làm gì cả.

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

Tắt tiếng quá trình phát âm thanh hiện tại cho 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 cuộc 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 ứng dụng gây ra lỗi để nghe cuộc gọi. Để ngăn chặn điều này, hệ thống có thể tắt tiếng âm thanh từ các ứng dụng khác khi có cuộc gọi đến. Hệ thống gọi tính năng này khi nhận được cuộc gọi điện thoại đến và một ứ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 quyền phát âm thanh thành công (bất kỳ mức tăng quyền phát âm thanh nào) 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ì quá trình 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 đã cố ý bắt đầu phát.

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

Bắt đầu từ Android 8.0 (API cấp 26), khi gọi requestAudioFocus() , bạn phải cung cấp tham số AudioFocusRequest. AudioFocusRequest chứa thông tin về bối cảnh âm thanh và khả năng 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 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 AudioFocusRequest thực thể khi bạn yêu cầu và từ bỏ quyền phát âm thanh.

Để tạo AudioFocusRequest, hãy sử dụng AudioFocusRequest.Builder. Vì yêu cầu quyền phát âm thanh phải luôn chỉ định loại yêu cầu, nên loại này đượ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.

Bạn phải điền trường FocusGain; tất cả các trường khác đều không bắt buộc.

Phương thứcGhi chú
setFocusGain() Bạn phải điền trường này trong mọi yêu cầu. Trường này lấy các giá trị giống như durationHint được sử 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 cho ứng dụng của bạn. Hệ thống sẽ xem xét các thuộc tính này khi một ứng dụng có được và mất quyền phát âm thanh. Các 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 mọi thao tác khác ngoài các nút điều khiển âm lượng đều không được dùng nữa. Sử dụng các thuộc tính giống nhau trong yêu cầu quyền phát âm thanh mà bạn sử dụng trong trình phát âm thanh (như trong ví dụ sau bảng này).

Trước tiên, hãy sử dụng AudioAttributes.Builder để chỉ định các thuộc tính, 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ẽ mặc định là AudioAttributes.USAGE_MEDIA.

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

Xin lưu ý rằng tính năng tăng quyền phát âm thanh bị trì hoãn 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 được lệnh gọi lại để biết rằng quyền phát âm thanh đã được cấp.

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

Có 2 phương thức để đặt trình nghe: một phương thức có đối số trình xử lý 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 trình xử lý, thì trình xử lý được liên kết với Looper chính sẽ được sử dụng.

Ví dụ sau đây cho thấy cách sử dụng AudioFocusRequest.Builder để tạo AudioFocusRequest và yêu cầu cũng như từ 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 điều chỉnh âm lượng

Trong Android 8.0 (API cấp 26), khi một ứng dụng khác yêu cầu quyền phát âm thanh bằng AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK hệ thống có thể điều chỉnh 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 điều chỉnh âm lượng 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 giọng nói, 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 điều chỉnh âm lượng thay vì giảm âm lượng, hãy tạo OnAudioFocusChangeListener bằng một phương thức gọi lại onAudioFocusChange() triển khai hành vi tạm dừng/tiếp tục mong muốn. 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ì thực hiện tính năng tự động điều chỉnh âm lượng.

Tăng quyền phát âm thanh bị trì hoãn

Đôi khi, hệ thống không thể cấp yêu cầu quyền phát âm thanh vì quyền phát âm thanh bị "khoá" bởi một ứng dụng khác, chẳng hạn như trong 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 được tiếp tục phát âm thanh vì không có được quyền phát âm thanh.

Phương thức setAcceptsDelayedFocusGain(true) cho phép ứng dụng của bạn xử lý yêu cầu quyền phát âm thanh một cách không đồng bộ. Khi đặt cờ này, yêu cầu được thực hiện khi quyền phát âm thanh bị khoá sẽ trả về AUDIOFOCUS_REQUEST_DELAYED. Khi điều kiện khoá quyền phát â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 quyền phát âm thanh đang chờ xử lý và gọi onAudioFocusChange() để thông báo cho ứng dụng của bạn.

Để xử lý việc tăng quyền phát âm thanh bị trì hoãn, bạn phải tạo OnAudioFocusChangeListener bằng 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 một gợi ý về thời lượng. Gợi ý này có thể được một ứng dụng khác đang giữ quyền phát âm thanh và phát tuân theo:

  • 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 tương lai gần (ví dụ: khi phát nhạc) và bạn mong muốn người giữ quyền phát âm thanh trước đó sẽ dừng phát.
  • Yêu cầu quyền phát âm thanh tạm thời (AUDIOFOCUS_GAIN_TRANSIENT) khi bạn dự định chỉ phát âm thanh trong một thời gian ngắn và bạn mong muốn người giữ quyền phát âm thanh trước đó sẽ tạm dừng phát.
  • Yêu cầu quyền phát âm thanh tạm thời bằng tính năng điều chỉnh âm lượng (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) để cho biết rằng bạn dự định chỉ phát âm thanh trong một thời gian ngắn và chủ sở hữu quyền phát âm thanh trước đó có thể tiếp tục phát nếu "điều chỉnh âm lượng" (giảm) đầu ra âm thanh. Cả hai đầu ra âm thanh đều được trộn vào luồng âm thanh. Tính năng điều chỉnh âm lượng đặ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ư hướng dẫn lái xe bằng âm thanh.

Phương thức requestAudioFocus() cũng yêu cầu AudioManager.OnAudioFocusChangeListener. Trình nghe này phải được tạo trong cùng một hoạt động hoặc dịch vụ sở hữu phiên đa phương tiện của bạn. Trình nghe 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 ứng dụng khác có được 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ý OnAudioFocusChangeListener để xử lý các thay đổi tiếp theo về 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);

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

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

Khi một ứng dụng có được quyền phát âm thanh, ứ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 của bạn sẽ nhận được lệnh gọi đến onAudioFocusChange() phương thức 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 xảy ra. Tham số này tương ứng với gợi ý về thời lượng được ứng dụng đang có được quyền phát âm thanh sử dụng. Ứng dụng của bạn phải phản hồi một cách thích hợp.

Mất quyền phát âm thanh tạm thời
Nếu thay đổi về quyền phát âm thanh là tạm thời (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK hoặc AUDIOFOCUS_LOSS_TRANSIENT), thì ứng dụng của bạn phải điều chỉnh âm lượng (nếu bạn không dựa vào tính năng tự động điều chỉnh âm lượng) hoặc tạm dừng phát nhưng vẫn duy trì trạng thái tương tự.

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

Mất quyền phát âm thanh vĩnh viễn
Nếu mất quyền phát âm thanh vĩnh viễn (AUDIOFOCUS_LOSS), thì một ứng dụng khác đang phát âm thanh. Ứng dụng của bạn phải tạm dừng phát ngay lập tức vì sẽ không bao giờ nhận được lệnh gọi lại AUDIOFOCUS_GAIN. Để khởi động lại quá trình phát, 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 phát trong 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 ý cách 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 lệnh dừng bị trì hoãn không kích hoạt nếu người dùng khởi động lại quá trình phát, 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. 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.