Tổng quan về MediaPlayer

Khung đa phương tiện của Android hỗ trợ việc phát nhiều loại nội dung nghe nhìn phổ biến để bạn có thể dễ dàng tích hợp âm thanh, video và hình ảnh vào ứng dụng của mình. Bạn có thể phát âm thanh hoặc video từ các tệp đa phương tiện được lưu trữ trong tài nguyên của ứng dụng (tài nguyên thô), từ các tệp độc lập trong hệ thống tệp hoặc từ một luồng dữ liệu đến qua kết nối mạng, tất cả đều sử dụng các API MediaPlayer.

Tài liệu này cho bạn biết cách sử dụng MediaPlayer để viết ứng dụng phát nội dung nghe nhìn tương tác với người dùng và hệ thống nhằm đạt được hiệu suất tốt và trải nghiệm người dùng dễ chịu. Ngoài ra, bạn nên sử dụng ExoPlayer, một thư viện nguồn mở có thể tuỳ chỉnh nhằm hỗ trợ các tính năng hiệu suất cao không có trong MediaPlayer

Lưu ý: Bạn chỉ có thể phát lại dữ liệu âm thanh cho thiết bị đầu ra chuẩn. Hiện tại, đó là loa thiết bị di động hoặc tai nghe Bluetooth. Bạn không thể phát tệp âm thanh trong âm thanh cuộc trò chuyện khi đang gọi điện.

Thông tin cơ bản

Các lớp sau đây được dùng để phát âm thanh và video trong khung Android:

MediaPlayer
Lớp này là API chính để phát âm thanh và video.
AudioManager
Lớp học này quản lý các nguồn âm thanh và đầu ra âm thanh trên thiết bị.

Nội dung khai báo trong tệp kê khai

Trước khi bắt đầu phát triển ứng dụng bằng MediaPlayer, hãy đảm bảo tệp kê khai có nội dung khai báo thích hợp để cho phép sử dụng các tính năng liên quan.

  • Quyền truy cập Internet – Nếu bạn đang sử dụng MediaPlayer để phát trực tuyến nội dung dựa trên mạng, thì ứng dụng của bạn phải yêu cầu quyền truy cập mạng.
    <uses-permission android:name="android.permission.INTERNET" />
    
  • Quyền khoá chế độ thức – Nếu ứng dụng trình phát của bạn cần giữ cho màn hình giảm độ sáng hoặc bộ xử lý không chuyển sang chế độ ngủ, hay cần sử dụng các phương thức MediaPlayer.setScreenOnWhilePlaying() hoặc MediaPlayer.setWakeMode(), thì bạn phải yêu cầu quyền này.
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    

Sử dụng MediaPlayer

Một trong những thành phần quan trọng nhất của khung nội dung đa phương tiện là lớp MediaPlayer. Một đối tượng của lớp này có thể tìm nạp, giải mã và phát cả âm thanh lẫn video mà không cần thiết lập quá nhiều. Công cụ này hỗ trợ nhiều nguồn nội dung nghe nhìn chẳng hạn như:

  • Tài nguyên cục bộ
  • URI nội bộ, chẳng hạn như URI mà bạn có thể lấy từ Trình phân giải nội dung
  • URL bên ngoài (phát trực tuyến)

Để biết danh sách các định dạng nội dung nghe nhìn mà Android hỗ trợ, hãy xem trang Định dạng nội dung nghe nhìn được hỗ trợ.

Dưới đây là ví dụ về cách phát âm thanh có sẵn dưới dạng tài nguyên thô cục bộ (được lưu trong thư mục res/raw/ của ứng dụng):

Kotlin

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

Java

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

Trong trường hợp này, tài nguyên "thô" là một tệp mà hệ thống không cố gắng phân tích cú pháp theo bất kỳ cách cụ thể nào. Tuy nhiên, nội dung của tài nguyên này không được là âm thanh thô. Đó phải là tệp nội dung nghe nhìn được mã hoá và định dạng đúng cách ở một trong các định dạng được hỗ trợ.

Và đây là cách bạn có thể phát từ một URI có sẵn cục bộ trong hệ thống (ví dụ: bạn lấy được thông qua Trình phân giải nội dung):

Kotlin

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

Java

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

