mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-24 00:50:32 +00:00
-Added playback speed control dialog to allow full user control over player tempo and pitch parameters.
-Changed tempo and pitch button in service player activity and tempo button in main video player to open speed control dialog. -Changed LIVE button to be no longer clickable when player position is at or beyond default position. -Changed main video player to use AppCompatActivity rather than Activity. -Fixed video player tempo button not updating when player speed parameters change. -Fixed player crashing on lower sdk versions due to no MediaButtonReceiver, added intent back to manifest. -Fixed inconsistent gradle library naming. -Fixed stetho dependencies incorrect version.
This commit is contained in:
parent
5167fe078b
commit
e885822a34
@ -51,9 +51,10 @@ ext {
|
|||||||
supportLibVersion = '27.1.0'
|
supportLibVersion = '27.1.0'
|
||||||
exoPlayerLibVersion = '2.7.1'
|
exoPlayerLibVersion = '2.7.1'
|
||||||
roomDbLibVersion = '1.0.0'
|
roomDbLibVersion = '1.0.0'
|
||||||
leakCanaryVersion = '1.5.4'
|
leakCanaryLibVersion = '1.5.4'
|
||||||
okHttpVersion = '1.5.0'
|
okHttpLibVersion = '1.5.0'
|
||||||
icepickVersion = '3.2.0'
|
icepickLibVersion = '3.2.0'
|
||||||
|
stethoLibVersion = '1.5.0'
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||||
@ -81,8 +82,8 @@ dependencies {
|
|||||||
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion"
|
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion"
|
||||||
implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion"
|
implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion"
|
||||||
|
|
||||||
debugImplementation "com.facebook.stetho:stetho:$okHttpVersion"
|
debugImplementation "com.facebook.stetho:stetho:$stethoLibVersion"
|
||||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:$okHttpVersion"
|
debugImplementation "com.facebook.stetho:stetho-urlconnection:$stethoLibVersion"
|
||||||
debugImplementation 'com.android.support:multidex:1.0.3'
|
debugImplementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.10'
|
implementation 'io.reactivex.rxjava2:rxjava:2.1.10'
|
||||||
@ -93,13 +94,13 @@ dependencies {
|
|||||||
implementation "android.arch.persistence.room:rxjava2:$roomDbLibVersion"
|
implementation "android.arch.persistence.room:rxjava2:$roomDbLibVersion"
|
||||||
annotationProcessor "android.arch.persistence.room:compiler:$roomDbLibVersion"
|
annotationProcessor "android.arch.persistence.room:compiler:$roomDbLibVersion"
|
||||||
|
|
||||||
implementation "frankiesardo:icepick:$icepickVersion"
|
implementation "frankiesardo:icepick:$icepickLibVersion"
|
||||||
annotationProcessor "frankiesardo:icepick-processor:$icepickVersion"
|
annotationProcessor "frankiesardo:icepick-processor:$icepickLibVersion"
|
||||||
|
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
|
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryLibVersion"
|
||||||
betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
|
betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion"
|
||||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
|
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion"
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
|
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
|
||||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpVersion"
|
debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpLibVersion"
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,12 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".player.old.PlayVideoActivity"
|
android:name=".player.old.PlayVideoActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
|
@ -553,7 +553,7 @@ public abstract class BasePlayer implements
|
|||||||
// Ensure dynamic/livestream timeline changes does not cause negative position
|
// Ensure dynamic/livestream timeline changes does not cause negative position
|
||||||
if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) {
|
if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) {
|
||||||
if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " +
|
if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " +
|
||||||
"clamping position to default time.");
|
"clamping position to 0ms.");
|
||||||
seekTo(/*clampToTime=*/0);
|
seekTo(/*clampToTime=*/0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -639,7 +639,6 @@ public abstract class BasePlayer implements
|
|||||||
"[" + getTimeString((int)recoveryPositionMillis) + "]");
|
"[" + getTimeString((int)recoveryPositionMillis) + "]");
|
||||||
seekTo(recoveryPositionMillis);
|
seekTo(recoveryPositionMillis);
|
||||||
playQueue.unsetRecovery(currentSourceIndex);
|
playQueue.unsetRecovery(currentSourceIndex);
|
||||||
isSynchronizing = false;
|
|
||||||
|
|
||||||
} else if (isSynchronizing && simpleExoPlayer.isCurrentWindowDynamic()) {
|
} else if (isSynchronizing && simpleExoPlayer.isCurrentWindowDynamic()) {
|
||||||
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
|
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
|
||||||
@ -1111,6 +1110,24 @@ public abstract class BasePlayer implements
|
|||||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
|
||||||
|
public boolean isLiveEdge() {
|
||||||
|
if (simpleExoPlayer == null) return false;
|
||||||
|
final boolean isLive = simpleExoPlayer.isCurrentWindowDynamic();
|
||||||
|
if (!isLive) return false;
|
||||||
|
|
||||||
|
final Timeline currentTimeline = simpleExoPlayer.getCurrentTimeline();
|
||||||
|
final int currentWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
|
||||||
|
if (currentTimeline.isEmpty() || currentWindowIndex < 0 ||
|
||||||
|
currentWindowIndex >= currentTimeline.getWindowCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeline.Window timelineWindow = new Timeline.Window();
|
||||||
|
currentTimeline.getWindow(currentWindowIndex, timelineWindow);
|
||||||
|
return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
final int state = simpleExoPlayer.getPlaybackState();
|
final int state = simpleExoPlayer.getPlaybackState();
|
||||||
return (state == Player.STATE_READY || state == Player.STATE_BUFFERING)
|
return (state == Player.STATE_READY || state == Player.STATE_BUFFERING)
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe.player;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@ -33,6 +32,7 @@ import android.preference.PreferenceManager;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@ -49,6 +49,7 @@ import android.widget.SeekBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||||
@ -57,6 +58,7 @@ import org.schabi.newpipe.R;
|
|||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||||
|
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.playlist.PlayQueue;
|
import org.schabi.newpipe.playlist.PlayQueue;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
@ -87,7 +89,8 @@ import static org.schabi.newpipe.util.StateSaver.KEY_SAVED_STATE;
|
|||||||
*
|
*
|
||||||
* @author mauriciocolli
|
* @author mauriciocolli
|
||||||
*/
|
*/
|
||||||
public final class MainVideoPlayer extends Activity implements StateSaver.WriteRead {
|
public final class MainVideoPlayer extends AppCompatActivity
|
||||||
|
implements StateSaver.WriteRead, PlaybackParameterDialog.Callback {
|
||||||
private static final String TAG = ".MainVideoPlayer";
|
private static final String TAG = ".MainVideoPlayer";
|
||||||
private static final boolean DEBUG = BasePlayer.DEBUG;
|
private static final boolean DEBUG = BasePlayer.DEBUG;
|
||||||
|
|
||||||
@ -340,6 +343,15 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Playback Parameters Listener
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
|
||||||
|
if (playerImpl != null) playerImpl.setPlaybackParameters(playbackTempo, playbackPitch);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
@ -630,6 +642,12 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR
|
|||||||
showControlsThenHide();
|
showControlsThenHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackSpeedClicked() {
|
||||||
|
PlaybackParameterDialog.newInstance(getPlaybackSpeed(), getPlaybackPitch())
|
||||||
|
.show(getSupportFragmentManager(), TAG);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
super.onStopTrackingTouch(seekBar);
|
super.onStopTrackingTouch(seekBar);
|
||||||
|
@ -31,6 +31,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|||||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||||
import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog;
|
import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog;
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||||
|
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||||
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
|
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
|
||||||
@ -43,7 +44,8 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch;
|
|||||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||||
|
|
||||||
public abstract class ServicePlayerActivity extends AppCompatActivity
|
public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener {
|
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
|
||||||
|
View.OnClickListener, PlaybackParameterDialog.Callback {
|
||||||
|
|
||||||
private boolean serviceBound;
|
private boolean serviceBound;
|
||||||
private ServiceConnection serviceConnection;
|
private ServiceConnection serviceConnection;
|
||||||
@ -57,8 +59,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
|
private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47;
|
||||||
private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61;
|
|
||||||
private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97;
|
|
||||||
|
|
||||||
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80;
|
||||||
|
|
||||||
@ -85,9 +85,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
private TextView playbackSpeedButton;
|
private TextView playbackSpeedButton;
|
||||||
private PopupMenu playbackSpeedPopupMenu;
|
|
||||||
private TextView playbackPitchButton;
|
private TextView playbackPitchButton;
|
||||||
private PopupMenu playbackPitchPopupMenu;
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Abstracts
|
// Abstracts
|
||||||
@ -317,45 +315,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
shuffleButton.setOnClickListener(this);
|
shuffleButton.setOnClickListener(this);
|
||||||
playbackSpeedButton.setOnClickListener(this);
|
playbackSpeedButton.setOnClickListener(this);
|
||||||
playbackPitchButton.setOnClickListener(this);
|
playbackPitchButton.setOnClickListener(this);
|
||||||
|
|
||||||
playbackSpeedPopupMenu = new PopupMenu(this, playbackSpeedButton);
|
|
||||||
playbackPitchPopupMenu = new PopupMenu(this, playbackPitchButton);
|
|
||||||
buildPlaybackSpeedMenu();
|
|
||||||
buildPlaybackPitchMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildPlaybackSpeedMenu() {
|
|
||||||
if (playbackSpeedPopupMenu == null) return;
|
|
||||||
|
|
||||||
playbackSpeedPopupMenu.getMenu().removeGroup(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID);
|
|
||||||
for (int i = 0; i < BasePlayer.PLAYBACK_SPEEDS.length; i++) {
|
|
||||||
final float playbackSpeed = BasePlayer.PLAYBACK_SPEEDS[i];
|
|
||||||
final String formattedSpeed = formatSpeed(playbackSpeed);
|
|
||||||
final MenuItem item = playbackSpeedPopupMenu.getMenu().add(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedSpeed);
|
|
||||||
item.setOnMenuItemClickListener(menuItem -> {
|
|
||||||
if (player == null) return false;
|
|
||||||
|
|
||||||
player.setPlaybackSpeed(playbackSpeed);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildPlaybackPitchMenu() {
|
|
||||||
if (playbackPitchPopupMenu == null) return;
|
|
||||||
|
|
||||||
playbackPitchPopupMenu.getMenu().removeGroup(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID);
|
|
||||||
for (int i = 0; i < BasePlayer.PLAYBACK_PITCHES.length; i++) {
|
|
||||||
final float playbackPitch = BasePlayer.PLAYBACK_PITCHES[i];
|
|
||||||
final String formattedPitch = formatPitch(playbackPitch);
|
|
||||||
final MenuItem item = playbackPitchPopupMenu.getMenu().add(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedPitch);
|
|
||||||
item.setOnMenuItemClickListener(menuItem -> {
|
|
||||||
if (player == null) return false;
|
|
||||||
|
|
||||||
player.setPlaybackPitch(playbackPitch);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
|
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
|
||||||
@ -474,10 +433,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
player.onShuffleClicked();
|
player.onShuffleClicked();
|
||||||
|
|
||||||
} else if (view.getId() == playbackSpeedButton.getId()) {
|
} else if (view.getId() == playbackSpeedButton.getId()) {
|
||||||
playbackSpeedPopupMenu.show();
|
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(),
|
||||||
|
player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag());
|
||||||
|
|
||||||
} else if (view.getId() == playbackPitchButton.getId()) {
|
} else if (view.getId() == playbackPitchButton.getId()) {
|
||||||
playbackPitchPopupMenu.show();
|
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(),
|
||||||
|
player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag());
|
||||||
|
|
||||||
} else if (view.getId() == metadata.getId()) {
|
} else if (view.getId() == metadata.getId()) {
|
||||||
scrollToSelected();
|
scrollToSelected();
|
||||||
@ -488,6 +449,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Playback Parameters Listener
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
|
||||||
|
if (player != null) player.setPlaybackParameters(playbackTempo, playbackPitch);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Seekbar Listener
|
// Seekbar Listener
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -539,6 +509,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||||||
progressSeekBar.setProgress(currentProgress);
|
progressSeekBar.setProgress(currentProgress);
|
||||||
progressCurrentTime.setText(Localization.getDurationString(currentProgress / 1000));
|
progressCurrentTime.setText(Localization.getDurationString(currentProgress / 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (player != null) {
|
||||||
|
progressLiveSync.setClickable(!player.isLiveEdge());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,6 +49,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||||
@ -523,6 +524,12 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
onTextTrackUpdate();
|
onTextTrackUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
|
super.onPlaybackParametersChanged(playbackParameters);
|
||||||
|
playbackSpeedTextView.setText(formatSpeed(playbackParameters.speed));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -615,6 +622,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
if (DEBUG && bufferPercent % 20 == 0) { //Limit log
|
if (DEBUG && bufferPercent % 20 == 0) { //Limit log
|
||||||
Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
|
Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
|
||||||
}
|
}
|
||||||
|
playbackLiveSync.setClickable(!isLiveEdge());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -718,7 +726,7 @@ public abstract class VideoPlayer extends BasePlayer
|
|||||||
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPlaybackSpeedClicked() {
|
public void onPlaybackSpeedClicked() {
|
||||||
if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called");
|
if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called");
|
||||||
playbackSpeedPopupMenu.show();
|
playbackSpeedPopupMenu.show();
|
||||||
isSomePopupMenuVisible = true;
|
isSomePopupMenuVisible = true;
|
||||||
|
@ -0,0 +1,348 @@
|
|||||||
|
package org.schabi.newpipe.player.helper;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.player.BasePlayer.DEBUG;
|
||||||
|
|
||||||
|
public class PlaybackParameterDialog extends DialogFragment {
|
||||||
|
private static final String TAG = "PlaybackParameterDialog";
|
||||||
|
|
||||||
|
public static final float MINIMUM_PLAYBACK_VALUE = 0.25f;
|
||||||
|
public static final float MAXIMUM_PLAYBACK_VALUE = 3.00f;
|
||||||
|
|
||||||
|
public static final String STEP_UP_SIGN = "+";
|
||||||
|
public static final String STEP_DOWN_SIGN = "-";
|
||||||
|
public static final float PLAYBACK_STEP_VALUE = 0.05f;
|
||||||
|
|
||||||
|
public static final float NIGHTCORE_TEMPO = 1.20f;
|
||||||
|
public static final float NIGHTCORE_PITCH_LOWER = 1.15f;
|
||||||
|
public static final float NIGHTCORE_PITCH_UPPER = 1.25f;
|
||||||
|
|
||||||
|
public static final float DEFAULT_TEMPO = 1.00f;
|
||||||
|
public static final float DEFAULT_PITCH = 1.00f;
|
||||||
|
|
||||||
|
private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
|
||||||
|
private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Callback callback;
|
||||||
|
|
||||||
|
private float initialTempo = DEFAULT_TEMPO;
|
||||||
|
private float initialPitch = DEFAULT_PITCH;
|
||||||
|
|
||||||
|
private SeekBar tempoSlider;
|
||||||
|
private TextView tempoMinimumText;
|
||||||
|
private TextView tempoMaximumText;
|
||||||
|
private TextView tempoCurrentText;
|
||||||
|
private TextView tempoStepDownText;
|
||||||
|
private TextView tempoStepUpText;
|
||||||
|
|
||||||
|
private SeekBar pitchSlider;
|
||||||
|
private TextView pitchMinimumText;
|
||||||
|
private TextView pitchMaximumText;
|
||||||
|
private TextView pitchCurrentText;
|
||||||
|
private TextView pitchStepDownText;
|
||||||
|
private TextView pitchStepUpText;
|
||||||
|
|
||||||
|
private CheckBox unhookingCheckbox;
|
||||||
|
|
||||||
|
private TextView nightCorePresetText;
|
||||||
|
private TextView resetPresetText;
|
||||||
|
|
||||||
|
public static PlaybackParameterDialog newInstance(final float playbackTempo,
|
||||||
|
final float playbackPitch) {
|
||||||
|
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||||
|
dialog.initialTempo = playbackTempo;
|
||||||
|
dialog.initialPitch = playbackPitch;
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Lifecycle
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context != null && context instanceof Callback) {
|
||||||
|
callback = (Callback) context;
|
||||||
|
} else {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
initialTempo = savedInstanceState.getFloat(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
|
||||||
|
initialPitch = savedInstanceState.getFloat(INITIAL_PITCH_KEY, DEFAULT_PITCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putFloat(INITIAL_TEMPO_KEY, initialTempo);
|
||||||
|
outState.putFloat(INITIAL_PITCH_KEY, initialPitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Dialog
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||||
|
final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null);
|
||||||
|
setupView(view);
|
||||||
|
|
||||||
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.playback_speed_control)
|
||||||
|
.setView(view)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
|
||||||
|
setPlaybackParameters(initialTempo, initialPitch))
|
||||||
|
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
|
||||||
|
setPlaybackParameters(getCurrentTempo(), getCurrentPitch()));
|
||||||
|
|
||||||
|
return dialogBuilder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Dialog Builder
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void setupView(@NonNull View rootView) {
|
||||||
|
setupHookingControl(rootView);
|
||||||
|
setupTempoControl(rootView);
|
||||||
|
setupPitchControl(rootView);
|
||||||
|
setupPresetControl(rootView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTempoControl(@NonNull View rootView) {
|
||||||
|
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
|
||||||
|
tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||||
|
tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||||
|
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
|
||||||
|
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
||||||
|
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||||
|
|
||||||
|
tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo));
|
||||||
|
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
|
||||||
|
|
||||||
|
tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
||||||
|
tempoStepUpText.setOnClickListener(view ->
|
||||||
|
setTempo(getCurrentTempo() + PLAYBACK_STEP_VALUE));
|
||||||
|
|
||||||
|
tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
||||||
|
tempoStepDownText.setOnClickListener(view ->
|
||||||
|
setTempo(getCurrentTempo() - PLAYBACK_STEP_VALUE));
|
||||||
|
|
||||||
|
tempoSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialTempo));
|
||||||
|
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() {
|
||||||
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
final float currentTempo = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress);
|
||||||
|
if (fromUser) { // this change is first in chain
|
||||||
|
setTempo(currentTempo);
|
||||||
|
} else {
|
||||||
|
setPlaybackParameters(currentTempo, getCurrentPitch());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
// Do Nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
// Do Nothing.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPitchControl(@NonNull View rootView) {
|
||||||
|
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
|
||||||
|
pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||||
|
pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||||
|
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
|
||||||
|
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
||||||
|
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||||
|
|
||||||
|
pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch));
|
||||||
|
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
|
||||||
|
|
||||||
|
pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
||||||
|
pitchStepUpText.setOnClickListener(view ->
|
||||||
|
setPitch(getCurrentPitch() + PLAYBACK_STEP_VALUE));
|
||||||
|
|
||||||
|
pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
||||||
|
pitchStepDownText.setOnClickListener(view ->
|
||||||
|
setPitch(getCurrentPitch() - PLAYBACK_STEP_VALUE));
|
||||||
|
|
||||||
|
pitchSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE));
|
||||||
|
pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialPitch));
|
||||||
|
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() {
|
||||||
|
return new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
final float currentPitch = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress);
|
||||||
|
if (fromUser) { // this change is first in chain
|
||||||
|
setPitch(currentPitch);
|
||||||
|
} else {
|
||||||
|
setPlaybackParameters(getCurrentTempo(), currentPitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
// Do Nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
// Do Nothing.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupHookingControl(@NonNull View rootView) {
|
||||||
|
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
||||||
|
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
||||||
|
if (isChecked) return;
|
||||||
|
// When unchecked, slide back to the minimum of current tempo or pitch
|
||||||
|
final float minimum = Math.min(getCurrentPitch(), getCurrentTempo());
|
||||||
|
setSliders(minimum);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPresetControl(@NonNull View rootView) {
|
||||||
|
nightCorePresetText = rootView.findViewById(R.id.presetNightcore);
|
||||||
|
nightCorePresetText.setOnClickListener(view -> {
|
||||||
|
final float randomPitch = NIGHTCORE_PITCH_LOWER +
|
||||||
|
(float) Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER);
|
||||||
|
|
||||||
|
setTempoSlider(NIGHTCORE_TEMPO);
|
||||||
|
setPitchSlider(randomPitch);
|
||||||
|
});
|
||||||
|
|
||||||
|
resetPresetText = rootView.findViewById(R.id.presetReset);
|
||||||
|
resetPresetText.setOnClickListener(view -> {
|
||||||
|
setTempoSlider(DEFAULT_TEMPO);
|
||||||
|
setPitchSlider(DEFAULT_PITCH);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helper
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void setTempo(final float newTempo) {
|
||||||
|
if (unhookingCheckbox == null) return;
|
||||||
|
if (!unhookingCheckbox.isChecked()) {
|
||||||
|
setSliders(newTempo);
|
||||||
|
} else {
|
||||||
|
setTempoSlider(newTempo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPitch(final float newPitch) {
|
||||||
|
if (unhookingCheckbox == null) return;
|
||||||
|
if (!unhookingCheckbox.isChecked()) {
|
||||||
|
setSliders(newPitch);
|
||||||
|
} else {
|
||||||
|
setPitchSlider(newPitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSliders(final float newValue) {
|
||||||
|
setTempoSlider(newValue);
|
||||||
|
setPitchSlider(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTempoSlider(final float newTempo) {
|
||||||
|
if (tempoSlider == null) return;
|
||||||
|
// seekbar doesn't register progress if it is the same as the existing progress
|
||||||
|
tempoSlider.setProgress(Integer.MAX_VALUE);
|
||||||
|
tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newTempo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPitchSlider(final float newPitch) {
|
||||||
|
if (pitchSlider == null) return;
|
||||||
|
pitchSlider.setProgress(Integer.MAX_VALUE);
|
||||||
|
pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newPitch));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPlaybackParameters(final float tempo, final float pitch) {
|
||||||
|
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
|
||||||
|
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
|
||||||
|
"tempo=[" + tempo + "], " +
|
||||||
|
"pitch=[" + pitch + "]");
|
||||||
|
|
||||||
|
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
||||||
|
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
||||||
|
callback.onPlaybackParameterChanged(tempo, pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getCurrentTempo() {
|
||||||
|
return tempoSlider == null ? initialTempo : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE,
|
||||||
|
tempoSlider.getProgress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getCurrentPitch() {
|
||||||
|
return pitchSlider == null ? initialPitch : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE,
|
||||||
|
pitchSlider.getProgress());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts from zeroed float with a minimum offset to the nearest rounded slider
|
||||||
|
* equivalent integer
|
||||||
|
* */
|
||||||
|
private static int getSliderEquivalent(final float minimumValue, final float floatValue) {
|
||||||
|
return Math.round((floatValue - minimumValue) * 100f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts from slider integer value to an equivalent float value with a given minimum offset
|
||||||
|
* */
|
||||||
|
private static float getSliderEquivalent(final float minimumValue, final int intValue) {
|
||||||
|
return ((float) intValue) / 100f + minimumValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getStepUpPercentString(final float percent) {
|
||||||
|
return STEP_UP_SIGN + PlayerHelper.formatPitch(percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getStepDownPercentString(final float percent) {
|
||||||
|
return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent);
|
||||||
|
}
|
||||||
|
}
|
313
app/src/main/res/layout/dialog_playback_parameter.xml
Normal file
313
app/src/main/res/layout/dialog_playback_parameter.xml
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clickable="false"
|
||||||
|
android:paddingLeft="@dimen/video_item_search_padding"
|
||||||
|
android:paddingRight="@dimen/video_item_search_padding"
|
||||||
|
android:paddingTop="@dimen/video_item_search_padding">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:scrollbarAlwaysDrawVerticalTrack="true">
|
||||||
|
|
||||||
|
<!-- START HERE -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tempoControlText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="@string/playback_tempo"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_alignParentTop="true"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/tempoControl"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_below="@id/tempoControlText">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tempoStepDown"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="--%"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="-5%"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/tempoDisplay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_toRightOf="@id/tempoStepDown"
|
||||||
|
android:layout_toEndOf="@id/tempoStepDown"
|
||||||
|
android:layout_toLeftOf="@id/tempoStepUp"
|
||||||
|
android:layout_toStartOf="@id/tempoStepUp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tempoMinimumText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="-.--x"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="1.00x"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tempoCurrentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="---%"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="100%"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tempoMaximumText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="---%"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="300%"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatSeekBar
|
||||||
|
android:id="@+id/tempoSeekbar"
|
||||||
|
style="@style/Widget.AppCompat.SeekBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/tempoCurrentText"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
tools:progress="50"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tempoStepUp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="+-%"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="+5%"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/separatorPitch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_below="@id/tempoControl"
|
||||||
|
android:layout_margin="@dimen/video_item_search_padding"
|
||||||
|
android:background="?attr/separator_color"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pitchControlText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="@string/playback_pitch"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_below="@id/separatorPitch"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/pitchControl"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_below="@id/pitchControlText">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pitchStepDown"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="--%"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="-5%"/>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/pitchDisplay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_toRightOf="@+id/pitchStepDown"
|
||||||
|
android:layout_toEndOf="@+id/pitchStepDown"
|
||||||
|
android:layout_toLeftOf="@+id/pitchStepUp"
|
||||||
|
android:layout_toStartOf="@+id/pitchStepUp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pitchMinimumText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="---%"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="25%"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pitchCurrentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="---%"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="100%"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pitchMaximumText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="---%"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="300%"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatSeekBar
|
||||||
|
android:id="@+id/pitchSeekbar"
|
||||||
|
style="@style/Widget.AppCompat.SeekBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/pitchCurrentText"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
tools:progress="50"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pitchStepUp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="+-%"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?attr/colorAccent"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="+5%"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/separatorCheckbox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_below="@+id/pitchControl"
|
||||||
|
android:layout_margin="@dimen/video_item_search_padding"
|
||||||
|
android:background="?attr/separator_color"/>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/unhookCheckbox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:text="@string/unhook_checkbox"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_below="@id/separatorCheckbox"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/presetSelector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_below="@id/unhookCheckbox">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/presetNightcore"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/playback_nightcore"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:textColor="?attr/colorAccent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/presetReset"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/playback_default"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:textColor="?attr/colorAccent"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- END HERE -->
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</ScrollView>
|
@ -456,4 +456,12 @@
|
|||||||
<string name="import_soundcloud_instructions_hint">yourid, soundcloud.com/yourid</string>
|
<string name="import_soundcloud_instructions_hint">yourid, soundcloud.com/yourid</string>
|
||||||
|
|
||||||
<string name="import_network_expensive_warning">Keep in mind that this operation can be network expensive.\n\nDo you want to continue?</string>
|
<string name="import_network_expensive_warning">Keep in mind that this operation can be network expensive.\n\nDo you want to continue?</string>
|
||||||
|
|
||||||
|
<!-- Playback Parameters -->
|
||||||
|
<string name="playback_speed_control">Playback Speed Control</string>
|
||||||
|
<string name="playback_tempo">Tempo</string>
|
||||||
|
<string name="playback_pitch">Pitch</string>
|
||||||
|
<string name="unhook_checkbox">Unhook (may cause distortion)</string>
|
||||||
|
<string name="playback_nightcore">Nightcore</string>
|
||||||
|
<string name="playback_default">Default</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user