จัดการโฟกัสเสียง

แอป Android ตั้งแต่ 2 แอปขึ้นไปสามารถเล่นเสียงไปยังสตรีมเอาต์พุตเดียวกันได้พร้อมกัน และระบบจะมิกซ์เสียงทั้งหมดเข้าด้วยกัน แม้ว่าจะเป็นเรื่องที่น่าประทับใจในทางเทคนิค แต่ก็อาจทำให้ผู้ใช้รู้สึกหงุดหงิดได้ Android จึงนำแนวคิดเรื่อง โฟกัสเสียง มาใช้เพื่อป้องกันไม่ให้แอปเพลงทุกแอปเล่นพร้อมกัน โดยจะมีเพียงแอปเดียวเท่านั้นที่สามารถมีโฟกัสเสียงได้ในแต่ละครั้ง

เมื่อแอปของคุณต้องการส่งออกเสียง แอปควรขอโฟกัสเสียง เมื่อมีโฟกัสแล้ว แอปจะสามารถเล่นเสียงได้ อย่างไรก็ตาม หลังจากได้รับโฟกัสเสียงแล้ว คุณอาจไม่สามารถเก็บโฟกัสเสียงไว้ได้จนกว่าจะเล่นเสร็จ แอปอื่นสามารถขอโฟกัสได้ ซึ่งจะแทนที่การถือครองโฟกัสเสียงของคุณ หากเกิดกรณีดังกล่าว แอปของคุณควรหยุดเล่นชั่วคราวหรือลดระดับเสียงลงเพื่อให้ผู้ใช้ได้ยินแหล่งเสียงใหม่ได้ง่ายขึ้น

ก่อน Android 12 (API ระดับ 31) ระบบจะไม่ได้จัดการโฟกัสเสียง ดังนั้น แม้ว่าเราจะสนับสนุนให้นักพัฒนาแอปปฏิบัติตามหลักเกณฑ์เกี่ยวกับโฟกัสเสียง แต่หากแอปยังคงเล่นเสียงดังแม้หลังจากสูญเสียโฟกัสเสียงในอุปกรณ์ที่ใช้ Android 11 (ระดับ API 30) หรือต่ำกว่า ระบบก็ไม่สามารถป้องกันได้ อย่างไรก็ตาม ลักษณะการทำงานของแอปเช่นนี้จะทำให้ผู้ใช้ได้รับประสบการณ์ที่ไม่ดีและอาจทำให้ผู้ใช้ถอนการติดตั้งแอปที่ทำงานผิดพลาด

แอปเสียงที่ออกแบบมาอย่างดีควรจัดการโฟกัสเสียงตามหลักเกณฑ์ทั่วไปต่อไปนี้

  • เรียกใช้ requestAudioFocus() ทันทีก่อนเริ่มเล่นและตรวจสอบว่า การเรียกใช้ส่งคืน AUDIOFOCUS_REQUEST_GRANTED เรียกใช้ requestAudioFocus() ในการเรียกกลับ onPlay() ของเซสชันสื่อ

  • เมื่อแอปอื่นได้รับโฟกัสเสียง ให้หยุดหรือหยุดเล่นชั่วคราว หรือลดระดับเสียง

  • เมื่อการเล่นหยุดลง (เช่น เมื่อแอปไม่มีอะไรให้เล่นอีก) ให้ละทิ้งโฟกัสเสียง แอปของคุณไม่จำเป็นต้องละทิ้งโฟกัสเสียงหากผู้ใช้หยุดเล่นชั่วคราวแต่จะกลับมาเล่นต่อในภายหลัง

  • ใช้ AudioAttributes เพื่ออธิบาย ประเภทเสียงที่แอปของคุณกำลังเล่น เช่น สำหรับแอปที่เล่นเสียงพูด ให้ระบุ CONTENT_TYPE_SPEECH

ระบบจะจัดการโฟกัสเสียงแตกต่างกันไปตามเวอร์ชันของ Android ที่ใช้งาน

