Android SurfaceView plays video source code

Android SurfaceView plays video source code

SurfaceView

Let's first introduce how most software parses a video stream. First, it needs to determine the format of the video, which is related to decoding. Different formats of video are encoded differently, which is not the focus here. After knowing the encoding format of the video, it is decoded according to the encoding format, and finally a frame of images is obtained, and these images are quickly displayed on the interface, which is to play a video. SurfaceView in Android completes this function.

Since SurfaceView is used in conjunction with MediaPlayer, MediaPlayer also provides corresponding methods to set SurfaceView to display images. You only need to specify SurfaceView to display images for MediaPlayer. Its complete signature is as follows:

void setDisplay(SurfaceHolder sh)

It needs to pass a SurfaceHolder object. SurfaceHolder can be understood as a container for SurfaceView to load the frames of images that need to be displayed. It can be obtained through the SurfaceHolder.getHolder() method.

The steps for using MediaPlayer with SurfaceView to play videos are basically the same as using MediaPlayer to play MP3. You only need to set up the display SurfaceView.

SurfaceView double buffering

As mentioned above, SurfaceView, like most video applications, parses the video stream into frames for display. However, if this parsing process is completed in one thread, the next frame may not be parsed in time after the previous frame has been displayed, which will cause problems such as unsmooth images or asynchrony between sound and video. Therefore, SurfaceView, like most video applications, displays frames through a double buffering mechanism. So what is double buffering? Double buffering can be understood as two threads taking turns to parse the frame images of the video stream. When one thread finishes parsing the frame image, it renders the image into the interface, and at the same time, the other thread starts parsing the next frame image, so that the two threads take turns to parse the video stream to achieve a smooth playback effect.

SurfaceHolder

SurfaceView implements a double buffering mechanism internally, but this function consumes a lot of system memory. Due to the limitations of mobile devices, Android stipulates in its design that when SurfaceView is visible to the user, SurfaceHolder of SurfaceView is created to display the frame images parsed by the video stream. If SurfaceView becomes invisible to the user, SurfaceHolder of SurfaceView is destroyed immediately to save system resources.

If the developer does not maintain SurfaceHolder, after minimizing the program, when you open the application again, the sound of the video continues to play, but the picture is not displayed. This is because when the SurfaceView is not visible to the user, the previous SurfaceHolder has been destroyed. When you enter again, the SurfaceHolder on the interface is already a new SurfaceHolder. Therefore, SurfaceHolder requires our developers to code and maintain it. To maintain SurfaceHolder, you need to use its callback, SurfaceHolder.Callback(), which needs to implement the following three methods:

  • void surfaceDestroyed(SurfaceHolder holder): callback when SurfaceHolder is destroyed.
  • void surfaceCreated(SurfaceHolder holder): callback when SurfaceHolder is created.
  • void surfaceChange(SurfaceHolder holder): Called back when the size of SurfaceHolder changes.

The following is the calling process of these three methods. These three methods are implemented for SurfaceHolder in the application. First enter the application, SurfaceHolder is created, and the size of SurfaceHolder is changed after creation. Then press the Home button to return to the desktop and destroy SurfaceHolder. Finally, enter the application again, recreate SurfaceHolder and change its size.

SurfaceView Demo Example

So much about SurfaceView has been talked about above. The following is a demo to demonstrate how SurfaceView plays videos. A scroll bar is added to show the progress. You can also drag the scroll bar to select the playback position. The comments of the Demo are relatively complete, so I will not repeat them here. The video is found randomly on the Internet. When running, make sure that there is this file in the directory /sdcard/ykzzldx.mp4.

Layout file: activity_main.xml