Việc phát từ một URL từ xa qua truyền trực tuyến HTTP sẽ có dạng như sau:

Kotlin

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

Java

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

Lưu ý: Nếu bạn đang truyền URL để truyền trực tuyến tệp nội dung nghe nhìn trực tuyến, thì tệp đó phải có khả năng tải xuống tiến trình.

Thận trọng: Bạn phải nắm bắt hoặc truyền IllegalArgumentExceptionIOException khi sử dụng setDataSource(), vì tệp mà bạn đang tham chiếu có thể không tồn tại.

Chuẩn bị không đồng bộ

Việc sử dụng MediaPlayer có thể khá đơn giản về nguyên tắc. Tuy nhiên, bạn cần lưu ý rằng cần thực hiện thêm một vài bước nữa để tích hợp đúng cách với một ứng dụng Android thông thường. Ví dụ: lệnh gọi đến prepare() có thể mất nhiều thời gian để thực thi, vì lệnh gọi này có thể liên quan đến việc tìm nạp và giải mã dữ liệu nội dung nghe nhìn. Vì vậy, như trong trường hợp bất kỳ phương thức nào có thể mất nhiều thời gian để thực thi, bạn không nên gọi phương thức đó từ luồng giao diện người dùng của ứng dụng. Việc đó sẽ khiến giao diện người dùng bị treo cho đến khi phương thức này trả về. Đây là trải nghiệm người dùng rất kém và có thể gây ra lỗi ANR (Ứng dụng không phản hồi). Ngay cả khi bạn muốn tài nguyên tải nhanh, hãy nhớ rằng bất kỳ điều gì mất hơn 1/10 giây để phản hồi trong giao diện người dùng đều gây ra sự tạm dừng đáng kể và khiến người dùng thấy rằng ứng dụng của bạn chậm.

Để tránh bị treo luồng giao diện người dùng, hãy tạo một luồng khác để chuẩn bị MediaPlayer và thông báo cho luồng chính khi hoàn tất. Tuy nhiên, mặc dù bạn có thể tự viết logic phân luồng, nhưng mẫu này rất phổ biến khi sử dụng MediaPlayer đến mức khung cung cấp một cách thuận tiện để hoàn thành tác vụ này bằng cách sử dụng phương thức prepareAsync(). Phương thức này bắt đầu chuẩn bị nội dung nghe nhìn ở chế độ nền và trả về ngay lập tức. Khi nội dung nghe nhìn chuẩn bị xong, phương thức onPrepared() của MediaPlayer.OnPreparedListener, được định cấu hình thông qua setOnPreparedListener() sẽ được gọi.

Quản lý trạng thái

Một khía cạnh khác của MediaPlayer mà bạn nên lưu ý là tính năng này dựa trên trạng thái. Tức là MediaPlayer có một trạng thái nội bộ mà bạn phải luôn chú ý khi viết mã, vì một số thao tác chỉ có hiệu lực khi người chơi ở các trạng thái cụ thể. Nếu bạn thực hiện một thao tác khi ở trạng thái không chính xác, thì hệ thống có thể gửi một trường hợp ngoại lệ hoặc gây ra các hành vi không mong muốn khác.

Tài liệu trong lớp MediaPlayer hiển thị một sơ đồ trạng thái hoàn chỉnh, giúp làm rõ các phương thức di chuyển MediaPlayer từ trạng thái này sang trạng thái khác. Ví dụ: khi bạn tạo một MediaPlayer mới, nó đang ở trạng thái Không hoạt động. Tại thời điểm đó, bạn nên khởi chạy bằng cách gọi setDataSource(), đưa nó về trạng thái Initialized (Đã khởi động). Sau đó, bạn phải chuẩn bị bằng phương thức prepare() hoặc prepareAsync(). Khi MediaPlayer chuẩn bị xong, thiết bị sẽ chuyển sang trạng thái Đã chuẩn bị, tức là bạn có thể gọi start() để thiết bị phát nội dung nghe nhìn. Tại thời điểm đó, như sơ đồ minh hoạ, bạn có thể di chuyển giữa các trạng thái Started (Bắt đầu), Paused (Đã tạm dừng) và PlaybackFinish (Phát lại đã hoàn tất) bằng cách gọi các phương thức như start(), pause()seekTo(), cùng với các trạng thái khác. Tuy nhiên, khi gọi stop(), hãy lưu ý rằng bạn không thể gọi lại start() cho đến khi bạn chuẩn bị lại MediaPlayer.

