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

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

لإجراء ذلك، عليك تضمين 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 is playing، عليك استدعاء طريقة setWakeMode() عند تهيئة your 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);

ومع ذلك، يضمن قفل التنشيط الذي تم الحصول عليه في هذا المثال بقاء وحدة المعالجة المركزية نشطة فقط. إذا كنت تبث الوسائط عبر الشبكة وتستخدم شبكة 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 هو الحلّ المقترَح لتشغيل الوسائط في تطبيقك. مزيد من المعلومات

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