Implementation code:

  1. package cn.bgxt.surfaceviewdemo;
  2.   
  3. import java.io.File;
  4.   
  5. import android.media.AudioManager;
  6. import android.media.MediaPlayer;
  7. import android.media.MediaPlayer.OnCompletionListener;
  8. import android.media.MediaPlayer.OnErrorListener;
  9. import android.media.MediaPlayer.OnPreparedListener;
  10. import android.os.Bundle;
  11. import android.app.Activity;
  12. import android.util.Log;
  13. import android.view.SurfaceHolder;
  14. import android.view.SurfaceHolder.Callback;
  15. import android.view.SurfaceView;
  16. import android.view.View;
  17. import android.widget.Button;
  18. import android.widget.EditText;
  19. import android.widget.SeekBar;
  20. import android.widget.SeekBar.OnSeekBarChangeListener;
  21. import android.widget.Toast;
  22.   
  23. public   class MainActivity extends Activity {
  24. private   final String TAG = "main" ;
  25. private EditText et_path;
  26. private SurfaceView sv;
  27. private Button btn_play, btn_pause, btn_replay, btn_stop;
  28. private MediaPlayer mediaPlayer;
  29. private SeekBar seekBar;
  30. private   int currentPosition = 0 ;
  31. private   boolean isPlaying;
  32.   
  33. @Override  
  34. protected   void onCreate(Bundle savedInstanceState) {
  35. super .onCreate(savedInstanceState);
  36. setContentView(R.layout.activity_main);
  37.   
  38. seekBar = (SeekBar) findViewById(R.id.seekBar);
  39. sv = (SurfaceView) findViewById(R.id.sv);
  40. et_path = (EditText) findViewById(R.id.et_path);
  41.   
  42. btn_play = (Button) findViewById(R.id.btn_play);
  43. btn_pause = (Button) findViewById(R.id.btn_pause);
  44. btn_replay = (Button) findViewById(R.id.btn_replay);
  45. btn_stop = (Button) findViewById(R.id.btn_stop);
  46.   
  47. btn_play.setOnClickListener(click);
  48. btn_pause.setOnClickListener(click);
  49. btn_replay.setOnClickListener(click);
  50. btn_stop.setOnClickListener(click);
  51.   
  52. // Add callback for SurfaceHolder  
  53. sv.getHolder().addCallback(callback);
  54. // Properties that need to be set under version 4.0  
  55. // Set Surface not to maintain its own buffer, but wait for the screen's rendering engine to push content to the interface  
  56. // sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  
  57. // Add progress change event to the progress bar  
  58. seekBar.setOnSeekBarChangeListener(change);
  59. }
  60.   
  61. private Callback callback = new Callback() {
  62. // Callback when SurfaceHolder is modified  
  63. @Override  
  64. public   void surfaceDestroyed(SurfaceHolder holder) {
  65. Log.i(TAG, "SurfaceHolder is destroyed" );
  66. // When destroying SurfaceHolder, record the current playback position and stop playback  
  67. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  68. currentPosition = mediaPlayer.getCurrentPosition();
  69. mediaPlayer.stop();
  70. }
  71. }
  72.   
  73. @Override  
  74. public   void surfaceCreated(SurfaceHolder holder) {
  75. Log.i(TAG, "SurfaceHolder is created" );
  76. if (currentPosition > 0 ) {
  77. // When creating SurfaceHolder, if the last playback position exists, play according to the last playback position  
  78. play(currentPosition);
  79. currentPosition = 0 ;
  80. }
  81. }
  82.   
  83. @Override  
  84. public   void surfaceChanged(SurfaceHolder holder, int format, int width,
  85. int height) {
  86. Log.i(TAG, "SurfaceHolder size changed" );
  87. }
  88.   
  89. };
  90.   
  91. private OnSeekBarChangeListener change = new OnSeekBarChangeListener() {
  92.   
  93. @Override  
  94. public   void onStopTrackingTouch(SeekBar seekBar) {
  95. // Triggered when the progress bar stops modifying  
  96. // Get the current progress bar scale  
  97. int progress = seekBar.getProgress();
  98. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  99. // Set the current playback position  
  100. mediaPlayer.seekTo(progress);
  101. }
  102. }
  103.   
  104. @Override  
  105. public   void onStartTrackingTouch(SeekBar seekBar) {
  106.   
  107. }
  108.   
  109. @Override  
  110. public   void onProgressChanged(SeekBar seekBar, int progress,
  111. boolean fromUser) {
  112.   
  113. }
  114. };
  115.   
  116. private View.OnClickListener click = new View.OnClickListener() {
  117.   
  118. @Override  
  119. public   void onClick(View v) {
  120.   
  121. switch (v.getId()) {
  122. case R.id.btn_play:
  123. play( 0 );
  124. break ;
  125. case R.id.btn_pause:
  126. pause();
  127. break ;
  128. case R.id.btn_replay:
  129. replay();
  130. break ;
  131. case R.id.btn_stop:
  132. stop();
  133. break ;
  134. default :
  135. break ;
  136. }
  137. }
  138. };
  139.   
  140.   
  141. /*
  142. * Stop playback
  143. */  
  144. protected   void stop() {
  145. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  146. mediaPlayer.stop();
  147. mediaPlayer.release();
  148. mediaPlayer = null ;
  149. btn_play.setEnabled( true );
  150. isPlaying = false ;
  151. }
  152. }
  153.   
  154. /**
  155. * Start playing
  156. *
  157. * @param msec initial playback position
  158. */  
  159. protected   void play( final   int msec) {
  160. // Get the video file address  
  161. String path = et_path.getText().toString().trim();
  162. File file = new File(path);
  163. if (!file.exists()) {
  164. Toast.makeText( this , "Video file path error" , 0 ).show();
  165. return ;
  166. }
  167. try {
  168. mediaPlayer = new MediaPlayer();
  169. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  170. // Set the video source for playback  
  171. mediaPlayer.setDataSource(file.getAbsolutePath());
  172. // Set the SurfaceHolder to display the video  
  173. mediaPlayer.setDisplay(sv.getHolder());
  174. Log.i(TAG, "Start loading" );
  175. mediaPlayer.prepareAsync();
  176. mediaPlayer.setOnPreparedListener( new OnPreparedListener() {
  177.   
  178. @Override  
  179. public   void onPrepared(MediaPlayer mp) {
  180. Log.i(TAG, "Loading completed" );
  181. mediaPlayer.start();
  182. // Play according to the initial position  
  183. mediaPlayer.seekTo(msec);
  184. // Set the maximum progress of the progress bar to the maximum playback time of the video stream  
  185. seekBar.setMax(mediaPlayer.getDuration());
  186. // Start the thread and update the progress bar scale  
  187. new Thread() {
  188.   
  189. @Override  
  190. public   void run() {
  191. try {
  192. isPlaying = true ;
  193. while (isPlaying) {
  194. int current = mediaPlayer
  195. .getCurrentPosition();
  196. seekBar.setProgress(current);
  197. sleep( 500 );
  198. }
  199. } catch (Exception e) {
  200. e.printStackTrace();
  201. }
  202. }
  203. }.start();
  204.   
  205. btn_play.setEnabled( false );
  206. }
  207. });
  208. mediaPlayer.setOnCompletionListener( new OnCompletionListener() {
  209.   
  210. @Override  
  211. public   void onCompletion(MediaPlayer mp) {
  212. // Called back when playback is complete  
  213. btn_play.setEnabled( true );
  214. }
  215. });
  216.   
  217. mediaPlayer.setOnErrorListener( new OnErrorListener() {
  218.   
  219. @Override  
  220. public   boolean onError(MediaPlayer mp, int what, int extra) {
  221. // Replay if an error occurs  
  222. play( 0 );
  223. isPlaying = false ;
  224. return   false ;
  225. }
  226. });
  227. } catch (Exception e) {
  228. e.printStackTrace();
  229. }
  230.   
  231. }
  232.   
  233. /**
  234. * Restart playback
  235. */  
  236. protected   void replay() {
  237. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  238. mediaPlayer.seekTo( 0 );
  239. Toast.makeText( this , "Replay" , 0 ).show();
  240. btn_pause.setText( "Pause" );
  241. return ;
  242. }
  243. isPlaying = false ;
  244. play( 0 );
  245.   
  246. }
  247.   
  248. /**
  249. * Pause or Continue
  250. */  
  251. protected   void pause() {
  252. if (btn_pause.getText().toString().trim().equals( "continue" )) {
  253. btn_pause.setText( "Pause" );
  254. mediaPlayer.start();
  255. Toast.makeText( this , "Continue playing" , 0 ).show();
  256. return ;
  257. }
  258. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  259. mediaPlayer.pause();
  260. btn_pause.setText( "Continue" );
  261. Toast.makeText( this , "Pause playback" , 0 ).show();
  262. }
  263.   
  264. }
  265.   
  266. }

