تشغيل الوسائط في الخلفية

يمكنك تشغيل الوسائط في الخلفية حتى عندما لا يكون تطبيقك معروضًا على الشاشة، مثلاً عندما يتفاعل المستخدم مع تطبيقات أخرى.

لإجراء ذلك، يمكنك تضمين MediaPlayer في خدمة MediaBrowserServiceCompat وجعله يتفاعل مع MediaBrowserCompat في نشاط آخر.

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

توضّح هذه الصفحة تعليمات خاصة لإدارة MediaPlayer عند تنفيذه داخل خدمة.

التشغيل بشكل غير متزامن

تمامًا مثل Activity، يتم تنفيذ جميع الأعمال في Service في سلسلة محادثات واحدة تلقائيًا. في الواقع، عند تشغيل نشاط وخدمة من التطبيق نفسه، يستخدمان الخيط نفسه (المعروف باسم "الخيط الرئيسي") تلقائيًا.

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

على سبيل المثال، عند استخدام MediaPlayer من سلسلة المحادثات الرئيسية، عليك إجراء ما يلي:

مثلاً:

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

التعامل مع الأخطاء غير المتزامنة

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

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

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

استخدام عمليات قفل التنشيط

عند تشغيل الموسيقى أو بثها في الخلفية، يجب استخدام عمليات قفل التنشيط لمنع النظام من التدخل في عملية التشغيل، على سبيل المثال، من خلال وضع الجهاز في وضع السكون.

قفل التنشيط هو إشارة للنظام بأنّ تطبيقك يستخدم ميزات يجب أن تظل متاحة حتى عندما يكون الهاتف في وضع السكون.

لضمان استمرار عمل وحدة المعالجة المركزية أثناء تشغيل MediaPlayer، استخدِم طريقة setWakeMode() عند بدء استخدام MediaPlayer. يحافظ الرمز MediaPlayer على القفل المحدّد أثناء التشغيل ويزيل القفل عند الإيقاف المؤقت أو الإيقاف:

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

ومع ذلك، لا يضمن قفل التنشيط الذي تم الحصول عليه في هذا المثال سوى إبقاء وحدة المعالجة المركزية (CPU) في وضع التشغيل. إذا كنت تبثّ الوسائط عبر الشبكة وكنت تستخدم شبكة Wi-Fi، من المحتمل أن تحتاج إلى الحصول على WifiLock أيضًا، ويجب الحصول عليه وإلغاء الحصول عليه يدويًا. لذلك، عند بدء إعداد MediaPlayer باستخدام عنوان URL البعيد، عليك إنشاء ميزة قفل Wi-Fi والحصول عليها.

مثلاً:

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

عند إيقاف الوسائط مؤقتًا أو إيقافها، أو عند عدم الحاجة إلى الشبكة، عليك إزالة القفل:

Kotlin

wifiLock.release()

Java

wifiLock.release();

إجراء عملية تنظيف

كما ذكرنا سابقًا، يمكن أن يستهلك عنصر MediaPlayer مقدارًا كبيرًا من موارد النظام، لذا يجب الاحتفاظ به فقط للفترة التي تحتاج إليها واستخدام release() عند الانتهاء منه. من المهمّ استدعاء طريقة التنظيف هذه صراحةً بدلاً من الاعتماد على ميزة جمع المهملات في النظام لأنّه قد يستغرق بعض الوقت قبل أن يستردّ مجمع المهملات MediaPlayer، لأنّه لا يهتم إلا باحتياجات الذاكرة وليس بنقص الموارد الأخرى ذات الصلة بالوسائط. لذلك، في حال استخدام خدمة، يجب إلغاء طريقة onDestroy() دائمًا للتأكّد من إلغاء MediaPlayer:

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

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

مزيد من المعلومات

‫Jetpack Media3 هو الحلّ المُقترَح لتشغيل الوسائط في تطبيقك. اطّلِع على مزيد من المعلومات حوله.

تتناول هذه الصفحات مواضيع تتعلّق بتسجيل المحتوى الصوتي والفيديو وتخزينه وتشغيله: