我正在开发一款可在零售点无限期循环本地视频文件的应用。我正在使用Android MediaPlayer类,并为Minix X8 Plus开发,这是一个在Kodi的Android 4.4.2版本上运行的多媒体中心。设备将在HDMI屏幕上播放视频。
大多数情况下,视频播放成功,但我很难定期冻结图像,而音频一直在播放。冻结的频率会使视频与视频不同,但几乎总是在视频播放的开始时,可以是第一遍或后续循环。媒体播放器设置为循环mediaPlayer.setLooping(true)
我启用了以下侦听器:
在冻结期间,我没有得到任何回调,没有错误,没有信息回调。我认为问题必须是Android MediaPlayer的内部问题,因为我没有得到异常或错误,问题只发生在5-20%的时间内,具体取决于视频。
我有一个工作环境,我可以监控视频的位置"播放头" mediaPlayer.getCurrentPosition()
与媒体播放器缓存的数量MediaPlayer.OnBufferingUpdateListener.onBufferingUpdate()
相比,后者返回当前缓冲的视频文件的百分比。我发现有时会发生冻结并且播放位置不断更新,好像一切都很好,但最终播放位置将超过播放器缓冲的数量。如果发生这种情况,请致电mediaPlayer.reset()
,然后再次调用mediaPlayer.setDataSource()
和mediaPlayer.prepareAsync()
完成整个设置流程。我还监控mediaPlayer.getCurrentPosition()
重复返回相同的位置。在某些视频中,这可能发生在开头(卡在0位置)或在视频冻结后结束(卡在最后一个位置,无法循环)。
我真的很想找到另一种解决方案。所有视频的行为都不同,即使重置了媒体播放器,我也会留下一个冻结的图片,时间长得足以成为一个问题。
我已经梳理了SO以寻求解决方案。问题与此类似:SO post here, but not identical.
正如该帖子中所建议的,我在轮询mediaPlayer.getVideoWidth() and mediaPlayer.getVideoHeight()
之前直到它在开始播放之前返回非0数字。
我看过ExoPlayer,但是在播放Android MediaPlayer没有问题的视频文件时崩溃了。我还读到here使用ExoPlayer播放本地文件没有多大优势。
我也看过Grafika作为MediaPlayer的替代品,但它只是视频,没有音频,并且不是一个完整的媒体播放器。
我也看过VLC for Android,但是没有很多关于在你的应用程序中将它用作库的文档,并且它仍然在大量工作。
如果有人遇到过类似的问题,或者建议使用Android MediaPlayer的开源替代品,我很乐意听取您的意见。
以下是播放视频的活动中的一些代码。它已经很多了,我试图将其削减到媒体播放器代码。
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() -> 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 && 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 > 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");
}
});
}
以下是重置媒体播放器的后台线程:
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 > ZERO && (buffPercent + BUFFER_PERCENT_SAFETY_THRESHOLD) < currentPlaybackPercent) {
bufferExceededCount++;
}
boolean playbackStuck = playbackPositionStuckCount > playbackStuckThresh;
boolean bufferExceeded = bufferExceededCount > 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 < percentOfVideoDurationThreshold && 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 < percentOfVideoDurationThreshold && !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");
}
}