directory

  1. Overview of ExoPlayer
  2. Basic use of ExoPlayer
  3. Problems encountered
  4. data
  5. harvest

From this post we move on to Phase FIVE – learning usage analysis of some audio and video open source projects. Today we move on to learning practices in the ExoPlayer section

1. Introduction to ExoPlayer

ExoPlayer is a Google open source, app-level audio and video player. ExoPlayer supports DYNAMIC Adaptive streaming (DASH) over HTTP, SmoothStreaming, and universal encryption, as well as excellent support for playback queues and seamless switching between playback sources. It has a design that is easy to customize and extend. The internal implementation also calls low-level apis such as MediaCodec, AudioTrack, and so on

Let me draw a table comparing ExoPlayer and MediaPlayer to give you an intuition

ExoPlayer’s repository is located at * github.com/google/ExoP…

The red box, the core and UI library is also the focus of this series.

The core of ExoPlayer is the Interface of ExoPlayer, which defines the functions of traditional players (buffer audio and video, play, pause, seek, etc.). ExoPlayer does not specify the media type that can be played, how to store it, how to render it, nor does it directly load and play it. This is done by proxying the work to a registered component when the player is created or ready to play. Here are some common ExoPlayer component implementations:

  1. MediaSource to load media, register with exoplayer.prepare
  2. TrackSelector: Audio/visual track extractor that extracts track data from MediaSource
  3. Render: Render the data extracted by TrackSelector, AudioTrack plays the audio and Surface renders the video
  4. LoadControl: Controls MediaSource (when to start buffering, how much to buffer, etc.) ExoPlayer provides default implementations for these components and can extend the implementation by customizing components if needed.

Through ExoPlayer’s architecture diagram, we can also see the modular design of its components, which is worth learning and an important requirement of a good component /SDK. In our daily project development, we develop a component from the perspective of ease of use and extensibility. We need to ensure that it is easy for users to use (provide a set of default implementations), but also have the ability for users to easily expand according to their own scenarios.

Before looking at the State machine in ExoPlayer, let’s take a look at the MeidaPlayer state machine

You can see that MediaPlayer has a lot of state, and if you trigger mismatched operations in the wrong place when you use MediaPlayer, you will crash. Unlike MediaPlayer, which cannot play until prepared, ExoPlayer has many listeners and isplaying apis that listen for state changes. The four states of an ExoPlayer are as follows

/** * Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or * {@link #STATE_ENDED}. */ @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED}) @interface State {} /** The player does not have any media to play. */ int STATE_IDLE = 1; /** * The player is not able to immediately play from its current position. This state typically * occurs when more data  needs to be loaded. */ int STATE_BUFFERING = 2; /** * The player is able to immediately play from its current position. The player will be playing if * {@link #getPlayWhenReady()} is true, and paused otherwise. */ int STATE_READY = 3; /** The player has finished playing the media. */ int STATE_ENDED = 4;Copy the code

STATE_IDLE: Indicates the initial state. The player does not have resources to play. The player is in this state even when the player stops playing or fails to play. The player can play immediately, depending on the value of playWhenReady, which indicates the user’s intention. If true, the player will play, otherwise it will not play. STATE_ENDED: the state is changed after all resources are played

2. Easy to use ExoPlayer

In this section we learn to practice using ExoPlayer

2.1 The introduction of Library ExoPlayer in AS has good scalability and customisability. You can select the corresponding modules according to the needs of the project, or you can include all of them.

exoplayer-core: Core functionality (required).

exoplayer-dash: Support for DASH content.

exoplayer-hls: Support for HLS content.

exoplayer-smoothstreaming: Support for SmoothStreaming content.

exoplayer-ui: UI components and resources for use with ExoPlayer.

We add libraries as needed

Implementation 'com. Google. Android. Exoplayer: exoplayer - core: 2.13.3' implementation 'com. Google. Android. Exoplayer: exoplayer - UI: 2.13.3'Copy the code

The next step is to create a container PlayerView and an ExoPlayerView to play

2.2 Create a player, bind the player container, set data source, and prepare

Player = simpleExoplayer.builder (this).build() printCurPlaybackState("init"); //2. Player and player container binding playerView.player = player //3. Set up the data source / / audio val mediaItem = mediaItem. FromUri (" https://storage.googleapis.com/exoplayer-test-media-0/play.mp3 ") player.setMediaItem(mediaItem) //4. Player. playWhenReady = true //5. Player.prepare () printCurPlaybackState("prepare") // At STATE_BUFFERING = 2;Copy the code

2.3 Playing Listening Whether listening is playing

public final boolean isPlaying() {
    return getPlaybackState() == Player.STATE_READY
        && getPlayWhenReady()
        && getPlaybackSuppressionReason() == PLAYBACK_SUPPRESSION_REASON_NONE;
  }
