نمای کلی MediaPlayer

چارچوب چند رسانه ای اندروید شامل پشتیبانی از پخش انواع رسانه های رایج است، به طوری که می توانید به راحتی صدا، تصویر و تصاویر را در برنامه های خود ادغام کنید. می‌توانید صدا یا ویدیو را از فایل‌های رسانه‌ای ذخیره‌شده در منابع برنامه‌تان (منابع خام)، از فایل‌های مستقل در سیستم فایل، یا از جریان داده‌ای که از طریق اتصال شبکه می‌رسد، پخش کنید، همه با استفاده از MediaPlayer API.

این سند به شما نشان می دهد که چگونه از MediaPlayer برای نوشتن یک برنامه پخش رسانه ای استفاده کنید که با کاربر و سیستم در تعامل است تا عملکرد خوب و تجربه کاربری دلپذیری را به دست آورید. از طرف دیگر، ممکن است بخواهید از ExoPlayer استفاده کنید، که یک کتابخانه منبع باز قابل تنظیم است که از ویژگی های با کارایی بالا پشتیبانی می کند که در MediaPlayer موجود نیستند.

توجه: می توانید داده های صوتی را فقط در دستگاه خروجی استاندارد پخش کنید. در حال حاضر، این بلندگوی دستگاه تلفن همراه یا هدست بلوتوث است. در حین تماس نمی توانید فایل های صوتی را در صدای مکالمه پخش کنید.

اصول اولیه

کلاس های زیر برای پخش صدا و ویدئو در فریم ورک اندروید استفاده می شود:

MediaPlayer
این کلاس API اصلی برای پخش صدا و ویدئو است.
AudioManager
این کلاس منابع صوتی و خروجی صدا را روی یک دستگاه مدیریت می کند.

اعلامیه های آشکار

قبل از شروع توسعه برنامه خود با استفاده از MediaPlayer، مطمئن شوید که مانیفست شما دارای اعلان های مناسب برای اجازه استفاده از ویژگی های مرتبط است.

  • مجوز اینترنت - اگر از MediaPlayer برای پخش محتوای مبتنی بر شبکه استفاده می کنید، برنامه شما باید دسترسی به شبکه را درخواست کند.
    <uses-permission android:name="android.permission.INTERNET" />
    
  • مجوز Wake Lock - اگر برنامه پخش شما نیاز دارد که صفحه نمایش را کم رنگ نکند یا پردازنده در حالت خواب نباشد، یا از روش های MediaPlayer.setScreenOnWhilePlaying() یا MediaPlayer.setWakeMode() استفاده کند، باید این مجوز را درخواست کنید.
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    

با استفاده از MediaPlayer

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

  • منابع محلی
  • URI های داخلی، مانند نمونه ای که ممکن است از یک Content Resolver دریافت کنید
  • URL های خارجی (جریان سازی)

برای فهرستی از فرمت‌های رسانه‌ای که اندروید پشتیبانی می‌کند، به صفحه فرمت‌های رسانه پشتیبانی شده مراجعه کنید.

در اینجا مثالی از نحوه پخش صدایی که به عنوان یک منبع خام محلی در دسترس است (ذخیره شده در دایرکتوری res/raw/ برنامه شما) آورده شده است:

کاتلین

var mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1)
mediaPlayer.start() // no need to call prepare(); create() does that for you

جاوا

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

در این مورد، یک منبع "خام" فایلی است که سیستم سعی نمی کند به روش خاصی آن را تجزیه کند. با این حال، محتوای این منبع نباید صوتی خام باشد. باید یک فایل رسانه ای با کدگذاری و فرمت مناسب در یکی از فرمت های پشتیبانی شده باشد.

و در اینجا نحوه بازی از یک URI موجود در سیستم به صورت محلی (که به عنوان مثال از طریق یک Content Resolver به دست آورده اید):

کاتلین

val myUri: Uri = .... // initialize Uri here
val mediaPlayer = MediaPlayer().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(applicationContext, myUri)
    prepare()
    start()
}

جاوا

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
    new AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

پخش از یک URL راه دور از طریق جریان HTTP به شکل زیر است:

کاتلین

val url = "http://........" // your URL here
val mediaPlayer = MediaPlayer().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(url)
    prepare() // might take long! (for buffering, etc)
    start()
}

جاوا

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
    new AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

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

