The preface
Recently, I took over the project and used the function of video playback. I used the open-source project JiaoZiVideo, which is more commonly used, so that we can quickly realize the related functions of video playback.
Jz player simple to use
JZVideoPlayerStandard jzVideoPlayerStandard = (JZVideoPlayerStandard) findViewById(R.id.jz_vedio); / / set the broadcast video links and video title jzVideoPlayerStandard. SetUp (VEDIO_URL, JZVideoPlayer SCREEN_WINDOW_NORMAL,"Dumplings close your eyes"); / / to play the video Settings cover figure jzVideoPlayerStandard. ThumbImageView. SetImageResource (R.m ipmap. Ic_launcher);Copy the code
The simple use of the Jc player is to introduce the file into the layout file, and then set the link to the video to play and the cover image of the video to play. We don’t have to worry about anything else.
Code structure analysis
The core implementation classes of the player are the above.
- JZVideoPlayer implements video player view-related content for a composite custom View inherited from the FrameLayout implementation.
- JZVideoPlayerStandard is inherited from JZVideoPlayer to achieve some of its own functions.
- JZMediaManager is used for MediaPlayer management, callbacks to listener methods of MediaPlayer, and related callbacks to TextrueView.
- JZVideoPlayerManager management JZVideoPlayer
The View to achieve
Player View implementation through a combination of custom View, the bottom layer has a View to place the video, and then in the upper layer some decorative controls and related prompt View.
- 0: the bottom View is the container reserved for TextureView
- 1: video title and return key
- 2: electric quantity display and time
- 3: Play button, prompt View area in case of video playback problems
- 4: video window maximization and minimization control
- 5: Video playback progress bar (SeekBar)
Playback process
The entry to play initialization is also triggered by the start button click, so the source code is analyzed from the start click event handler. For the start button click processing, there are many situations involved, such as playing, not playing, playing network files in what network, the current full screen or small screen, and so on. Here is no longer posted source code, just for the relevant judgment process to comb.
The first thing we’ll look at here is that in the case of playback, the startVedio method will be called.
public void startVideo() {/ / end of the current state of play JZVideoPlayerManager.com pleteAll (); // initialize TextureView() for video playback; addTextureView(); AudioManager mAudioManager = (AudioManager) getContext().getSystemService(context.audio_service); mAudioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); / / set the screen normally on JZUtils. ScanForActivity (getContext ()). The getWindow () addFlags (WindowManager. LayoutParams. FLAG_KEEP_SCREEN_ON); / / set broadcast related configuration information for MediaManager JZMediaManager. CURRENT_PLAYING_URL = JZUtils. GetCurrentUrlFromMap (urlMap, currentUrlMapIndex); JZMediaManager.CURRENT_PLING_LOOP = loop; JZMediaManager.MAP_HEADER_DATA = headData; // Start playing state preparing onStatePreparing(); JZVideoPlayerManager.setFirstFloor(this); JZMediaManager.instance().positionInList = positionInList; }Copy the code
- onStatePreParing
public void onStatePreparing() { currentState = CURRENT_STATE_PREPARING; resetProgressAndTime(); }Copy the code
Sets the current state and resets the schedule and time. In the startVedio method, we did not see a specific call to start playing. In the process of reading the source code, we started to be curious. The process of starting playing here is in the corresponding callback of TextureView.
public void initTextureView() {
removeTextureView();
JZMediaManager.textureView = new JZResizeTextureView(getContext());
JZMediaManager.textureView.setSurfaceTextureListener(JZMediaManager.instance());
}Copy the code
SurfaceTexture listener callbacks are set for TextureView when it is initialized. Before moving on to the playback process, a quick introduction to TextureView.
The video or OpengL content of an application is often displayed in a special UI control: the SurfaceView. SurfaceView works by creating a new window after the application window. This is very efficient because the SurfaceView window refreshes without redrawing the application window. (Android’s normal window drawing mechanism is layer by layer, and any child or local refresh will cause the entire view structure to be redrawn, which is very inefficient.) But SurfaceView has some very inconvenient limitations.
Because the contents of the SurfaceView are not in the application window, you cannot use transformations (pan, scale, rotate, etc.). It is also difficult to place in a ListView or ScrollView and cannot use UI control features such as view.setalpha (). In contrast to SurfaceView, TextureView does not create a single Surface to draw on, which allows it to perform transformations, set transparency, etc., just like a regular View. In addition, Textureview must be in a hardware accelerated window. TextureView was introduced in Android 4.0 to address this issue. When TextureView is attach to the current Window, onSurfaceTextureAvailable method will be called back.
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
if (savedSurfaceTexture == null) {
savedSurfaceTexture = surfaceTexture;
prepare();
} else{ textureView.setSurfaceTexture(savedSurfaceTexture); }}Copy the code
The SurfaceTexture is cached, and when the cache is judged to be empty, a new value is set for the original cache, and the perpare method is called.
public void prepare() {
releaseMediaPlayer();
Message msg = new Message();
msg.what = HANDLER_PREPARE;
mMediaHandler.sendMessage(msg);
}Copy the code
A HandlerThread is created in JZMediaManager and processed after receiving the message. The following is the related processing logic.
currentVideoWidth = 0; currentVideoHeight = 0; // Create a new MediaPlayer mediaPlayer.release(); mediaPlayer = new MediaPlayer(); / / set related attributes for MediaPlayer and listener MediaPlayer. SetAudioStreamType (AudioManager. STREAM_MUSIC); mediaPlayer.setLooping(CURRENT_PLING_LOOP); mediaPlayer.setOnPreparedListener(JZMediaManager.this); mediaPlayer.setOnCompletionListener(JZMediaManager.this); mediaPlayer.setOnBufferingUpdateListener(JZMediaManager.this); mediaPlayer.setScreenOnWhilePlaying(true); mediaPlayer.setOnSeekCompleteListener(JZMediaManager.this); mediaPlayer.setOnErrorListener(JZMediaManager.this); mediaPlayer.setOnInfoListener(JZMediaManager.this); mediaPlayer.setOnVideoSizeChangedListener(JZMediaManager.this); Class<MediaPlayer> clazz = mediaplayer.class; Method method = clazz.getDeclaredMethod("setDataSource", String.class, Map.class); method.invoke(mediaPlayer, CURRENT_PLAYING_URL, MAP_HEADER_DATA); PrepareAsync (); // MediaPlayer.prepareAsync ();if(surface ! = null) { surface.release(); } surface = new Surface(savedSurfaceTexture); mediaPlayer.setSurface(surface);Copy the code
Here you create an instance of MediaPlayer, set its listeners, and set its data source by reflection.
MediaPlayer plays files from three main sources:
- The resource that the user has provided in the application
MediaPlayer.create(this, R.raw.test);Copy the code
- Media files stored in the SD card or other file paths
mp.setDataSource("/sdcard/test.mp3");Copy the code
- Media files on the network
mp.setDataSource("http://www.citynorth.cn/music/confucius.mp3");Copy the code
The prepareAsync method is called after setting the playback source, which is a native method. Before playing the video, we can call prepare or prepareAsync methods. The first one blocks and the second one is non-blocking. When our video data comes in, we can play it. For the video playback, MediaPlayer is used to decode the data, and then TextureView will render the decoded data for display. (Issues related to TextureView and rendering will be discussed in the next source code analysis article.)
Full screen playback implementation
if (currentState == CURRENT_STATE_AUTO_COMPLETE)
return;
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
//quit fullscreen
backPress();
} else {
onEvent(JZUserAction.ON_ENTER_FULLSCREEN);
startWindowFullscreen();
}Copy the code
If the current screen is full, call backPress to return to the previous small screen; otherwise, call startWindowFullscreen to enable the full screen.
public static void startFullscreen(Context context, Class _class, String url, Object... objects) {
LinkedHashMap map = new LinkedHashMap();
map.put(URL_KEY_DEFAULT, url);
startFullscreen(context, _class, map, 0, objects);
}Copy the code
Call the startFullscreen method for full-screen playback
public static void startFullscreen(Context context, Class _class, LinkedHashMap urlMap, int defaultUrlMapIndex, Object... Object) {// Hide ActionBar hideSupportActionBar(context); / / get the current window contentView, if the current has a full screen video View, remove the View JZUtils. SetRequestedOrientation (context, FULLSCREEN_ORIENTATION); ViewGroup vp = (JZUtils.scanForActivity(context))//.getWindow().getDecorView(); .findViewById(Window.ID_ANDROID_CONTENT); View old = vp.findViewById(R.id.jz_fullscreen_id);if(old ! = null) { vp.removeView(old); } // Create an instance of JZVideoPlayer, Try {Constructor<JZVideoPlayer> Constructor = _class.getconstructor (context.class); final JZVideoPlayer jzVideoPlayer = constructor.newInstance(context); jzVideoPlayer.setId(R.id.jz_fullscreen_id); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); vp.addView(jzVideoPlayer, lp); jzVideoPlayer.setUp(urlMap, defaultUrlMapIndex, JZVideoPlayerStandard.SCREEN_WINDOW_FULLSCREEN, objects); CLICK_QUIT_FULLSCREEN_TIME = System.currentTimeMillis(); / / triggers the start button jzVideoPlayer. StartButton. PerformClick (); } catch (InstantiationException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }}Copy the code
Take the current page’s contentView, create a JZVideoPlayer, set the layout property to matchParent and add it to the contentView. Then the previous page will be overwritten. It seems that a new page has been opened to play. At this time, there is another problem that is the update of the progress. How do the playing progress of the two TextureView keep synchronized? The previous analysis of startVedio method mainly focused on the process of starting playing, and here we will focus on the processing of the previous video playing. It’s called first in startVedio
JZVideoPlayerManager.completeAll();Copy the code
This method will call JZVideoPlayer’s onComplete method that we set up earlier. The purpose of this method is to save the current playback progress and free up some resources held by the previous playback. Meanwhile, a series of modifications are made to the existing View.
public void onCompletion() {
if(currentState = = CURRENT_STATE_PLAYING | | currentState = = CURRENT_STATE_PAUSE) {/ / get the current schedule, Save the current broadcast video progress int position = getCurrentPositionWhenPlaying (); JZUtils.saveProgress(getContext(), JZUtils.getCurrentUrlFromMap(urlMap, currentUrlMapIndex), position); } // cancelProgressTimer(); onStateNormal(); / / removed from the current View of the current textureView textureViewContainer. RemoveView (JZMediaManager. TextureView); JZMediaManager.instance().currentVideoWidth = 0; JZMediaManager.instance().currentVideoHeight = 0; AudioManager mAudioManager = (AudioManager) getContext().getSystemService(context.audio_service); mAudioManager.abandonAudioFocus(onAudioFocusChangeListener); // Clear the View in full screen mode JZUtils.scanForActivity(getContext()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); clearFullscreenLayout(); JZUtils.setRequestedOrientation(getContext(), NORMAL_ORIENTATION); // Release the current bound textureView and surfaceTextureif(JZMediaManager.surface ! = null) JZMediaManager.surface.release(); JZMediaManager.textureView = null; JZMediaManager.savedSurfaceTexture = null; }Copy the code
There’s nothing about the progress cache here, so here we go back to the startVedio method area, and here we can see that while we’re playing, we create a new MediaPlayer object, and then we set multiple listeners to it, and one of them is
mediaPlayer.setOnPreparedListener(JZMediaManager.this);Copy the code
The callback function is as follows, which starts playing the video and calls the player’s onPrepared method.
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if(JZVideoPlayerManager.getCurrentJzvd() ! = null) { JZVideoPlayerManager.getCurrentJzvd().onPrepared(); }}}); }Copy the code
public void onPrepared() {
if (JZUtils.getCurrentUrlFromMap(urlMap, currentUrlMapIndex).toLowerCase().contains("mp3")) { onStatePrepared(); onStatePlaying(); }}Copy the code
This method will get our previous playback progress and then adjust the current MediaPlayer to the current progress.
public void onStatePrepared() {
if(seekToInAdvance ! = 0) { JZMediaManager.instance().mediaPlayer.seekTo(seekToInAdvance); seekToInAdvance = 0; }else {
int position = JZUtils.getSavedProgress(getContext(), JZUtils.getCurrentUrlFromMap(urlMap, currentUrlMapIndex));
if(position ! = 0) { JZMediaManager.instance().mediaPlayer.seekTo(position); }}}Copy the code
Start the timing of the progress bar.
public void onStatePlaying() {
currentState = CURRENT_STATE_PLAYING;
startProgressTimer();
}Copy the code
As you can see in Prepared, only for the MP3 type the progress is changed, but for the video type the OnInfo method of the registered OnInfo listener is called back.
public void onInfo(int what, int extra) {
if(what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { onStatePrepared(); onStatePlaying(); }}Copy the code
When the message is returned to start rendering playback, the onStatePrepared and onStatePlaying methods are called to set our save progress and enable the timing mechanism. Compared to the onPrepared callback, this will ensure that we don’t make progress adjustments until the video starts showing.
The player also supports a small window in the lower right corner of the playback, the playback principle and normal play to the full screen implementation is also similar. The analysis of the source code here is only in the implementation of the relevant business of the player, the specific core is in Mediaplayer and TextureView, the next will be a comb and analysis of the source code for these two.
reference
Android TextureView easy tutorial
Android MediaPlayer plays audio from a variety of sources