媒体播放器概述

Android 多媒体框架支持播放多种常见的媒体类型,以便您可以轻松地将音频、视频和图像集成到您的应用程序中。您可以使用 MediaPlayer API 播放存储在应用程序资源(原始资源)中的媒体文件,播放文件系统中的独立文件,或播放通过网络连接接收的数据流。

本文档将向您展示如何使用 MediaPlayer 编写一个媒体播放应用程序,该应用程序与用户和系统交互以获得良好的性能和愉悦的用户体验。或者,您可能希望使用 ExoPlayer,它是一个可自定义的开源库,支持 MediaPlayer 中不可用的高性能功能。

注意:您只能将音频数据回放至标准输出设备。目前,该设备为移动设备扬声器或蓝牙耳机。您无法在通话期间的通话音频中播放声音文件。

基础知识

以下类用于在 Android 框架中播放声音和视频

MediaPlayer
此类是播放声音和视频的主要 API。
AudioManager
此类管理设备上的音频源和音频输出。

清单声明

在开始使用 MediaPlayer 开发应用程序之前,请确保您的清单具有相应的声明以允许使用相关功能。

  • 网络权限 - 如果您正在使用 MediaPlayer 流式传输基于网络的内容,则您的应用程序必须请求网络访问权限。
    <uses-permission android:name="android.permission.INTERNET" />
  • 唤醒锁权限 - 如果您的播放器应用程序需要防止屏幕变暗或处理器进入睡眠状态,或者使用 MediaPlayer.setScreenOnWhilePlaying()MediaPlayer.setWakeMode() 方法,则必须请求此权限。
    <uses-permission android:name="android.permission.WAKE_LOCK" />

使用 MediaPlayer

媒体框架最重要的组件之一是 MediaPlayer 类。此类的一个对象可以获取、解码和播放音频和视频,只需最少的设置。它支持多种不同的媒体源,例如

  • 本地资源
  • 内部 URI,例如您可能从内容解析器获得的 URI
  • 外部 URL(流媒体)

有关 Android 支持的媒体格式列表,请参阅 支持的媒体格式 页面。

以下是如何播放作为本地原始资源(保存在应用程序的 res/raw/ 目录中)提供的音频的示例

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

在这种情况下,“原始”资源是指系统不会以任何特定方式尝试解析的文件。但是,此资源的内容不应是原始音频。它应该是一个以支持的格式之一正确编码和格式化的媒体文件。

以下是如何从系统中本地可用的 URI(例如,您通过内容解析器获得的 URI)播放的示例

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

通过 HTTP 流媒体从远程 URL 播放如下所示

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

注意:如果您传递一个 URL 来流式传输在线媒体文件,则该文件必须能够进行渐进式下载。

警告:使用 setDataSource() 时,您必须捕获或传递 IllegalArgumentExceptionIOException,因为您引用的文件可能不存在。

异步准备

原则上,使用 MediaPlayer 可以很简单。但是,重要的是要记住,正确地将其集成到典型的 Android 应用程序中还需要做一些额外的事情。例如,调用 prepare() 可能需要很长时间才能执行,因为它可能涉及获取和解码媒体数据。因此,与任何可能需要很长时间才能执行的方法一样,您**永远不要从应用程序的 UI 线程调用它**。这样做会导致 UI 挂起,直到方法返回,这是一种非常糟糕的用户体验,并且可能导致 ANR(应用程序无响应)错误。即使您预计您的资源加载速度很快,请记住,任何在 UI 中响应时间超过十分之一秒的内容都会导致明显的暂停,并给用户留下应用程序速度缓慢的印象。

为了避免挂起 UI 线程,可以生成另一个线程来准备 MediaPlayer,并在完成后通知主线程。但是,虽然您可以自己编写线程逻辑,但在使用 MediaPlayer 时,这种模式非常常见,因此框架提供了一种方便的方法来完成此任务,即使用 prepareAsync() 方法。此方法在后台开始准备媒体并立即返回。媒体准备完成后,将调用通过 setOnPreparedListener() 配置的 MediaPlayer.OnPreparedListeneronPrepared() 方法。

管理状态

您在使用 MediaPlayer 时应牢记的另一个方面是它基于状态。也就是说,MediaPlayer 具有一个内部状态,在编写代码时您必须始终注意它,因为某些操作仅在播放器处于特定状态时才有效。如果您在错误的状态下执行操作,系统可能会抛出异常或导致其他不良行为。

MediaPlayer 类中的文档显示了一个完整的状态图,该图阐明了哪些方法将 MediaPlayer 从一个状态移动到另一个状态。例如,当您创建一个新的 MediaPlayer 时,它处于空闲状态。此时,您应该通过调用 setDataSource() 来初始化它,使其进入已初始化状态。之后,您必须使用 prepare()prepareAsync() 方法来准备它。当 MediaPlayer 准备完毕时,它将进入已准备状态,这意味着您可以调用 start() 使其播放媒体。此时,如该图所示,您可以通过调用诸如 start()pause()seekTo() 等方法在已启动已暂停播放完成状态之间切换。但是,当您调用 stop() 时,请注意,在再次准备 MediaPlayer 之前,您无法再次调用 start()

在编写与 MediaPlayer 对象交互的代码时,请始终牢记 状态图,因为从错误的状态调用其方法是导致错误的常见原因。

释放 MediaPlayer

MediaPlayer 可以消耗宝贵的系统资源。因此,您应该始终采取额外的预防措施,以确保您不会比必要的时间更长地保留 MediaPlayer 实例。当您完成使用时,您应该始终调用 release() 以确保分配给它的任何系统资源都得到正确释放。例如,如果您正在使用 MediaPlayer 并且您的活动接收到对 onStop() 的调用,则必须释放 MediaPlayer,因为当您的活动没有与用户交互时(除非您在后台播放媒体,这将在下一节中讨论),保留它没有意义。当然,当您的活动恢复或重新启动时,您需要创建一个新的 MediaPlayer 并再次准备它,然后再恢复播放。

以下是如何释放然后使 MediaPlayer 为空的示例

Kotlin

mediaPlayer?.release()
mediaPlayer = null

Java

mediaPlayer.release();
mediaPlayer = null;

例如,考虑一下如果您忘记在活动停止时释放 MediaPlayer,但在活动再次启动时创建了一个新的 MediaPlayer,可能会发生什么问题。如您所知,当用户更改屏幕方向(或以其他方式更改设备配置)时,系统会通过重新启动活动(默认情况下)来处理这种情况,因此当用户在纵向和横向之间来回旋转设备时,您可能会很快耗尽所有系统资源,因为在每次方向更改时,您都会创建一个从未释放的新 MediaPlayer。(有关运行时重新启动的更多信息,请参阅 处理运行时更改。)

您可能想知道,如果您希望即使在用户离开您的活动后也能继续播放“后台媒体”(与内置的音乐应用程序的行为非常相似),该怎么办。在这种情况下,您需要的是由服务控制的 MediaPlayer,如下一节所述。

在服务中使用 MediaPlayer

如果您希望媒体即使在应用程序不在屏幕上时也能在后台播放——也就是说,您希望它在用户与其他应用程序交互时也能继续播放——则必须启动一个服务并从那里控制 MediaPlayer 实例。您需要将 MediaPlayer 嵌入到 MediaBrowserServiceCompat 服务中,并使其与另一个活动中的 MediaBrowserCompat 交互。

您应该谨慎处理这种客户端/服务器设置。系统对在后台服务中运行的播放器与系统其他部分的交互方式有一些预期。如果您的应用程序未满足这些预期,用户可能会遇到不良体验。阅读构建音频应用以获取完整详细信息。

本节描述了在服务内部实现 MediaPlayer 时的一些特殊说明。

异步运行

首先,与Activity类似,默认情况下,Service中的所有工作都在单个线程中完成——事实上,如果您从同一个应用程序运行一个 Activity 和一个 Service,默认情况下它们使用同一个线程(“主线程”)。因此,服务需要快速处理传入的 Intent,并且在响应它们时永远不要执行冗长的计算。如果预期有任何繁重的任务或阻塞调用,您必须异步执行这些任务:要么来自您自己实现的其他线程,要么使用框架提供的众多异步处理工具。

