Commit 89908794 by Oliver Woodman

Merge pull request #5496 from szaboa:dev-v2-5040

PiperOrigin-RevId: 237412166
parents 3ea6d78e eaf0d408
...@@ -205,6 +205,7 @@ public class DefaultTimeBar extends View implements TimeBar { ...@@ -205,6 +205,7 @@ public class DefaultTimeBar extends View implements TimeBar {
private final CopyOnWriteArraySet<OnScrubListener> listeners; private final CopyOnWriteArraySet<OnScrubListener> listeners;
private final int[] locationOnScreen; private final int[] locationOnScreen;
private final Point touchPosition; private final Point touchPosition;
private final float density;
private int keyCountIncrement; private int keyCountIncrement;
private long keyTimeIncrement; private long keyTimeIncrement;
...@@ -242,13 +243,14 @@ public class DefaultTimeBar extends View implements TimeBar { ...@@ -242,13 +243,14 @@ public class DefaultTimeBar extends View implements TimeBar {
// Calculate the dimensions and paints for drawn elements. // Calculate the dimensions and paints for drawn elements.
Resources res = context.getResources(); Resources res = context.getResources();
DisplayMetrics displayMetrics = res.getDisplayMetrics(); DisplayMetrics displayMetrics = res.getDisplayMetrics();
fineScrubYThreshold = dpToPx(displayMetrics, FINE_SCRUB_Y_THRESHOLD_DP); density = displayMetrics.density;
int defaultBarHeight = dpToPx(displayMetrics, DEFAULT_BAR_HEIGHT_DP); fineScrubYThreshold = dpToPx(density, FINE_SCRUB_Y_THRESHOLD_DP);
int defaultTouchTargetHeight = dpToPx(displayMetrics, DEFAULT_TOUCH_TARGET_HEIGHT_DP); int defaultBarHeight = dpToPx(density, DEFAULT_BAR_HEIGHT_DP);
int defaultAdMarkerWidth = dpToPx(displayMetrics, DEFAULT_AD_MARKER_WIDTH_DP); int defaultTouchTargetHeight = dpToPx(density, DEFAULT_TOUCH_TARGET_HEIGHT_DP);
int defaultScrubberEnabledSize = dpToPx(displayMetrics, DEFAULT_SCRUBBER_ENABLED_SIZE_DP); int defaultAdMarkerWidth = dpToPx(density, DEFAULT_AD_MARKER_WIDTH_DP);
int defaultScrubberDisabledSize = dpToPx(displayMetrics, DEFAULT_SCRUBBER_DISABLED_SIZE_DP); int defaultScrubberEnabledSize = dpToPx(density, DEFAULT_SCRUBBER_ENABLED_SIZE_DP);
int defaultScrubberDraggedSize = dpToPx(displayMetrics, DEFAULT_SCRUBBER_DRAGGED_SIZE_DP); int defaultScrubberDisabledSize = dpToPx(density, DEFAULT_SCRUBBER_DISABLED_SIZE_DP);
int defaultScrubberDraggedSize = dpToPx(density, DEFAULT_SCRUBBER_DRAGGED_SIZE_DP);
if (attrs != null) { if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DefaultTimeBar, 0, TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DefaultTimeBar, 0,
0); 0);
...@@ -437,6 +439,14 @@ public class DefaultTimeBar extends View implements TimeBar { ...@@ -437,6 +439,14 @@ public class DefaultTimeBar extends View implements TimeBar {
} }
@Override @Override
public long getPreferredUpdateDelay() {
int timeBarWidthDp = pxToDp(density, progressBar.width());
return timeBarWidthDp == 0 || duration == 0 || duration == C.TIME_UNSET
? Long.MAX_VALUE
: duration / timeBarWidthDp;
}
@Override
public void setAdGroupTimesMs(@Nullable long[] adGroupTimesMs, @Nullable boolean[] playedAdGroups, public void setAdGroupTimesMs(@Nullable long[] adGroupTimesMs, @Nullable boolean[] playedAdGroups,
int adGroupCount) { int adGroupCount) {
Assertions.checkArgument(adGroupCount == 0 Assertions.checkArgument(adGroupCount == 0
...@@ -832,7 +842,11 @@ public class DefaultTimeBar extends View implements TimeBar { ...@@ -832,7 +842,11 @@ public class DefaultTimeBar extends View implements TimeBar {
return 0x33000000 | (adMarkerColor & 0x00FFFFFF); return 0x33000000 | (adMarkerColor & 0x00FFFFFF);
} }
private static int dpToPx(DisplayMetrics displayMetrics, int dps) { private static int dpToPx(float density, int dps) {
return (int) (dps * displayMetrics.density + 0.5f); return (int) (dps * density + 0.5f);
}
private static int pxToDp(float density, int px) {
return (int) (px / density);
} }
} }
...@@ -85,6 +85,12 @@ import java.util.Locale; ...@@ -85,6 +85,12 @@ import java.util.Locale;
* <li>Corresponding method: {@link #setShowShuffleButton(boolean)} * <li>Corresponding method: {@link #setShowShuffleButton(boolean)}
* <li>Default: false * <li>Default: false
* </ul> * </ul>
* <li><b>{@code time_bar_min_update_interval}</b> - Specifies the minimum interval between time
* bar position updates.
* <ul>
* <li>Corresponding method: {@link #setTimeBarMinUpdateInterval(int)}
* <li>Default: {@link #DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS}
* </ul>
* <li><b>{@code controller_layout_id}</b> - Specifies the id of the layout to be inflated. See * <li><b>{@code controller_layout_id}</b> - Specifies the id of the layout to be inflated. See
* below for more details. * below for more details.
* <ul> * <ul>
...@@ -191,11 +197,14 @@ public class PlayerControlView extends FrameLayout { ...@@ -191,11 +197,14 @@ public class PlayerControlView extends FrameLayout {
/** The default repeat toggle modes. */ /** The default repeat toggle modes. */
public static final @RepeatModeUtil.RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES = public static final @RepeatModeUtil.RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES =
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE; RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE;
/** The default minimum interval between time bar position updates. */
public static final int DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS = 200;
/** The maximum number of windows that can be shown in a multi-window time bar. */ /** The maximum number of windows that can be shown in a multi-window time bar. */
public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100; public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100;
private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000; private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
/** The maximum interval between time bar position updates. */
private static final int MAX_UPDATE_INTERVAL_MS = 1000;
private final ComponentListener componentListener; private final ComponentListener componentListener;
private final View previousButton; private final View previousButton;
...@@ -236,6 +245,7 @@ public class PlayerControlView extends FrameLayout { ...@@ -236,6 +245,7 @@ public class PlayerControlView extends FrameLayout {
private int rewindMs; private int rewindMs;
private int fastForwardMs; private int fastForwardMs;
private int showTimeoutMs; private int showTimeoutMs;
private int timeBarMinUpdateIntervalMs;
private @RepeatModeUtil.RepeatToggleModes int repeatToggleModes; private @RepeatModeUtil.RepeatToggleModes int repeatToggleModes;
private boolean showShuffleButton; private boolean showShuffleButton;
private long hideAtMs; private long hideAtMs;
...@@ -243,6 +253,7 @@ public class PlayerControlView extends FrameLayout { ...@@ -243,6 +253,7 @@ public class PlayerControlView extends FrameLayout {
private boolean[] playedAdGroups; private boolean[] playedAdGroups;
private long[] extraAdGroupTimesMs; private long[] extraAdGroupTimesMs;
private boolean[] extraPlayedAdGroups; private boolean[] extraPlayedAdGroups;
private long currentWindowOffset;
public PlayerControlView(Context context) { public PlayerControlView(Context context) {
this(context, null); this(context, null);
...@@ -264,6 +275,7 @@ public class PlayerControlView extends FrameLayout { ...@@ -264,6 +275,7 @@ public class PlayerControlView extends FrameLayout {
fastForwardMs = DEFAULT_FAST_FORWARD_MS; fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES; repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
timeBarMinUpdateIntervalMs = DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS;
hideAtMs = C.TIME_UNSET; hideAtMs = C.TIME_UNSET;
showShuffleButton = false; showShuffleButton = false;
if (playbackAttrs != null) { if (playbackAttrs != null) {
...@@ -402,7 +414,7 @@ public class PlayerControlView extends FrameLayout { ...@@ -402,7 +414,7 @@ public class PlayerControlView extends FrameLayout {
*/ */
public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
this.showMultiWindowTimeBar = showMultiWindowTimeBar; this.showMultiWindowTimeBar = showMultiWindowTimeBar;
updateTimeBarMode(); updateTimeline();
} }
/** /**
...@@ -426,7 +438,7 @@ public class PlayerControlView extends FrameLayout { ...@@ -426,7 +438,7 @@ public class PlayerControlView extends FrameLayout {
this.extraAdGroupTimesMs = extraAdGroupTimesMs; this.extraAdGroupTimesMs = extraAdGroupTimesMs;
this.extraPlayedAdGroups = extraPlayedAdGroups; this.extraPlayedAdGroups = extraPlayedAdGroups;
} }
updateProgress(); updateTimeline();
} }
/** /**
...@@ -584,6 +596,22 @@ public class PlayerControlView extends FrameLayout { ...@@ -584,6 +596,22 @@ public class PlayerControlView extends FrameLayout {
} }
/** /**
* Sets the minimum interval between time bar position updates.
*
* <p>Note that smaller intervals, e.g. 33ms, will result in a smooth movement but will use more
* CPU resources while the time bar is visible, whereas larger intervals, e.g. 200ms, will result
* in a step-wise update with less CPU usage.
*
* @param minUpdateIntervalMs The minimum interval between time bar position updates, in
* milliseconds.
*/
public void setTimeBarMinUpdateInterval(int minUpdateIntervalMs) {
// Do not accept values below 16ms (60fps) and larger than the maximum update interval.
timeBarMinUpdateIntervalMs =
Util.constrainValue(minUpdateIntervalMs, 16, MAX_UPDATE_INTERVAL_MS);
}
/**
* Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will * Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will
* be automatically hidden after this duration of time has elapsed without user input. * be automatically hidden after this duration of time has elapsed without user input.
*/ */
...@@ -635,7 +663,7 @@ public class PlayerControlView extends FrameLayout { ...@@ -635,7 +663,7 @@ public class PlayerControlView extends FrameLayout {
updateNavigation(); updateNavigation();
updateRepeatModeButton(); updateRepeatModeButton();
updateShuffleButton(); updateShuffleButton();
updateProgress(); updateTimeline();
} }
private void updatePlayPauseButton() { private void updatePlayPauseButton() {
...@@ -735,12 +763,74 @@ public class PlayerControlView extends FrameLayout { ...@@ -735,12 +763,74 @@ public class PlayerControlView extends FrameLayout {
} }
} }
private void updateTimeBarMode() { private void updateTimeline() {
if (player == null) { if (player == null) {
return; return;
} }
multiWindowTimeBar = multiWindowTimeBar =
showMultiWindowTimeBar && canShowMultiWindowTimeBar(player.getCurrentTimeline(), window); showMultiWindowTimeBar && canShowMultiWindowTimeBar(player.getCurrentTimeline(), window);
currentWindowOffset = 0;
long durationUs = 0;
int adGroupCount = 0;
Timeline timeline = player.getCurrentTimeline();
if (!timeline.isEmpty()) {
int currentWindowIndex = player.getCurrentWindowIndex();
int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex;
int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex;
for (int i = firstWindowIndex; i <= lastWindowIndex; i++) {
if (i == currentWindowIndex) {
currentWindowOffset = C.usToMs(durationUs);
}
timeline.getWindow(i, window);
if (window.durationUs == C.TIME_UNSET) {
Assertions.checkState(!multiWindowTimeBar);
break;
}
for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) {
timeline.getPeriod(j, period);
int periodAdGroupCount = period.getAdGroupCount();
for (int adGroupIndex = 0; adGroupIndex < periodAdGroupCount; adGroupIndex++) {
long adGroupTimeInPeriodUs = period.getAdGroupTimeUs(adGroupIndex);
if (adGroupTimeInPeriodUs == C.TIME_END_OF_SOURCE) {
if (period.durationUs == C.TIME_UNSET) {
// Don't show ad markers for postrolls in periods with unknown duration.
continue;
}
adGroupTimeInPeriodUs = period.durationUs;
}
long adGroupTimeInWindowUs = adGroupTimeInPeriodUs + period.getPositionInWindowUs();
if (adGroupTimeInWindowUs >= 0 && adGroupTimeInWindowUs <= window.durationUs) {
if (adGroupCount == adGroupTimesMs.length) {
int newLength = adGroupTimesMs.length == 0 ? 1 : adGroupTimesMs.length * 2;
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength);
playedAdGroups = Arrays.copyOf(playedAdGroups, newLength);
}
adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs);
playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex);
adGroupCount++;
}
}
}
durationUs += window.durationUs;
}
}
long durationMs = C.usToMs(durationUs);
if (durationView != null) {
durationView.setText(Util.getStringForTime(formatBuilder, formatter, durationMs));
}
if (timeBar != null) {
timeBar.setDuration(durationMs);
int extraAdGroupCount = extraAdGroupTimesMs.length;
int totalAdGroupCount = adGroupCount + extraAdGroupCount;
if (totalAdGroupCount > adGroupTimesMs.length) {
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, totalAdGroupCount);
playedAdGroups = Arrays.copyOf(playedAdGroups, totalAdGroupCount);
}
System.arraycopy(extraAdGroupTimesMs, 0, adGroupTimesMs, adGroupCount, extraAdGroupCount);
System.arraycopy(extraPlayedAdGroups, 0, playedAdGroups, adGroupCount, extraAdGroupCount);
timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, totalAdGroupCount);
}
updateProgress();
} }
private void updateProgress() { private void updateProgress() {
...@@ -750,71 +840,9 @@ public class PlayerControlView extends FrameLayout { ...@@ -750,71 +840,9 @@ public class PlayerControlView extends FrameLayout {
long position = 0; long position = 0;
long bufferedPosition = 0; long bufferedPosition = 0;
long duration = 0;
if (player != null) { if (player != null) {
long currentWindowTimeBarOffsetMs = 0; position = currentWindowOffset + player.getContentPosition();
long durationUs = 0; bufferedPosition = currentWindowOffset + player.getContentBufferedPosition();
int adGroupCount = 0;
Timeline timeline = player.getCurrentTimeline();
if (!timeline.isEmpty()) {
int currentWindowIndex = player.getCurrentWindowIndex();
int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex;
int lastWindowIndex =
multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex;
for (int i = firstWindowIndex; i <= lastWindowIndex; i++) {
if (i == currentWindowIndex) {
currentWindowTimeBarOffsetMs = C.usToMs(durationUs);
}
timeline.getWindow(i, window);
if (window.durationUs == C.TIME_UNSET) {
Assertions.checkState(!multiWindowTimeBar);
break;
}
for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) {
timeline.getPeriod(j, period);
int periodAdGroupCount = period.getAdGroupCount();
for (int adGroupIndex = 0; adGroupIndex < periodAdGroupCount; adGroupIndex++) {
long adGroupTimeInPeriodUs = period.getAdGroupTimeUs(adGroupIndex);
if (adGroupTimeInPeriodUs == C.TIME_END_OF_SOURCE) {
if (period.durationUs == C.TIME_UNSET) {
// Don't show ad markers for postrolls in periods with unknown duration.
continue;
}
adGroupTimeInPeriodUs = period.durationUs;
}
long adGroupTimeInWindowUs = adGroupTimeInPeriodUs + period.getPositionInWindowUs();
if (adGroupTimeInWindowUs >= 0 && adGroupTimeInWindowUs <= window.durationUs) {
if (adGroupCount == adGroupTimesMs.length) {
int newLength = adGroupTimesMs.length == 0 ? 1 : adGroupTimesMs.length * 2;
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength);
playedAdGroups = Arrays.copyOf(playedAdGroups, newLength);
}
adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs);
playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex);
adGroupCount++;
}
}
}
durationUs += window.durationUs;
}
}
duration = C.usToMs(durationUs);
position = currentWindowTimeBarOffsetMs + player.getContentPosition();
bufferedPosition = currentWindowTimeBarOffsetMs + player.getContentBufferedPosition();
if (timeBar != null) {
int extraAdGroupCount = extraAdGroupTimesMs.length;
int totalAdGroupCount = adGroupCount + extraAdGroupCount;
if (totalAdGroupCount > adGroupTimesMs.length) {
adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, totalAdGroupCount);
playedAdGroups = Arrays.copyOf(playedAdGroups, totalAdGroupCount);
}
System.arraycopy(extraAdGroupTimesMs, 0, adGroupTimesMs, adGroupCount, extraAdGroupCount);
System.arraycopy(extraPlayedAdGroups, 0, playedAdGroups, adGroupCount, extraAdGroupCount);
timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, totalAdGroupCount);
}
}
if (durationView != null) {
durationView.setText(Util.getStringForTime(formatBuilder, formatter, duration));
} }
if (positionView != null && !scrubbing) { if (positionView != null && !scrubbing) {
positionView.setText(Util.getStringForTime(formatBuilder, formatter, position)); positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));
...@@ -822,33 +850,29 @@ public class PlayerControlView extends FrameLayout { ...@@ -822,33 +850,29 @@ public class PlayerControlView extends FrameLayout {
if (timeBar != null) { if (timeBar != null) {
timeBar.setPosition(position); timeBar.setPosition(position);
timeBar.setBufferedPosition(bufferedPosition); timeBar.setBufferedPosition(bufferedPosition);
timeBar.setDuration(duration);
} }
// Cancel any pending updates and schedule a new one if necessary. // Cancel any pending updates and schedule a new one if necessary.
removeCallbacks(updateProgressAction); removeCallbacks(updateProgressAction);
int playbackState = player == null ? Player.STATE_IDLE : player.getPlaybackState(); int playbackState = player == null ? Player.STATE_IDLE : player.getPlaybackState();
if (playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED) { if (playbackState == Player.STATE_READY && player.getPlayWhenReady()) {
long delayMs; long mediaTimeDelayMs =
if (player.getPlayWhenReady() && playbackState == Player.STATE_READY) { timeBar != null ? timeBar.getPreferredUpdateDelay() : MAX_UPDATE_INTERVAL_MS;
float playbackSpeed = player.getPlaybackParameters().speed;
if (playbackSpeed <= 0.1f) { // Limit delay to the start of the next full second to ensure position display is smooth.
delayMs = 1000; long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
} else if (playbackSpeed <= 5f) { mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
long mediaTimeUpdatePeriodMs = 1000 / Math.max(1, Math.round(1 / playbackSpeed));
long mediaTimeDelayMs = mediaTimeUpdatePeriodMs - (position % mediaTimeUpdatePeriodMs); // Calculate the delay until the next update in real time, taking playbackSpeed into account.
if (mediaTimeDelayMs < (mediaTimeUpdatePeriodMs / 5)) { float playbackSpeed = player.getPlaybackParameters().speed;
mediaTimeDelayMs += mediaTimeUpdatePeriodMs; long delayMs =
} playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
delayMs =
playbackSpeed == 1 ? mediaTimeDelayMs : (long) (mediaTimeDelayMs / playbackSpeed); // Constrain the delay to avoid too frequent / infrequent updates.
} else { delayMs = Util.constrainValue(delayMs, timeBarMinUpdateIntervalMs, MAX_UPDATE_INTERVAL_MS);
delayMs = 200;
}
} else {
delayMs = 1000;
}
postDelayed(updateProgressAction, delayMs); postDelayed(updateProgressAction, delayMs);
} else if (playbackState != Player.STATE_ENDED && playbackState != Player.STATE_IDLE) {
postDelayed(updateProgressAction, MAX_UPDATE_INTERVAL_MS);
} }
} }
...@@ -1119,15 +1143,14 @@ public class PlayerControlView extends FrameLayout { ...@@ -1119,15 +1143,14 @@ public class PlayerControlView extends FrameLayout {
@Override @Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
updateNavigation(); updateNavigation();
updateProgress(); updateTimeline();
} }
@Override @Override
public void onTimelineChanged( public void onTimelineChanged(
Timeline timeline, @Nullable Object manifest, @Player.TimelineChangeReason int reason) { Timeline timeline, @Nullable Object manifest, @Player.TimelineChangeReason int reason) {
updateNavigation(); updateNavigation();
updateTimeBarMode(); updateTimeline();
updateProgress();
} }
@Override @Override
......
...@@ -85,6 +85,14 @@ public interface TimeBar { ...@@ -85,6 +85,14 @@ public interface TimeBar {
void setDuration(long duration); void setDuration(long duration);
/** /**
* Returns the preferred delay in milliseconds of media time after which the time bar position
* should be updated.
*
* @return Preferred delay, in milliseconds of media time.
*/
long getPreferredUpdateDelay();
/**
* Sets the times of ad groups and whether each ad group has been played. * Sets the times of ad groups and whether each ad group has been played.
* *
* @param adGroupTimesMs An array where the first {@code adGroupCount} elements are the times of * @param adGroupTimesMs An array where the first {@code adGroupCount} elements are the times of
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
<flag name="all" value="2"/> <flag name="all" value="2"/>
</attr> </attr>
<attr name="show_shuffle_button" format="boolean"/> <attr name="show_shuffle_button" format="boolean"/>
<attr name="time_bar_min_update_interval" format="integer"/>
<declare-styleable name="PlayerView"> <declare-styleable name="PlayerView">
<attr name="use_artwork" format="boolean"/> <attr name="use_artwork" format="boolean"/>
...@@ -66,6 +67,7 @@ ...@@ -66,6 +67,7 @@
<attr name="fastforward_increment"/> <attr name="fastforward_increment"/>
<attr name="repeat_toggle_modes"/> <attr name="repeat_toggle_modes"/>
<attr name="show_shuffle_button"/> <attr name="show_shuffle_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/> <attr name="controller_layout_id"/>
</declare-styleable> </declare-styleable>
...@@ -79,6 +81,7 @@ ...@@ -79,6 +81,7 @@
<attr name="fastforward_increment"/> <attr name="fastforward_increment"/>
<attr name="repeat_toggle_modes"/> <attr name="repeat_toggle_modes"/>
<attr name="show_shuffle_button"/> <attr name="show_shuffle_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/> <attr name="controller_layout_id"/>
</declare-styleable> </declare-styleable>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment