【问题标题】:How to play multiple videos in a list view using MediaPlayer?如何使用 MediaPlayer 在列表视图中播放多个视频?
【发布时间】:2015-10-29 08:07:43
【问题描述】:

我正在尝试使用视频作为元素来实现列表视图。我正在使用this project 在纹理视图上显示视频。它在下面使用 MediaPlayer。同时加载两个视频时(大多数情况下)失败。

我得到的错误是:

TextureVideoView error. File or network related operation errors.

MediaPlayer: error (1, -2147479551)

当从磁盘加载文件时也会发生这种情况

在错误处理部分,我尝试重置 URL。然后我大多得到

E/BufferQueueProducer: [unnamed-30578-12] disconnect(P): connected to another API (cur=0 req=3)

错误。我不清楚的是,从网络上设置一些任意视频会起作用,但重试相同的 URL 会失败。

所以在 OnErrorListener 中:

textureView.setVideo(item.getUriMp4(),MediaFensterPlayerController.DEFAULT_VIDEO_START); 

会失败,但是:

textureView.setVideo("http://different.video" ... )

效果很好。

这也不是特定文件的问题,因为滚动不同的视频文件会失败。有时那些失败的将在下一次工作等。

我也尝试了 MediaCodecMediaExtractor 组合而不是 MediaPlayer 方法,但我遇到了,看起来像 device specific platform bug

有什么提示吗?有什么建议吗?

谢谢

w.

【问题讨论】:

  • 您是否要让多个视频在列表视图中同时*播放?或者,如果用户点击它们,您是否只想加载它们并准备播放(一次播放一个)?
  • 我试图在可见时立即播放它们,但如果这是限制,我可以以某种方式延迟其他视频的开始。
  • @Yvette ,如果屏幕上出现两行(99% 的时间),那么它将尝试同时播放。
  • @Yvette 理想情况下两者都会玩。它们不需要在完全相同的时间开始,但如果两个视频都适合屏幕,则两者都应该播放
  • @Yvette 他们在混音时很不愉快。我将玩家静音,所以这不是这里的问题。

标签: android video android-listview android-mediaplayer mp4


【解决方案1】:

您可以试试这个而不是库它取自 Google 在 github 上的示例:

同时将两个视频流解码为两个 TextureView。

一个关键特性是视频解码器在活动重新启动时不会停止,因为 到方向改变。这是为了模拟实时视频流的播放。如果 Activity 正在暂停,因为它“已完成”(表明我们要离开 Activity 一段不平凡的时间),视频解码器被关闭。

TODO:考虑在屏幕关闭时关闭,以节省电池电量。

Java:

DoubleDecodeActivity.java

public class DoubleDecodeActivity extends Activity {
    private static final String TAG = MainActivity.TAG;

    private static final int VIDEO_COUNT = 2;
    //How many videos to play simultaneously.

