我正在开发一个应用程序,可以在零售点无限期地循环本地视频文件 . 我正在使用Android MediaPlayer类,并为Minix X8 Plus开发,这是一个在Kodi版本的Android 4.4.2上运行的多媒体中心 . 设备将在HDMI屏幕上播放视频 .

大多数情况下,视频播放成功,但我很难定期冻结图像,而音频一直播放 . 冻结的频率会使视频与视频不同,但几乎总是在视频播放的开始时,可以是第一遍或后续循环 . 媒体播放器设置为循环 mediaPlayer.setLooping(true) 我启用了以下侦听器:

  • MediaPlayer.onErrorListener

  • MediaPlayer.onInfoListener

  • MediaPlayer.OnVideoSizeChangedListener

  • MediaPlayer.OnSeekCompleteListener

  • MediaPlayer.OnBufferingUpdateListener

  • MediaPlayer.OnPreparedListener

在冻结期间,我没有得到任何回调,没有错误,没有信息回调 . 我认为问题必须是Android MediaPlayer的内部问题,因为我没有得到异常或错误,问题只发生在5-20%的时间内,具体取决于视频 .

我有一个解决方法,我监视视频的位置"playhead" 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");
    }
}