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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

خفض مستوى الصوت تلقائيًا

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

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

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

يتمّ كتم الصوت تلقائيًا عند استيفاء الشروط التالية:

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

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

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

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

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

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

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

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

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

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

هناك طريقتان لضبط المستمع: إحداهما مع وسيطة 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;
        }
    }
}

خفض مستوى الصوت تلقائيًا

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