Android 12 (ระดับ API 31) ขึ้นไป
ระบบจะจัดการโฟกัสเสียง ระบบจะบังคับให้การเล่นเสียงจากแอปค่อยๆ ลดระดับเสียงลงเมื่อแอปอื่นขอโฟกัสเสียง นอกจากนี้ ระบบยังปิดเสียงการเล่นเสียงเมื่อได้รับสายเรียกเข้า
Android 8.0 (ระดับ API 26) ถึง Android 11 (ระดับ API 30)
ระบบจะไม่ได้จัดการโฟกัสเสียง แต่มีการเปลี่ยนแปลงบางอย่างที่เริ่มใช้ใน Android 8.0 (ระดับ API 26)
Android 7.1 (ระดับ API 25) และต่ำกว่า
ระบบจะไม่ได้จัดการโฟกัสเสียง และแอปจะจัดการโฟกัสเสียงโดยใช้ requestAudioFocus() และ abandonAudioFocus()

โฟกัสเสียงใน Android 12 ขึ้นไป

แอปสื่อหรือแอปเกมที่ใช้โฟกัสเสียงไม่ควรเล่นเสียงหลังจากสูญเสียโฟกัส ใน Android 12 (ระดับ API 31) ขึ้นไป ระบบจะบังคับใช้ลักษณะการทำงานนี้ เมื่อแอปขอโฟกัสเสียงขณะที่แอปอื่นมีโฟกัสและกำลังเล่นอยู่ ระบบจะบังคับให้แอปที่กำลังเล่นค่อยๆ ลดระดับเสียงลง การเพิ่มการค่อยๆ ลดระดับเสียงลงจะช่วยให้การเปลี่ยนจากแอปหนึ่งไปยังอีกแอปหนึ่งเป็นไปอย่างราบรื่นมากขึ้น

ลักษณะการทำงานของการค่อยๆ ลดระดับเสียงลงจะเกิดขึ้นเมื่อตรงตามเงื่อนไขต่อไปนี้

  1. แอปแรกที่กำลังเล่นอยู่ตรงตามเกณฑ์ทั้งหมดต่อไปนี้

  2. แอปที่ 2 ขอโฟกัสเสียงด้วย AudioManager.AUDIOFOCUS_GAIN

เมื่อตรงตามเงื่อนไขเหล่านี้ ระบบเสียงจะค่อยๆ ลดระดับเสียงของแอปแรกลง และเมื่อสิ้นสุดการค่อยๆ ลดระดับเสียงลง ระบบจะแจ้งให้แอปแรกทราบว่าสูญเสียโฟกัส เพลเยอร์ของแอปจะยังคงปิดเสียงอยู่จนกว่าแอปจะขอโฟกัสเสียงอีกครั้ง

ลักษณะการทำงานของโฟกัสเสียงที่มีอยู่

คุณควรทราบถึงกรณีอื่นๆ ที่เกี่ยวข้องกับการเปลี่ยนโฟกัสเสียงด้วย

การลดเสียงอัตโนมัติ

การลดเสียงอัตโนมัติ (การลดระดับเสียงของแอปหนึ่งลงชั่วคราวเพื่อให้ได้ยินเสียงของอีกแอปหนึ่งอย่างชัดเจน) เริ่มใช้ใน Android 8.0 (ระดับ API 26)

การให้ระบบดำเนินการลดเสียงจะช่วยให้คุณไม่ต้องดำเนินการลดเสียงในแอป

การลดเสียงอัตโนมัติจะเกิดขึ้นเมื่อการแจ้งเตือนเสียงดึงโฟกัสจากแอปที่กำลังเล่นอยู่ โดยการเริ่มเล่นการแจ้งเตือนจะซิงค์กับการสิ้นสุดการค่อยๆ ลดระดับเสียงลง

การลดเสียงอัตโนมัติจะเกิดขึ้นเมื่อตรงตามเงื่อนไขต่อไปนี้

  1. แอปแรกที่กำลังเล่นอยู่ตรงตามเกณฑ์ทั้งหมดต่อไปนี้

  2. แอปที่ 2 ขอโฟกัสเสียงด้วย AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

เมื่อตรงตามเงื่อนไขเหล่านี้ ระบบเสียงจะลดเสียงเพลเยอร์ที่ใช้งานอยู่ทั้งหมดของแอปแรกขณะที่แอปที่ 2 มีโฟกัส เมื่อแอปที่ 2 ละทิ้งโฟกัส ระบบจะคืนระดับเสียงเดิมให้เพลเยอร์ แอปแรกจะไม่ได้รับการแจ้งเตือนเมื่อสูญเสียโฟกัส จึงไม่จำเป็นต้องดำเนินการใดๆ

