ניהול המיקוד באודיו

שתי אפליקציות ל-Android או יותר יכולות להפעיל אודיו לאותו זרם פלט בו-זמנית, והמערכת משלבת את הכול יחד. למרות שזה מרשים מבחינה טכנית, זה יכול להיות מאוד מעצבן למשתמש. כדי למנוע מצב שבו כל אפליקציות המוזיקה פועלות בו-זמנית, ב-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. אפליקציה שנייה מבקשת הרשאת אודיו עם AudioManager.AUDIOFOCUS_GAIN.

כשמתקיימים התנאים האלה, מערכת האודיו מעמעמת את האפליקציה הראשונה. בסוף ההנמכה, המערכת מודיעה לאפליקציה הראשונה על אובדן המיקוד. הנגנים של האפליקציה יישארו מושתקים עד שהאפליקציה תבקש שוב הרשאת אודיו.

התנהגויות קיימות של מיקוד אודיו

כדאי גם לשים לב למקרים אחרים שבהם יש מעבר בין מקורות אודיו.

הנמכה אוטומטית של עוצמת הקול

התכונה 'הנמכה אוטומטית של עוצמת השמע' (החלשת עוצמת השמע של אפליקציה אחת באופן זמני כדי שאפשר יהיה לשמוע בבירור אפליקציה אחרת) הושקה ב-Android 8.0 (רמת API‏ 26).

אם המערכת מטמיעה את ההנמכה, אתם לא צריכים להטמיע אותה באפליקציה.

הנמכה אוטומטית מתרחשת גם כשמתקבלת התראה קולית שמושכת את המיקוד מאפליקציה שמופעלת. תחילת ההשמעה של ההתראה מסונכרנת עם סוף ההנמכה.

הנמכת עוצמה אוטומטית מתרחשת כשמתקיימים התנאים הבאים:

  1. האפליקציה הראשונה שמופעלת כרגע עומדת בכל הקריטריונים האלה:

  2. אפליקציה שנייה מבקשת הרשאת אודיו עם AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

כשהתנאים האלה מתקיימים, מערכת האודיו מנמיכה את כל השחקנים הפעילים של האפליקציה הראשונה בזמן שהאפליקציה השנייה נמצאת במוקד. כשהאפליקציה השנייה מפסיקה להיות במרכז, היא מבטלת את ההצמדה. האפליקציה הראשונה לא מקבלת הודעה כשהיא מאבדת את המיקוד, ולכן היא לא צריכה לעשות כלום.

שימו לב: השתקה אוטומטית לא מתבצעת כשהמשתמש מאזין לתוכן דיבור, כי יכול להיות שהוא יפספס חלק מהתוכנית. לדוגמה, הנחיות קוליות למסלולי נסיעה לא מושתקות.

השתקת השמע שמופעל כרגע בשיחות טלפון נכנסות

חלק מהאפליקציות לא מתנהגות כמו שצריך וממשיכות להפעיל אודיו במהלך שיחות טלפון. במצב כזה, המשתמש נאלץ למצוא את האפליקציה הבעייתית, להשתיק אותה או לצאת ממנה כדי לשמוע את השיחה. כדי למנוע זאת, המערכת יכולה להשתיק את האודיו מאפליקציות אחרות בזמן שיש שיחה נכנסת. המערכת מפעילה את התכונה הזו כשמתקבלת שיחה נכנסת ואפליקציה עומדת בתנאים הבאים:

  • באפליקציה מוגדר מאפיין השימוש 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 מתאר את תרחיש השימוש באפליקציה. המערכת בודקת את הנתונים האלה כשאפליקציה מקבלת או מאבדת את פוקוס האודיו. מאפיינים מחליפים את המושג של סוג הזרם. ב-Android 8.0 (רמת API‏ 26) ואילך, סוגי הזרמים לכל פעולה מלבד בקרת עוצמת הקול הוצאו משימוש. צריך להשתמש באותם מאפיינים בבקשת המיקוד שבהם משתמשים בנגן האודיו (כפי שמוצג בדוגמה שבהמשך הטבלה).

משתמשים ב-AudioAttributes.Builder כדי לציין את המאפיינים קודם, ואז משתמשים בשיטה הזו כדי להקצות את המאפיינים לבקשה.

