1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2; 17 18 import androidx.annotation.Nullable; 19 import com.google.android.exoplayer2.util.Util; 20 import java.util.Collections; 21 import java.util.List; 22 23 /** Abstract base {@link Player} which implements common implementation independent methods. */ 24 public abstract class BasePlayer implements Player { 25 26 protected final Timeline.Window window; 27 BasePlayer()28 public BasePlayer() { 29 window = new Timeline.Window(); 30 } 31 32 @Override setMediaItem(MediaItem mediaItem)33 public void setMediaItem(MediaItem mediaItem) { 34 setMediaItems(Collections.singletonList(mediaItem)); 35 } 36 37 @Override setMediaItem(MediaItem mediaItem, long startPositionMs)38 public void setMediaItem(MediaItem mediaItem, long startPositionMs) { 39 setMediaItems(Collections.singletonList(mediaItem), /* startWindowIndex= */ 0, startPositionMs); 40 } 41 42 @Override setMediaItem(MediaItem mediaItem, boolean resetPosition)43 public void setMediaItem(MediaItem mediaItem, boolean resetPosition) { 44 setMediaItems(Collections.singletonList(mediaItem), resetPosition); 45 } 46 47 @Override setMediaItems(List<MediaItem> mediaItems, boolean resetPosition)48 public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) { 49 setMediaItems( 50 mediaItems, /* startWindowIndex= */ C.INDEX_UNSET, /* startPositionMs= */ C.TIME_UNSET); 51 } 52 53 @Override setMediaItems(List<MediaItem> mediaItems)54 public void setMediaItems(List<MediaItem> mediaItems) { 55 setMediaItems(mediaItems, /* resetPosition= */ true); 56 } 57 58 @Override addMediaItem(int index, MediaItem mediaItem)59 public void addMediaItem(int index, MediaItem mediaItem) { 60 addMediaItems(index, Collections.singletonList(mediaItem)); 61 } 62 63 @Override addMediaItem(MediaItem mediaItem)64 public void addMediaItem(MediaItem mediaItem) { 65 addMediaItems(Collections.singletonList(mediaItem)); 66 } 67 68 @Override moveMediaItem(int currentIndex, int newIndex)69 public void moveMediaItem(int currentIndex, int newIndex) { 70 if (currentIndex != newIndex) { 71 moveMediaItems(/* fromIndex= */ currentIndex, /* toIndex= */ currentIndex + 1, newIndex); 72 } 73 } 74 75 @Override removeMediaItem(int index)76 public void removeMediaItem(int index) { 77 removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1); 78 } 79 80 @Override play()81 public final void play() { 82 setPlayWhenReady(true); 83 } 84 85 @Override pause()86 public final void pause() { 87 setPlayWhenReady(false); 88 } 89 90 @Override isPlaying()91 public final boolean isPlaying() { 92 return getPlaybackState() == Player.STATE_READY 93 && getPlayWhenReady() 94 && getPlaybackSuppressionReason() == PLAYBACK_SUPPRESSION_REASON_NONE; 95 } 96 97 @Override seekToDefaultPosition()98 public final void seekToDefaultPosition() { 99 seekToDefaultPosition(getCurrentWindowIndex()); 100 } 101 102 @Override seekToDefaultPosition(int windowIndex)103 public final void seekToDefaultPosition(int windowIndex) { 104 seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET); 105 } 106 107 @Override seekTo(long positionMs)108 public final void seekTo(long positionMs) { 109 seekTo(getCurrentWindowIndex(), positionMs); 110 } 111 112 @Override hasPrevious()113 public final boolean hasPrevious() { 114 return getPreviousWindowIndex() != C.INDEX_UNSET; 115 } 116 117 @Override previous()118 public final void previous() { 119 int previousWindowIndex = getPreviousWindowIndex(); 120 if (previousWindowIndex != C.INDEX_UNSET) { 121 seekToDefaultPosition(previousWindowIndex); 122 } 123 } 124 125 @Override hasNext()126 public final boolean hasNext() { 127 return getNextWindowIndex() != C.INDEX_UNSET; 128 } 129 130 @Override next()131 public final void next() { 132 int nextWindowIndex = getNextWindowIndex(); 133 if (nextWindowIndex != C.INDEX_UNSET) { 134 seekToDefaultPosition(nextWindowIndex); 135 } 136 } 137 138 @Override stop()139 public final void stop() { 140 stop(/* reset= */ false); 141 } 142 143 @Override getNextWindowIndex()144 public final int getNextWindowIndex() { 145 Timeline timeline = getCurrentTimeline(); 146 return timeline.isEmpty() 147 ? C.INDEX_UNSET 148 : timeline.getNextWindowIndex( 149 getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); 150 } 151 152 @Override getPreviousWindowIndex()153 public final int getPreviousWindowIndex() { 154 Timeline timeline = getCurrentTimeline(); 155 return timeline.isEmpty() 156 ? C.INDEX_UNSET 157 : timeline.getPreviousWindowIndex( 158 getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); 159 } 160 161 @Override 162 @Nullable getCurrentTag()163 public final Object getCurrentTag() { 164 Timeline timeline = getCurrentTimeline(); 165 return timeline.isEmpty() ? null : timeline.getWindow(getCurrentWindowIndex(), window).tag; 166 } 167 168 @Override 169 @Nullable getCurrentManifest()170 public final Object getCurrentManifest() { 171 Timeline timeline = getCurrentTimeline(); 172 return timeline.isEmpty() ? null : timeline.getWindow(getCurrentWindowIndex(), window).manifest; 173 } 174 175 @Override getBufferedPercentage()176 public final int getBufferedPercentage() { 177 long position = getBufferedPosition(); 178 long duration = getDuration(); 179 return position == C.TIME_UNSET || duration == C.TIME_UNSET 180 ? 0 181 : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); 182 } 183 184 @Override isCurrentWindowDynamic()185 public final boolean isCurrentWindowDynamic() { 186 Timeline timeline = getCurrentTimeline(); 187 return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; 188 } 189 190 @Override isCurrentWindowLive()191 public final boolean isCurrentWindowLive() { 192 Timeline timeline = getCurrentTimeline(); 193 return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isLive; 194 } 195 196 @Override getCurrentLiveOffset()197 public final long getCurrentLiveOffset() { 198 Timeline timeline = getCurrentTimeline(); 199 if (timeline.isEmpty()) { 200 return C.TIME_UNSET; 201 } 202 long windowStartTimeMs = timeline.getWindow(getCurrentWindowIndex(), window).windowStartTimeMs; 203 if (windowStartTimeMs == C.TIME_UNSET) { 204 return C.TIME_UNSET; 205 } 206 return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition(); 207 } 208 209 @Override isCurrentWindowSeekable()210 public final boolean isCurrentWindowSeekable() { 211 Timeline timeline = getCurrentTimeline(); 212 return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; 213 } 214 215 @Override getContentDuration()216 public final long getContentDuration() { 217 Timeline timeline = getCurrentTimeline(); 218 return timeline.isEmpty() 219 ? C.TIME_UNSET 220 : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); 221 } 222 223 @RepeatMode getRepeatModeForNavigation()224 private int getRepeatModeForNavigation() { 225 @RepeatMode int repeatMode = getRepeatMode(); 226 return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode; 227 } 228 229 /** Holds a listener reference. */ 230 protected static final class ListenerHolder { 231 232 /** 233 * The listener on which {link #invoke} will execute {@link ListenerInvocation listener 234 * invocations}. 235 */ 236 public final Player.EventListener listener; 237 238 private boolean released; 239 ListenerHolder(Player.EventListener listener)240 public ListenerHolder(Player.EventListener listener) { 241 this.listener = listener; 242 } 243 244 /** Prevents any further {@link ListenerInvocation} to be executed on {@link #listener}. */ release()245 public void release() { 246 released = true; 247 } 248 249 /** 250 * Executes the given {@link ListenerInvocation} on {@link #listener}. Does nothing if {@link 251 * #release} has been called on this instance. 252 */ invoke(ListenerInvocation listenerInvocation)253 public void invoke(ListenerInvocation listenerInvocation) { 254 if (!released) { 255 listenerInvocation.invokeListener(listener); 256 } 257 } 258 259 @Override equals(@ullable Object other)260 public boolean equals(@Nullable Object other) { 261 if (this == other) { 262 return true; 263 } 264 if (other == null || getClass() != other.getClass()) { 265 return false; 266 } 267 return listener.equals(((ListenerHolder) other).listener); 268 } 269 270 @Override hashCode()271 public int hashCode() { 272 return listener.hashCode(); 273 } 274 } 275 276 /** Parameterized invocation of a {@link Player.EventListener} method. */ 277 protected interface ListenerInvocation { 278 279 /** Executes the invocation on the given {@link Player.EventListener}. */ invokeListener(Player.EventListener listener)280 void invokeListener(Player.EventListener listener); 281 } 282 } 283