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

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

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

قبل الإصدار 12 من Android (المستوى 31 لواجهة برمجة التطبيقات)، لم يكن النظام يدير التركيز على الصوت. لذلك، على الرغم من تشجيع مطوّري التطبيقات على الالتزام بإرشادات أولويّة الصوت، إذا استمر التطبيق في التشغيل بصوت عالٍ حتى بعد فقدان أولويّة الصوت على جهاز يعمل بنظام التشغيل Android 11 (مستوى واجهة برمجة التطبيقات 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 من واجهة برمجة التطبيقات).
الإصدار 7.1 من نظام التشغيل Android (المستوى 25 لواجهة برمجة التطبيقات) والإصدارات الأقدم
لا يدير النظام التركيز على الصوت، وتدير التطبيقات التركيز على الصوت باستخدام requestAudioFocus() و abandonAudioFocus().

التحكّم في الصوت في Android 12 والإصدارات الأحدث

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

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

هناك طريقتان لضبط المستمع: إحداهما باستخدام وسيطة معالِج والأخرى بدونها. المعالج هو سلسلة المحادثات التي يعمل عليها المستمع. في حال عدم تحديد معالج، يتم استخدام المعالج المرتبط بـ 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() لـ Callback، onSkipToNext()، وما إلى ذلك. يجب أيضًا استدعاء هذه الطريقة في رد الاتصال onDestroy() لخدمتك عند تنظيف الموارد التي تستخدمها خدمتك.