Luôn lưu ý đến sơ đồ trạng thái khi viết mã tương tác với đối tượng MediaPlayer, vì việc gọi phương thức của đối tượng từ trạng thái không chính xác là nguyên nhân phổ biến gây ra lỗi.

Phát hành MediaPlayer

MediaPlayer có thể tiêu thụ các tài nguyên hệ thống có giá trị. Do đó, bạn phải luôn thực hiện thêm các biện pháp phòng ngừa để đảm bảo bạn không treo một thực thể MediaPlayer lâu hơn cần thiết. Sau khi thực hiện xong, bạn phải luôn gọi release() để đảm bảo mọi tài nguyên hệ thống được phân bổ cho thực thể đó được giải phóng đúng cách. Ví dụ: nếu bạn đang sử dụng MediaPlayer và hoạt động của bạn nhận được lệnh gọi đến onStop(), thì bạn phải giải phóng MediaPlayer vì việc giữ lại mã này trong khi hoạt động không tương tác với người dùng (trừ phi bạn đang phát nội dung đa phương tiện trong nền như sẽ được thảo luận trong phần tiếp theo). Dĩ nhiên, khi hoạt động của bạn được tiếp tục hoặc khởi động lại, bạn cần tạo MediaPlayer mới và chuẩn bị lại nó trước khi tiếp tục phát.

Đây là cách bạn nên huỷ bỏ rồi vô hiệu hoá MediaPlayer:

Kotlin

mediaPlayer?.release()
mediaPlayer = null

Java

mediaPlayer.release();
mediaPlayer = null;

Ví dụ: hãy cân nhắc các sự cố có thể xảy ra nếu bạn quên phát hành MediaPlayer khi hoạt động bị dừng, nhưng tạo một sự cố mới khi hoạt động bắt đầu lại. Như bạn có thể biết, khi người dùng thay đổi hướng màn hình (hoặc thay đổi cấu hình thiết bị theo cách khác), hệ thống sẽ xử lý việc đó bằng cách khởi động lại hoạt động (theo mặc định) để bạn có thể nhanh chóng sử dụng tất cả tài nguyên hệ thống khi người dùng xoay thiết bị qua lại giữa chế độ dọc và ngang, vì ở mỗi hướng thay đổi, bạn sẽ tạo một MediaPlayer mới mà bạn không bao giờ phát hành. (Để biết thêm thông tin về việc khởi động lại thời gian chạy, hãy xem phần Xử lý các thay đổi về thời gian chạy.)

Có thể bạn sẽ muốn biết điều gì sẽ xảy ra nếu muốn tiếp tục phát "nội dung nghe nhìn ở chế độ nền" ngay cả khi người dùng rời khỏi hoạt động của bạn, giống như cách hoạt động của ứng dụng Music tích hợp sẵn. Trong trường hợp này, bạn cần một MediaPlayer do một Dịch vụ kiểm soát, như sẽ thảo luận trong phần tiếp theo

Sử dụng MediaPlayer trong dịch vụ

Nếu muốn nội dung nghe nhìn phát ở chế độ nền ngay cả khi ứng dụng không xuất hiện trên màn hình (tức là bạn muốn nội dung đó tiếp tục phát trong khi người dùng đang tương tác với các ứng dụng khác), thì bạn phải khởi động Dịch vụ và kiểm soát thực thể MediaPlayer từ đó. Bạn cần nhúng MediaPlayer vào dịch vụ MediaBrowserServiceCompat và yêu cầu phương tiện này tương tác với MediaBrowserCompat trong một hoạt động khác.

Bạn nên cẩn thận với việc thiết lập máy khách/máy chủ này. Có những kỳ vọng về cách người chơi đang chạy trong dịch vụ nền tương tác với phần còn lại của hệ thống. Nếu ứng dụng không đáp ứng được những kỳ vọng đó, thì người dùng có thể có trải nghiệm không tốt. Hãy đọc bài viết Xây dựng ứng dụng âm thanh để biết toàn bộ thông tin chi tiết.

Phần này mô tả các hướng dẫn đặc biệt để quản lý MediaPlayer khi được triển khai bên trong một dịch vụ.

