[MUSIC PLAYING]
DON TURNER: Hi.
I'm Don, and I'm an engineer on the Android Developer Relations
team.
ANDREW LEWIS: Hi.
I'm Andrew, and I'm a software engineer on the Android Media
team.
DON TURNER: Today, we're really excited to share
our plans for a new collection of Jetpack libraries called
Media3.
Media3 is the new home for Android media support
libraries, including ExoPlayer.
In this talk, we'll explain what Media3 is and the libraries it
contains, the benefits it provides
and how it solves current developer problems,
and how to start using Media3 in your app.
To understand how Media3 can help you build a media playback
app on Android, let's take a look at a typical media app
architecture.
Fundamentally, you need two components--
a media player, which renders audio and video content,
and a user interface, which shows
what's playing and provides a way for the user to control
playback.
The UI also displays metadata, such as title and duration,
and reflects the current playback state.
The user interface sends commands like Play, Pause,
and Seek to the player and receives callbacks
from the player when its state changes.
However, in this architecture, the Android system
and other apps running on the device
aren't aware that media is being played,
and so they can't provide convenient ways for your users
to view and control the playback.
Connection your player to a media session
allows you to expose information about what you're
playing to the system and provides
a mechanism for controlling playback from outside your app.
This enables a bunch of useful integrations.
For example, from Android 11 onwards,
your playback will be reflected in the Android system UI
as a Media Controls box.
This shows metadata and playback controls
under the Quick Settings panel.
Users with Wear OS devices will be
able to control your app's playback from their wrist using
a media notification.
And you can choose for your playback
to be controllable from other apps and Google Assistant
so users can use voice commands instead of tapping on a screen.
There are other benefits, too, like automatic handling
of media key events.
For example, when using a Bluetooth headset
with a Play/Pause button or when using
a TV remote through HDMI CEC.
Creating a media session enables you to provide a better user
experience, primarily because it provides users
with a convenient way to view and control playback
from a number of different contexts.
However, knowing what library to use to implement this
can be quite a challenge.
Over to Andrew to explain why.
ANDREW LEWIS: Let's take a quick look
at some of the libraries that are available for implementing
these use cases.
Firstly, there's androidx.media2,
which was the successor to the original media compatibility
support library.
This has modules covering playback, UI components, media
sessions, and common data structures
and functionality used across the modules.
There's also ExoPlayer.
This provides a player implementation
that's used by hundreds of thousands of apps,
along with libraries for building user interfaces,
dash playback, client-side ad insertion, and so on.
There's a lot of duplication across these APIs.
There are two player libraries.
There are also two sets of UI components
and two common library modules.
And there are also libraries for integrating
with media sessions.
And in addition to this duplication,
there are several libraries that provide
additional functionality that aren't available in media2.
This is just a partial view of the APIs that are available.
For example, there's also the original androidx.media
compatibility support library.
The existence of various libraries supporting
similar use cases, each with their own limitations
and incompatibilities, makes it difficult to know
what library to use.
For the Media3 project, we set out to unify these libraries.
To start with, we only need one common module.
We standardized on ExoPlayer as the player implementation
and kept its extensive set of helper modules.
There's one UI module providing player controls
and a video output view, and there's
a session module that provides media session integration.
The result is androidx.media3, which
provides a cohesive set of libraries for media use cases.
Now back to Don to see how to implement the most important
playback use cases with these libraries
and highlight some of its benefits
compared to previous versions.
DON TURNER: Starting with the simplest use case,
foreground playback.
If your app just plays content when it's in the foreground--
for example, a simple video player--
then you can use this architecture.
Your player, UI, and media session
are all contained within a single activity.
In this case, the media session allows
you to support media key events and display controls when
in picture-in-picture mode.
The problem with this approach is that,
with the previous APIs, the media session cannot
communicate directly with the player.
That means you have to have a connector object which
translates commands and callbacks between the player
and the media session.
For example, when the media session
receives a Play command, it calls its onPlay callback
method.
This is implemented by the connector, which
then tells the player to play.
The connector must also listen for events from the player
and update the media session state.
If you extend this to all possible commands
which the media session can receive,
and handling the various player states,
you end up with a lot of code.
And much of it is more complicated than
these minimal code snippets.
This can lead to bugs.
In fact, during an internal testing some years ago,
we found that over 50% of top apps
had at least one issue with their media session
integration.
In Media3, we wanted to avoid the need
for a connecting layer.
Media3 provides ExoPlayer as its media player,
and this already implements a player interface.
So we updated the media session and UI widgets
to accept the same player interface,
linking them directly together.
No need for a connecting component.
The background playback case is slightly more complicated.
The architecture is separated between a service,
which contains the player, and an activity for the UI.
The service creates a media session,
which is used to advertise playback and pass
commands to the player.
And inside the activity, we create a media controller
which is used to communicate with the media session.
As we saw already, the player can't talk directly
to the media session, so it needs a connector.
Additionally, the media controller
has its own player-like interface
that's different from what the UI components expect.
So that also needs a connector.
And we have the same problem, where the connectors add
complexity and are error-prone.
With Media3, again, we wanted to eliminate
this connecting component for the media controller as well.
Now, the media controller and player both
implement the same player interface,
and the session and UI components
take the same player interface.
So, in summary, Media3 has one common player interface
used throughout, which is implemented by ExoPlayer
and is directly compatible with media sessions.
The media controller, which also implements the common player
interface, works directly with the UI.
This avoids a lot of unnecessary connector code,
resulting in a code which is easier to maintain
and less prone to errors.
ANDREW LEWIS: Let's take a closer look
at how to build this with Media3,
starting with the PlayerService.
The first thing we need to do is to create an ExoPlayer
instance.
Once we've done that, all we have
to do is create a media session, and as we mentioned earlier,
this takes a player.
Media3 will take care of updating the media
session based on the player state automatically.
There's no need to handle callbacks or use a connector.
Moving on to the activity, where we're going to show the UI,
we first need to establish a link to the session
in the onStartLifecycle method.
We create a sessionToken which identifies the session
we want to connect to.
Then, we build a mediaController,
which connects to the session asynchronously.
We can listen for the connection to be established,
and because the mediaController is just
a player implementation, we can pass that directly
to the playerView.
Having set this up, the UI gets updated
in the same way it would be for a foreground playback,
where the UI and player are in the same activity.
This even works if you have your service and player running
in separate processes.
Media3 session functionality also
interoperates with the older media session
APIs in Media2 and MediaCompat.
One caveat is that we're still putting the finishing touches
on Media3 in alpha, so the details of the code
may change by the time we launch into stable.
But the overall flow will remain the same.
Now we've seen how having a common player interface used
throughout the APIs in Media3 makes it easier
to build playback apps with sessions in UI.
Let's shift our focus to look at the player interface itself.
When we started work towards bringing these libraries
together a couple of years ago, we
decided to use ExoPlayer's player interface
as the common player.
We reviewed the whole API surface,
making simplifications and adding new functionality that
was necessary to generalize it for use as a media controller.
We've been incrementally releasing
these improvements over the last few ExoPlayer releases.
Here are a couple of examples.
Firstly, we added a much easier to use
API for telling the player what to play.
It used to be necessary to create a data source specifying
how to load data and then pass that
in to a specific media source implementation that
would handle it.
The MediaItem API makes this much easier.
In the simplest case, you can just
pass a Uri to create a MediaItem,
but there's also a builder for more advanced use
cases like DRM playback.
MediaItems can be added to the player
like this, forming a playlist.
You can add, remove, and reorder items.
Then, when you're ready to play, you just
call prepare and play to allow playback to start.
You can modify the playlist during playback,
and ExoPlayer will take care of rebuffering when necessary.
There are a bunch of other improvements,
too, including more descriptive error codes
and a structured metadata type.
These are already in ExoPlayer and so will
be included in Media3.
DON TURNER: ExoPlayer has a number
of features that are useful when you're playing audio by itself
or an audio track which is part of a video.
Firstly, to coordinate audio playback with other apps,
ExoPlayer can handle audio focus automatically.
That means, when your app starts playing, audio from other apps
is faded out automatically.
Secondly, BecomingNoisy handling takes
care of pausing playback automatically
if the audio path changes in a way that would cause the user's
device to make a sudden noise, like when
disconnecting headphones and switching to a device speaker.
Finally, specifically for background audio playback,
ExoPlayer can take care of acquiring and releasing
wake and Wi-Fi locks so the device doesn't
sleep when playing audio with the screen off.
This functionality is turned off by default,
so you need to explicitly enable it.
Now let's talk about how your app
can work with other apps in the Android system.
There are two main use cases here.
Firstly, exposing your media session so that other apps
can control your media player.
And secondly, exposing your content library
so other apps can provide their own UI for it.
In our previous media APIs, both these goals
are achieved using a media browser service.
In Media3, they are achieved using two separate classes.
A media session service is used to provide access to your media
session, and a MediaLibrary service
is used to expose your content library.
MediaLibrary service actually extends
from media session service, so it
can achieve both these goals.
Let's take a look at why you might
do these things in your app and how to do them using Media3.
By providing access to your media session,
you can allow other apps and devices to control playback.
This can be much more convenient for users
compared to using your app's UI.
To do this in Media3, use a MediaSessionService.
This indicates that your app can create a media session.
What you then need to do is override the onGetSession
method to return your media session to the controller.
The controller can then connect to your media session
and start controlling playback through it.
As well as MediaSessionService being simpler than
MediaBrowserService, there are several other benefits to using
this new class, including automatic handling of media key
events-- for example, with a Bluetooth headset--
and providing a default media notification.
Another thing your app can expose is a content library.
This allows you to support Android Auto, which
provides its own driver-safe UI for your content.
It also allows you to support a new feature on Android 11
and above called playback resumption.
This feature enables users to pick up playback
from where they left off.
In this case, instead of supplying a whole content tree,
your app supplies the most recently played media item
and the system UI will create static media controls for it.
Here's how to expose your content
library using the Media3 APIs.
Firstly, create a MediaLibraryService, and inside
it, create a MediaLibrarySession.
This is an extended media session which
can provide a content library.
As before, the browsing app obtains
your MediaLibrarySession by calling onGetSession.
Inside your MediaLibrarySession, you
can create an object for library callbacks.
The browsing app will call these library callbacks in order
to obtain your content.
Whether you're using MediaSessionService
or MediaLibraryService, you'll need
to advertise your service in your app manifest.
To maintain compatibility with the previous
MediaBrowserService, you also need
to include the MediaBrowserService action.
ANDREW LEWIS: The last topic we'll cover
is how versioning works for Media3.
This will be familiar to users of other Jetpack libraries
but is different from the model ExoPlayer
has been using in the past.
Firstly, there is a stable API service.
If your app only uses symbols that are part of the stable API
service, then generally, you can upgrade your Media3 dependency
without needing to modify your code.
For example, if you write your app
against any version under media version 1,
then you can pick up later releases
without needing to make any changes.
We'll increment the minor version for compatible API
changes like adding new methods, but fixes
increment the third element of the version string.
However, one of our main concerns
with bringing ExoPlayer into androidx
was that its API surface is very large,
on the order of 10,000 symbols.
The large API surface means that app developers can customize
functionality by overriding methods
and implementing interfaces that change how ExoPlayer components
work, which is a big benefit.
However, we can't commit to keeping this full API
surface stable, as the code would get messy and complicated
very quickly.
To address this problem, we are partitioning ExoPlayer's API
service into mature APIs that we are
able to commit to keeping stable in the longer
term and unstable APIs that won't form
part of the stable API surface.
We're planning to start off carefully
with just the functionality needed for basic use cases.
That includes stabilizing common data structures and interfaces
and media session functionality.
Most of ExoPlayer's API surface will
remain unstable for the time being,
including more advanced customization points.
Over time, we plan to gradually migrate more APIs
to the stable surface.
In this context, the word unstable
doesn't imply anything about performance or quality
of the APIs.
It's just an indication that we aren't
ready to freeze them yet.
If your app currently uses ExoPlayer APIs that
aren't part of the stable surface,
when you migrate to Media3, you'll
need to opt in to using those symbols.
This gives you access to the full API surface like before,
but it does mean that you may need to make code changes when
upgrading Media3 in future, just like was
necessary with ExoPlayer.
You can opt in at the level of an individual method,
as shown here, but for large code bases
doing extensive customization, it will be easier
to opt in a whole project.
You can configure that by adding a suppression
in your project-wide lint.xml file, like this.
By the time we launch, we're expecting there to be a simpler
syntax, so this just serves to highlight that there's a way
to avoid adding lots of opt-in annotations
if you're doing a lot of customization.
If you're a current ExoPlayer user,
we want to minimize disruption for the migration to Media3.
We plan to provide at least one quarterly release
where both ExoPlayer and Media3 are available,
and we're going to continue to publish to GitHub, just
like ExoPlayer.
DON TURNER: Just to summarize, Media3
is the new home for all media support libraries
and contains libraries for UI, playback, and handling media
sessions.
It also allows a simplified architecture
for common use cases as well as sensible default behavior.
It's the new home for ExoPlayer, which
provides all the underlying playback capabilities,
and now has a subset of its API marked as stable,
making upgrades easier.
To get started with Media3, you can add the libraries
to your app using this artifact ID, although just
be aware that the version number might have changed
since this was recorded.
There's a demo app which shows many of the concepts
from this talk, so please give that a try.
Also, Media3 is in alpha, which means that the API can change,
and we'd love to hear your feedback.
If you have suggestions or find any bugs,
please file an issue on GitHub.
We're really looking forward to seeing what you can build
with this new set of libraries.
Thank you very much.
[MUSIC PLAYING]