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

שתי אפליקציות ל-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 עד גרסה 11

החל מ-Android 8.0 (רמת API‏ 26), כשמפעילים את requestAudioFocus(), צריך לספק פרמטר AudioFocusRequest. האובייקט AudioFocusRequest מכיל מידע על ההקשר והיכולות של האודיו באפליקציה. המערכת משתמשת במידע הזה כדי לנהל באופן אוטומטי את קבלת המיקוד באודיו ואת אובדן המיקוד. כדי לשחרר את המיקוד באודיו, צריך להפעיל את ה-method‏ abandonAudioFocusRequest(), שגם מקבלת את הארגומנט AudioFocusRequest. צריך להשתמש באותו מופע של AudioFocusRequest גם כשמבקשים את המיקוד וגם כשמוותרים עליו.

כדי ליצור AudioFocusRequest, משתמשים ב-AudioFocusRequest.Builder. מכיוון שבקשת מיקוד חייבת תמיד לציין את סוג הבקשה, הסוג נכלל ב-constructor של ה-builder. משתמשים ב-methods של ה-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 עם שיטת קריאה חוזרת (callback) מסוג onAudioFocusChange() שמטמיעה את ההתנהגות הרצויה של השהיה/המשך. מפעילים את setOnAudioFocusChangeListener() כדי לרשום את המאזין, ומפעילים את setWillPauseWhenDucked(true) כדי להורות למערכת להשתמש בקריאה החוזרת (callback) במקום לבצע הנמכה אוטומטית של עוצמת השמע.

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

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

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

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

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

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

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

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

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

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

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