אם לא מציינים ערך, ברירת המחדל של AudioAttributes היא AudioAttributes.USAGE_MEDIA.

setWillPauseWhenDucked() כשמבקשים להעביר את המיקוד לאפליקציה אחרת באמצעות AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, האפליקציה שמוגדרת כברירת מחדל לא מקבלת בדרך כלל קריאה חוזרת (callback) של onAudioFocusChange(), כי המערכת יכולה להנמיך את עוצמת הקול בעצמה. אם רוצים להשהות את ההשמעה ולא להנמיך את עוצמת הקול, צריך להתקשר אל setWillPauseWhenDucked(true) וליצור ולהגדיר OnAudioFocusChangeListener, כמו שמתואר במאמר בנושא הנמכה אוטומטית של עוצמת הקול.
setAcceptsDelayedFocusGain() בקשה להרשאת אודיו עלולה להיכשל אם ההרשאה נעולה על ידי אפליקציה אחרת. השיטה הזו מאפשרת קבלת הרשאה בהשהיה: היכולת לקבל הרשאה באופן אסינכרוני כשהיא הופכת לזמינה.

שימו לב: השגת פוקוס מושהית פועלת רק אם מציינים גם AudioManager.OnAudioFocusChangeListener בבקשת האודיו, כי האפליקציה צריכה לקבל את הקריאה החוזרת כדי לדעת שהפוקוס הוענק.

setOnAudioFocusChangeListener() נדרש OnAudioFocusChangeListener רק אם מציינים גם willPauseWhenDucked(true) או setAcceptsDelayedFocusGain(true) בבקשה.

יש שתי שיטות להגדרת מאזין: אחת עם ארגומנט handler ואחת בלי. ה-handler הוא ה-thread שבו ה-listener פועל. אם לא מציינים handler, המערכת משתמשת ב-handler שמשויך ל-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 עם שיטת קריאה חוזרת onAudioFocusChange() שמטמיעה את התנהגות ההשהיה וההפעלה הרצויה. מתקשרים אל setOnAudioFocusChangeListener() כדי לרשום את המאזין, ומתקשרים אל setWillPauseWhenDucked(true) כדי להגדיר למערכת להשתמש בהתקשרות חזרה במקום בהנמכה אוטומטית של עוצמת השמע.

השהיית המיקוד

לפעמים המערכת לא יכולה לאשר בקשה להרשאת אודיו כי ההרשאה 'נעולה' על ידי אפליקציה אחרת, למשל במהלך שיחת טלפון. במקרה הזה, הפונקציה requestAudioFocus() מחזירה AUDIOFOCUS_REQUEST_FAILED. במצב כזה, האפליקציה לא אמורה להמשיך בהפעלת האודיו כי היא לא קיבלה פוקוס.

השיטה, setAcceptsDelayedFocusGain(true), שמאפשרת לאפליקציה לטפל בבקשה להעברת המיקוד באופן אסינכרוני. אם הדגל הזה מוגדר, בקשה שנשלחת כשהמיקוד נעול מחזירה AUDIOFOCUS_REQUEST_DELAYED. כשהתנאי שגרם לנעילת המיקוד באודיו כבר לא מתקיים, למשל כששיחת טלפון מסתיימת, המערכת מאשרת את בקשת המיקוד בהמתנה ומפעילה את onAudioFocusChange() כדי להודיע לאפליקציה.

כדי לטפל בהשהיה בהעברת המיקוד, צריך ליצור OnAudioFocusChangeListener עם שיטת קריאה חוזרת onAudioFocusChange() שמטמיעה את ההתנהגות הרצויה, ולרשום את ה-listener על ידי שליחת קריאה אל setOnAudioFocusChangeListener().

הרשאת אודיו ב-Android מגרסה 7.1 ומטה

