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

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

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

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

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

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

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

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

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

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

‫Android 12 (مستوى واجهة برمجة التطبيقات 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() الذي يسبق الإصدار 8.0 من نظام Android: 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 من واجهة برمجة التطبيقات)، عندما يطلب تطبيق آخر الحصول على التركيز، AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK يمكن للنظام خفض مستوى الصوت ثم إعادته إلى مستواه السابق بدون استدعاء معاودة الاتصال onAudioFocusChange() في التطبيق.

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

إذا كنت تريد أن يتوقف تطبيقك مؤقتًا عند طلب خفض مستوى الصوت بدلاً من خفضه، أنشئ 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() الخاص بالخدمة عند تنظيف الموارد التي تستخدمها الخدمة.