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

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

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

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

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

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

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

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

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

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

الإصدار 12 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 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.

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

سلوكيات أولويّة الصوت الحالية

عليك أيضًا أن تكون على دراية بهذه الحالات الأخرى التي تتضمّن تغييرًا في أولويّة الصوت.

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

تم طرح ميزة تجنُّب التداخل مع أصوات أخرى تلقائيًا (خفض مستوى الصوت مؤقتًا في أحد التطبيقات ليتم سماع تطبيق آخر بوضوح) في الإصدار 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 حالة الاستخدام لتطبيقك. ينظر النظام إلى هذه السمات عندما يحصل تطبيق على أولويّة الصوت ويفقدها. تحلّ السمات محل مفهوم نوع مصدر الصوت. في الإصدار 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() وonSkipToNext() وما إلى ذلك من معاودات الاتصال. عليك أيضًا استدعاء هذه الطريقة في معاودة الاتصال onDestroy() لخدمتك عند تنظيف الموارد التي تستخدمها خدمتك.