    // Must be static storage so they'll survive Activity restart.
    private static boolean sVideoRunning = false;
    private static VideoBlob[] sBlob = new VideoBlob[VIDEO_COUNT];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_double_decode);

        if (!sVideoRunning) {
            sBlob[0] = new VideoBlob((TextureView) findViewById(R.id.double1_texture_view),
                    ContentManager.MOVIE_SLIDERS, 0);
            sBlob[1] = new VideoBlob((TextureView) findViewById(R.id.double2_texture_view),
                    ContentManager.MOVIE_EIGHT_RECTS, 1);
            sVideoRunning = true;
        } else {
            sBlob[0].recreateView((TextureView) findViewById(R.id.double1_texture_view));
            sBlob[1].recreateView((TextureView) findViewById(R.id.double2_texture_view));
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        boolean finishing = isFinishing();
        Log.d(TAG, "isFinishing: " + finishing);
        for (int i = 0; i < VIDEO_COUNT; i++) {
            if (finishing) {
                sBlob[i].stopPlayback();
                sBlob[i] = null;
            }
        }
        sVideoRunning = !finishing;
        Log.d(TAG, "onPause complete");
    }


    /**
     * Video playback blob.
     * <p>
     * Encapsulates the video decoder and playback surface.
     * <p>
     * We want to avoid tearing down and recreating the video decoder on orientation changes,
     * because it can be expensive to do so.  That means keeping the decoder's output Surface
     * around, which means keeping the SurfaceTexture around.
     * <p>
     * It's possible that the orientation change will cause the UI thread's EGL context to be
     * torn down and recreated (the app framework docs don't seem to make any guarantees here),
     * so we need to detach the SurfaceTexture from EGL on destroy, and reattach it when
     * the new SurfaceTexture becomes available.  Happily, TextureView does this for us.
     */
    private static class VideoBlob implements TextureView.SurfaceTextureListener {
        private final String LTAG;
        private TextureView mTextureView;
        private int mMovieTag;

        private SurfaceTexture mSavedSurfaceTexture;
        private PlayMovieThread mPlayThread;
        private SpeedControlCallback mCallback;

        /**
         * Constructs the VideoBlob.
         *
         * @param view The TextureView object we want to draw into.
         * @param movieTag Which movie to play.
         * @param ordinal The blob's ordinal (only used for log messages).
         */
        public VideoBlob(TextureView view, int movieTag, int ordinal) {
            LTAG = TAG + ordinal;
            Log.d(LTAG, "VideoBlob: tag=" + movieTag + " view=" + view);
            mMovieTag = movieTag;

            mCallback = new SpeedControlCallback();

            recreateView(view);
        }

        /**
         * Performs partial construction.  The VideoBlob is already created, but the Activity
         * was recreated, so we need to update our view.
         */
        public void recreateView(TextureView view) {
            Log.d(LTAG, "recreateView: " + view);
            mTextureView = view;
            mTextureView.setSurfaceTextureListener(this);
            if (mSavedSurfaceTexture != null) {
                Log.d(LTAG, "using saved st=" + mSavedSurfaceTexture);
                view.setSurfaceTexture(mSavedSurfaceTexture);
            }
        }

        /**
         * Stop playback and shut everything down.
         */
        public void stopPlayback() {
            Log.d(LTAG, "stopPlayback");
            mPlayThread.requestStop();
            // TODO: wait for the playback thread to stop so we don't kill the Surface
            //       before the video stops

            // We don't need this any more, so null it out.  This also serves as a signal
            // to let onSurfaceTextureDestroyed() know that it can tell TextureView to
            // free the SurfaceTexture.
            mSavedSurfaceTexture = null;
        }

        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) {
            Log.d(LTAG, "onSurfaceTextureAvailable size=" + width + "x" + height + ", st=" + st);

            // If this is our first time though, we're going to use the SurfaceTexture that
            // the TextureView provided.  If not, we're going to replace the current one with
            // the original.

            if (mSavedSurfaceTexture == null) {
                mSavedSurfaceTexture = st;

                File sliders = ContentManager.getInstance().getPath(mMovieTag);
                mPlayThread = new PlayMovieThread(sliders, new Surface(st), mCallback);
            } else {
                // Can't do it here in Android <= 4.4.  The TextureView doesn't add a
                // listener on the new SurfaceTexture, so it never sees any updates.
                // Needs to happen from activity onCreate() -- see recreateView().
                //Log.d(LTAG, "using saved st=" + mSavedSurfaceTexture);
                //mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
            }
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) {
            Log.d(LTAG, "onSurfaceTextureSizeChanged size=" + width + "x" + height + ", st=" + st);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
            Log.d(LTAG, "onSurfaceTextureDestroyed st=" + st);
            // The SurfaceTexture is already detached from the EGL context at this point, so
            // we don't need to do that.
            //
            // The saved SurfaceTexture will be null if we're shutting down, so we want to
            // return "true" in that case (indicating that TextureView can release the ST).
            return (mSavedSurfaceTexture == null);
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture st) {
            //Log.d(TAG, "onSurfaceTextureUpdated st=" + st);
        }
    }

    /**
     * Thread object that plays a movie from a file to a surface.
     * <p>
     * Currently loops until told to stop.
     */
    private static class PlayMovieThread extends Thread {
        private final File mFile;
        private final Surface mSurface;
        private final SpeedControlCallback mCallback;
        private MoviePlayer mMoviePlayer;

        /**
         * Creates thread and starts execution.
         * <p>
         * The object takes ownership of the Surface, and will access it from the new thread.
         * When playback completes, the Surface will be released.
         */
        public PlayMovieThread(File file, Surface surface, SpeedControlCallback callback) {
            mFile = file;
            mSurface = surface;
            mCallback = callback;

            start();
        }

        /**
         * Asks MoviePlayer to halt playback.  Returns without waiting for playback to halt.
         * <p>
         * Call from UI thread.
         */
        public void requestStop() {
            mMoviePlayer.requestStop();
        }

        @Override
        public void run() {
            try {
                mMoviePlayer = new MoviePlayer(mFile, mSurface, mCallback);
                mMoviePlayer.setLoopMode(true);
                mMoviePlayer.play();
            } catch (IOException ioe) {
                Log.e(TAG, "movie playback failed", ioe);
            } finally {
                mSurface.release();
                Log.d(TAG, "PlayMovieThread stopping");
            }
        }
    }
}

