title: android media player video freezes playing local file
body: <p>I'm developing an app that loops local video files indefinitely in retail locations.  I'm using the Android MediaPlayer class for this, and developing for the Minix X8 Plus, which is a multimedia hub that runs on Kodi's version of Android 4.4.2.  The device will play the videos on an HDMI screen.</p>
  
  <p>Most of the time, the video plays successfully, but I'm having trouble with the image freezing fairly regularly, while the audio keeps playing.  The frequency of the freeze varies video to video, but it is almost always at the start of the video playback, and can be on either the first pass or subsequent loops. The media player is set to loop with <code>mediaPlayer.setLooping(true)</code>I have the following listeners enabled:</p>
  
  <ul>
  <li>MediaPlayer.onErrorListener</li>
  <li>MediaPlayer.onInfoListener</li>
  <li>MediaPlayer.OnVideoSizeChangedListener</li>
  <li>MediaPlayer.OnSeekCompleteListener</li>
  <li>MediaPlayer.OnBufferingUpdateListener</li>
  <li>MediaPlayer.OnPreparedListener</li>
  </ul>
  
  <p>During the freezes, I'm not getting any callbacks, no errors, no info callbacks.  I think the problem must be internal to Android MediaPlayer, because I'm not getting exceptions or errors, and the problem only occurs between 5-20% of the time, depending on the video.</p>
  
  <p>I have a work around enabled where I monitor the position of the video "playhead" <code>mediaPlayer.getCurrentPosition()</code>vs the amount the media player has buffered <code>MediaPlayer.OnBufferingUpdateListener.onBufferingUpdate()</code>, which returns a percentage of the video file currently buffered.  I find that sometimes a freeze happens and the playback position keeps updating as if all was well, but eventually the playback position will pass the amount that the player has buffered.  When this happens I call <code>mediaPlayer.reset()</code> and go through the whole set up process again calling <code>mediaPlayer.setDataSource()</code> and  <code>mediaPlayer.prepareAsync()</code>.  I also monitor for <code>mediaPlayer.getCurrentPosition()</code> returning the same position repeatedly.  On some videos this either happens at the beginning (stuck on position 0) or at the end after the video has frozen (stuck on the last position, unable to loop).</p>
  
  <p>I'd really like to find another solution.  All the videos behave differently, and even with resetting the media player, I'm left with a frozen image for long enough to be a problem.</p>
  
  <p>I've combed SO for a solution. The problem is similar to this:<a href="https://stackoverflow.com/questions/26385336/mediaplayer-freezes-instead-of-looping-video-on-android-4-4">SO post here, but not identical. </a></p>
  
  <p>As suggested in that post, I'm polling <code>mediaPlayer.getVideoWidth() and mediaPlayer.getVideoHeight()</code> until it returns a non 0 number before starting playback.</p>
  
  <p>I've looked at ExoPlayer, but had problems with it crashing when playing a video file that Android MediaPlayer has no problem with.  I also read <a href="https://github.com/google/ExoPlayer/issues/21" rel="noreferrer">here</a> that there isn't much advantage to using ExoPlayer for playing local files.</p>
  
  <p>I've also looked at Grafika as an alternative to MediaPlayer, but it is video only, no audio, and isn't really a complete media player.</p>
  
  <p>I have also looked at VLC for Android, but there isn't much documentation about using it as a library with in your app, and it is still being worked on quite heavily.</p>
  
  <p>If anyone has had similar issues, or could suggest an open source alternative to Android MediaPlayer, I would love to hear from you. </p>
  
  <p>Here is some of the code from the Activity that plays the videos.  It's a lot, I've tried to pare it down to the media player code.</p>
  
  <pre><code> private void initMediaPlayerOnPreparedListener() {
      try {
          mediaPlayer.prepareAsync();
          mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
              @Override
              public void onPrepared(MediaPlayer mp) {
                  obtainVideoDimens();
                  obtainDisplayDimens();
                  calcVidToDisplayRatio();
                  applyTransformTextureView();
                  applyLayoutParamsTextureView();
                  if (playbackStuckAtZeroReset) {
                      mediaPlayer.seekTo((33)); // should be one one frame into the video at 30 frames per second
                  }
  
                  mediaPlayer.start();
                  launchCheckVidProgThread();
                  Log.d(J, "initMediaPlayerOnPreparedListener() -&gt; mediaPlayer playing: " + mediaPlayer
                          .isPlaying());
                  MyApplication.setVideoPlaying(true);
  
              }
          });
      } catch (IllegalArgumentException | SecurityException | IllegalStateException e) {
          e.printStackTrace();
      }
  }
  
  private void obtainVideoDimens() {
      videoWidth = 0;
      videoHeight = 0;
  
      do {
          videoWidth = mediaPlayer.getVideoWidth();
          videoHeight = mediaPlayer.getVideoHeight();
          Log.d(J, "video width: " + videoWidth + " x video height: " + videoHeight);
      } while (videoWidth == 0 &amp;&amp; videoHeight == 0);
  }
  
  private void obtainDisplayDimens() {
      DisplayMetrics displayMetrics = new DisplayMetrics();
      getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
      displayWidth = displayMetrics.widthPixels;
      int displayHeight = displayMetrics.heightPixels;
      Log.d(J, "real display metrics width pix: " + displayWidth + " x height pix: " + displayHeight);
  }
  
  
  private void calcVidToDisplayRatio() {
      videoToDisplayScaleWidth = displayWidth / videoWidth;
      Log.d(J, "video to display scale width: " + videoToDisplayScaleWidth);
  }
  
  private Matrix applyMatrixScale(float w, float h) {
      Matrix matrix = new Matrix();
      matrix.setScale(w, h);
      return matrix;
  }
  
  private void initMediaPlayerOnSeekListener() {
      mediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
          @Override
          public void onSeekComplete(MediaPlayer mp) {
          }
      });
  }
  
  private void initMediaPlayerOnErrorListener() {
      if (mediaPlayer != null) {
          mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
              @Override
              public boolean onError(MediaPlayer mp, int what, int extra) {
                  Log.d(J, "MediaPlayer error: " + mp.toString() + " what: " + what + " extra: " + extra);
                  stopAndResetMediaPlayer();
                  return true;
              }
          });
      }
  }
  
  private void initMediaPlayerOnInfoListener() {
      if (mediaPlayer != null) {
          mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
              @Override
              public boolean onInfo(MediaPlayer mp, int what, int extra) {
                  Log.d(J, "MediaPlayer info: " + mp.toString() + " what: " + what + " extra: " + extra);
                  if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
  
                  }
                  // never was called,
                  if (what == MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING) {
                      Log.d(J, "media player video track lagging!");
                  }
                  return false;
              }
          });
      }
  }
  
  private void initMediaPlayerOnBufferingListener() {
      mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
          @Override
          public void onBufferingUpdate(MediaPlayer mp, int percent) {
              if (percent &gt; 0) {
                  setLastBuffPercent(percent);
              }
          }
      });
  }
  
  private static void stopAndResetMediaPlayer() {
      if (!isMediaPlayerNull()) {
          if (mediaPlayer.isPlaying()) {
              mediaPlayer.stop();
              MyApplication.setVideoPlaying(false);
          }
          resetMediaPlayer();
      }
  }
  
  private static void resetMediaPlayer() {
      if (!isMediaPlayerNull()) {
          Log.d(J, "resetting media player!!!!!!!!");
          mediaPlayer.reset();
          declareMediaPlayerAttributes();
          mediaPlayer.prepareAsync();
      }
  }
  
  private void initMediaPlayerOnVideoSizeChangedListener() {
      mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
          @Override
          public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
              Log.d(J, "onVideoSizeChanged! width: " + width + " height: " + height);
              if (videoWidth != width || videoHeight != height) { // if obtainVideoDimens returns either dimen to 0, screen goes black, never recovers
                  if (mp.isPlaying()) {
                      mp.pause();
                  }
                  videoWidth = width;
                  videoHeight = height;
                  calcVidToDisplayRatio();
                  applyTransformTextureView();
                  applyLayoutParamsTextureView();
                  mp.start();
              }
          }
      });
  }
  
  private void initMediaPlayerOnSeekCompleteListener() {
      mediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
          @Override
          public void onSeekComplete(MediaPlayer mp) {
              Log.d(J, "onSeekComplete!");
              launchCheckVidProgThread();
              mp.start(); // see if this changes things
          }
      });
  }
  
  private void initMediaPlayerOnCompletionListener() {
      mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
          @Override
          public void onCompletion(MediaPlayer mp) {
              Log.d(J, "media player onCompletion");
          }
      });
  }
  </code></pre>
  
  <p>Here is the background thread that resets the media player:</p>
  
  <pre><code>private static class CheckVideoProgressRunnable implements Runnable {
  
      private static final int ZERO = 0;
      private static final int SECONDS_IN_ERROR_BEFORE_RESET = 5;
      //private static final int POLLS_PER_SECOND = 12;
      private static final int POLLS_PER_SECOND = 5;
      private static final int FIFTY = 50;
      private static final int ONE_HUNDRED = 100;
      private static final int ONE_THOUSAND = 1000;
      private static final int BUFFER_PERCENT_SAFETY_THRESHOLD = 15;
      private static final long SLEEP_TIME = ONE_THOUSAND / POLLS_PER_SECOND; // roughly 5 x per second
      private int lastPlaybackPosition;
      private int videoDuration;
      private int playbackPositionStuckCount; // tracks the amount of times the playback position remains static
      private int bufferExceededCount; // tracks the amount of times the playback position has exceeded the buffer position + BUFFER_PERCENT_SAFETY_THRESHOLD
  
  
      @Override
      public void run() {
  
          Log.d(J, "start of run in video monitoring thread!!!!!!");
  
          CrashHandler crashHandler = new CrashHandler(context);
          crashHandler.initExceptionHandler();
  
          boolean newLoop = false;
          videoDuration = mediaPlayer.getDuration();
          int percentOfVideoDurationThreshold = videoDuration / FIFTY; // 2% of video duration
          int loopCount = ZERO;
          int playbackStuckThresh =  POLLS_PER_SECOND * SECONDS_IN_ERROR_BEFORE_RESET; // if video is stuck for roughly five seconds, reset mediaPlayer
  
          Log.d(J, "sleep time millis: " + SLEEP_TIME);
          Log.d(J, "playbackStuckThresh: " + playbackStuckThresh);
          Log.d(J, "percent of video duration threshold: " + percentOfVideoDurationThreshold);
          Log.d(J, "video duration: " + videoDuration);
  
          while (!isMediaPlayerNull()) {
              loopCount++;
              try {
                  Thread.sleep(SLEEP_TIME);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              int currentPos = ZERO;
              int currentPlaybackPercent = ZERO;
              int buffPercent = ZERO;
  
              if (!isMediaPlayerNull()) {
                  currentPos = mediaPlayer.getCurrentPosition();
                  if (currentPos == lastPlaybackPosition) {
                      playbackPositionStuckCount++;
                  } else {
                      lastPlaybackPosition = currentPos;
                      playbackPositionStuckCount = ZERO;
                  }
                  currentPlaybackPercent = (int) (((double) currentPos / videoDuration) * ONE_HUNDRED);
  
                  buffPercent = getLastBuffPercent();
  
                  if (buffPercent &gt; ZERO &amp;&amp; (buffPercent + BUFFER_PERCENT_SAFETY_THRESHOLD) &lt; currentPlaybackPercent) {
                      bufferExceededCount++;
                  }
  
                  boolean playbackStuck = playbackPositionStuckCount &gt; playbackStuckThresh;
                  boolean bufferExceeded = bufferExceededCount &gt; playbackStuckThresh;
  
                  if (((bufferExceeded) || playbackStuck)) {
  
                      Log.d(J, "buffer: " + buffPercent + "%");
                      Log.d(J, "position: " + currentPlaybackPercent + "%");
                      Log.d(J, "playback stuck count: " + playbackPositionStuckCount);
                      Log.d(J, "buffer exceeded count: " + bufferExceededCount);
                      Log.d(J, "buffer exceeded: " + bufferExceeded + " playback stuck: " + playbackStuck);
  
                      if (currentPos == 0) {
                          //handler.post(new MediaPlayerSeekRunnable(videoDuration));
                          handler.post(new ResetMediaPlayerRunnable());
                          playbackStuckAtZeroReset = true;
                          break;
                      } else {
                          handler.post(new ResetMediaPlayerRunnable());
                          break;
                      }
                  }
              }
  
              // for setting the volume the video loops (volume does not stay muted on loop without this)
              if (!isMediaPlayerNull()) {
                  if (currentPos &lt; percentOfVideoDurationThreshold &amp;&amp; newLoop) {
  
                      if (volumeMuted) {
                          float zero = Constants.ZERO_FLOAT;
                          mediaPlayer.setVolume(zero, zero);
                          Log.d(J, "mediaPlayer volume: " + zero);
                      } else {
                          float loopingVolume = MyApplication.getCurrentDeviceVolume();
                          mediaPlayer.setVolume(loopingVolume, loopingVolume);
                          Log.d(J, "mediaPlayer volume: " + loopingVolume);
                      }
                      Log.d(J, "video looping!!!!!!!!!!!!!!!!!!! volume muted: " + volumeMuted);
                      newLoop = false;
                      Log.d(J, "new loop set: " + newLoop);
                  }
              }
  
              if (videoDuration - currentPos &lt; percentOfVideoDurationThreshold &amp;&amp; !newLoop) {
                  newLoop = true;
                  Log.d(J, "new loop set: " + newLoop);
              }
  
              // these conditions are for logging only
              if (newLoop) {
                  Log.d(J, "current position: " + currentPos + "  duration: " + videoDuration);
              }
              if (loopCount % (ONE_THOUSAND / SLEEP_TIME) == ZERO) { // once a second
                  Log.d(J, "current position: " + currentPos);
                  Log.d(J, "position: " + currentPlaybackPercent + "%");
                  Log.d(J, "buffer: " + buffPercent + "%");
              }
          }
          Log.d(J, "video monitoring thread hit break");
      }
  }
  </code></pre>