Chạy không đồng bộ

Trước hết, giống như Activity, theo mặc định, tất cả công việc trong Service đều được thực hiện trong một luồng duy nhất. Trên thực tế, nếu bạn đang chạy một hoạt động và một dịch vụ từ cùng một ứng dụng, thì theo mặc định, chúng sẽ sử dụng cùng một luồng ("luồng chính"). Do đó, các dịch vụ cần xử lý ý định đến một cách nhanh chóng và không bao giờ thực hiện các phép tính kéo dài khi phản hồi các ý định đó. Nếu dự kiến có bất kỳ lệnh gọi chặn hoặc công việc nặng nhọc nào, bạn phải thực hiện các tác vụ đó một cách không đồng bộ: từ một luồng khác mà bạn tự triển khai, hoặc sử dụng nhiều phương tiện của khung để xử lý không đồng bộ.

Ví dụ: khi sử dụng MediaPlayer từ luồng chính, bạn nên gọi prepareAsync() thay vì prepare() và triển khai một MediaPlayer.OnPreparedListener để nhận thông báo khi quá trình chuẩn bị hoàn tất và bạn có thể bắt đầu chơi. Ví dụ:

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

Xử lý lỗi không đồng bộ

Trên các hoạt động đồng bộ, lỗi thường sẽ được báo hiệu với một ngoại lệ hoặc mã lỗi, nhưng bất cứ khi nào sử dụng tài nguyên không đồng bộ, bạn phải đảm bảo ứng dụng của mình được thông báo về lỗi một cách thích hợp. Trong trường hợp MediaPlayer, bạn có thể thực hiện việc này bằng cách triển khai MediaPlayer.OnErrorListener và thiết lập thuộc tính này trong thực thể 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!
    }
}

Điều quan trọng cần nhớ là khi xảy ra lỗi, MediaPlayer sẽ chuyển sang trạng thái Error (Lỗi) (xem tài liệu về lớp MediaPlayer để biết sơ đồ trạng thái đầy đủ) và bạn phải đặt lại mã này thì mới có thể sử dụng lại.

Sử dụng tính năng khóa chế độ thức

Khi thiết kế các ứng dụng phát nội dung nghe nhìn ở chế độ nền, thiết bị có thể chuyển sang chế độ ngủ trong khi dịch vụ đang chạy. Vì hệ thống Android cố gắng tiết kiệm pin trong khi thiết bị ở chế độ ngủ, nên hệ thống sẽ cố gắng tắt mọi tính năng không cần thiết của điện thoại, bao gồm cả CPU và phần cứng Wi-Fi. Tuy nhiên, nếu dịch vụ của bạn đang phát hoặc phát nhạc trực tuyến, thì bạn nên ngăn hệ thống can thiệp vào việc phát nhạc.

Để đảm bảo dịch vụ của bạn tiếp tục chạy trong các điều kiện đó, bạn phải sử dụng "khoá chế độ thức". Khoá chế độ thức là một cách báo hiệu cho hệ thống rằng ứng dụng của bạn đang dùng một tính năng nào đó ngay cả khi điện thoại ở trạng thái rảnh.

Lưu ý: Bạn nên luôn sử dụng khoá chế độ thức một cách hạn chế và chỉ giữ khoá chế độ này trong thời gian thực sự cần thiết, vì loại khoá này làm giảm đáng kể thời lượng pin của thiết bị.

Để đảm bảo rằng CPU vẫn tiếp tục chạy trong khi MediaPlayer đang phát, hãy gọi phương thức setWakeMode() khi khởi chạy MediaPlayer. Sau khi bạn làm vậy, MediaPlayer sẽ giữ khoá đã chỉ định trong khi phát và mở khoá khi tạm dừng hoặc dừng:

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);

Tuy nhiên, khoá chế độ thức thu được trong ví dụ này chỉ đảm bảo rằng CPU vẫn hoạt động. Nếu đang phát trực tuyến nội dung nghe nhìn qua mạng và đang sử dụng Wi-Fi, thì bạn cũng nên giữ lại WifiLock. Bạn phải có và phát hành loại này theo cách thủ công. Vì vậy, khi bắt đầu chuẩn bị MediaPlayer bằng URL từ xa, bạn nên tạo và có được khoá Wi-Fi. Ví dụ:

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

