如何使用MediaPlayer在列表视图中播放多个视频?

7
我将尝试实现视频作为元素的列表视图。我正在使用此项目在纹理视图上显示视频。它在底层使用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方法,但我遇到了看起来像设备特定平台错误

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

谢谢

w.


1
你是想要在一个ListView中同时播放多个视频吗?还是只是想要加载这些视频并为播放做好准备(一次只播放一个),以便用户点击后播放? - Yevgeny Simkin
1
@Yvette 理想情况下,两个视频都应该播放。它们不需要在完全相同的时间开始,但如果两个视频适合屏幕,那么两个视频都应该播放。 - wonglik
1
@Yvette 当声音混合时它们会变得不愉快。我将播放器静音,所以这不是问题所在。 - wonglik
创建另一个线程来播放视频怎么样? - Randyka Yudhistira
你不觉得是ListView搞砸了你的派对吗?我是说,好好想想。 - Elltz
显示剩余7条评论
5个回答

2

您可以尝试使用此方法代替库。这是从Google在github上的示例中提取的:

Decodes two video streams simultaneously to two TextureViews.

One key feature is that the video decoders do not stop when the activity is restarted due to an orientation change. This is to simulate playback of a real-time video stream. If the Activity is pausing because it's "finished" (indicating that we're leaving the Activity for a nontrivial amount of time), the video decoders are shut down.

TODO: consider shutting down when the screen is turned off, to preserve battery.

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>


1
这是来自Google Grafika的一个示例。它大部分时间都能正常工作。如果你好奇为什么我不能使用它,请查看我的其他SO问题 - wonglik

2

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

我只是告诉你这个逻辑,希望这能起作用。


问题是这些视频应该循环播放。所以当完成钩子被调用时,我只需倒回到开头并重新播放。此外,最初的想法是在屏幕上可见时播放视频。问题在于同时播放视频似乎是不可能的,而且使用MediaPlayer也有问题。 - wonglik
我找到了一些可能对你有帮助的东西,当我在寻找解决方案时,我也想起了一条新闻,说一些高端三星手机可以播放多个视频文件,我几年前也看到过这条新闻,所以这完全取决于硬件... - Abdul Aziz

1

这些答案都没有被采纳,也不适用于我的问题。 - wonglik
@wonglik,你的问题是关于ListView,对吗?还是你指的是一个“列表视图”?如果是ListView,你能否澄清一下你的问题与我给出的链接中的问题有何不同? - user5487924
我的问题是同时播放两个视频,这些视频恰好在列表视图中。它们也可以在LinearLayout中,问题仍然是一样的。 - wonglik

1

但我确实需要一个ListView。 - wonglik

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接