إدارة التركيز الصوتي

يمكن لتطبيقَين أو أكثر من تطبيقات Android تشغيل الصوت في مصدر إخراج الصوت نفسه في الوقت نفسه، ويخلط النظام كل شيء معًا. على الرغم من أنّ هذا الإجراء مثير للإعجاب من الناحية التقنية، إلا أنّه قد يكون مزعجًا جدًا للمستخدم. لتجنُّب تشغيل كل تطبيقات الموسيقى في الوقت نفسه، يقدّم نظام التشغيل Android مفهوم تركيز الصوت. يمكن لتطبيق واحد فقط الاحتفاظ بتركيز الصوت في كل مرة.

عندما يحتاج تطبيقك إلى إخراج الصوت، يجب أن يطلب الحصول على تركيز الصوت. وعندما يكون التركيز عليها، يمكنها تشغيل الصوت. ومع ذلك، بعد الحصول على تركيز الصوت، قد لا تتمكّن من الاحتفاظ به إلى أن تنتهي من التشغيل. يمكن لتطبيق آخر طلب التركيز، ما يؤدي إلى إيقاف التركيز الصوتي مؤقتًا. وفي هذه الحالة، يجب أن يوقف تطبيقك مؤقتًا تشغيل الصوت أو يخفّض مستوى صوته ليتمكّن المستخدمون من سماع مصدر الصوت الجديد بسهولة أكبر.

قبل الإصدار 12 من نظام التشغيل Android (المستوى 31 لواجهة برمجة التطبيقات)، لا يدير النظام ميزة "التركيز على الصوت". لذلك، على الرغم من أنّنا نشجّع مطوّري التطبيقات على الالتزام بإرشادات التركيز الصوتي، إذا استمر تطبيق في تشغيل الصوت بصوت عالٍ حتى بعد أن فقد التركيز الصوتي على جهاز يعمل بالإصدار 11 من نظام التشغيل Android (المستوى 30 لواجهة برمجة التطبيقات) أو إصدار أقدم، لن يتمكّن النظام من منعه. ومع ذلك، يؤدي سلوك التطبيق هذا إلى تجربة سيئة للمستخدمين، ويمكن أن يدفعهم في كثير من الأحيان إلى إلغاء تثبيت التطبيق الذي يتصرف بشكل غير سليم.

يجب أن يدير تطبيق الصوت المصمَّم بشكل جيد تركيز الصوت وفقًا للإرشادات العامة التالية:

  • اتّصِل بالرقم requestAudioFocus() على الفور قبل بدء اللعب وتأكَّد من أنّ المكالمة تعرض AUDIOFOCUS_REQUEST_GRANTED. أضِف طلبًا إلى requestAudioFocus() في دالة معاودة الاتصال onPlay() الخاصة بجلسة الوسائط.

  • عندما يحصل تطبيق آخر على تركيز الصوت، أوقِف التشغيل مؤقتًا أو بشكل دائم، أو خفِّض مستوى الصوت.

  • عند توقّف التشغيل (على سبيل المثال، عندما لا يتبقّى أي محتوى لتشغيله في التطبيق)، يجب إلغاء التركيز الصوتي. ليس على تطبيقك التخلي عن التركيز الصوتي إذا أوقف المستخدم التشغيل مؤقتًا، ولكن قد يستأنف التشغيل لاحقًا.

  • استخدِم AudioAttributes لوصف نوع الصوت الذي يشغّله تطبيقك. على سبيل المثال، بالنسبة إلى التطبيقات التي تشغّل الكلام، حدِّد CONTENT_TYPE_SPEECH.

تتم معالجة التركيز على الصوت بشكل مختلف حسب إصدار Android الذي يتم تشغيله:

