How to optimize media streaming with ExoPlayer

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] NEVIN MITAL: Hi, everyone. Welcome to the media streaming with ExoPlayer workshop. My name is Nevin, and I'm a developer relations engineer on the Android team. ExoPlayer is an open source media player that you can include in your app for audio and video playback. Unlike Android's built-in media player class, it's easy to customize and extend and supports features such as adaptive streaming, playlists, and DRM-protected playback. ExoPlayer is available in Jetpack as part of the Media3 library, which is a collection of support libraries for implementing media use cases on Android. Today, I'd like to show you how simple it can be to add ExoPlayer to your app. If you'd like to follow along in Android Studio yourself, you can search for media streaming with ExoPlayer to find the Codelab I'll be going through. Or head to developer.Androi d.com/codelabs/ExoPlayer-intro to get started. To get the most out of this work-through, you'll want to be familiar with the basics of Android development. But otherwise, we're starting today's app from scratch. That being said, let's get into it. We've prepared a project on our Codelabs GitHub repository that you can use as a starting point. Cloning this repo will get you set up with a blank project. And it also includes folders with completed code at various steps during the Codelab. Switching over to Android Studio, I've already imported the project. And I'm ready to start with step 00. The application module named app is actually empty aside from an Android manifest file and only has a Gradle dependency on the currently specified ExoPlayer-codelab library module. Setting up separate modules like this is a great way to share code among different APKs. For example, if you're targeting different platforms like mobile and Android TV. I've also got an emulator running so I can build and test my app as we go. If I run the project right off the bat, the app should launch and fill the screen with a blank background. Looks like we're ready to go. First things first, we need to add ExoPlayer to the project. Let's navigate to the build.gradle file in the ExoPlayer-codelab-00 module. As of this recording, the latest release of the Media3 library is 1.0.0-alpha03. The Media3 library is split into several different modules so you can import only the functionality that you need. For this workshop, we'll include the following three modules-- Media3-ExoPlayer, which adds the actual player implementation called ExoPlayer that we'll use, Media3-UI, which adds the UI element called player view that we'll use to add a playback view to our activity, and Media3-ExoPlayer-- which adds support for dynamic adaptive streaming for later on in the workshop. Make sure to sync your project after adding these dependencies. Next, let's add the player view to our UI. Open the layout resource file activity underscore player.xml. Here, inside the frame layout is where the player view element will go. For both the height and the width, I'm using match parent. And I've also set the ID to video underscore view. Is the ID that I'll use to refer to the UI element going forward. Jumping into the player activity now, we can get a reference to the view tree created by the XML file. This will initialize the view binding variable the first time it is used, which in our case will be the on create lifecycle callback for the activity. Speaking of which, in the on create callback, we can set the root of the view tree as the content view of the activity. To double-check things, we can make sure that the video view property is available from the view binding. Looks like the type is player view. So everything looks good so far. Now that our UI is set up, we need to create an ExoPlayer object to actually play a streaming media. I'll start by declaring a member field called player. Let's create a private helper method called initialize player to build our ExoPlayer instance. In this method, we'll use the ExoPlayer.builder class to create an ExoPlayer object using the activities context. Don't forget to call the build method. We're assigning this newly created ExoPlayer object to our player member field. But additionally, we'll bind this object to the player property of our video view. And now that we have a player, we need to give it some content to play. For this, we have to build a media item. Let's start with creating a media item for an MP3 file that's available on the internet. We've already provided a URL for this file in the strings.xml resource file. Back inside the initialized player method, we can create the media item using the fromUri method. Once it's created, we can add it to the player by calling set media item. This player object has the potential to use up a lot of device resources, including memory, CPU, network connections, and hardware codecs, many of which are in short supply. To make sure that we're being a good Android citizen, we should tie the player's life cycle to the life cycle of our app so that the player's resources are released to other apps when we're not using them. There are four life cycle methods of the player activity for us to override. Let's start with the on start and on resume methods Android API level 24 and up support multiple Windows, which means your app can be visible but not active. In these cases, your player should be initialized in on start. So we'll call our initialized player helper function if the API level is higher than 23. Prior to API level 24, you should wait as long as possible to initialize the player. So for that case, we'll use on resume. We'll also add a helper method called hide system UI to enable a full-screen experience. Now, we'll turn our attention to on pause and on stop. Similar to before, with API level 24 and up, your app might be paused but still visible. So we'll only release the player's resources in on stop. Prior to API level 24, there's no guarantee that on stop will be called. So we release resources in on pause instead. We'll add a release player helper method to take care of freeing up resources and destroying our player. But before releasing the resources, we'll want to record three pieces of information. Play when ready, which stores the play pause state of the player; current item, which stores the index of the current media item; and playback position, which stores the current playback position within the current media item. These three items allow us to resume playback from where the user left off when they put the app into the background. Up top, we'll initialize these variables so that by default, the player will automatically start playback from the beginning of the first media item. We can now go back to the initialized player method to use the state information that we've stored. We'll instruct the player whether or not to start playing immediately once all required resources have been acquired, which media item to start with and at what position within it, and finally, to acquire all the required resources. And that's it. We have everything we need for playback to work. We can launch the app to play the MP3 file and see the embedded artwork. This is a good opportunity for you to test that the app works as expected in all the different activity lifecycle states. [MUSIC PLAYING] But what if we wanted to play a video instead? This can be as easy as modifying the media item Uri to an MP4 file instead of an MP3 file. We've already provided a URL to try this out as well in the strings.xml file. If we start up the app again, you'll see that the player view is handling everything. And instead of the artwork, the video is being rendered in full screen mode. [MUSIC PLAYING] OK. So we have a single media file playing now. But ExoPlayer also makes it easy to play multiple media files one after the other. We can create a playlist by adding multiple media items to the player using add media item. ExoPlayer will handle buffering in the background to allow for seamless playback. The player also includes move media item and remove media item methods for you to modify the playlist on the fly. Now if we restart the app, we can use the next and previous buttons provided by the player view to navigate between our media items. [MUSIC PLAYING] Next, I'd like to talk about adaptive streaming. Adaptive streaming refers to a technique in streaming media where the player varies the quality of the stream based on the available network bandwidth. Adaptive media content generally consists of several tracks with different qualities such as bit rates and resolutions. Each track is also split into smaller chunks, typically between two and 10 seconds. ExoPlayer player can choose one of these tracks based on the available network bandwidth. The smaller subdivisions of each track also allow ExoPlayer to quickly switch between these tracks as the amount of bandwidth available changes. ExoPlayer will handle stitching the audio together for seamless playback even as it performs these switches. Back in our initialized player method, let's start by creating a default track selector, which is in charge of choosing tracks within media items. We'll also instruct it to only pick tracks of standard definition or lower. This is one option at your disposal for saving your users' data at the expense of quality. Lastly, we'll pass the track selector into the builder for creating an ExoPlayer instance. We also need to provide the player with a media item in an adaptive streaming format. Dynamic adaptive streaming over HTTP, otherwise known as DASH, is one such popular format. Instead of using media item.fromUri, we'll need to use a media item builder. This will give us more freedom to specify the format of the media item. Some properties that we can set using the builder class include the MIME type of the content, subtitle files to use during playback, and protected content properties such as the DRM type. Same as before, we've provided a URL to content in the dash format for you to use, so we can update our initializer player method with the media item that uses the new URL and has a MIME type of MPD, which tells ExoPlayer that the content is in the DASH format. With that done, all we have to do is restart the app to see this new file being played. We can also see the adaptive streaming in action. I'll double-tap shift to open the search everywhere bar. I'm looking for the adaptive track selection class, and specifically the update selected track method. This is the method that's called when the track is being updated. I'll put a breakpoint towards the end and restart the app in debug mode. This way, you can see each time the track selector needs to decide which track to use. Of course, DASH is only one adaptive streaming format. Other commonly used formats are HLS and smooth streaming, both of which are also supported by ExoPlayer. So far, we've learned that ExoPlayer is handling a lot of things for us behind the scenes, such as allocating memory, downloading the media content and extracting its metadata, decoding the media file, and rendering it on the device. But you might find it helpful to understand what ExoPlayer player is doing at runtime so you can improve the experience accordingly. For example, you may want to display a loading spinner when the player is buffering or display a watch next overlay when the media item has ended. Luckily, ExoPlayer provides a listener interface that we can use to get callbacks for useful events. In the player activity, we need a private player.listener member variable. And I'll create a tag constant outside the player activity that we can use for logging in a moment. Now, also outside the player activity class, let's implement the player.listener interface inside a factory function. And we'll override the on playback state changed callback. This callback will trigger any time the playback state changes. So we can use it to print the current state of the player. The player can be in one of four states. Idol, meaning that the player has been created, but not prepared. Buffering, meaning that the player hasn't buffered enough data yet and can't play from the current position. Ready, meaning that the player can immediately play from the current position once the play when ready property is set to true. And ended, which means that the player has finished playing the provided media. But how would you know if your player is actually playing media? There are a few factors that must be true. First, playback state must be ready. Second, the play when ready property must be true. And lastly, playback must not be suppressed for some other reason, such as loss of audio focus. To make this easier to know, ExoPlayer provides both an is playing method and an on is playing callback. We can update our listener to also log changes in the playing state. Now that we've defined what our callback should do, we need to register the listener with our player. Back in the initialized player method, we'll add our new listener prior to the prepare call. And remember, that we'll need to free up the listener's resources as well. So in our released player helper method, we can remove the listener before releasing the player. If we rerun the app with logcat open in a separate window, we can use the UI controls to navigate playback and see the relevant playback state changes in the logs. [MUSIC PLAYING] SPEAKER 1: OK class, record a video. NEVIN MITAL: ExoPlayer player offers a number of other callbacks for specific audio and video events. For example, on rendered first frame is called when the first frame of a video is rendered. This can be useful in calculating how long the user had to wait until playback started. Another callback worth calling out is on events. This callback reports all state changes that happen in a single iteration together and also gives you access to the player object within the callback. This can be useful if you want to use multiple state values together or if you need the player object to use a getter method or to trigger further events. For example, if I wanted to update the UI whether the playback state changed, or the is playing property changed, we could override on events callback as follows. As another example, if we wanted to skip ahead by five seconds any time the media item transitions, we could issue a seek command to the player when the desired event is detected. I'll also add our second media item back in so we can see this new callback in action. Now when we switch media items, notice that the timestamp starts at five seconds, since we're skipping ahead as soon as we get a media item transition event. [MUSIC PLAYING] And with that, I've shared all I have for you today. We covered a lot of ground, including adding ExoPlayer to your project, creating media items and taking advantage of adaptive streaming, and responding to playback events to improve the user experience on the fly. But of course, this is just the tip of the iceberg. Head over to the ExoPlayer docs in our Android Media Docs to learn more. Thanks for tuning in, and have a great Google IO. [MUSIC PLAYING]
Info
Channel: Android Developers
Views: 25,000
Rating: undefined out of 5
Keywords: Android developers, codelab, back navigation, migrating apps to androidx, app development, app migration, Google I/O, Google IO, I/O, IO, Google I/O 2022, IO 2022
Id: Hw0Jeq42FNU
Channel Id: undefined
Length: 21min 58sec (1318 seconds)
Published: Wed May 11 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.