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

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

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

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

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

  • เรียกใช้ requestAudioFocus() ทันทีก่อนเริ่มเล่นและตรวจสอบว่า การเรียกใช้ส่งคืน AUDIOFOCUS_REQUEST_GRANTED โทรหา requestAudioFocus() ใน Callback ของ 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 ไม่ได้โฟกัสแล้ว ระบบจะยกเลิกการซ่อนแอป ระบบจะไม่แจ้งเตือนแอปแรกเมื่อแอปดังกล่าวไม่ได้โฟกัส จึงไม่ต้องดำเนินการใดๆ

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

ปิดเสียงที่กำลังเล่นอยู่เมื่อมีสายโทรเข้า

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

  • แอปมีแอตทริบิวต์การใช้งาน 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 เนื่องจากคำขอโฟกัสต้องระบุประเภทคำขอเสมอ ประเภทจึงรวมอยู่ในตัวสร้าง สำหรับเครื่องมือสร้าง ใช้วิธีการของ Builder เพื่อตั้งค่าฟิลด์อื่นๆ ของคำขอ

ต้องระบุข้อมูลในช่อง FocusGain ส่วนช่องอื่นๆ ทั้งหมดไม่บังคับ

วิธีการหมายเหตุ
setFocusGain() ต้องระบุข้อมูลในช่องนี้ในทุกคำขอ โดยจะใช้ค่าเดียวกับ durationHint ที่ใช้ในการเรียก requestAudioFocus() ก่อน Android 8.0 ดังนี้ AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK หรือ AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
setAudioAttributes() AudioAttributes อธิบาย Use Case สำหรับแอปของคุณ ระบบจะพิจารณา Use Case เหล่านี้เมื่อแอปได้รับและสูญเสียโฟกัสเสียง แอตทริบิวต์ จะแทนที่แนวคิดเรื่องประเภทสตรีม ใน 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 วิธี ได้แก่ วิธีที่มีและไม่มีอาร์กิวเมนต์ตัวแฮนเดิล แฮนเดิลคือเธรดที่ Listener ทำงานอยู่ หากคุณไม่ได้ระบุแฮนเดิล ระบบจะใช้แฮนเดิลที่เชื่อมโยงกับLooperหลัก

ตัวอย่างต่อไปนี้แสดงวิธีใช้ AudioFocusRequest.Builder เพื่อสร้าง 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()ได้โดยไม่ต้องเรียกใช้แฮนเดิล onAudioFocusChange() ของแอป

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

หากต้องการให้แอปหยุดชั่วคราวเมื่อได้รับแจ้งให้หลบเสียงแทนที่จะลดระดับเสียง ให้สร้าง OnAudioFocusChangeListener ด้วยเมธอดเรียกกลับ onAudioFocusChange() ที่ใช้ลักษณะการทำงานของการหยุดชั่วคราว/เล่นต่อที่ต้องการ โทร setOnAudioFocusChangeListener() เพื่อลงทะเบียนผู้ฟัง และโทร 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 นี้ในกิจกรรมหรือบริการเดียวกันกับที่เป็นเจ้าของเซสชันสื่อ โดยจะใช้การเรียกกลับ onAudioFocusChange() ที่แอปของคุณได้รับเมื่อ แอปอื่นได้รับหรือละทิ้งโฟกัสเสียง

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

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) แอปของคุณควรลดระดับเสียง (หากคุณไม่ได้ใช้การลดระดับเสียงอัตโนมัติ) หรือหยุดเล่นชั่วคราว แต่ควรคงสถานะเดิมไว้

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

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

ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้OnAudioFocusChangeListener และonAudioFocusChange() Callback โปรดสังเกตการใช้ 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
      }
    }
  };

แฮนเดิลเลอร์ใช้ 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() ของ Callback onSkipToNext() เป็นต้น คุณควรเรียกใช้เมธอดนี้ใน Callback ของ onDestroy() บริการด้วยเมื่อล้างข้อมูลทรัพยากรที่บริการใช้