โปรดทราบว่าระบบจะไม่ลดเสียงอัตโนมัติเมื่อผู้ใช้กำลังฟังเนื้อหาเสียงพูด เนื่องจากผู้ใช้อาจพลาดเนื้อหาบางส่วนของโปรแกรม เช่น ระบบจะไม่ลดเสียงคำแนะนำด้วยเสียงสำหรับการนำทาง

ปิดเสียงการเล่นเสียงปัจจุบันเมื่อมีสายเรียกเข้า

แอปบางแอปทำงานไม่ถูกต้องและยังคงเล่นเสียงระหว่างการโทร สถานการณ์นี้บังคับให้ผู้ใช้ต้องค้นหาและปิดเสียงหรือออกจากแอปที่มีปัญหาเพื่อให้ได้ยินเสียงการโทร ระบบจึงสามารถปิดเสียงจากแอปอื่นๆ ขณะมีสายเรียกเข้าเพื่อป้องกันไม่ให้เกิดเหตุการณ์ดังกล่าว ระบบจะเรียกใช้ฟีเจอร์นี้เมื่อได้รับสายเรียกเข้าและแอปเป็นไปตามเงื่อนไขต่อไปนี้

  • แอปมีแอตทริบิวต์การใช้งาน AudioAttributes.USAGE_MEDIA หรือ AudioAttributes.USAGE_GAME
  • แอปขอโฟกัสเสียง (การเพิ่มโฟกัสประเภทใดก็ได้) สำเร็จและกำลังเล่นเสียงอยู่

หากแอปยังคงเล่นเสียงระหว่างการโทร การเล่นจะถูกปิดเสียงจนกว่าการโทรจะสิ้นสุดลง อย่างไรก็ตาม หากแอปเริ่มเล่นเสียงระหว่างการโทร ระบบจะไม่ปิดเสียงเพลเยอร์ดังกล่าว โดยสันนิษฐานว่าผู้ใช้เริ่มเล่นโดยตั้งใจ

โฟกัสเสียงใน Android 8.0 ถึง Android 11

เริ่มตั้งแต่ Android 8.0 (API ระดับ 26) เป็นต้นไป เมื่อเรียกใช้ requestAudioFocus() คุณต้องระบุพารามิเตอร์ AudioFocusRequest AudioFocusRequest มีข้อมูลเกี่ยวกับบริบทเสียงและความสามารถของแอป ระบบจะใช้ข้อมูลนี้เพื่อจัดการการเพิ่มและลดโฟกัสเสียงโดยอัตโนมัติ หากต้องการปล่อยโฟกัสเสียง ให้เรียกใช้เมธอด abandonAudioFocusRequest() ซึ่งรับ AudioFocusRequest เป็นอาร์กิวเมนต์ด้วย ใช้อินสแตนซ์ AudioFocusRequest เดียวกันทั้งเมื่อขอและละทิ้งโฟกัส

หากต้องการสร้าง AudioFocusRequest ให้ใช้ AudioFocusRequest.Builder เนื่องจากคำขอโฟกัสต้องระบุประเภทของคำขอเสมอ ประเภทจึงรวมอยู่ในคอนสตรักเตอร์สำหรับตัวสร้าง ใช้เมธอดของตัวสร้างเพื่อตั้งค่าช่องอื่นๆ ของคำขอ

ต้องระบุช่อง FocusGain ส่วนช่องอื่นๆ ทั้งหมดเป็นช่องที่ไม่บังคับ

เมธอดหมายเหตุ
setFocusGain() ต้องระบุช่องนี้ในทุกคำขอ โดยจะใช้ค่าเดียวกับ durationHint ที่ใช้ในการเรียกใช้ requestAudioFocus() ก่อน Android 8.0 ได้แก่ AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK หรือ AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
setAudioAttributes() AudioAttributes อธิบายกรณีการใช้งานสำหรับแอปของคุณ ระบบจะดูแอตทริบิวต์เหล่านี้เมื่อแอปได้รับและสูญเสียโฟกัสเสียง แอตทริบิวต์จะแทนที่แนวคิดเรื่องประเภทสตรีม ใน Android 8.0 (ระดับ API 26) ขึ้นไป ระบบจะเลิกใช้งานประเภทสตรีมสำหรับการดำเนินการอื่นๆ นอกเหนือจากการควบคุมระดับเสียง ใช้แอตทริบิวต์เดียวกันในคำขอโฟกัสกับที่ใช้ในเพลเยอร์เสียง (ตามที่แสดงในตัวอย่างหลังตารางนี้)

