Android中的背景音频与MediaSessionCompat

来源:互联网 发布:西安软件科技学院 编辑:程序博客网 时间:2024/06/05 00:13

Background Audio in Android With MediaSessionCompat

One of the most popular uses for mobile devices is playing back audio through music streaming services, downloaded podcasts, or any other number of audio sources. While this is a fairly common feature, it's hard to implement, with lots of different pieces that need to be built correctly in order to give your user the full Android experience. 移动设备最流行播放音频,下载播客或任何其他音频源是通过音乐流服务。虽然这是一个相当常见的功能,它很难实现,有很多不同的部分需要正确构建,以给您的用户完整的Android体验。

In this tutorial you will learn about MediaSessionCompat from the Android support library, and how it can be used to create a proper background audio service for your users.在本教程中,您将从Android支持库中了解MediaSessionCompat,以及如何使用它为用户创建合适的背景音频服务。

The first thing you will need to do is include the Android support library into your project. This can be done by adding the following line into your module's build.gradle file under the dependencies node.您需要做的第一件事是将Android支持库包含到您的项目中。这可以通过在模块的build.gradle文件中的dependencies节点下添加以下行来完成。

After you have synced your project, create a new Java class. For this example I will call the classBackgroundAudioService. This class will need to extend MediaBrowserServiceCompat. We will also implement the following interfaces: MediaPlayer.OnCompletionListener and AudioManager.OnAudioFocusChangeListener.同步您的项目后,创建一个新的Java类。对于这个例子,我将调用classBackgroundAudioService。这个类将需要扩展MediaBrowserServiceCompat。我们还将实现以下接口:MediaPlayer.OnCompletionListener和AudioManager.OnAudioFocusChangeListener。

Now that your MediaBrowserServiceCompat implementation is created, let's take a moment to updateAndroidManifest.xml before returning to this class. At the top of the class, you will need to request theWAKE_LOCK permission.现在您的MediaBrowserServiceCompat实现已创建,让我们花一点时间来updateAndroidManifest.xml,然后返回到此类。在类的顶部,您需要请求WAKE_LOCK权限。

