com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator.java

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.exoplayer2.ext.mediasession;

import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * An abstract implementation of the {@link MediaSessionConnector.QueueNavigator} that maps the
 * windows of a {@link Player}'s {@link Timeline} to the media session queue.
 */
public abstract class TimelineQueueNavigator implements MediaSessionConnector.QueueNavigator {

    public static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
    public static final int DEFAULT_MAX_QUEUE_SIZE = 10;

    private final MediaSessionCompat mediaSession;
    protected final int maxQueueSize;

    private long activeQueueItemId;

    /**
     * Creates an instance for a given {@link MediaSessionCompat}.
     * <p>
     * Equivalent to {@code TimelineQueueNavigator(mediaSession, DEFAULT_MAX_QUEUE_SIZE)}.
     *
     * @param mediaSession The {@link MediaSessionCompat}.
     */
    public TimelineQueueNavigator(MediaSessionCompat mediaSession) {
        this(mediaSession, DEFAULT_MAX_QUEUE_SIZE);
    }

    /**
     * Creates an instance for a given {@link MediaSessionCompat} and maximum queue size.
     * <p>
     * If the number of windows in the {@link Player}'s {@link Timeline} exceeds {@code maxQueueSize},
     * the media session queue will correspond to {@code maxQueueSize} windows centered on the one
     * currently being played.
     *
     * @param mediaSession The {@link MediaSessionCompat}.
     * @param maxQueueSize The maximum queue size.
     */
    public TimelineQueueNavigator(MediaSessionCompat mediaSession, int maxQueueSize) {
        this.mediaSession = mediaSession;
        this.maxQueueSize = maxQueueSize;
        activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
    }

    /**
     * Gets the {@link MediaDescriptionCompat} for a given timeline window index.
     *
     * @param windowIndex The timeline window index for which to provide a description.
     * @return A {@link MediaDescriptionCompat}.
     */
    public abstract MediaDescriptionCompat getMediaDescription(int windowIndex);

    @Override
    public long getSupportedQueueNavigatorActions(Player player) {
        if (player == null || player.getCurrentTimeline().getWindowCount() < 2) {
            return 0;
        }
        if (player.getRepeatMode() != Player.REPEAT_MODE_OFF) {
            return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
                    | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
        }

        int currentWindowIndex = player.getCurrentWindowIndex();
        long actions;
        if (currentWindowIndex == 0) {
            actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
        } else if (currentWindowIndex == player.getCurrentTimeline().getWindowCount() - 1) {
            actions = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
        } else {
            actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
        }
        return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
    }

    @Override
    public final void onTimelineChanged(Player player) {
        publishFloatingQueueWindow(player);
    }

    @Override
    public final void onCurrentWindowIndexChanged(Player player) {
        if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID
                || player.getCurrentTimeline().getWindowCount() > maxQueueSize) {
            publishFloatingQueueWindow(player);
        } else if (!player.getCurrentTimeline().isEmpty()) {
            activeQueueItemId = player.getCurrentWindowIndex();
        }
    }

    @Override
    public final long getActiveQueueItemId(@Nullable Player player) {
        return activeQueueItemId;
    }

    @Override
    public void onSkipToPrevious(Player player) {
        Timeline timeline = player.getCurrentTimeline();
        if (timeline.isEmpty()) {
            return;
        }
        int previousWindowIndex = player.getPreviousWindowIndex();
        if (player.getCurrentPosition() > MAX_POSITION_FOR_SEEK_TO_PREVIOUS
                || previousWindowIndex == C.INDEX_UNSET) {
            player.seekTo(0);
        } else {
            player.seekTo(previousWindowIndex, C.TIME_UNSET);
        }
    }

    @Override
    public void onSkipToQueueItem(Player player, long id) {
        Timeline timeline = player.getCurrentTimeline();
        if (timeline.isEmpty()) {
            return;
        }
        int windowIndex = (int) id;
        if (0 <= windowIndex && windowIndex < timeline.getWindowCount()) {
            player.seekTo(windowIndex, C.TIME_UNSET);
        }
    }

    @Override
    public void onSkipToNext(Player player) {
        Timeline timeline = player.getCurrentTimeline();
        if (timeline.isEmpty()) {
            return;
        }
        int nextWindowIndex = player.getNextWindowIndex();
        if (nextWindowIndex != C.INDEX_UNSET) {
            player.seekTo(nextWindowIndex, C.TIME_UNSET);
        }
    }

    // CommandReceiver implementation.

    @Override
    public String[] getCommands() {
        return null;
    }

    @Override
    public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
        // Do nothing.
    }

    private void publishFloatingQueueWindow(Player player) {
        if (player.getCurrentTimeline().isEmpty()) {
            mediaSession.setQueue(Collections.<MediaSessionCompat.QueueItem>emptyList());
            activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
            return;
        }
        int windowCount = player.getCurrentTimeline().getWindowCount();
        int currentWindowIndex = player.getCurrentWindowIndex();
        int queueSize = Math.min(maxQueueSize, windowCount);
        int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0,
                windowCount - queueSize);
        List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
        for (int i = startIndex; i < startIndex + queueSize; i++) {
            queue.add(new MediaSessionCompat.QueueItem(getMediaDescription(i), i));
        }
        mediaSession.setQueue(queue);
        activeQueueItemId = currentWindowIndex;
    }

}