Khi tạm dừng hoặc dừng nội dung đa phương tiện hay khi không cần mạng nữa, bạn nên nhả khoá:

Kotlin

wifiLock.release()

Java

wifiLock.release();

Đang tiến hành dọn dẹp

Như đã đề cập trước đó, đối tượng MediaPlayer có thể tiêu tốn một lượng tài nguyên hệ thống đáng kể. Vì vậy, bạn chỉ nên giữ đối tượng này trong thời gian cần thiết và gọi release() sau khi hoàn tất. Bạn cần phải gọi phương thức dọn dẹp này một cách rõ ràng thay vì dựa vào tính năng thu thập rác hệ thống, vì có thể mất một khoảng thời gian trước khi trình thu gom rác thu hồi MediaPlayer, vì phương thức này chỉ phù hợp với nhu cầu của bộ nhớ và không thiếu các tài nguyên khác liên quan đến nội dung nghe nhìn. Vì vậy, trong trường hợp đang sử dụng một dịch vụ, bạn phải luôn ghi đè phương thức onDestroy() để đảm bảo bạn đang phát hành 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();
   }
}

Bạn cũng nên tìm các cơ hội khác để phát hành MediaPlayer, ngoài việc phát hành khi bị tắt. Ví dụ: nếu muốn không thể phát nội dung nghe nhìn trong một khoảng thời gian dài (chẳng hạn như sau khi mất quyền phát âm thanh), bạn nên huỷ bỏ MediaPlayer hiện có rồi tạo lại sau. Mặt khác, nếu chỉ muốn dừng phát trong một thời gian rất ngắn, thì bạn nên giữ lại MediaPlayer để tránh chi phí khi tạo và chuẩn bị lại.

Quản lý quyền kỹ thuật số (DRM)

Kể từ Android 8.0 (API cấp 26), MediaPlayer sẽ có các API hỗ trợ phát nội dung được bảo vệ bằng DRM. Các API này tương tự như API cấp thấp do MediaDrm cung cấp, nhưng hoạt động ở cấp cao hơn và không hiển thị các đối tượng trình trích xuất, drm và Crypto cơ bản.

Mặc dù MediaPlayer DRM không cung cấp chức năng đầy đủ của MediaDrm, nhưng API này hỗ trợ các trường hợp sử dụng phổ biến nhất. Phương thức triển khai hiện tại có thể xử lý các loại nội dung sau:

  • Tệp nội dung nghe nhìn cục bộ được bảo vệ bằng Widevine
  • Tệp nội dung nghe nhìn phát trực tuyến/từ xa được bảo vệ bằng Widevine

Đoạn mã sau đây minh hoạ cách sử dụng các phương thức DRM MediaPlayer mới trong một quy trình triển khai đồng bộ đơn giản.

Để quản lý nội dung nghe nhìn do DRM kiểm soát, bạn cần đưa các phương thức mới vào cùng với luồng lệnh gọi MediaPlayer thông thường, như thể hiện dưới đây:

Kotlin

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

Java

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

Bắt đầu bằng cách khởi chạy đối tượng MediaPlayer và thiết lập nguồn của đối tượng đó bằng setDataSource() như bình thường. Sau đó, để sử dụng DRM, hãy thực hiện các bước sau:

  1. Nếu bạn muốn ứng dụng của mình thực hiện cấu hình tuỳ chỉnh, hãy xác định giao diện OnDrmConfigHelper và đính kèm giao diện đó vào trình phát bằng setOnDrmConfigHelper().
  2. Gọi prepare().
  3. Gọi getDrmInfo(). Nếu nguồn có nội dung DRM, phương thức này sẽ trả về một giá trị MediaPlayer.DrmInfo không rỗng.

