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

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

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

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

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

יש שתי שיטות להגדרת ה-listen: אחת עם ואחת בלי של ה-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() מיישמים את ההתנהגות הרצויה ורושמים את המאזינים באמצעות קריאה setOnAudioFocusChangeListener().

מיקוד אודיו ב-Android 7.1 ומטה

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

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

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

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

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

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