احتیاط: هنگام استفاده از setDataSource() باید IllegalArgumentException و IOException بگیرید یا پاس کنید، زیرا ممکن است فایلی که به آن ارجاع می دهید وجود نداشته باشد.

آماده سازی ناهمزمان

استفاده از MediaPlayer در اصل می تواند ساده باشد. با این حال، مهم است که در نظر داشته باشید که چند چیز دیگر برای ادغام صحیح آن با یک برنامه معمولی اندروید ضروری است. برای مثال، اجرای فراخوانی prepare() می‌تواند زمان زیادی طول بکشد، زیرا ممکن است شامل واکشی و رمزگشایی داده‌های رسانه باشد. بنابراین، مانند هر روشی که اجرای آن ممکن است طولانی باشد، هرگز نباید آن را از رشته رابط کاربری برنامه خود فراخوانی کنید . انجام این کار باعث می شود UI تا زمانی که متد برگردد هنگ کند، که تجربه کاربری بسیار بدی است و می تواند باعث خطای ANR (Application Not Responding) شود. حتی اگر انتظار دارید منبع شما به سرعت بارگذاری شود، به یاد داشته باشید که هر چیزی که بیش از یک دهم ثانیه طول می کشد تا در UI پاسخ دهد باعث توقف قابل توجهی می شود و به کاربر این تصور را می دهد که برنامه شما کند است.

برای جلوگیری از آویزان شدن رشته رابط کاربری خود، یک رشته دیگر ایجاد کنید تا MediaPlayer آماده شود و پس از اتمام به موضوع اصلی اطلاع دهید. با این حال، در حالی که می‌توانید منطق threading را خودتان بنویسید، این الگو در هنگام استفاده از MediaPlayer آنقدر رایج است که فریم‌ورک با استفاده از متد prepareAsync() راه مناسبی برای انجام این کار ارائه می‌کند. این روش شروع به آماده سازی رسانه در پس زمینه می کند و بلافاصله برمی گردد. هنگامی که آماده سازی رسانه تمام شد، متد onPrepared() MediaPlayer.OnPreparedListener که از طریق setOnPreparedListener() پیکربندی شده است فراخوانی می شود.

دولت مدیریت

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

اسناد موجود در کلاس MediaPlayer یک نمودار وضعیت کامل را نشان می دهد که مشخص می کند کدام روش ها MediaPlayer از یک حالت به حالت دیگر منتقل می کنند. به عنوان مثال، هنگامی که یک MediaPlayer جدید ایجاد می کنید، در حالت Idle است. در آن مرحله، باید آن را با فراخوانی setDataSource() مقداردهی کنید و آن را به حالت Initialized برسانید. پس از آن، باید آن را با استفاده از متد prepare() یا prepareAsync() آماده کنید. وقتی MediaPlayer آماده‌سازی شد، وارد حالت Prepared می‌شود، یعنی می‌توانید start() را فراخوانی کنید تا رسانه را پخش کند. در آن نقطه، همانطور که نمودار نشان می‌دهد، می‌توانید با فراخوانی متدهایی مانند start() ، pause() و seekTo() و سایر حالت‌های Started ، Paused و PlaybackCompleted حرکت کنید. با این حال، هنگامی که stop() را فرا می خوانید، متوجه شوید که تا زمانی که MediaPlayer دوباره آماده نکنید، نمی توانید دوباره start() فراخوانی کنید.

همیشه هنگام نوشتن کدی که با یک شی MediaPlayer تعامل دارد ، نمودار حالت را در نظر داشته باشید، زیرا فراخوانی متدهای آن از حالت اشتباه یکی از دلایل رایج اشکال است.

انتشار MediaPlayer

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

در اینجا نحوه انتشار و سپس لغو MediaPlayer آمده است:

کاتلین

mediaPlayer?.release()
mediaPlayer = null

جاوا

mediaPlayer.release();
mediaPlayer = null;

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

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

استفاده از MediaPlayer در یک سرویس

اگر می‌خواهید رسانه‌تان حتی زمانی که برنامه‌تان روی صفحه نیست، در پس‌زمینه پخش شود - یعنی می‌خواهید در حالی که کاربر با برنامه‌های دیگر در حال تعامل است به پخش ادامه دهد - باید یک Service را راه‌اندازی کنید و نمونه MediaPlayer را از آنجا کنترل کنید. شما باید MediaPlayer را در یک سرویس MediaBrowserServiceCompat جاسازی کنید و آن را در یک فعالیت دیگر با MediaBrowserCompat تعامل کنید.

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

