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

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

ההתנהגות הזו של דעיכה מתרחשת כשהתנאים הבאים מתקיימים:

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