Copy the code

Play the status change listener, audio related listener, and video related listener

playbackListener = PlaybackListener() player.addListener(playbackListener) player.addAudioListener(playbackListener) player.addVideoListener(playbackListener) class PlaybackListener : Player.EventListener, AudioListener, VideoListener { override fun onPlaybackStateChanged(playbackState: Int) { val stateString: String stateString = when (playbackState) { ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE -" ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -" ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY -" ExoPlayer.STATE_ENDED -> "Exoplayer.state_ended -" else -> "UNKNOWN_STATE -"} log.d ("ExoBaseUserActivity"); "changed state to $stateString") } override fun onAudioSessionIdChanged(audioSessionId: Int) { Log.d("ExoBaseUserActivity", "onAudioSessionIdChanged--sessionId=" + audioSessionId) } override fun onAudioAttributesChanged(audioAttributes: AudioAttributes) { Log.d("ExoBaseUserActivity", "onAudioAttributesChanged--audioAttributes=" + audioAttributes.toString()) } override fun onVolumeChanged(volume: Float) { Log.d("ExoBaseUserActivity", "onVolumeChanged--volume=" + volume) } override fun onSkipSilenceEnabledChanged(skipSilenceEnabled: Boolean) { Log.d("ExoBaseUserActivity", "onSkipSilenceEnabledChanged--skipSilenceEnabled=" + skipSilenceEnabled) } override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) { Log.d("ExoBaseUserActivity", "onVideoSizeChanged--width=" + width + " height=" + height + " unappliedRotationDegrees=" + unappliedRotationDegrees + "  pixelWidthHeightRatio=" + pixelWidthHeightRatio) } override fun onSurfaceSizeChanged(width: Int, height: Int) { Log.d("ExoBaseUserActivity", "onSurfaceSizeChanged--width=" + width + " height=" + height) } override fun onRenderedFirstFrame() { Log.d("ExoBaseUserActivity", "onRenderedFirstFrame") } }Copy the code

A listener for analysis (which outputs more detailed information)

AnalyticsListener = EventLogger(DefaultTrackSelector()) player.addAnalyticsListener(analyticsListener)Copy the code

2.4 Releasing Resources Resources should be released when the page is not visible or destroyed (to see whether background playback is required)

    override fun onDestroy() {
        super.onDestroy()
        player.removeAnalyticsListener(analyticsListener)
        player.removeListener(playbackListener)
        player.removeAudioListener(playbackListener)
        player.removeVideoListener(playbackListener)

        player.release()
    }
Copy the code

The full code has been uploaded to github github.com/ayyb1988/me…

Three, encountered problems

Question 1

Failed to resolve: com. Google. Android. Exoplayer: exoplayer: 2.13.3)Copy the code

2.13.3 There is an extra space in the beginning, which is too… Details sometimes you can waste a lot of time without paying attention.

Question 2

java.lang.SecurityException: Permission denied (missing INTERNET permission?) at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:150) at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103) at java.net.InetAddress.getAllByName(InetAddress.java:1152) at com.android.okhttp.Dns$1.lookup(Dns.java:41) at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178) at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144) at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86) at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:176) at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128) at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97) at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289) at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232) at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465) at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131) at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90) at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30) at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:641) at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:528) at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:349) at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:201) at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:84) at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1015) at  com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:919)Copy the code

There is no reason to add network permission, after static registration in Mainfest, under dynamic request in requesetPermission. From this crash stack, we can see that ExoPlayer loads web videos using Okhttp

Question 3

The 2021-05-15 18:41:17. 414, 11144-11144 /? I/ av.mediaJourne: Not late enabling Xcheck: JNI (already on) 2021-05-15 18:41:17.487 11144-11144/? E/av. mediaJourne: Unknown bits set in runtime_flags: 0x8000 2021-05-15 18:41:17.489 11144-11144/? W/av.mediajourne: Unexpected CPU variant for X86 using defaults: x86Copy the code

The X86 emulator will occasionally flash out, but the real machine is normal. Model equipment adaptation is always a big problem

Four, data

  1. Media streaming with ExoPlayer
  2. ExoPlayer blog
  3. ExoPlayer developer guide
  4. How to use ExoPlayer to play audio and video

Five, the harvest

The results of this study and practice are as follows:

  1. Understand the background of ExoPlayer and its advantages and disadvantages compared to MediaPlayer
  2. Understand the basic functions of ExoPlayer
  3. Simple and practical

Thank you for reading

In the next article, we will continue to learn how to implement ExoPlayer, a simple audio player. Please follow the official account “Audio and Video Development Journey” to learn and grow together.

Welcome to communicate