XML:

activity_double_decode.xml

<?xml version="1.0" encoding="utf-8"?>

<!-- portrait layout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:baselineAligned="false"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal"
        android:layout_weight="1"
        android:layout_marginBottom="8dp" >

        <TextureView
            android:id="@+id/double1_texture_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal"
        android:layout_weight="1" >

        <TextureView
            android:id="@+id/double2_texture_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

【讨论】:

  • 这是来自 Google Grafika 的示例。它大部分时间都有效。如果你好奇为什么我不能使用它,请查看我的另一个SO question
【解决方案2】:

将所有视频路径添加到数组或ArrayList中并实现mediaplayer.setOnMediaPlayerCompletionListener,当媒体播放时,将从这里调用此接口初始化提供新媒体的新媒体播放器实例并调用start()

我只是告诉你逻辑,我希望这会奏效

【讨论】:

  • 问题是这些视频应该循环播放。因此,当调用完成钩子时,我只需倒退到开始并重新播放。除了最初的想法是在屏幕上可见时播放视频。问题在于同时播放视频,MediaPlayer 似乎不可能
  • 我为您找到了一些可能对您有帮助的东西,在寻找解决方案的同时,我还想起了一个消息,一些高端三星手机可以播放多个视频文件,我在几年前的新闻中也看到了所以它也完全取决于硬件......stackoverflow.com/a/10161316/5479863
【解决方案3】:

使用 VideoView 而不是 ListView 它可能会起作用。看看这里 http://developer.android.com/reference/android/widget/VideoView.html

【讨论】:

  • 但我确实需要一个 ListView。
【解决方案4】:

这个问题在这里已经有好几个答案了:stackoverflow.com/questions/31532893/i-want-to-display-multiple-video-in-listview-using-video-but-not-able-to-do-this。除非您的问题不同或更具体,否则此主题将被标记为重复。

【讨论】:

  • 实际上没有一个答案被接受或适用于我的问题。
  • @woglik 你的问题是关于 ListView 的,对吧?还是您的意思是列表视图?如果是 ListView,您能否说明您的问题与我提供的链接中的问题有何不同?
  • 我的问题是同时播放两个恰好在列表视图中的视频。它们也可以在 LinearLayout 中,问题完全相同。
【解决方案5】:

当前解决方案: 我推荐 JavaCV / OpenCV 在 Java 中一次播放多个视频。支持多种格式。

教程 - http://ganeshtiwaridotcomdotnp.blogspot.co.nz/search/label/OpenCV-JavaCV

JavaFX 还可以播放一些 .MP4 视频格式。

旧解决方案:- 尽管 JMF 可以一次播放多个视频,但它已过时且不再维护。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-10
    • 2017-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多