安卓:MediaPlayer实现无缝视频播放

26
我通过实现OnCompletionListener将数据源设置为不同的文件,可以正常连续播放视频。这部分没有问题。我成功调用了reset()和prepare()。
但是我一直没能解决的问题是如何消除数据源更改和新视频开始之间的1-2秒黑屏闪烁。这个间隙会显示一个黑屏,我找不到任何方法来解决它。
我尝试将父视图的背景设置为图像,但仍然无法解决。即使SurfaceView是透明的(默认情况下也是如此)。我还尝试过同时播放多个视频文件,并在其中一个结束并应该开始另一个时切换mediaplayer的显示。
我最后尝试的是,在视频“准备”时显示一个临时的背景视图,并在视频准备好开始时将其删除。但这也不是非常流畅。
有没有办法消除这个间隙?循环播放视频非常出色,除了选择的不同视频以外,它完全满足我的要求。
main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:background="@drawable/background"
    android:layout_height="fill_parent">
    <SurfaceView
        android:id="@+id/surface"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center">
    </SurfaceView>
</FrameLayout>

Player.java

public class Player extends Activity implements
        OnCompletionListener, MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
        private MediaPlayer player;
    private SurfaceView surface;
    private SurfaceHolder holder;

    public void onCreate(Bundle b) {
        super.onCreate(b);
        setContentView(R.layout.main);
        surface = (SurfaceView)findViewById(R.id.surface);
        holder = surface.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);    
    }

    public void onCompletion(MediaPlayer arg0) {
        File clip = new File(Environment.getExternalStorageDirectory(),"file2.mp4");
        playVideo(clip.getAbsolutePath());
    }
    public void onPrepared(MediaPlayer mediaplayer) {
        holder.setFixedSize(player.getVideoWidth(), player.getVideoHeight());
        player.start();
    }

    private void playVideo(String url) {
        try {
            File clip = new File(Environment.getExternalStorageDirectory(),"file1.mp4");

            if (player == null) {
                player = new MediaPlayer();
                player.setScreenOnWhilePlaying(true);
            }
            else {
                player.stop();
                player.reset();
            }
            player.setDataSource(url);
            player.setDisplay(holder);
            player.setOnPreparedListener(this);
            player.prepare();
            player.setOnCompletionListener(this);
        }
        catch (Throwable t) {
            Log.e("ERROR", "Exception Error", t);
        }
    }

虽然这个问题已经在一段时间前发布过了,但我仍然面临同样的问题。即使使用了VideoView,当切换视频时仍然存在间隙。有没有其他方法可以解决这个问题? - dulys
7个回答

5
我也遇到了以下链接中所述的同样问题:VideoView在更改视频时会消失一秒钟。但如果您尝试使用Android 4.0+ (ICS) ,则不会出现此问题。我开始将VideoView.java和MediaPlayer.java从4.0移植到我的应用程序中,但似乎这很复杂,并且到目前为止没有成功。基本上,这似乎是旧版本本地代码中的错误。

3

经过很多浪费时间试图找出如何播放连续视频而没有“间隙”,我倾向于认为这是不可能的。除非你能深入到本地层面并实现自己的播放器,否则Android的媒体播放器目前无法支持无缝播放。


我通过一种hack的方式实现了这个功能。我在服务中的两个MediaPlayer实例之间切换。我有一个可配置的int变量,使得在媒体资产A结束前“N”秒开始播放媒体资产B。 - Krafty
这很酷,但从经验来看,即使在同一设备上进行测试,确切的时间设置几乎是不可能的,因为设备有时会内存不足,使得过渡不精确。 - josephus
@user721448 无论如何,我建议您将代码发布在此处作为答案,并让我们测试它。我已经开始进行其他项目,但仍然对解决方案感到好奇。 - josephus
我的代码涵盖了许多函数,长度超过了14,000个字符,无法在注释中进行说明。我曾经试过:))但是由于声望不够,我不能发表答案,否则我会很乐意帮忙的。 - Krafty
顺便说一下,这确实是一个hack,有时候轨道会重叠,有时会有间隙,有时则完美无缺。我使用获取持续时间的方法,它是我的自定义播放器用来填充进度条的,作为检查何时该去获取下一个媒体资产进行流式传输的方式。我将其设置为可配置,并且目前设置为7秒。 - Krafty
作为最后的评论,我目前正在尝试获取基于C++代码的mediaplayer副本,并将其覆盖到我的播放器中。媒体播放器有一个可怕的“功能”,如果它尝试流式传输不支持的编解码器,它会弹出模态框,显示“无法播放此视频”,并且无法通过编程方式防止或删除它们,这破坏了用户体验。由于我从第三方流式传输编解码器,因此无法更改编解码器,因此仅因为这个“功能”,我对mediaplayer表示反对。 - Krafty

