<commit_before>/*
 * 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.cast;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.MediaTrack;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManager;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * {@link Player} implementation that communicates with a Cast receiver app.
 *
 * <p>The behavior of this class depends on the underlying Cast session, which is obtained from the
 * Cast context passed to {@link #CastPlayer}. To keep track of the session,
 * {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
 * implemented and attached to the player.</p>
 *
 * <p> If no session is available, the player state will remain unchanged and calls to methods that
 * alter it will be ignored. Querying the player state is possible even when no session is
 * available, in which case, the last observed receiver app state is reported.</p>
 *
 * <p>Methods should be called on the application's main thread.</p>
 */
public final class CastPlayer implements Player {

  /**
   * Listener of changes in the cast session availability.
   */
  public interface SessionAvailabilityListener {

    /**
     * Called when a cast session becomes available to the player.
     */
    void onCastSessionAvailable();

    /**
     * Called when the cast session becomes unavailable.
     */
    void onCastSessionUnavailable();

  }

  private static final String TAG = "CastPlayer";

  private static final int RENDERER_COUNT = 3;
  private static final int RENDERER_INDEX_VIDEO = 0;
  private static final int RENDERER_INDEX_AUDIO = 1;
  private static final int RENDERER_INDEX_TEXT = 2;
  private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
  private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
      new TrackSelectionArray(null, null, null);
  private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];

  private final CastContext castContext;
  // TODO: Allow custom implementations of CastTimelineTracker.
  private final CastTimelineTracker timelineTracker;
  private final Timeline.Window window;
  private final Timeline.Period period;

  private RemoteMediaClient remoteMediaClient;

  // Result callbacks.
  private final StatusListener statusListener;
  private final SeekResultCallback seekResultCallback;

  // Listeners.
  private final CopyOnWriteArraySet<EventListener> listeners;
  private SessionAvailabilityListener sessionAvailabilityListener;

  // Internal state.
  private CastTimeline currentTimeline;
  private TrackGroupArray currentTrackGroups;
  private TrackSelectionArray currentTrackSelection;
  private int playbackState;
  private int repeatMode;
  private int currentWindowIndex;
  private boolean playWhenReady;
  private long lastReportedPositionMs;
  private int pendingSeekCount;
  private int pendingSeekWindowIndex;
  private long pendingSeekPositionMs;
  private boolean waitingForInitialTimeline;

  /**
   * @param castContext The context from which the cast session is obtained.
   */
  public CastPlayer(CastContext castContext) {
    this.castContext = castContext;
    timelineTracker = new CastTimelineTracker();
    window = new Timeline.Window();
    period = new Timeline.Period();
    statusListener = new StatusListener();
    seekResultCallback = new SeekResultCallback();
    listeners = new CopyOnWriteArraySet<>();

    SessionManager sessionManager = castContext.getSessionManager();
    sessionManager.addSessionManagerListener(statusListener, CastSession.class);
    CastSession session = sessionManager.getCurrentCastSession();
    remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;

    playbackState = STATE_IDLE;
    repeatMode = REPEAT_MODE_OFF;
    currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
    currentTrackGroups = TrackGroupArray.EMPTY;
    currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
    pendingSeekWindowIndex = C.INDEX_UNSET;
    pendingSeekPositionMs = C.TIME_UNSET;
    updateInternalState();
  }

  // Media Queue manipulation methods.

  /**
   * Loads a single item media queue. If no session is available, does nothing.
   *
   * @param item The item to load.
   * @param positionMs The position at which the playback should start in milliseconds relative to
   *     the start of the item at {@code startIndex}. If {@link C#TIME_UNSET} is passed, playback
   *     starts at position 0.
   * @return The Cast {@code PendingResult}, or null if no session is available.
   */
  public PendingResult<MediaChannelResult> loadItem(MediaQueueItem item, long positionMs) {
    return loadItems(new MediaQueueItem[] {item}, 0, positionMs, REPEAT_MODE_OFF);
  }

  /**
   * Loads a media queue. If no session is available, does nothing.
   *
   * @param items The items to load.
   * @param startIndex The index of the item at which playback should start.
   * @param positionMs The position at which the playback should start in milliseconds relative to
   *     the start of the item at {@code startIndex}. If {@link C#TIME_UNSET} is passed, playback
   *     starts at position 0.
   * @param repeatMode The repeat mode for the created media queue.
   * @return The Cast {@code PendingResult}, or null if no session is available.
   */
  public PendingResult<MediaChannelResult> loadItems(MediaQueueItem[] items, int startIndex,
      long positionMs, @RepeatMode int repeatMode) {
    if (remoteMediaClient != null) {
      positionMs = positionMs != C.TIME_UNSET ? positionMs : 0;
      waitingForInitialTimeline = true;
      return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode),
          positionMs, null);
    }
    return null;
  }

  /**
   * Appends a sequence of items to the media queue. If no media queue exists, does nothing.
   *
   * @param items The items to append.
   * @return The Cast {@code PendingResult}, or null if no media queue exists.
   */
  public PendingResult<MediaChannelResult> addItems(MediaQueueItem... items) {
    return addItems(MediaQueueItem.INVALID_ITEM_ID, items);
  }

  /**
   * Inserts a sequence of items into the media queue. If no media queue or period with id
   * {@code periodId} exist, does nothing.
   *
   * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
   *     that will follow immediately after the inserted items.
   * @param items The items to insert.
   * @return The Cast {@code PendingResult}, or null if no media queue or no period with id
   *     {@code periodId} exist.
   */
  public PendingResult<MediaChannelResult> addItems(int periodId, MediaQueueItem... items) {
    if (getMediaStatus() != null && (periodId == MediaQueueItem.INVALID_ITEM_ID
        || currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET)) {
      return remoteMediaClient.queueInsertItems(items, periodId, null);
    }
    return null;
  }

  /**
   * Removes an item from the media queue. If no media queue or period with id {@code periodId}
   * exist, does nothing.
   *
   * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
   *     to remove.
   * @return The Cast {@code PendingResult}, or null if no media queue or no period with id
   *     {@code periodId} exist.
   */
  public PendingResult<MediaChannelResult> removeItem(int periodId) {
    if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
      return remoteMediaClient.queueRemoveItem(periodId, null);
    }
    return null;
  }

  /**
   * Moves an existing item within the media queue. If no media queue or period with id
   * {@code periodId} exist, does nothing.
   *
   * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
   *     to move.
   * @param newIndex The target index of the item in the media queue. Must be in the range
   *     0 &lt;= index &lt; {@link Timeline#getPeriodCount()}, as provided by
   *     {@link #getCurrentTimeline()}.
   * @return The Cast {@code PendingResult}, or null if no media queue or no period with id
   *     {@code periodId} exist.
   */
  public PendingResult<MediaChannelResult> moveItem(int periodId, int newIndex) {
    Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getPeriodCount());
    if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
      return remoteMediaClient.queueMoveItemToNewIndex(periodId, newIndex, null);
    }
    return null;
  }

  /**
   * Returns the item that corresponds to the period with the given id, or null if no media queue or
   * period with id {@code periodId} exist.
   *
   * @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
   *     to get.
   * @return The item that corresponds to the period with the given id, or null if no media queue or
   *     period with id {@code periodId} exist.
   */
  public MediaQueueItem getItem(int periodId) {
    MediaStatus mediaStatus = getMediaStatus();
    return mediaStatus != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET
        ? mediaStatus.getItemById(periodId) : null;
  }

  // CastSession methods.

  /**<commit_msg>Fix bad @see Javadoc in CastPlayer<commit_after>/*
 * 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.cast;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.MediaTrack;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManager;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * {@link Player} implementation that communicates with a Cast receiver app.
 *
 * <p>The behavior of this class depends on the underlying Cast session, which is obtained from the
 * Cast context passed to {@link #CastPlayer}. To keep track of the session,
 * {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
 * implemented and attached to the player.</p>
 *
 * <p> If no session is available, the player state will remain unchanged and calls to methods that
 * alter it will be ignored. Querying the player state is possible even when no session is
 * available, in which case, the last observed receiver app state is reported.</p>
 *
 * <p>Methods should be called on the application's main thread.</p>
 */