این بخش دستورالعمل های ویژه ای را برای مدیریت MediaPlayer هنگامی که در داخل یک سرویس پیاده سازی می شود، توضیح می دهد.

در حال اجرا به صورت ناهمزمان

اول از همه، مانند یک Activity ، تمام کارها در یک Service به طور پیش‌فرض در یک رشته انجام می‌شود—در واقع، اگر یک اکتیویتی و یک سرویس را از یک برنامه اجرا می‌کنید، آن‌ها از همان رشته استفاده می‌کنند («رشته اصلی» ") به طور پیش فرض. بنابراین، سرویس ها باید مقاصد ورودی را به سرعت پردازش کنند و هرگز محاسبات طولانی را هنگام پاسخ دادن به آنها انجام ندهند. اگر کار سنگین یا مسدود کردن تماس‌ها انتظار می‌رود، باید آن وظایف را به‌صورت ناهمزمان انجام دهید: یا از رشته دیگری که خودتان پیاده‌سازی می‌کنید، یا با استفاده از امکانات فراوان چارچوب برای پردازش ناهمزمان.

به عنوان مثال، هنگام استفاده از MediaPlayer از رشته اصلی خود، باید به‌جای آماده‌سازی prepareAsync() prepare() را فراخوانی کنید و یک 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 به حالت Error منتقل می شود (به مستندات کلاس MediaPlayer برای نمودار وضعیت کامل مراجعه کنید) و قبل از اینکه بتوانید دوباره از آن استفاده کنید باید آن را بازنشانی کنید.

استفاده از ویک لاک

هنگام طراحی برنامه‌هایی که رسانه‌ها را در پس‌زمینه پخش می‌کنند، ممکن است وقتی سرویس شما در حال اجرا است، دستگاه به حالت خواب برود. از آنجایی که سیستم اندروید سعی می‌کند در زمانی که دستگاه در حالت خواب است، باتری را ذخیره کند، سیستم سعی می‌کند هر یک از ویژگی‌های تلفن را که ضروری نیستند، از جمله CPU و سخت‌افزار WiFi را خاموش کند. با این حال، اگر سرویس شما در حال پخش یا پخش موسیقی است، می‌خواهید از تداخل سیستم در پخش شما جلوگیری کنید.

برای اطمینان از اینکه سرویس شما تحت آن شرایط به کار خود ادامه می دهد، باید از "wake locks" استفاده کنید. Wake lock راهی برای سیگنال دادن به سیستم است که برنامه شما از برخی ویژگی‌ها استفاده می‌کند که حتی اگر تلفن بی‌حرکت است در دسترس بماند.

توجه: همیشه باید از wake lock ها کم استفاده کنید و آنها را فقط تا زمانی که واقعاً لازم است نگه دارید، زیرا عمر باتری دستگاه را به میزان قابل توجهی کاهش می دهند.

برای اطمینان از ادامه کار CPU در حین پخش MediaPlayer ، روش setWakeMode() را هنگام راه اندازی MediaPlayer فراخوانی کنید. پس از انجام این کار، 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);

با این حال، 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 خود را نگه دارید تا از هزینه های اضافی ایجاد و آماده سازی مجدد آن جلوگیری کنید.

مدیریت حقوق دیجیتال (DRM)

با شروع Android 8.0 (سطح API 26)، MediaPlayer شامل APIهایی است که از پخش مواد محافظت شده با DRM پشتیبانی می کند. آنها شبیه به API سطح پایین ارائه شده توسط MediaDrm هستند، اما در سطح بالاتری کار می کنند و استخراج کننده، drm و اشیاء رمزنگاری زیرین را در معرض دید قرار نمی دهند.

اگرچه MediaPlayer DRM API عملکرد کامل MediaDrm را ارائه نمی دهد، اما از رایج ترین موارد استفاده پشتیبانی می کند. پیاده سازی فعلی می تواند انواع محتوای زیر را مدیریت کند:

  • فایل‌های رسانه محلی محافظت شده با Widevine
  • فایل‌های رسانه‌ای از راه دور/جریان‌گذاری محافظت‌شده با Widevine

قطعه کد زیر نحوه استفاده از روش های جدید DRM MediaPlayer را در یک پیاده سازی همزمان ساده نشان می دهد.

برای مدیریت رسانه های کنترل شده با DRM، باید روش های جدید را در کنار جریان معمول تماس های MediaPlayer قرار دهید، همانطور که در زیر نشان داده شده است:

کاتلین

mediaPlayer?.apply {
    setDataSource()
    setOnDrmConfigHelper() // optional, for custom configuration
    prepare()
    drmInfo?.also {
        prepareDrm()
        getKeyRequest()
        provideKeyResponse()
    }

    // MediaPlayer is now ready to use
    start()
    // ...play/pause/resume...
    stop()
    releaseDrm()
}

جاوا

setDataSource();
setOnDrmConfigHelper(); // optional, for custom configuration
prepare();
if (getDrmInfo() != null) {
  prepareDrm();
  getKeyRequest();
  provideKeyResponse();
}

// MediaPlayer is now ready to use
start();
// ...play/pause/resume...
stop();
releaseDrm();

طبق معمول، با مقداردهی اولیه شی MediaPlayer و تنظیم منبع آن با استفاده از setDataSource() شروع کنید. سپس برای استفاده از DRM این مراحل را انجام دهید:

  1. اگر می خواهید برنامه شما پیکربندی سفارشی را انجام دهد، یک رابط OnDrmConfigHelper تعریف کنید و با استفاده از setOnDrmConfigHelper() آن را به پخش کننده متصل کنید.
  2. prepare() .
  3. با getDrmInfo() تماس بگیرید. اگر منبع دارای محتوای DRM باشد، روش یک مقدار MediaPlayer.DrmInfo غیر تهی را برمی‌گرداند.

اگر MediaPlayer.DrmInfo وجود داشته باشد:

  1. نقشه UUID های موجود را بررسی کنید و یکی را انتخاب کنید.
  2. با فراخوانی prepareDrm() پیکربندی DRM را برای منبع فعلی آماده کنید.
    • اگر یک OnDrmConfigHelper را ایجاد و ثبت کرده باشید، در حالی که prepareDrm() در حال اجرا است فراخوانی می شود. این به شما امکان می دهد تا قبل از باز کردن جلسه DRM، تنظیمات سفارشی خصوصیات DRM را انجام دهید. فراخوانی به طور همزمان در رشته ای که prepareDrm() نامیده می شود فراخوانی می شود. برای دسترسی به خصوصیات DRM، getDrmPropertyString() و setDrmPropertyString() را فراخوانی کنید. از انجام عملیات طولانی مدت خودداری کنید.
    • اگر دستگاه هنوز تدارک دیده نشده است، prepareDrm() نیز به سرور تأمین کننده دسترسی پیدا می کند تا دستگاه را تأمین کند. بسته به اتصال شبکه، ممکن است زمان متغیری طول بکشد.
  3. برای دریافت آرایه بایت درخواست کلید غیرشفاف برای ارسال به سرور مجوز، getKeyRequest() را فراخوانی کنید.
  4. برای اطلاع دادن به موتور DRM در مورد پاسخ کلید دریافتی از سرور مجوز، با provideKeyResponse() تماس بگیرید. نتیجه به نوع درخواست کلید بستگی دارد:
    • اگر پاسخ برای درخواست کلید آفلاین باشد، نتیجه یک شناسه مجموعه کلید است. می توانید از این شناسه مجموعه کلید با restoreKeys() برای بازگرداندن کلیدها به یک جلسه جدید استفاده کنید.
    • اگر پاسخ برای درخواست پخش یا انتشار باشد، نتیجه صفر است.

اجرای prepareDrm() به صورت ناهمزمان

به طور پیش فرض، prepareDrm() به صورت همزمان اجرا می شود و تا زمانی که آماده سازی تمام شود مسدود می شود. با این حال، اولین آماده سازی DRM در یک دستگاه جدید نیز ممکن است نیاز به تهیه داشته باشد، که به صورت داخلی توسط prepareDrm() مدیریت می شود، و ممکن است به دلیل عملیات شبکه درگیر مدتی طول بکشد تا پایان یابد. شما می توانید با تعریف و تنظیم MediaPlayer.OnDrmPreparedListener از مسدود کردن در prepareDrm() جلوگیری کنید.

