媒体播放器概述

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 时管理 MediaPlayer 的特殊说明。

异步运行

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

例如,当从主线程使用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 硬件。但是,如果您的服务正在播放或流式传输音乐,则需要阻止系统干扰您的播放。

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

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

为了确保 CPU 在 MediaPlayer 播放期间继续运行,请在初始化 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() 上阻塞。

当您设置 MediaPlayer.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...

了解更多

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