الإصدار 12 من نظام التشغيل Android (المستوى 31 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث
يتولّى النظام إدارة ميزة "التركيز على الصوت". يفرض النظام خفض مستوى صوت التشغيل من أحد التطبيقات تدريجيًا عندما يطلب تطبيق آخر الحصول على أولويّة الصوت. يكتم النظام أيضًا صوت التشغيل عند تلقّي مكالمة واردة.
Android 8.0 (المستوى 26 لواجهة برمجة التطبيقات) إلى Android 11 (المستوى 30 لواجهة برمجة التطبيقات)
لا يدير النظام ميزة "التركيز على الصوت"، ولكنها تتضمّن بعض التغييرات التي تم طرحها بدءًا من الإصدار 8.0 من نظام التشغيل Android (المستوى 26 من واجهة برمجة التطبيقات).
Android 7.1 (المستوى 25 لواجهة برمجة التطبيقات) والإصدارات الأقدم
لا يدير النظام التركيز الصوتي، وتدير التطبيقات التركيز الصوتي باستخدام requestAudioFocus() وabandonAudioFocus().

التركيز على الصوت في الإصدار 12 من نظام التشغيل Android والإصدارات الأحدث

يجب ألا يشغّل تطبيق وسائط أو ألعاب يستخدم ميزة "التركيز الصوتي" الصوت بعد أن يفقد التركيز. في نظام التشغيل Android 12 (المستوى 31 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يفرض النظام هذا السلوك. عندما يطلب تطبيق التركيز على الصوت بينما يركّز تطبيق آخر على الصوت ويتم تشغيله، يفرض النظام على التطبيق الذي يتم تشغيله أن يتلاشى. يوفّر التلاشي انتقالاً أكثر سلاسة عند التبديل من تطبيق إلى آخر.

يحدث هذا التلاشي عند استيفاء الشروط التالية:

  1. يستوفي التطبيق الأول الذي يتم تشغيله حاليًا جميع المعايير التالية:

  2. يطلب تطبيق ثانٍ أولوية الصوت باستخدام AudioManager.AUDIOFOCUS_GAIN.

عند استيفاء هذه الشروط، يخفّض نظام الصوت مستوى صوت التطبيق الأول تدريجيًا، وعند انتهاء عملية التخفيض، يرسل النظام إشعارًا إلى التطبيق الأول بأنّه فقد التركيز. ستبقى مشغّلات التطبيق مكتومة إلى أن يطلب التطبيق استعادة أولويّة الصوت.

سلوكيات التركيز على الصوت الحالية

يجب أيضًا الانتباه إلى الحالات الأخرى التي يتم فيها التبديل إلى مصدر صوت آخر.

تجنُّب التداخل مع أصوات أخرى تلقائيًا

تم طرح ميزة "تجنُّب التداخل مع أصوات أخرى" (خفض مستوى صوت أحد التطبيقات مؤقتًا لكي يمكن سماع تطبيق آخر بوضوح) في الإصدار 8.0 من نظام التشغيل Android (المستوى 26 من واجهة برمجة التطبيقات).

من خلال السماح للنظام بتنفيذ عملية خفض مستوى الصوت، لن تحتاج إلى تنفيذها في تطبيقك.

يحدث التخفيض التلقائي لمستوى الصوت أيضًا عندما يستحوذ إشعار صوتي على التركيز من تطبيق قيد التشغيل، ويتم مزامنة بداية تشغيل الإشعار مع نهاية عملية التخفيض التدريجي لمستوى الصوت.

يحدث التخفيض التلقائي لمستوى الصوت عند استيفاء الشروط التالية:

  1. يستوفي التطبيق الأول الذي يتم تشغيله حاليًا جميع المعايير التالية:

  2. يطلب تطبيق ثانٍ الحصول على أولويّة الصوت باستخدام AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

عند استيفاء هذه الشروط، يخفّض نظام الصوت مستوى صوت جميع مشغّلات التطبيق الأول النشطة بينما يكون التطبيق الثاني هو محل التركيز. وعندما يتوقف التطبيق الثاني عن التركيز، يتم إلغاء تثبيتهما. لا يتم إعلام التطبيق الأول عندما يفقد التركيز، لذلك لا يحتاج إلى اتّخاذ أي إجراء.

يُرجى العِلم أنّه لا يتم خفض مستوى الصوت تلقائيًا عندما يستمع المستخدم إلى محتوى يتضمّن كلامًا، لأنّ ذلك قد يؤدي إلى تفويت بعض أجزاء البرنامج. على سبيل المثال، لا يتم خفض مستوى صوت الإرشادات الصوتية الخاصة باتجاهات القيادة.

كتم صوت تشغيل الصوت الحالي للمكالمات الهاتفية الواردة

لا تتصرف بعض التطبيقات بشكل صحيح وتستمر في تشغيل الصوت أثناء المكالمات الهاتفية. ويجبر هذا الموقف المستخدم على البحث عن التطبيق الذي يسبب المشكلة وكتم صوته أو إغلاقه ليتمكّن من سماع المكالمة. لمنع حدوث ذلك، يمكن للنظام كتم صوت التطبيقات الأخرى أثناء تلقّي مكالمة واردة. يستدعي النظام هذه الميزة عند تلقّي مكالمة هاتفية واردة واستيفاء أحد التطبيقات للشروط التالية:

  • يحتوي التطبيق على سمة الاستخدام AudioAttributes.USAGE_MEDIA أو AudioAttributes.USAGE_GAME.
  • طلب التطبيق الحصول على إذن بالتركيز على الصوت (أي اكتساب التركيز) وتم تشغيل الصوت بنجاح.

إذا استمر تطبيق في التشغيل أثناء المكالمة، سيتم كتم صوت التشغيل إلى أن تنتهي المكالمة. ومع ذلك، إذا بدأ تطبيق تشغيل الصوت أثناء المكالمة، لن يتم كتم صوت المشغّل على افتراض أنّ المستخدم بدأ التشغيل عن قصد.

التركيز على الصوت في الإصدارات من Android 8.0 إلى Android 11

بدءًا من الإصدار 8.0 من نظام التشغيل Android (المستوى 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 حالة استخدام تطبيقك، ويستند إليها النظام عندما يكتسب التطبيق التركيز الصوتي ويفقده. تحلّ السمات محلّ مفهوم نوع البث. في نظام التشغيل Android 8.0 (المستوى 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) في الطلب.

تتوفّر طريقتان لضبط أداة معالجة الأحداث: إحداهما تتضمّن وسيطة معالج والأخرى لا تتضمّنها. المعالج هو سلسلة التعليمات التي يتم تشغيل المستمع عليها. إذا لم تحدّد معالجًا، سيتم استخدام المعالج المرتبط بـ 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;
        }
    }
}