例如,当从您的主线程使用MediaPlayer时,您应该调用prepareAsync()而不是prepare(),并实现一个MediaPlayer.OnPreparedListener以便在准备完成后收到通知,然后您可以开始播放。例如

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类的文档以获取完整的状态图),并且您必须在再次使用它之前重置它。

使用唤醒锁

在设计在后台播放媒体的应用程序时,设备可能会在您的服务运行时进入睡眠状态。由于 Android 系统试图在设备休眠时节省电量,因此系统会尝试关闭所有不必要的手机功能,包括 CPU 和 WiFi 硬件。但是,如果您的服务正在播放或流式传输音乐,您希望阻止系统干扰您的播放。

为了确保您的服务在这些条件下继续运行,您必须使用“唤醒锁”。唤醒锁是一种向系统发出信号的方式,表明您的应用程序正在使用某些功能,即使手机处于空闲状态也应保持可用。

注意:您应该始终谨慎使用唤醒锁,并且只在真正需要时保持唤醒锁,因为它们会显着降低设备的电池寿命。

为了确保在您的MediaPlayer播放时 CPU 继续运行,请在初始化您的MediaPlayer时调用setWakeMode()方法。执行此操作后,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,您必须手动获取和释放它。因此,当您开始使用远程 URL 准备MediaPlayer时,您应该创建并获取 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以避免重新创建和准备它的开销。

数字版权管理 (DRM)

从 Android 8.0(API 级别 26)开始,MediaPlayer包含支持播放受 DRM 保护的材料的 API。它们类似于MediaDrm提供的低级 API,但它们在更高级别上运行,并且不公开底层的提取器、drm 和加密对象。

虽然 MediaPlayer DRM API 没有提供MediaDrm的全部功能,但它支持最常见的用例。当前实现可以处理以下内容类型

  • 受 Widevine 保护的本地媒体文件
  • 受 Widevine 保护的远程/流媒体文件

以下代码片段演示了如何在简单的同步实现中使用新的 DRM MediaPlayer 方法。

要管理受 DRM 控制的媒体,您需要将新方法与 MediaPlayer 调用的通常流程一起包含在内,如下所示

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

首先初始化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()一起使用以将密钥恢复到新会话。
    • 如果响应是针对流请求或释放请求,则结果为 null。

异步运行prepareDrm()

默认情况下,prepareDrm()同步运行,并在准备完成之前阻塞。但是,新设备上的第一次 DRM 准备也可能需要预置,这由prepareDrm()在内部处理,并且由于涉及网络操作,可能需要一些时间才能完成。您可以通过定义和设置MediaPlayer.OnDrmPreparedListener来避免在prepareDrm()上阻塞。

当您设置一个 OnDrmPreparedListener 时,prepareDrm() 会在后台执行预配(如果需要)和准备工作。预配和准备完成后,将调用侦听器。您不应对侦听器运行的调用顺序或线程做出任何假设(除非侦听器已在处理程序线程中注册)。侦听器可以在 prepareDrm() 返回之前或之后被调用。

异步设置 DRM

您可以通过创建和注册 MediaPlayer.OnDrmInfoListener 用于 DRM 准备,以及 MediaPlayer.OnDrmPreparedListener 用于启动播放器来异步初始化 DRM。它们与 prepareAsync() 协同工作,如下所示

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

处理加密媒体

从 Android 8.0(API 级别 26)开始,MediaPlayer 还可以解密基本流类型 H.264 和 AAC 的通用加密方案 (CENC) 和 HLS 样本级加密媒体 (METHOD=SAMPLE-AES)。以前支持完整片段加密媒体 (METHOD=AES-128)。

从 ContentResolver 中检索媒体

媒体播放器应用程序中可能还有另一个有用的功能,即检索用户设备上的音乐。您可以通过查询 ContentResolver 获取外部媒体来实现这一点。

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

要将此与 MediaPlayer 一起使用,您可以执行以下操作

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

了解更多

这些页面涵盖了与录制、存储和播放音频和视频相关的主题。