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

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

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

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

การโฟกัสที่ล่าช้า

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

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

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

ในระหว่างที่สูญเสียโฟกัสเสียงชั่วคราว คุณควรตรวจสอบการเปลี่ยนแปลงในโฟกัสเสียงต่อไปและเตรียมพร้อมที่จะเล่นต่อตามปกติเมื่อได้รับโฟกัสอีกครั้ง เมื่อแอปที่บล็อกละทิ้งโฟกัส คุณจะได้รับการเรียกกลับ (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
      }
    }
  };

แฮนเดิลเลอร์ใช้ 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() เมื่อล้างข้อมูลทรัพยากรที่บริการใช้ด้วย