ExoPlayer 播放列表使用 LoopingMediaSource 抛出 IndexOutOfBoundsException

ExoPlayer playlist throws IndexOutOfBoundsException with LoopingMediaSource

我必须播放一次视频 A,播放完后无限循环播放视频 B。我正尝试为此使用 ConcatenatingMediaSource:

private SimpleExoPlayer initPlayer(ViewGroup layout, int playerViewId, ExoPlayer.EventListener eventListener) {
    // 1. Create a default TrackSelector
    TrackSelector trackSelector = new DefaultTrackSelector();

    // 2. Create a default LoadControl
    LoadControl loadControl = new DefaultLoadControl();

    // 3. Create the player
    this.player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, loadControl);

    SimpleExoPlayerView simpleExoPlayerView = (SimpleExoPlayerView) layout.findViewById(playerViewId);
    // Bind the player to the view.
    simpleExoPlayerView.setUseController(false);
    simpleExoPlayerView.setPlayer(player);

    if (eventListener != null)
        player.addListener(eventListener);
    // Prepare the player with the source.
    player.setPlayWhenReady(true);

    return player;
}

public void startPlayer(String firstURL, String loopingURL) {
    initProxy();

    DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    MediaSource firstSource = getVideoPlayerMediaSource(bandwidthMeter, firstURL);
    MediaSource secondSource = new LoopingMediaSource(getVideoPlayerMediaSource(bandwidthMeter, loopingURL));

    ConcatenatingMediaSource concatenatedSource =
            new ConcatenatingMediaSource(firstSource, secondSource);

    player.setPlayWhenReady(true);
    player.prepare(concatenatedSource);
    player.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);

    setPlayerPlaying(true);
}

private void initProxy() {
    if (proxy == null)
        proxy = VideoCache.getProxy(getContext());
}

@NonNull
private MediaSource getVideoPlayerMediaSource(DefaultBandwidthMeter bandwidthMeter, String videoUrl) {
    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(),
            Util.getUserAgent(getContext(), "com.myapp"), bandwidthMeter);

    ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();

    Uri url = Uri.parse(videoUrl);
    MediaSource videoSource;

    if (videoUrl.contains(".mp4")) {
        url = Uri.parse(proxy.getProxyUrl(videoUrl));
        videoSource = new ExtractorMediaSource(url,
                dataSourceFactory, extractorsFactory, null, null);
    } else {
        videoSource = new HlsMediaSource(url, dataSourceFactory, null, null);
    }


    return videoSource;
}

但这抛出:

Internal runtime error.
java.lang.IndexOutOfBoundsException
    at com.google.android.exoplayer2.util.Assertions.checkIndex(Assertions.java:66)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.getPeriodPosition(ExoPlayerImplInternal.java:1077)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.getPeriodPosition(ExoPlayerImplInternal.java:1059)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.getPeriodPosition(ExoPlayerImplInternal.java:1050)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.handleSourceInfoRefreshed(ExoPlayerImplInternal.java:872)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:320)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:148)
    at android.os.HandlerThread.run(HandlerThread.java:61)
    at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)

仅当将 secondSource 用作 LoopingMediaSource 时才会出现此问题。没有它它也能工作,但显然不会循环播放第二个视频。

(ExoPlayer 版本 r2.3.1)

尝试为 LoopingMediaSource 使用其他构造函数,在其中指定循环计数并查看是否有效:

public LoopingMediaSource(MediaSource childSource, int loopCount) {
    Assertions.checkArgument(loopCount > 0);
    this.childSource = childSource;
    this.loopCount = loopCount;
}

您似乎收到了 handleSourceInfoRefreshed 消息,该消息是从 LoopingMediaSource prepareSource() 方法发送的:

@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, final Listener listener) {
    childSource.prepareSource(player, false, new Listener() {
      @Override
      public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
        childPeriodCount = timeline.getPeriodCount();
        listener.onSourceInfoRefreshed(new LoopingTimeline(timeline, loopCount), manifest);
      }
    });
}

但这会创建一个新的 LoopingTimeline 对象并传递给它 loopCount,在您的情况下为 0。 LoopingTimeline class 覆盖 getWindowCount():

@Override
public int getWindowCount() {
  return childWindowCount * loopCount;
}

如果 loopCount 为 0,则 return0。

如果可行,那么您可以通过传递 (Integer.MAX_VALUE - 1) 作为循环计数来让您的视频无限循环,因为 LoopingTimeline 无论如何都会限制循环计数。