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

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

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

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

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

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

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

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

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

يتم التعامل مع التركيز على الصوت بشكل مختلف بناءً على إصدار Android الذي يعمل به:

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

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

ويجب ألا يشغِّل تطبيق الوسائط أو الألعاب الذي يستخدم التركيز الصوتي الصوت بعد أن يفقد التركيز. في Android 12 (المستوى 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، أما جميع الحقول الأخرى فهي اختيارية.

الطريقةNotes
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;
        }
    }
}

تجنب التداخل تلقائيًا

في الإصدار Android 8.0 (المستوى 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().

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

عند استدعاء requestAudioFocus() يجب تحديد تلميح بشأن المدة، وقد يتم الالتزام به من خلال تطبيق آخر يركّز حاليًا على الإعلان ويشغِّله:

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

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