ใช้ AudioAttributes.Builder เพื่อระบุแอตทริบิวต์ก่อน จากนั้นใช้เมธอดนี้เพื่อกำหนดแอตทริบิวต์ให้กับคำขอ

หากไม่ได้ระบุไว้ AudioAttributes จะใช้ AudioAttributes.USAGE_MEDIA เป็นค่าเริ่มต้น

setWillPauseWhenDucked() เมื่อแอปอื่นขอโฟกัสด้วย AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK แอปที่มีโฟกัสมักจะไม่ ได้รับการเรียกกลับ onAudioFocusChange() เนื่องจากระบบสามารถลดเสียงได้ด้วยตัวเอง เมื่อคุณต้องการหยุดเล่นชั่วคราวแทนที่จะลดระดับเสียง ให้เรียกใช้ setWillPauseWhenDucked(true) และสร้างและตั้งค่า OnAudioFocusChangeListener ตามที่อธิบายไว้ในการลดเสียงอัตโนมัติ
setAcceptsDelayedFocusGain() คำขอโฟกัสเสียงอาจล้มเหลวเมื่อแอปอื่นล็อกโฟกัส เมธอดนี้จะเปิดใช้การเพิ่มโฟกัสที่ล่าช้า ซึ่งเป็นความสามารถ ในการรับโฟกัสแบบไม่พร้อมกันเมื่อพร้อมใช้งาน

โปรดทราบว่าการเพิ่มโฟกัสที่ล่าช้าจะทำงานได้ก็ต่อเมื่อคุณระบุ AudioManager.OnAudioFocusChangeListener ในคำขอเสียงด้วย เนื่องจากแอปของคุณต้องได้รับการเรียกกลับเพื่อให้ทราบว่าได้รับโฟกัสแล้ว

setOnAudioFocusChangeListener() OnAudioFocusChangeListener จำเป็นก็ต่อเมื่อคุณระบุ willPauseWhenDucked(true) หรือ setAcceptsDelayedFocusGain(true) ในคำขอด้วย

การตั้งค่า Listener มี 2 วิธี ได้แก่ วิธีที่มีอาร์กิวเมนต์ Handler และวิธีที่ไม่มี Handler คือเธรดที่ Listener ทำงาน หากคุณไม่ได้ระบุ Handler ระบบจะใช้ Handler ที่เชื่อมโยงกับ Looper หลัก

ตัวอย่างต่อไปนี้แสดงวิธีใช้ AudioFocusRequest.Builder เพื่อสร้าง an AudioFocusRequest รวมถึงขอและละทิ้งโฟกัสเสียง

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;
        }
    }
}

การลดเสียงอัตโนมัติ

ใน Android 8.0 (ระดับ API 26) เมื่อแอปอื่นขอโฟกัสด้วย AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ระบบจะลดและคืนระดับเสียงได้โดยไม่ต้องเรียกใช้การเรียกกลับ onAudioFocusChange() ของแอป

แม้ว่าการลดเสียงอัตโนมัติจะเป็นลักษณะการทำงานที่ยอมรับได้สำหรับแอปเล่นเพลงและวิดีโอ แต่ก็ไม่มีประโยชน์เมื่อเล่นเนื้อหาเสียงพูด เช่น ในแอปหนังสือเสียง ในกรณีนี้ แอปควรหยุดเล่นชั่วคราวแทน

หากต้องการให้แอปหยุดเล่นชั่วคราวเมื่อได้รับคำขอให้ลดเสียงแทนที่จะลดระดับเสียง ให้สร้าง OnAudioFocusChangeListener ที่มีเมธอด Callback onAudioFocusChange() ซึ่งใช้ลักษณะการทำงานของการหยุดเล่นชั่วคราว/เล่นต่อที่ต้องการ เรียกใช้ setOnAudioFocusChangeListener() เพื่อลงทะเบียน Listener และเรียกใช้ setWillPauseWhenDucked(true) เพื่อบอกให้ระบบใช้การเรียกกลับของคุณแทนที่จะลดเสียงอัตโนมัติ