public final class CastPlayer implements Player {

  /**
   * Listener of changes in the cast session availability.
   */
  public interface SessionAvailabilityListener {

    /**
     * Called when a cast session becomes available to the player.
     */
    void onCastSessionAvailable();

    /**
     * Called when the cast session becomes unavailable.
     */
    void onCastSessionUnavailable();

  }

  private static final String TAG = "CastPlayer";

  private static final int RENDERER_COUNT = 3;
  private static final int RENDERER_INDEX_VIDEO = 0;
  private static final int RENDERER_INDEX_AUDIO = 1;
  private static final int RENDERER_INDEX_TEXT = 2;
  private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
  private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
      new TrackSelectionArray(null, null, null);
  private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];

  private final CastContext castContext;
  // TODO: Allow custom implementations of CastTimelineTracker.
  private final CastTimelineTracker timelineTracker;
  private final Timeline.Window window;
  private final Timeline.Period period;

  private RemoteMediaClient remoteMediaClient;

  // Result callbacks.
  private final StatusListener statusListener;
  private final SeekResultCallback seekResultCallback;

  // Listeners.
  private final CopyOnWriteArraySet<EventListener> listeners;
  private SessionAvailabilityListener sessionAvailabilityListener;

  // Internal state.
  private CastTimeline currentTimeline;
  private TrackGroupArray currentTrackGroups;
  private TrackSelectionArray currentTrackSelection;
  private int playbackState;
  private int repeatMode;
  private int currentWindowIndex;
  private boolean playWhenReady;
  private long lastReportedPositionMs;
  private int pendingSeekCount;
  private int pendingSeekWindowIndex;
  private long pendingSeekPositionMs;
  private boolean waitingForInitialTimeline;

  /**
   * @param castContext The context from which the cast session is obtained.
   */
  public CastPlayer(CastContext castContext) {
    this.castContext = castContext;
    timelineTracker = new CastTimelineTracker();
    window = new Timeline.Window();
    period = new Timeline.Period();
    statusListener = new StatusListener();
    seekResultCallback = new SeekResultCallback();
    listeners = new CopyOnWriteArraySet<>();

    SessionManager sessionManager = castContext.getSessionManager();
    sessionManager.addSessionManagerListener(statusListener, CastSession.class);
    CastSession session = sessionManager.getCurrentCastSession();
    remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;

    playbackState = STATE_IDLE;
    repeatMode = REPEAT_MODE_OFF;
    currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
    currentTrackGroups = TrackGroupArray.EMPTY;
    currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
    pendingSeekWindowIndex = C.INDEX_UNSET;
    pendingSeekPositionMs = C.TIME_UNSET;
    updateInternalState();
  }

  // Media Queue manipulation methods.

  /**
   * Loads a single item media queue. If no session is available, does nothing.
   *
   * @param item The item to load.
   * @param positionMs The position at which the playback should start in milliseconds relative to
   *     the start of the item at {@code startIndex}. If {@link C#TIME_UNSET} is passed, playback
   *     starts at position 0.
   * @return The Cast {@code PendingResult}, or null if no session is available.
   */
  public PendingResult<MediaChannelResult> loadItem(MediaQueueItem item, long positionMs) {
    return loadItems(new MediaQueueItem[] {item}, 0, positionMs, REPEAT_MODE_OFF);
  }

  /**
   * Loads a media queue. If no session is available, does nothing.
   *
   * @param items The items to load.
   * @param startIndex The index of the item at which playback should start.
   * @param positionMs The position at which the playback should start in milliseconds relative to
   *     the start of the item at {@code startIndex}. If {@link C#TIME_UNSET} is passed, playback
   *     starts at position 0.
   * @param repeatMode The repeat mode for the created media queue.
   * @return The Cast {@code PendingResult}, or null if no session is available.
   */
  public PendingResult<MediaChannelResult> loadItems(MediaQueueItem[] items, int startIndex,
      long positionMs, @RepeatMode int repeatMode) {
    if (remoteMediaClient != null) {
      positionMs = positionMs != C.TIME_UNSET ? positionMs : 0;
      waitingForInitialTimeline = true;
      return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode),
          positionMs, null);
    }
    return null;
  }

  /**
   * Appends a sequence of items to the media queue. If no media queue exists, does nothing.
   *
   * @param items The items to append.
   * @return The Cast {@code PendingResult}, or null if no media queue exists.
   */
  public PendingResult<MediaChannelResult> addItems(MediaQueueItem... items) {
    return addItems(MediaQueueItem.INVALID_ITEM_ID, items);
  }

  /**
   * Inserts a sequence of items into the media queue. If no media queue or period with id {@code
   * periodId} exist, does nothing.
   *
   * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
   *     that will follow immediately after the inserted items.
   * @param items The items to insert.
   * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code
   *     periodId} exist.
   */
  public PendingResult<MediaChannelResult> addItems(int periodId, MediaQueueItem... items) {
    if (getMediaStatus() != null && (periodId == MediaQueueItem.INVALID_ITEM_ID
        || currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET)) {
      return remoteMediaClient.queueInsertItems(items, periodId, null);
    }
    return null;
  }

  /**
   * Removes an item from the media queue. If no media queue or period with id {@code periodId}
   * exist, does nothing.
   *
   * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
   *     to remove.
   * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code
   *     periodId} exist.
   */
  public PendingResult<MediaChannelResult> removeItem(int periodId) {
    if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
      return remoteMediaClient.queueRemoveItem(periodId, null);
    }
    return null;
  }

  /**
   * Moves an existing item within the media queue. If no media queue or period with id {@code
   * periodId} exist, does nothing.
   *
   * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
   *     to move.
   * @param newIndex The target index of the item in the media queue. Must be in the range 0 &lt;=
   *     index &lt; {@link Timeline#getPeriodCount()}, as provided by {@link #getCurrentTimeline()}.
   * @return The Cast {@code PendingResult}, or null if no media queue or no period with id {@code
   *     periodId} exist.
   */
  public PendingResult<MediaChannelResult> moveItem(int periodId, int newIndex) {
    Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getPeriodCount());
    if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
      return remoteMediaClient.queueMoveItemToNewIndex(periodId, newIndex, null);
    }
    return null;
  }

  /**
   * Returns the item that corresponds to the period with the given id, or null if no media queue or
   * period with id {@code periodId} exist.
   *
   * @param periodId The id of the period ({@link #getCurrentTimeline}) that corresponds to the item
   *     to get.
   * @return The item that corresponds to the period with the given id, or null if no media queue or
   *     period with id {@code periodId} exist.
   */
  public MediaQueueItem getItem(int periodId) {
    MediaStatus mediaStatus = getMediaStatus();
    return mediaStatus != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET
        ? mediaStatus.getItemById(periodId) : null;
  }

  // CastSession methods.

  /**
{'fraction_non_alphanumeric': 0.08297985153764581, 'fraction_numerical': 0.003181336161187699, 'mean_word_length': 4.061046511627907, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2374615', 'n_tokens_mistral': 6294, 'n_tokens_neox': 6205, 'n_words': 2139}