هنگامی که یک OnDrmPreparedListener را تنظیم می کنید، prepareDrm() تهیه و آماده سازی (در صورت نیاز) را در پس زمینه انجام می دهد. هنگامی که تهیه و آماده سازی به پایان رسید، شنونده فراخوانی می شود. شما نباید هیچ گونه فرضی در مورد دنباله فراخوان یا رشته ای که شنونده در آن اجرا می شود داشته باشید (مگر اینکه شنونده با یک رشته کنترل کننده ثبت شده باشد). شنونده را می توان قبل یا بعد از prepareDrm() فراخوانی کرد.

راه اندازی DRM به صورت ناهمزمان

می توانید با ایجاد و ثبت MediaPlayer.OnDrmInfoListener برای آماده سازی DRM و MediaPlayer.OnDrmPreparedListener برای شروع پخش کننده، DRM را به صورت ناهمزمان مقداردهی کنید. همانطور که در زیر نشان داده شده است، آنها با prepareAsync() کار می کنند:

کاتلین

setOnPreparedListener()
setOnDrmInfoListener()
setDataSource()
prepareAsync()
// ...

// If the data source content is protected you receive a call to the onDrmInfo() callback.
override fun onDrmInfo(mediaPlayer: MediaPlayer, drmInfo: MediaPlayer.DrmInfo) {
    mediaPlayer.apply {
        prepareDrm()
        getKeyRequest()
        provideKeyResponse()
    }
}

// When prepareAsync() finishes, you receive a call to the onPrepared() callback.
// If there is a DRM, onDrmInfo() sets it up before executing this callback,
// so you can start the player.
override fun onPrepared(mediaPlayer: MediaPlayer) {
    mediaPlayer.start()
}

جاوا

setOnPreparedListener();
setOnDrmInfoListener();
setDataSource();
prepareAsync();
// ...

// If the data source content is protected you receive a call to the onDrmInfo() callback.
onDrmInfo() {
  prepareDrm();
  getKeyRequest();
  provideKeyResponse();
}

// When prepareAsync() finishes, you receive a call to the onPrepared() callback.
// If there is a DRM, onDrmInfo() sets it up before executing this callback,
// so you can start the player.
onPrepared() {

start();
}

مدیریت رسانه های رمزگذاری شده

شروع با Android 8.0 (سطح API 26) MediaPlayer همچنین می‌تواند Common Encryption Scheme (CENC) و رسانه رمزگذاری‌شده در سطح نمونه HLS (METHOD=SAMPLE-AES) را برای انواع جریان ابتدایی H.264 و AAC رمزگشایی کند. رسانه رمزگذاری شده تمام بخش (METHOD=AES-128) قبلاً پشتیبانی می شد.

بازیابی رسانه از ContentResolver

یکی دیگر از ویژگی هایی که ممکن است در برنامه پخش کننده رسانه مفید باشد، امکان بازیابی موسیقی است که کاربر روی دستگاه دارد. می توانید این کار را با پرس و جو از ContentResolver برای رسانه های خارجی انجام دهید:

کاتلین

val resolver: ContentResolver = contentResolver
val uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val cursor: Cursor? = resolver.query(uri, null, null, null, null)
when {
    cursor == null -> {
        // query failed, handle error.
    }
    !cursor.moveToFirst() -> {
        // no media on the device
    }
    else -> {
        val titleColumn: Int = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE)
        val idColumn: Int = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID)
        do {
            val thisId = cursor.getLong(idColumn)
            val thisTitle = cursor.getString(titleColumn)
            // ...process entry...
        } while (cursor.moveToNext())
    }
}
cursor?.close()

جاوا

ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
    // query failed, handle error.
} else if (!cursor.moveToFirst()) {
    // no media on the device
} else {
    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
    do {
       long thisId = cursor.getLong(idColumn);
       String thisTitle = cursor.getString(titleColumn);
       // ...process entry...
    } while (cursor.moveToNext());
}

برای استفاده از آن با MediaPlayer ، می توانید این کار را انجام دهید:

کاتلین

val id: Long = /* retrieve it from somewhere */
val contentUri: Uri =
    ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id )

mediaPlayer = MediaPlayer().apply {
    setAudioAttributes(
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()
    )
    setDataSource(applicationContext, contentUri)
}

// ...prepare and start...

جاوا

long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(
        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
    new AudioAttributes.Builder()
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .build()
);
mediaPlayer.setDataSource(getApplicationContext(), contentUri);

// ...prepare and start...

بیشتر بدانید

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