تجنُّب التداخل مع أصوات أخرى تلقائيًا

في الإصدار 8.0 من نظام التشغيل Android (المستوى 26 من واجهة برمجة التطبيقات)، عندما يطلب تطبيق آخر الحصول على التركيز، يمكن للنظام خفض مستوى الصوت ثم إعادته إلى مستواه السابق بدون استدعاء معاودة الاتصال onAudioFocusChange() في التطبيق.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

في حين أنّ التخفيض التلقائي لمستوى الصوت هو سلوك مقبول لتطبيقات تشغيل الموسيقى والفيديو، إلا أنّه غير مفيد عند تشغيل محتوى منطوق، مثل تطبيقات الكتب الصوتية. في هذه الحالة، يجب أن يوقف التطبيق الصوت مؤقتًا بدلاً من ذلك.

إذا كنت تريد أن يتوقف تطبيقك مؤقتًا عند طلب خفض مستوى الصوت بدلاً من خفضه، أنشئ OnAudioFocusChangeListener باستخدام طريقة معاودة الاتصال onAudioFocusChange() التي تنفّذ السلوك المطلوب للإيقاف المؤقت/الاستئناف. اتّصِل بالرقم setOnAudioFocusChangeListener() لتسجيل المستمع، واتّصِل بالرقم setWillPauseWhenDucked(true) لإخبار النظام باستخدام وظيفة رد الاتصال بدلاً من تنفيذ ميزة "تجنُّب التداخل مع أصوات أخرى" تلقائيًا.

اكتساب التركيز المتأخر

في بعض الأحيان، لا يمكن للنظام منح طلب الحصول على تركيز الصوت لأنّ تطبيقًا آخر "يحظر" التركيز، مثلاً أثناء إجراء مكالمة هاتفية. في هذه الحالة، تعرض الدالة requestAudioFocus() القيمة AUDIOFOCUS_REQUEST_FAILED. عند حدوث ذلك، يجب ألا يواصل تطبيقك تشغيل الصوت لأنّه لم يحصل على التركيز.

الطريقة setAcceptsDelayedFocusGain(true) التي تتيح لتطبيقك معالجة طلب التركيز بشكل غير متزامن. عند ضبط هذه العلامة، سيعرض الطلب الذي يتم إجراؤه عندما يكون التركيز مقفلاً القيمة AUDIOFOCUS_REQUEST_DELAYED. عندما لا يعود الشرط الذي أدى إلى حظر تركيز الصوت متوفّرًا، مثلاً عند انتهاء مكالمة هاتفية، يمنح النظام طلب التركيز المعلق ويستدعي onAudioFocusChange() لإرسال إشعار إلى تطبيقك.

للتعامل مع اكتساب التركيز المتأخّر، يجب إنشاء OnAudioFocusChangeListener باستخدام طريقة رد اتصال onAudioFocusChange() تنفّذ السلوك المطلوب وتسجيل أداة معالجة الأحداث من خلال استدعاء setOnAudioFocusChangeListener().

التركيز على الصوت في الإصدار 7.1 من نظام التشغيل Android والإصدارات الأقدم

عند طلب requestAudioFocus()، يجب تحديد تلميح مدة، وقد يتم تنفيذه من خلال تطبيق آخر يركّز حاليًا على تشغيل الصوت:

  • يجب طلب الحصول على تركيز صوتي دائم (AUDIOFOCUS_GAIN) عندما تخطّط لتشغيل الصوت في المستقبل المنظور (على سبيل المثال، عند تشغيل الموسيقى) وتتوقّع أن يتوقف المستخدم السابق لتركيز الصوت عن التشغيل.
  • يمكنك طلب التركيز المؤقت (AUDIOFOCUS_GAIN_TRANSIENT) عندما تتوقّع تشغيل الصوت لفترة قصيرة فقط، وعندما تتوقّع أن يوقف المشغّل السابق التشغيل مؤقتًا.
  • يمكنك طلب التركيز المؤقت باستخدام خفض مستوى الصوت (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)، يعني ذلك أنّ تطبيقًا آخر يشغّل الصوت. على تطبيقك إيقاف التشغيل مؤقتًا على الفور، لأنّه لن يتلقّى أي ردّ من 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
      }
    }
  };

يستخدم المعالج 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() الخاصة بالخدمة عند تنظيف الموارد التي تستخدمها الخدمة.