Nếu MediaPlayer.DrmInfo tồn tại:

  1. Kiểm tra bản đồ các UUID hiện có và chọn một mã.
  2. Chuẩn bị cấu hình DRM cho nguồn hiện tại bằng cách gọi prepareDrm().
    • Nếu bạn đã tạo và đăng ký lệnh gọi lại OnDrmConfigHelper, thì lệnh gọi lại này sẽ được gọi trong khi prepareDrm() đang thực thi. Điều này cho phép bạn thực hiện cấu hình tuỳ chỉnh của các thuộc tính DRM trước khi mở phiên DRM. Lệnh gọi lại được gọi đồng bộ trong luồng có tên prepareDrm(). Để truy cập vào các thuộc tính DRM, hãy gọi getDrmPropertyString()setDrmPropertyString(). Tránh thực hiện các thao tác kéo dài.
    • Nếu thiết bị chưa được cấp phép, prepareDrm() cũng sẽ truy cập vào máy chủ cấp phép để cấp phép cho thiết bị này. Quá trình này có thể mất một khoảng thời gian khác nhau, tuỳ thuộc vào khả năng kết nối mạng.
  3. Để lấy một mảng byte yêu cầu khoá mờ gửi tới máy chủ cấp phép, hãy gọi getKeyRequest().
  4. Để thông báo cho công cụ DRM về phản hồi chính nhận được từ máy chủ cấp phép, hãy gọi provideKeyResponse(). Kết quả phụ thuộc vào loại yêu cầu khoá:
    • Nếu phản hồi là dành cho một yêu cầu khoá ngoại tuyến, thì kết quả sẽ là giá trị nhận dạng tập hợp khoá. Bạn có thể sử dụng giá trị nhận dạng tập hợp khoá này với restoreKeys() để khôi phục các khoá vào một phiên mới.
    • Nếu phản hồi là dành cho yêu cầu phát trực tuyến hoặc yêu cầu phát hành, thì kết quả sẽ là rỗng.

Chạy prepareDrm() không đồng bộ

Theo mặc định, prepareDrm() sẽ chạy một cách đồng bộ, chặn cho đến khi quá trình chuẩn bị hoàn tất. Tuy nhiên, việc chuẩn bị DRM đầu tiên trên thiết bị mới cũng có thể cần phải cấp phép (do prepareDrm() xử lý nội bộ) và có thể mất một chút thời gian để hoàn tất do hoạt động mạng có liên quan. Bạn có thể tránh bị chặn trên prepareDrm() bằng cách xác định và đặt MediaPlayer.OnDrmPreparedListener.

Khi bạn thiết lập OnDrmPreparedListener, prepareDrm() sẽ thực hiện việc cấp phép (nếu cần) và chuẩn bị trong nền. Khi quá trình cấp phép và chuẩn bị hoàn tất, trình nghe sẽ được gọi. Bạn không nên đưa ra bất kỳ giả định nào về trình tự gọi hoặc luồng mà trình nghe chạy trong đó (trừ phi trình nghe được đăng ký với một luồng của trình xử lý). Trình nghe này có thể được gọi trước khi hoặc sau khi prepareDrm() trả về.

Thiết lập DRM không đồng bộ

Bạn có thể khởi chạy DRM một cách không đồng bộ bằng cách tạo và đăng ký MediaPlayer.OnDrmInfoListener để chuẩn bị DRM và MediaPlayer.OnDrmPreparedListener để khởi động trình phát. Các thư viện này hoạt động cùng với prepareAsync(), như thể hiện dưới đây:

Kotlin

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

Java

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

Xử lý nội dung nghe nhìn đã mã hoá

Kể từ Android 8.0 (API cấp 26) MediaPlayer cũng có thể giải mã Giao thức mã hoá chung (CENC) và nội dung nghe nhìn được mã hoá ở cấp mẫu HLS (METHOD=SAMPLE-AES) cho các loại luồng cơ bản H.264 và AAC. Trước đây, phương tiện được mã hoá phân đoạn đầy đủ (METHOD=AES-128) đã được hỗ trợ.

Truy xuất nội dung nghe nhìn từ ContentResolver

Một tính năng khác có thể hữu ích trong ứng dụng trình phát nội dung đa phương tiện là khả năng truy xuất nhạc mà người dùng có trên thiết bị. Bạn có thể thực hiện việc đó bằng cách truy vấn ContentResolver cho phương tiện bên ngoài:

Kotlin

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

Java

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

Để sử dụng với MediaPlayer, bạn có thể làm như sau:

Kotlin

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...

Java

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...

Tìm hiểu thêm

Những trang này đề cập đến những chủ đề liên quan đến việc ghi lại, lưu trữ và phát âm thanh cũng như video.