Next, within the application node, declare your new service with the following intent-filter items. These will allow your service to intercept control buttons, headphone events and media browsing for devices, such as Android Auto (although we won't do anything with Android Auto for this tutorial, some basic support for it is still required by MediaBrowserServiceCompat).接下来,在应用程序节点中,使用以下意向过滤器项目声明您的新服务。这些将允许您的服务拦截控制按钮,耳机事件和媒体浏览设备,如Android Auto(虽然我们不会做任何事情Android Auto本教程,MediaBrowserServiceCompat仍然需要一些基本的支持)。

Finally, you will need to declare the use of the MediaButtonReceiver from the Android support library. This will allow you to intercept media control button interactions and headphone events on devices running KitKat and earlier.最后,您需要从Android支持库声明使用MediaButtonReceiver。这将允许您在运行KitKat4.0及更早版本的设备上拦截媒体控制按钮交互和耳机事件。

Now that your AndroidManifest.xml file is finished, you can close it. We're also going to create another class named MediaStyleHelper, which was written by Ian Lake, Developer Advocate at Google, to clean up the creation of media style notifications.现在您的AndroidManifest.xml文件已完成,您可以关闭它。我们还将创建另一个名为MediaStyleHelper的类,由Google的开发者支持者Ian Lake撰写,用于清除媒体样式通知的创建。

Once that's created, go ahead and close the file. We will focus on the background audio service in the next section. 一旦创建,继续并关闭文件。我们将在下一节重点介绍背景音频服务。

Now it's time to dig into the core of creating your media app. There are a few member variables that you will want to declare first for this sample app: a MediaPlayer for the actual playback, and a MediaSessionCompatobject that will manage metadata and playback controls/states.现在是时候深入了解创建媒体应用的核心。有一些成员变量,您将要为此示例应用程序首先声明:用于实际播放的MediaPlayer,以及将管理元数据和播放控件/状态的MediaSessionCompatobject。

In addition, you will need a BroadcastReceiver that listens for changes in the headphone state. To keep things simple, this receiver will pause the MediaPlayer, if it is playing.此外,您将需要一个BroadcastReceiver监听耳机状态的变化。为了保持简单,这个接收器将暂停MediaPlayer,如果它正在播放。

For the final member variable, you will to create a MediaSessionCompat.Callback object, which is used for handling playback state when media session actions occur.对于最终成员变量,您将创建一个MediaSessionCompat.Callback对象,用于在发生媒体会话操作时处理播放状态。

We will revisit each of the above methods later in this tutorial, as they will be used to drive operations in our media app.我们将在本教程后面再次讨论上述每个方法,因为它们将用于驱动我们的媒体应用程序中的操作。

There are two methods that we will also need to declare, though they won't need to do anything for the purposes of this tutorial: onGetRoot() and onLoadChildren(). You can use the following code for your defaults.有两个方法,我们还需要声明,虽然他们不需要为本教程的目的做任何事情:onGetRoot()和onLoadChildren()。您可以使用以下代码作为默认值。

Lastly, you will want to override the onStartCommand() method, which is the entry point into your Service. This method will take the Intent that is passed to the Service and send it to the MediaButtonReceiver class. 最后,您将要覆盖onStartCommand()方法,这是进入您的服务的入口点。此方法将接受传递到服务的Intent,并将其发送到MediaButtonReceiver类。

Now that your base member variables are created, it's time to initialize everything. We'll do this by calling various helper methods in onCreate().现在您的基本成员变量已创建,现在是时候初始化一切。我们将通过调用onCreate()中的各种帮助方法来实现。

The first method, initMediaPlayer(), will initialize the MediaPlayer object that we created at the top of the class, request partial wake lock (which is why we required that permission in AndroidManifest.xml), and set the player's volume. 第一个方法initMediaPlayer()将初始化我们在类的顶部创建的MediaPlayer对象,请求部分唤醒锁(这是我们在AndroidManifest.xml中需要该权限的原因),并设置播放器的音量。

The next method, initMediaSession(), is where we initialize the MediaSessionCompat object and wire it to the media buttons and control methods that allow us to handle playback and user input. This method starts by creating a ComponentName object that points to the Android support library's MediaButtonReceiver class, and uses that to create a new MediaSessionCompat. We then pass the MediaSession.Callback object that we created earlier to it, and set the flags necessary for receiving media button inputs and control signals. Next, we create a new Intent for handling media button inputs on pre-Lollipop devices, and set the media session token for our service. 下一个方法initMediaSession()用于初始化MediaSessionCompat对象,并将其连接到媒体按钮和控制方法,以允许我们处理播放和用户输入。此方法首先创建一个ComponentName对象,该对象指向Android支持库的MediaButtonReceiver类,并使用该对象创建一个新的MediaSessionCompat。然后,我们将之前创建的MediaSession.Callback对象传递给它,并设置接收媒体按钮输入和控制信号所必需的标志。接下来,我们创建一个新的Intent,用于处理Lollipop设备上的媒体按钮输入,并为我们的服务设置媒体会话令牌。

Finally, we'll register the BroadcastReceiver that we created at the top of the class so that we can listen for headphone change events. 最后,我们将注册我们在类的顶部创建的BroadcastReceiver,以便我们可以监听耳机更改事件。

Now that you've finished initializing BroadcastReceiverMediaSessionCompat and MediaPlayer objects, it's time to look into handling audio focus. 现在,您已经完成了BroadcastReceiver,MediaSessionCompat和MediaPlayer对象的初始化,现在是处理音频焦点的时候了。

While we may think our own audio apps are the most important at the moment, other apps on the device will be competing to make their own sounds, such as an email notification or mobile game. In order to work with these various situations, the Android system uses audio focus to determine how audio should be handled. 虽然我们可能认为我们自己的音频应用程序是目前最重要的,设备上的其他应用程序将竞争产生自己的声音,如电子邮件通知或手机游戏。为了处理这些各种情况,Android系统使用音频焦点来确定应如何处理音频。

The first case we will want to handle is starting playback and attempting to receive the device's focus. In your MediaSessionCompat.Callback object, go into the onPlay() method and add the following condition check. 我们要处理的第一种情况是开始播放并尝试接收设备的焦点。在MediaSessionCompat.Callback对象中,转到onPlay()方法并添加以下条件检查。

The above code will call a helper method that attempts to retrieve focus, and if it cannot, it will simply return. In a real app, you would want to handle failed audio playback more gracefully. successfullyRetrievedAudioFocus() will get a reference to the system AudioManager, and attempt to request audio focus for streaming music. It will then return a boolean representing whether or not the request succeeded. 上面的代码将调用一个帮助方法来尝试检索焦点,如果不能,它将简单地返回。在真实的应用程序,你会想要更好地处理失败的音频播放。 successRetrievedAudioFocus()将获得对系统AudioManager的引用,并尝试请求音频焦点以进行流式传输音乐。然后它将返回一个布尔值,表示请求是否成功。

You'll notice that we are also passing this into the requestAudioFocus() method, which associates the OnAudioFocusChangeListener with our service. There are a few different states that you'll want to listen for in order to be a "good citizen" in the device's app ecosystem. 你会注意到,我们也将这个传递到requestAudioFocus()方法,它将OnAudioFocusChangeListener与我们的服务相关联。有一些不同的状态,你想听,以便在设备的应用程序生态系统中的“好公民”。

  • AudioManager.AUDIOFOCUS_LOSS: This occurs when another app has requested audio focus. When this happens, you should stop audio playback in your app.
  • AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: This state is entered when another app wants to play audio, but it only anticipates needing focus for a short time. You can use this state to pause your audio playback.
  • AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: When audio focus is requested, but throws a 'can duck' state, it means that you can continue your playback, but should bring the volume down a bit. This can occur when a notification sound is played by the device.
  • AudioManager.AUDIOFOCUS_GAIN: The final state we will discuss is AUDIOFOCUS_GAIN. This is the state when a duckable audio playback has completed, and your app can resume at its previous levels.

AudioManager.AUDIOFOCUS_LOSS:这发生在另一个应用程序请求音频焦点。发生这种情况时,您应该停止在应用程式中播放音讯。
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:当另一个应用程序想播放音频时输入此状态,但它只预计需要短时间的聚焦。您可以使用此状态暂停音频播放。
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:当请求音频焦点,但抛出“可以鸭子”状态,这意味着您可以继续播放,但应该使音量下降一点。这可能在设备播放通知声音时发生。
AudioManager.AUDIOFOCUS_GAIN:我们将讨论的最终状态是AUDIOFOCUS_GAIN。这是可达的音频播放完成时的状态,您的应用程序可以恢复其以前的级别。

A simplified onAudioFocusChange() callback may look like this: 简化的onAudioFocusChange()回调可能如下所示:

Now that you have a general structure together for your Service, it's time to dive into theMediaSessionCompat.Callback. In the last section you added a little bit to onPlay() to check if audio focus was granted. Below the conditional statement, you will want to set the MediaSessionCompat object to active, give it a state of STATE_PLAYING, and assign the proper actions necessary to create pause buttons on pre-Lollipop lock screen controls, phone and Android Wear notifications.
现在,你有一个一般的结构一起为您的服务,是时候潜入到MediaSessionCompat.Callback。在最后一节中,你添加了一点onPlay()来检查是否授予音频焦点。在条件语句下,您需要将MediaSessionCompat对象设置为活动状态,将其设置为STATE_PLAYING状态,并分配在Lollipop锁定屏幕控件,手机和Android Wear通知上创建暂停按钮所需的正确操作。

The setMediaPlaybackState() method above is a helper method that creates a PlaybackStateCompat.Builder object and gives it the proper actions and state, and then builds and associates a PlaybackStateCompat with yourMediaSessionCompat object.  上面的setMediaPlaybackState()方法是一个帮助方法,它创建一个PlaybackStateCompat.Builder对象并赋予它正确的动作和状态,然后构建并关联一个PlaybackStateCompat和yourMediaSessionCompat对象。

It's important to note that you will need both the ACTION_PLAY_PAUSE and either ACTION_PAUSE or ACTION_PLAY flags in your actions in order to get proper controls on Android Wear. 请注意,您需要在操作中同时使用ACTION_PLAY_PAUSE和ACTION_PAUSE或ACTION_PLAY标志,才能在Android Wear上获得正确的控制权。

Media notification on Android Wear

Back in onPlay(), you will want to show a playing notification that is associated with your MediaSessionCompatobject by using the MediaStyleHelper class that we defined earlier, and then show that notification. 回到onPlay()中,您将需要通过使用我们之前定义的MediaStyleHelper类来显示与您的MediaSessionCompatobject关联的播放通知,然后显示该通知。

Finally, you will start the MediaPlayer at the end of onPlay(). 最后,您将在onPlay()的结尾处启动MediaPlayer。

Media control notification on an Android Nougat device

When the callback receives a pause command, onPause() will be called. Here you will pause the MediaPlayer, set the state to STATE_PAUSED, and show a paused notification. 当回调接收到暂停命令时,将调用onPause()。在这里,您将暂停MediaPlayer,将状态设置为STATE_PAUSED,并显示暂停的通知。

Our showPausedNotification() helper method will look similar to the showPlayNotification() method. 我们的showPausedNotification()帮助方法将类似于showPlayNotification()方法。

The next method in the callback that we'll discuss, onPlayFromMediaId(), takes a String and a Bundle as parameters. This is the callback method that you can use for changing audio tracks/content within your app.  我们将讨论的回调中的下一个方法onPlayFromMediaId()将String和Bundle作为参数。这是可用于更改应用程序中的音轨/内容的回调方法。

For this tutorial, we will simply accept a raw resource ID and attempt to play that, and then reinitialize the session's metadata. As you are allowed to pass a Bundle into this method, you can use it to customize other aspects of your media playback, such as setting up a custom background sound for a track. 对于本教程,我们将接受原始资源ID并尝试播放,然后重新初始化会话的元数据。由于您允许将Bundle传递到此方法,您可以使用它来自定义媒体播放的其他方面,例如为轨道设置自定义背景声音。

Now that we've discussed the two main methods in this callback that you will use in your apps, it's important to know that there are other optional methods that you can use to customize your service. Some methods include onSeekTo(), which allows you to change the playback position of your content, andonCommand(), which will accept a String denoting the type of command, a Bundle for extra information about the command, and a ResultReceiver callback, which will allow you to send custom commands to yourService. 现在我们已经讨论了您将在应用程序中使用的这个回调中的两个主要方法,重要的是要知道还有其他可选方法可用于自定义服务。一些方法包括onSeekTo(),它允许你改变你的内容的播放位置,andonCommand(),它将接受一个String表示命令的类型,一个Bundle用于命令的额外信息,以及一个ResultReceiver回调允许您向yourService发送自定义命令。

When our audio file has completed, we will want to decide what our next action will be. While you may want to play the next track in your app, we'll keep things simple and release the MediaPlayer. 当我们的音频文件完成后,我们将决定我们的下一步操作。虽然你可能想播放应用程序中的下一个轨道,我们将保持简单和释放MediaPlayer。

Finally, we'll want to do a few things in the onDestroy() method of our Service. First, get a reference to the system service's AudioManager, and call abandonAudioFocus() with our AudioFocusChangeListener as a parameter, which will notify other apps on the device that you are giving up audio focus. Next, unregister theBroadcastReceiver that was set up to listen for headphone changes, and release the MediaSessionCompat object. Finally, you will want to cancel the playback control notification. 最后,我们将在服务的onDestroy()方法中做一些事情。首先,获取对系统服务的AudioManager的引用,并使用我们的AudioFocusChangeListener作为参数调用abandonAudioFocus(),这将通知设备上的其他应用程序您放弃了音频焦点。接下来,取消注册设置为侦听耳机更改的TheBroadcastReceiver,并释放MediaSessionCompat对象。最后,您将要取消播放控制通知。

At this point, you should have a working basic background audio Service using MediaSessionCompat for playback control across devices. While there has already been a lot involved in just creating the service, you should be able to control playback from your app, a notification, lock screen controls on pre-Lollipop devices (Lollipop and above will use the notification on the lock screen), and from peripheral devices, such as Android Wear, once the Service has been started. 此时,您应该有一个工作的基本背景音频服务,使用MediaSessionCompat来跨设备的播放控制。虽然已经有很多涉及创建服务,你应该能够控制从你的应用程序的播放,通知,锁定屏幕控制前Lollipop设备(Lollipop和以上将使用锁定屏幕上的通知)并在服务启动后从外围设备(如Android Wear)中删除。

Media lock screen controls on Android Kit Kat

While most controls will be automatic, you will still have a bit of work to start and control a media session from your in-app controls. At the very least, you will want a MediaBrowserCompat.ConnectionCallback,MediaControllerCompat.CallbackMediaBrowserCompat, and MediaControllerCompat objects created in your app. 


虽然大多数控件将是自动的,但您仍然需要一些工作来从应用程序内控制开始和控制媒体会话。至少,您将需要在您的应用程序中创建的MediaBrowserCompat.ConnectionCallback,MediaControllerCompat.Callback,MediaBrowserCompat和MediaControllerCompat对象。

MediaControllerCompat.Callback will have a method called onPlaybackStateChanged() that receives changes in playback state, and can be used to keep your UI in sync. MediaControllerCompat.Callback将有一个称为onPlaybackStateChanged()的方法,该方法接收播放状态的更改,并可用于保持UI同步。

MediaBrowserCompat.ConnectionCallback has an onConnected() method that will be called when a newMediaBrowserCompat object is created and connected. You can use this to initialize your MediaControllerCompatobject, link it to your MediaControllerCompat.Callback, and associate it with MediaSessionCompat from your Service. Once that is completed, you can start audio playback from this method.    MediaBrowserCompat.ConnectionCallback有一个onConnected()方法,将在创建和连接newMediaBrowserCompat对象时调用。您可以使用它来初始化您的MediaControllerCompatobject,将其链接到您的MediaControllerCompat.Callback,并将其与您的服务中的MediaSessionCompat相关联。一旦完成,您可以从此方法开始音频播放。

You'll notice that the above code snippet uses getSupportMediaController().getTransportControls() to communicate with the media session. Using the same technique, you can call onPlay() and onPause() in your audio service's MediaSessionCompat.Callback object. 你会注意到上面的代码片段使用getSupportMediaController()。getTransportControls()来与媒体会话进行通信。使用相同的技术,您可以在音频服务的MediaSessionCompat.Callback对象中调用onPlay()和onPause()。

When you're done with your audio playback, you can pause the audio service and disconnect yourMediaBrowserCompat object, which we'll do in this tutorial when this Activity is destroyed. 完成音频播放后,您可以暂停音频服务并断开您的MediaBrowserCompat对象,我们将在本教程中在此活动被销毁时执行此操作。



Whew! As you can see, there are a lot of moving pieces involved with creating and using a background audio service correctly. 


In this tutorial, you have created a Service that plays a simple audio file, listens for changes in audio focus, and links to MediaSessionCompat to provide universal playback control on Android devices, including handsets and Android Wear. If you run into roadblocks while working through this tutorial, I strongly recommend checking out the associated Android project code on Envato Tuts+'s GitHub.

哇! 正如你所看到的,有很多移动的部分涉及正确地创建和使用背景音频服务。

在本教程中,您创建了一个服务,它可以播放简单的音频文件,监听音频焦点的更改,以及指向MediaSessionCompat的链接,以在Android设备(包括手机和Android Wear)上提供通用播放控制。 如果在本教程中遇到障碍,我强烈建议您查看Envato Tuts +的GitHub上关联的Android项目代码。

0 0
原创粉丝点击