1

我没有在MediaPlayer上进行视频播放,但是当用户从3G切换到Wifi时,我已经用类似的方法处理过音频文件的流中断问题。

我认为你看到的延迟是由于媒体播放器正在缓冲输入文件。因此,也许你可以在开始时定义两个播放器?你应该定义数据源并准备好两个播放器,但只启动其中一个。

然后在你的onCompletionListener中,你可以翻转它们,而不是重置现有的播放器并设置新的数据源。

  player.release();
  player = flipPlayer;
  flipPlayer = null;
  player.start();

显然,您需要为flipPlayer使用不同的onPreparedListener,或者将player.start()移出onPrepare。(由于您正在同步调用它,我认为这不是问题)。


嗯,值得一试。不过 release() 会不会在那里留下一个空白屏幕呢? - josephus

1

0

你尝试过准备好两个VideoView,其中一个可见,另一个不可见且停止,当你收到OnCompletionListener回调时,让第二个可见,开始播放并隐藏第一个。同时,当第二个正在播放时,你可以调用第一个VideoView的open/prepare来打开/准备另一个文件。


onCompletionListener会在第一个视频播放完毕后立即被调用。间隙将从那里开始,在更改可见性后开始播放第二个视频,然后结束。这样做不起作用。 - josephus

0

@user1263019 - 你能够将Android 4.0 MediaPlayer移植到你的应用程序中吗?我也遇到了同样的问题,正在寻找一个好的解决方案。我的情况是在SurfaceView上有一张图片,应该隐藏它以显示视频播放,但在调用start()和实际开始播放视频之间存在间隙。目前唯一的解决方案是启动一个线程来检查getCurrentPosition()是否大于0。

关于这个话题 - 我认为无法实现无缝播放,尽管Winamp声称具有这种功能。一个不太优雅的解决方法是在第一个播放器播放结束几秒钟之前准备第二个播放器,并在播放结束前100-200毫秒调用第二个播放器的start()。


0
在您的ExoPlayer实现(或MediaPlayer)中,使用处理程序重复发布可运行项,同时播放,以获取当前跟踪时间并将其传递给回调函数:
public interface MediaAction {
  public void onTrackingChanged(long currentPosition);
}

public class ExoPlayerImplementation implements Exoplayer {

  ..

  private MediaAction mediaActionCallback;
  public void setMediaActionCallback(MediaAction ma) {
    mediaActionCallback = ma;
  }
  public void releaseMediaAction() { mediaActionCallback = null; } 

  private Handler trackingCallback = new Handler(Looper.getMainLooper());

  Runnable trackingTask;
  private Runnable getTrackingTask() {
    Runnable r = new Runnable {
      @Override public void run() {
         long currentPosition = getCurrentPosition();
         if (mediaActionCallback != null) {
           mediaActionCallback.onTrackingChanged(currentPosition);
         }
         // 60hz is 16 ms frame delay...
         trackingCallback.postDelayed(getTrackingTask(), 15);
       }
    };
    trackingTask = r;
    return r;
  }

  public void play() {
    // exoplayer start code
    trackingCallback.post(getTrackingTask());
  } 

  public void pause/stop() {
    trackingCallback.removeCallbacks(trackingTask);
  }

  ..

}

现在,您可以跟踪当前位置何时实际更改--如果已更改,则可以显示视频输出视图/交换视图/删除预览(即,使用MediaExtractor抓取初始帧,在ImageView中叠加作为预览,然后在当前位置增加时隐藏)。通过将其与状态侦听器结合使用,您可以使此代码更完整:然后您就知道是否正在缓冲(检查缓冲位置与当前位置),实际播放,暂停或停止,并具有适当的回调(在MediaAction中)。您可以将此方法应用于MediaPlayer和ExoPlayer类(因为它只是一个接口和处理程序)。
希望这有所帮助!

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