כשמתקשרים אל requestAudioFocus() צריך לציין רמז לגבי משך הזמן, שאפליקציה אחרת שמחזיקה כרגע במיקוד ומפעילה תוכן עשויה להתחשב בו:

  • שליחת בקשה למיקוד אודיו קבוע (AUDIOFOCUS_GAIN) כשמתכננים להפעיל אודיו בעתיד הנראה לעין (לדוגמה, כשמפעילים מוזיקה) ומצפים שהאפליקציה הקודמת שהחזיקה במיקוד האודיו תפסיק להפעיל אודיו.
  • שליחת בקשה להתמקדות זמנית (AUDIOFOCUS_GAIN_TRANSIENT) כשרוצים להפעיל אודיו לזמן קצר בלבד, ומצפים שהאפליקציה הקודמת שהחזיקה במיקוד תשהה את ההפעלה.
  • שליחת בקשה להעברת המיקוד באופן זמני באמצעות הנמכה (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) כדי לציין שאתם רוצים להפעיל אודיו רק למשך זמן קצר, ושאפשר להמשיך להפעיל את האודיו של בעל המיקוד הקודם אם הוא מונמך. שני פלטי האודיו מעורבבים בזרם האודיו. הנמכת עוצמת הקול מתאימה במיוחד לאפליקציות שמשתמשות בזרם האודיו לסירוגין, למשל להנחיות נהיגה קוליות.

בנוסף, בשיטה requestAudioFocus() נדרש AudioManager.OnAudioFocusChangeListener. צריך ליצור את מאזין האירועים הזה באותה פעילות או באותו שירות שמוגדרים כבעלים של סשן המדיה. הוא מטמיע את הקריאה החוזרת onAudioFocusChange() שהאפליקציה מקבלת כשממשק API אחר מקבל או מוותר על הרשאת אודיו.

בקטע הקוד הבא מוצגת בקשה למיקוד אודיו קבוע בסטרימינג 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 המשויך. אם ביקשתם מיקוד זמני, האפליקציה שהושהתה או שההנמכה שלה הופעלה תקבל הודעה שאפשר להמשיך להפעיל אותה או להחזיר את עוצמת הקול שלה.

תגובה לשינוי בהרשאת האודיו

כשלאפליקציה יש הרשאת אודיו, היא צריכה להיות מסוגלת לשחרר אותה כשלאפליקציה אחרת יש בקשה להרשאת אודיו משלה. במקרה כזה, האפליקציה מקבלת קריאה ל-method‏ onAudioFocusChange() ב-AudioFocusChangeListener שציינתם כשהאפליקציה הפעילה את requestAudioFocus().

הפרמטר focusChange שמועבר אל onAudioFocusChange() מציין את סוג השינוי שמתרחש. הערך הזה תואם לרמז לגבי משך הזמן שבו נעשה שימוש באפליקציה שמתמקדת בה. האפליקציה צריכה להגיב בצורה הולמת.

איבוד מיקוד זמני
אם שינוי המיקוד הוא זמני (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK או AUDIOFOCUS_LOSS_TRANSIENT), האפליקציה צריכה להנמיך את עוצמת הקול (אם אתם לא מסתמכים על הנמכה אוטומטית של עוצמת הקול) או להשהות את ההפעלה, אבל לשמור על אותו מצב.

במהלך אובדן זמני של הרשאת האודיו, צריך להמשיך לעקוב אחרי שינויים בהרשאת האודיו ולהיות מוכנים להמשיך בהפעלה הרגילה כשמקבלים שוב את ההרשאה. כשאפליקציית החסימה מפסיקה להתמקד, מתקבל קריאה חוזרת (callback) (AUDIOFOCUS_GAIN). בשלב הזה, אפשר להחזיר את עוצמת הקול לרמה הרגילה או להפעיל מחדש את ההפעלה.

אובדן מיקוד קבוע
אם אובדן הרשאת האודיו הוא קבוע (AUDIOFOCUS_LOSS), אפליקציה אחרת משמיעה אודיו. האפליקציה צריכה להשהות את ההפעלה באופן מיידי, כי היא לא תקבל אף פעם קריאה חוזרת (callback) של AUDIOFOCUS_GAIN. כדי להפעיל מחדש את ההפעלה, המשתמש צריך לבצע פעולה מפורשת, כמו לחיצה על לחצן ההפעלה של אמצעי הבקרה להפעלה בהתראה או בממשק המשתמש של האפליקציה.

בקטע הקוד הבא אפשר לראות איך מטמיעים את 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() ב-Callback של onPlay(),‏ onSkipToNext() וכו'. כדאי גם לקרוא ל-method הזה ב-Callback של onDestroy() בשירות כשמנקים את המשאבים שבהם השירות משתמש.