پخش رسانه در پس زمینه

شما می‌توانید حتی زمانی که برنامه شما روی صفحه نمایش نیست، مثلاً در حالی که کاربر در حال تعامل با برنامه‌های دیگر است، در پس‌زمینه رسانه پخش کنید.

برای انجام این کار، شما MediaPlayer را در یک سرویس MediaBrowserServiceCompat جاسازی می‌کنید و آن را با یک MediaBrowserCompat در یک activity دیگر تعامل می‌دهید.

در پیاده‌سازی این تنظیمات کلاینت و سرور محتاط باشید. انتظاراتی در مورد نحوه تعامل یک پخش‌کننده در حال اجرا در یک سرویس پس‌زمینه با بقیه سیستم وجود دارد. اگر برنامه شما آن انتظارات را برآورده نکند، ممکن است کاربر تجربه بدی داشته باشد. برای جزئیات بیشتر به بخش «ساخت یک برنامه صوتی» مراجعه کنید.

این صفحه دستورالعمل‌های ویژه‌ای را برای مدیریت MediaPlayer هنگام پیاده‌سازی آن درون یک سرویس شرح می‌دهد.

اجرا به صورت غیرهمزمان

مانند یک Activity ، تمام کارها در یک Service به طور پیش‌فرض در یک thread انجام می‌شود. در واقع، وقتی یک activity و یک service را از یک برنامه اجرا می‌کنید، آنها به طور پیش‌فرض از یک thread ("thread اصلی") استفاده می‌کنند.

سرویس‌ها باید به سرعت ورودی‌های ورودی را پردازش کنند و هرگز هنگام پاسخ دادن به آنها محاسبات طولانی انجام ندهند. شما باید هرگونه کار سنگین یا مسدود کردن تماس‌ها را به صورت غیرهمزمان انجام دهید: یا از طریق نخ دیگری که خودتان پیاده‌سازی می‌کنید، یا با استفاده از امکانات فراوان چارچوب برای پردازش غیرهمزمان.

برای مثال، وقتی از MediaPlayer در نخ اصلی خود استفاده می‌کنید، باید:

  • به جای prepare() تابع prepareAsync() را فراخوانی کنید، و
  • یک MediaPlayer.OnPreparedListener پیاده‌سازی کنید تا وقتی آماده‌سازی کامل شد و شما بتوانید پخش را شروع کنید، مطلع شوید.

برای مثال:

کاتلین

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()
    }
}

جاوا

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 خود تنظیم می‌کنید:

کاتلین

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!
    }
}

جاوا

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 مراجعه کنید.

از قفل‌های بیدارباش استفاده کنید

وقتی در پس‌زمینه موسیقی پخش یا پخش می‌کنید، باید از قفل‌های بیدارباش استفاده کنید تا از تداخل سیستم با پخش موسیقی شما، مثلاً با قرار دادن دستگاه در حالت خواب، جلوگیری شود.

قفل بیدارباش سیگنالی به سیستم است که نشان می‌دهد برنامه شما از ویژگی‌هایی استفاده می‌کند که باید حتی در زمان بیکاری گوشی نیز در دسترس باشند.

برای اطمینان از اینکه CPU در حین پخش MediaPlayer به کار خود ادامه می‌دهد، هنگام مقداردهی اولیه MediaPlayer ، متد setWakeMode() را فراخوانی کنید. MediaPlayer هنگام پخش، قفل مشخص شده را نگه می‌دارد و هنگام مکث یا توقف، قفل را آزاد می‌کند:

کاتلین

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

جاوا

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

با این حال، قفل بیداری که در این مثال به دست آمده است، فقط بیدار ماندن CPU را تضمین می‌کند. اگر در حال پخش رسانه از طریق شبکه هستید و از Wi-Fi استفاده می‌کنید، احتمالاً می‌خواهید WifiLock را نیز داشته باشید که باید آن را به صورت دستی دریافت و آزاد کنید. بنابراین، هنگامی که شروع به آماده‌سازی MediaPlayer با URL از راه دور می‌کنید، باید قفل Wi-Fi را ایجاد و دریافت کنید.

برای مثال:

کاتلین

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

wifiLock.acquire()

جاوا

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

wifiLock.acquire();

وقتی پخش رسانه را متوقف یا متوقف می‌کنید، یا وقتی دیگر به شبکه نیاز ندارید، باید قفل را آزاد کنید:

کاتلین

wifiLock.release()

جاوا

wifiLock.release();

انجام پاکسازی

همانطور که قبلاً ذکر شد، یک شیء MediaPlayer می‌تواند مقدار قابل توجهی از منابع سیستم را مصرف کند، بنابراین باید آن را فقط تا زمانی که نیاز دارید نگه دارید و وقتی کارتان با آن تمام شد release() را فراخوانی کنید. مهم است که این روش پاکسازی را به طور صریح فراخوانی کنید تا به جمع‌آوری زباله سیستم تکیه نکنید، زیرا ممکن است مدتی طول بکشد تا جمع‌آوری زباله MediaPlayer را پس بگیرد، زیرا فقط به نیازهای حافظه حساس است و به کمبود سایر منابع مرتبط با رسانه حساس نیست. بنابراین، در صورتی که از یک سرویس استفاده می‌کنید، همیشه باید روش onDestroy() را لغو کنید تا مطمئن شوید MediaPlayer آزاد می‌کنید:

کاتلین

class MyService : Service() {

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

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

جاوا

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

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

شما همیشه باید به دنبال فرصت‌های دیگری برای انتشار MediaPlayer خود نیز باشید، جدا از انتشار آن هنگام خاموش شدن. به عنوان مثال، اگر انتظار دارید که برای مدت طولانی نتوانید رسانه پخش کنید (مثلاً پس از از دست دادن تمرکز صوتی)، قطعاً باید MediaPlayer موجود خود را منتشر کنید و بعداً دوباره آن را ایجاد کنید. از سوی دیگر، اگر انتظار دارید که پخش را فقط برای مدت بسیار کوتاهی متوقف کنید، احتمالاً باید MediaPlayer خود را نگه دارید تا از سربار ایجاد و آماده‌سازی مجدد آن جلوگیری کنید.

بیشتر بدانید

Jetpack Media3 راهکار پیشنهادی برای پخش رسانه در برنامه شماست. درباره آن بیشتر بخوانید .

این صفحات موضوعات مربوط به ضبط، ذخیره و پخش صدا و تصویر را پوشش می‌دهند: