1 /*
2  * Copyright (C) 2017 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 /** Default {@link ControlDispatcher}. */
19 public class DefaultControlDispatcher implements ControlDispatcher {
20 
21   /** The default fast forward increment, in milliseconds. */
22   public static final int DEFAULT_FAST_FORWARD_MS = 15000;
23   /** The default rewind increment, in milliseconds. */
24   public static final int DEFAULT_REWIND_MS = 5000;
25 
26   private static final int MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
27 
28   private final Timeline.Window window;
29 
30   private long rewindIncrementMs;
31   private long fastForwardIncrementMs;
32 
33   /** Creates an instance. */
DefaultControlDispatcher()34   public DefaultControlDispatcher() {
35     this(DEFAULT_FAST_FORWARD_MS, DEFAULT_REWIND_MS);
36   }
37 
38   /**
39    * Creates an instance with the given increments.
40    *
41    * @param fastForwardIncrementMs The fast forward increment in milliseconds. A non-positive value
42    *     disables the fast forward operation.
43    * @param rewindIncrementMs The rewind increment in milliseconds. A non-positive value disables
44    *     the rewind operation.
45    */
DefaultControlDispatcher(long fastForwardIncrementMs, long rewindIncrementMs)46   public DefaultControlDispatcher(long fastForwardIncrementMs, long rewindIncrementMs) {
47     this.fastForwardIncrementMs = fastForwardIncrementMs;
48     this.rewindIncrementMs = rewindIncrementMs;
49     window = new Timeline.Window();
50   }
51 
52   @Override
dispatchSetPlayWhenReady(Player player, boolean playWhenReady)53   public boolean dispatchSetPlayWhenReady(Player player, boolean playWhenReady) {
54     player.setPlayWhenReady(playWhenReady);
55     return true;
56   }
57 
58   @Override
dispatchSeekTo(Player player, int windowIndex, long positionMs)59   public boolean dispatchSeekTo(Player player, int windowIndex, long positionMs) {
60     player.seekTo(windowIndex, positionMs);
61     return true;
62   }
63 
64   @Override
dispatchPrevious(Player player)65   public boolean dispatchPrevious(Player player) {
66     Timeline timeline = player.getCurrentTimeline();
67     if (timeline.isEmpty() || player.isPlayingAd()) {
68       return true;
69     }
70     int windowIndex = player.getCurrentWindowIndex();
71     timeline.getWindow(windowIndex, window);
72     int previousWindowIndex = player.getPreviousWindowIndex();
73     if (previousWindowIndex != C.INDEX_UNSET
74         && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
75             || (window.isDynamic && !window.isSeekable))) {
76       player.seekTo(previousWindowIndex, C.TIME_UNSET);
77     } else {
78       player.seekTo(windowIndex, /* positionMs= */ 0);
79     }
80     return true;
81   }
82 
83   @Override
dispatchNext(Player player)84   public boolean dispatchNext(Player player) {
85     Timeline timeline = player.getCurrentTimeline();
86     if (timeline.isEmpty() || player.isPlayingAd()) {
87       return true;
88     }
89     int windowIndex = player.getCurrentWindowIndex();
90     int nextWindowIndex = player.getNextWindowIndex();
91     if (nextWindowIndex != C.INDEX_UNSET) {
92       player.seekTo(nextWindowIndex, C.TIME_UNSET);
93     } else if (timeline.getWindow(windowIndex, window).isLive) {
94       player.seekTo(windowIndex, C.TIME_UNSET);
95     }
96     return true;
97   }
98 
99   @Override
dispatchRewind(Player player)100   public boolean dispatchRewind(Player player) {
101     if (isRewindEnabled() && player.isCurrentWindowSeekable()) {
102       seekToOffset(player, -rewindIncrementMs);
103     }
104     return true;
105   }
106 
107   @Override
dispatchFastForward(Player player)108   public boolean dispatchFastForward(Player player) {
109     if (isFastForwardEnabled() && player.isCurrentWindowSeekable()) {
110       seekToOffset(player, fastForwardIncrementMs);
111     }
112     return true;
113   }
114 
115   @Override
dispatchSetRepeatMode(Player player, @Player.RepeatMode int repeatMode)116   public boolean dispatchSetRepeatMode(Player player, @Player.RepeatMode int repeatMode) {
117     player.setRepeatMode(repeatMode);
118     return true;
119   }
120 
121   @Override
dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled)122   public boolean dispatchSetShuffleModeEnabled(Player player, boolean shuffleModeEnabled) {
123     player.setShuffleModeEnabled(shuffleModeEnabled);
124     return true;
125   }
126 
127   @Override
dispatchStop(Player player, boolean reset)128   public boolean dispatchStop(Player player, boolean reset) {
129     player.stop(reset);
130     return true;
131   }
132 
133   @Override
isRewindEnabled()134   public boolean isRewindEnabled() {
135     return rewindIncrementMs > 0;
136   }
137 
138   @Override
isFastForwardEnabled()139   public boolean isFastForwardEnabled() {
140     return fastForwardIncrementMs > 0;
141   }
142 
143   /** Returns the rewind increment in milliseconds. */
getRewindIncrementMs()144   public long getRewindIncrementMs() {
145     return rewindIncrementMs;
146   }
147 
148   /** Returns the fast forward increment in milliseconds. */
getFastForwardIncrementMs()149   public long getFastForwardIncrementMs() {
150     return fastForwardIncrementMs;
151   }
152 
153   /**
154    * @deprecated Create a new instance instead and pass the new instance to the UI component. This
155    *     makes sure the UI gets updated and is in sync with the new values.
156    */
157   @Deprecated
setRewindIncrementMs(long rewindMs)158   public void setRewindIncrementMs(long rewindMs) {
159     this.rewindIncrementMs = rewindMs;
160   }
161 
162   /**
163    * @deprecated Create a new instance instead and pass the new instance to the UI component. This
164    *     makes sure the UI gets updated and is in sync with the new values.
165    */
166   @Deprecated
setFastForwardIncrementMs(long fastForwardMs)167   public void setFastForwardIncrementMs(long fastForwardMs) {
168     this.fastForwardIncrementMs = fastForwardMs;
169   }
170 
171   // Internal methods.
172 
seekToOffset(Player player, long offsetMs)173   private static void seekToOffset(Player player, long offsetMs) {
174     long positionMs = player.getCurrentPosition() + offsetMs;
175     long durationMs = player.getDuration();
176     if (durationMs != C.TIME_UNSET) {
177       positionMs = Math.min(positionMs, durationMs);
178     }
179     positionMs = Math.max(positionMs, 0);
180     player.seekTo(player.getCurrentWindowIndex(), positionMs);
181   }
182 }
183