การเพิ่มโฟกัสที่ล่าช้า

บางครั้งระบบไม่สามารถให้คำขอโฟกัสเสียงได้เนื่องจากแอปอื่น "ล็อก" โฟกัสไว้ เช่น ระหว่างการโทร ในกรณีนี้ requestAudioFocus() จะส่งคืน AUDIOFOCUS_REQUEST_FAILED เมื่อเกิดกรณีนี้ แอปของคุณไม่ควรดำเนินการเล่นเสียงต่อเนื่องจากไม่ได้รับโฟกัส

เมธอด setAcceptsDelayedFocusGain(true) ช่วยให้แอปของคุณจัดการคำขอโฟกัสแบบไม่พร้อมกันได้ เมื่อตั้งค่าแฟล็กนี้ คำขอที่ส่งเมื่อโฟกัสถูกล็อกจะส่งคืน AUDIOFOCUS_REQUEST_DELAYED เมื่อเงื่อนไขที่ล็อกโฟกัสเสียงไม่มีอยู่อีกต่อไป เช่น เมื่อการโทรสิ้นสุดลง ระบบจะให้คำขอโฟกัสที่รอดำเนินการและเรียกใช้ onAudioFocusChange() เพื่อแจ้งให้แอปของคุณทราบ

หากต้องการจัดการการเพิ่มโฟกัสที่ล่าช้า คุณต้องสร้าง OnAudioFocusChangeListener ที่มีเมธอด Callback onAudioFocusChange() ซึ่ง ใช้ลักษณะการทำงานที่ต้องการ และลงทะเบียน Listener โดยเรียกใช้ setOnAudioFocusChangeListener()

โฟกัสเสียงใน Android 7.1 และต่ำกว่า

เมื่อเรียกใช้ requestAudioFocus() คุณต้องระบุคำแนะนำระยะเวลา ซึ่งแอปอื่นที่กำลังมีโฟกัสและเล่นอยู่จะ พิจารณาคำแนะนำนี้

  • ขอโฟกัสเสียงถาวร (AUDIOFOCUS_GAIN) เมื่อคุณวางแผนที่จะเล่นเสียงในอนาคตอันใกล้ (เช่น เมื่อเล่นเพลง) และคาดว่าผู้ที่เคยมีโฟกัสเสียงจะหยุดเล่น
  • ขอโฟกัสชั่วคราว (AUDIOFOCUS_GAIN_TRANSIENT) เมื่อคุณคาดว่าจะเล่นเสียงเพียงช่วงเวลาสั้นๆ และคาดว่าผู้ที่เคยมีโฟกัสจะหยุดเล่นชั่วคราว
  • ขอโฟกัสชั่วคราวพร้อม การลดเสียง (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) เพื่อระบุว่าคุณคาดว่าจะเล่นเสียงเพียงช่วงเวลาสั้นๆ และเจ้าของโฟกัสเดิมสามารถเล่นต่อได้หาก "ลด" เอาต์พุตเสียง ระบบจะมิกซ์เอาต์พุตเสียงทั้ง 2 รายการลงในสตรีมเสียง การลดเสียงเหมาะอย่างยิ่งสำหรับแอปที่ใช้สตรีมเสียงเป็นระยะๆ เช่น คำแนะนำด้วยเสียงสำหรับการนำทาง

เมธอด requestAudioFocus() ยังต้องใช้ AudioManager.OnAudioFocusChangeListener ด้วย คุณควรสร้าง Listener นี้ในกิจกรรมหรือบริการเดียวกันกับที่เป็นเจ้าของเซสชันสื่อ Listener จะใช้การเรียกกลับ onAudioFocusChange() ที่แอปของคุณได้รับเมื่อแอปอื่นได้รับหรือละทิ้งโฟกัสเสียง

ข้อมูลโค้ดต่อไปนี้ขอโฟกัสเสียงถาวรในสตรีม STREAM_MUSIC และลงทะเบียน OnAudioFocusChangeListener เพื่อจัดการการเปลี่ยนแปลงโฟกัสเสียงในภายหลัง (เราจะพูดถึง Listener การเปลี่ยนแปลงในหัวข้อ การตอบสนองต่อการเปลี่ยนแปลงโฟกัสเสียง)

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
}