Source code download address: http://pan.baidu.com/s/1lgKLS

Original address: Android, SurfaceView video playback source code

<<:  What does iOS 8 mean for app design?

>>:  The love and hate of a pixel, the troubles between programmers and designers

Recommend

28 thoughts on marketing, operations, copywriting, and new media!

1 What is a brand? It seems that there are very f...

How to run a million dollar sales bootcamp?

Nowadays, it is not uncommon for us to achieve us...

Huawei AppGallery Brand Resource Bidding Promotion Service Rules

These rules are based on the Huawei Developer Ser...

7600+ words to explain Zhihu traffic generation and transaction!

Today I want to share with you: How to operate Zh...

Would you buy a phone that costs $6,800?

On October 4, luxury phone maker Vertu launched a...

Apple WWDC bans selfie sticks: users will be asked to leave the venue

[[131851]] Beijing time, April 15th morning news,...

Xiaohongshu recommends 8 major tracks for Spring Festival topics

The Spring Festival is approaching. Although I do...

The top five marketing trends in 2018, you must know the third one!

In the past 2017, the mobile marketing market has...

Why are the speeds and versions of APP updates different for Android and Apple?

iOS has become a company of its own with its power...

My vision is only 0.1! I can’t use this kind of eye drops!

Review expert: Peng Guoqiu, deputy chief physicia...