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

แอป 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 เลิกโฟกัส ระบบจะเลิกซ่อนแอปเหล่านั้น แอปแรกจะไม่ได้รับการแจ้งเตือนเมื่อเสียโฟกัส จึงไม่ต้องดำเนินการใดๆ

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

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

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

  • แอปมีแอตทริบิวต์การใช้งาน 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 อธิบาย Use Case สําหรับแอปของคุณ ระบบจะดู Use Case เหล่านี้เมื่อแอปได้รับและเสียโฟกัสเสียง แอตทริบิวต์จะแทนที่แนวคิดประเภทสตรีม ใน Android 8.0 (API ระดับ 26) ขึ้นไป ระบบจะเลิกใช้งานประเภทสตรีมสำหรับการดำเนินการอื่นๆ นอกเหนือจากการควบคุมระดับเสียง ใช้แอตทริบิวต์เดียวกันในคำขอโฟกัสกับที่ใช้ในโปรแกรมเล่นเสียง (ดังที่แสดงในตัวอย่างหลังตารางนี้)

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

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

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

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

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

การตั้งค่า Listener มี 2 วิธี ได้แก่ 1 วิธีที่มีอาร์กิวเมนต์ตัวแฮนเดิลและอีก 1 วิธีที่ไม่มีอาร์กิวเมนต์ตัวแฮนเดิล แฮนเดิลคือเธรดที่ 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() callback ของแอป

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

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

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

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

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