เมื่อเล่นเสร็จแล้ว ให้เรียกใช้ abandonAudioFocus()

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

Java

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

การดำเนินการนี้จะแจ้งให้ระบบทราบว่าคุณไม่ต้องการโฟกัสอีกต่อไปและยกเลิกการลงทะเบียน OnAudioFocusChangeListener ที่เชื่อมโยง หากคุณขอโฟกัสชั่วคราว การดำเนินการนี้จะแจ้งให้แอปที่หยุดเล่นชั่วคราวหรือลดเสียงทราบว่าแอปอาจเล่นต่อหรือคืนระดับเสียงเดิมได้

การตอบสนองต่อการเปลี่ยนแปลงโฟกัสเสียง

เมื่อแอปได้รับโฟกัสเสียง แอปจะต้องสามารถปล่อยโฟกัสเสียงได้เมื่อแอปอื่นขอโฟกัสเสียงสำหรับตัวเอง เมื่อเกิดกรณีนี้ แอปของคุณ จะได้รับการเรียกใช้เมธอด onAudioFocusChange() ใน AudioFocusChangeListener ที่ระบุไว้เมื่อแอปเรียกใช้ requestAudioFocus()

พารามิเตอร์ focusChange ที่ส่งไปยัง onAudioFocusChange() จะระบุประเภทการเปลี่ยนแปลงที่เกิดขึ้น โดยจะสอดคล้องกับคำแนะนำระยะเวลาที่แอปที่ได้รับโฟกัสใช้ แอปของคุณควรตอบสนองอย่างเหมาะสม

การสูญเสียโฟกัสชั่วคราว
หากการเปลี่ยนแปลงโฟกัสเป็นแบบชั่วคราว (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK หรือ AUDIOFOCUS_LOSS_TRANSIENT) แอปของคุณควรลดเสียง (หากคุณไม่ได้ใช้ การลดเสียงอัตโนมัติ) หรือหยุดเล่นชั่วคราว แต่ ยังคงสถานะเดิมไว้

ระหว่างการสูญเสียโฟกัสเสียงชั่วคราว คุณควรตรวจสอบการเปลี่ยนแปลงโฟกัสเสียงต่อไปและเตรียมพร้อมที่จะเล่นต่อตามปกติเมื่อได้รับโฟกัสอีกครั้ง เมื่อแอปที่บล็อกละทิ้งโฟกัส คุณจะได้รับการเรียกกลับ (AUDIOFOCUS_GAIN) ซึ่งในจุดนี้คุณสามารถคืนระดับเสียงเดิมหรือเริ่มเล่นใหม่ได้

การสูญเสียโฟกัสถาวร
หากการสูญเสียโฟกัสเสียงเป็นแบบถาวร (AUDIOFOCUS_LOSS) แสดงว่าแอปอื่นกำลังเล่นเสียงอยู่ แอปของคุณควรหยุดเล่นชั่วคราวทันที เนื่องจากจะไม่ได้รับการเรียกกลับ AUDIOFOCUS_GAIN หากต้องการเริ่มเล่นใหม่ ผู้ใช้ต้องดำเนินการอย่างชัดเจน เช่น กดปุ่มควบคุมการเล่นใน UI ของการแจ้งเตือนหรือแอป

ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้ OnAudioFocusChangeListener และการเรียกกลับ onAudioFocusChange() โปรดสังเกตการใช้ Handler เพื่อหน่วงเวลาการเรียกกลับการหยุดเมื่อสูญเสียโฟกัสเสียงถาวร

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
      }
    }
  };

Handler ใช้ Runnable ที่มีลักษณะดังนี้

Kotlin

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

Java

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

เรียกใช้ mHandler.removeCallbacks(mDelayedStopRunnable) เพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะใดๆ เพื่อให้แน่ใจว่าการหยุดที่ล่าช้าจะไม่เริ่มทำงานหากผู้ใช้เริ่มเล่นใหม่ เช่น เรียกใช้ removeCallbacks() ใน onPlay(), onSkipToNext() ฯลฯ ของการเรียกกลับ นอกจากนี้ คุณควรเรียกใช้เมธอดนี้ในการเรียกกลับ onDestroy() ของบริการเมื่อล้างข้อมูลทรัพยากรที่บริการใช้