diff --git a/.travis.yml b/.travis.yml
index 36b71b8ca..3df95873e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,29 +11,9 @@ android:
- android-25
# Additional components
- - extra-android-support
- extra-android-m2repository
- - extra-google-m2repository
- # Emulators
- - sys-img-armeabi-v7a-android-21
- - sys-img-armeabi-v7a-android-19
- - sys-img-armeabi-v7a-android-15
-
-env:
- global:
- - ADB_INSTALL_TIMEOUT=8 # minutes (2 by default)
- - GRADLE_OPTS=-Xmx512m # give gradle more memory since it seem to fail otherwise
- matrix:
- - ANDROID_TARGET=android-21 ANDROID_ABI=armeabi-v7a
-
-before_script:
- - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI
- - emulator -avd test -no-skin -no-audio -no-window &
- - android-wait-for-emulator
- - adb shell input keyevent 82 &
-
-script: ./gradlew --info build connectedCheck
+script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
licenses:
- '.+'
diff --git a/app/build.gradle b/app/build.gradle
index 73f11aab0..52b2c673f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -32,6 +32,9 @@ android {
dependencies {
testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.10.19'
+ testCompile 'org.json:json:20160810'
+
compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.android.support:support-v4:25.1.0'
compile 'com.android.support:design:25.1.0'
@@ -45,8 +48,5 @@ dependencies {
compile 'com.google.code.gson:gson:2.4'
compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'ch.acra:acra:4.9.0'
- compile 'com.devbrackets.android:exomedia:3.1.1'
- testCompile 'junit:junit:4.12'
- testCompile 'org.mockito:mockito-core:1.10.19'
- testCompile 'org.json:json:20160810'
+ compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a7dcd9205..0cf26b394 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,24 +51,7 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleInstance"
- android:theme="@style/PlayerTheme">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:theme="@style/PlayerTheme"/>
+ android:label="@string/popup_mode_share_menu_title">
diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
index a221e4e94..719a6bdbd 100644
--- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
@@ -51,6 +51,7 @@ import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.info_list.InfoItemBuilder;
+import org.schabi.newpipe.player.AbstractPlayer;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.PlayVideoActivity;
@@ -59,6 +60,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack;
import org.schabi.newpipe.util.PermissionHelper;
+import java.util.ArrayList;
import java.util.Vector;
import static android.app.Activity.RESULT_OK;
@@ -331,10 +333,12 @@ public class VideoItemDetailFragment extends Fragment {
// so, I can notify the service through a broadcast, but the problem is
// when I click in another video, another thumbnail will be load, and will
// notify again, so I send the videoUrl and compare with the service's url
- ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- Intent intent = new Intent(PopupVideoPlayer.InternalListener.ACTION_UPDATE_THUMB);
- intent.putExtra(PopupVideoPlayer.VIDEO_URL, info.webpage_url);
- getContext().sendBroadcast(intent);
+ if (getContext() != null) {
+ ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
+ Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB);
+ intent.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url);
+ getContext().sendBroadcast(intent);
+ }
}
}
@@ -388,13 +392,15 @@ public class VideoItemDetailFragment extends Fragment {
if (streamThumbnail != null)
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- VideoStream selectedVideoStream = info.video_streams.get(selectedStreamId);
Intent i = new Intent(activity, PopupVideoPlayer.class);
- Toast.makeText(activity, "Starting in popup mode", Toast.LENGTH_SHORT).show();
- i.putExtra(PopupVideoPlayer.VIDEO_TITLE, info.title)
- .putExtra(PopupVideoPlayer.STREAM_URL, selectedVideoStream.url)
- .putExtra(PopupVideoPlayer.CHANNEL_NAME, info.uploader)
- .putExtra(PopupVideoPlayer.VIDEO_URL, info.webpage_url);
+ Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
+ i.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
+ .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
+ .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamId)
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams));
+ if (info.start_position > 0) i.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
+
activity.startService(i);
}
});
@@ -784,47 +790,31 @@ public class VideoItemDetailFragment extends Fragment {
builder.create().show();
}
} else {
- if (PreferenceManager.getDefaultSharedPreferences(activity)
- .getBoolean(activity.getString(R.string.use_exoplayer_key), false)) {
-
- // TODO: Fix this mess
- if (streamThumbnail != null)
- ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- // exo player
-
- if(info.dashMpdUrl != null && !info.dashMpdUrl.isEmpty()) {
- // try dash
- Intent intent = new Intent(activity, ExoPlayerActivity.class)
- .setData(Uri.parse(info.dashMpdUrl));
- //.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_DASH);
- startActivity(intent);
- } else if((info.audio_streams != null && !info.audio_streams.isEmpty()) &&
- (info.video_only_streams != null && !info.video_only_streams.isEmpty())) {
- // try smooth streaming
-
- } else {
- //default streaming
- Intent intent = new Intent(activity, ExoPlayerActivity.class)
- .setDataAndType(Uri.parse(selectedVideoStream.url),
- MediaFormat.getMimeById(selectedVideoStream.format))
-
- .putExtra(ExoPlayerActivity.VIDEO_TITLE, info.title)
- .putExtra(ExoPlayerActivity.CHANNEL_NAME, info.uploader);
- //.putExtra(ExoPlayerActivity.CONTENT_TYPE_EXTRA, Util.TYPE_OTHER);
-
- activity.startActivity(intent); // HERE !!!
- }
- //-------------
-
+ Intent intent;
+ boolean useOldPlayer = PreferenceManager
+ .getDefaultSharedPreferences(activity)
+ .getBoolean(activity.getString(R.string.use_old_player_key), false)
+ || (Build.VERSION.SDK_INT < 16);
+ if (!useOldPlayer) {
+ // ExoPlayer
+ if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
+ intent = new Intent(activity, ExoPlayerActivity.class)
+ .putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
+ .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
+ .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, actionBarHandler.getSelectedVideoStream())
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams));
+ if (info.start_position > 0) intent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
} else {
// Internal Player
- Intent intent = new Intent(activity, PlayVideoActivity.class)
+ intent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title)
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url)
.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
- activity.startActivity(intent); //also HERE !!!
}
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity.startActivity(intent);
}
// --------------------------------------------
diff --git a/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java b/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
new file mode 100644
index 000000000..1c6d4822b
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
@@ -0,0 +1,1104 @@
+package org.schabi.newpipe.player;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.support.v4.content.ContextCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.DefaultLoadControl;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
+import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
+import com.google.android.exoplayer2.source.hls.HlsMediaSource;
+import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
+import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
+import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
+import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
+import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
+import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
+import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
+import com.google.android.exoplayer2.upstream.cache.SimpleCache;
+import com.google.android.exoplayer2.util.Util;
+
+import org.schabi.newpipe.ActivityCommunicator;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.MediaFormat;
+import org.schabi.newpipe.extractor.stream_info.VideoStream;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Common properties of the players
+ *
+ * @author mauriciocolli
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBarChangeListener, View.OnClickListener, ExoPlayer.EventListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener, SimpleExoPlayer.VideoListener {
+ public static final boolean DEBUG = false;
+ public final String TAG;
+
+ protected Context context;
+ private SharedPreferences sharedPreferences;
+
+ private static int currentState = -1;
+ public static final String ACTION_UPDATE_THUMB = "org.schabi.newpipe.player.AbstractPlayer.UPDATE_THUMBNAIL";
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Intent
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final String VIDEO_URL = "video_url";
+ public static final String VIDEO_STREAMS_LIST = "video_streams_list";
+ public static final String VIDEO_TITLE = "video_title";
+ public static final String INDEX_SEL_VIDEO_STREAM = "index_selected_video_stream";
+ public static final String START_POSITION = "start_position";
+ public static final String CHANNEL_NAME = "channel_name";
+ public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe";
+
+ private String videoUrl = "";
+ private int videoStartPos = -1;
+ private String videoTitle = "";
+ private Bitmap videoThumbnail;
+ private String channelName = "";
+ private int selectedIndexStream;
+ private ArrayList videoStreamsList;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Player
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static final int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
+ public static final int DEFAULT_CONTROLS_HIDE_TIME = 3000; // 3 Seconds
+ public static final String CACHE_FOLDER_NAME = "exoplayer";
+
+ private boolean startedFromNewPipe = true;
+ private boolean isPrepared = false;
+ private boolean wasPlaying = false;
+ private SimpleExoPlayer simpleExoPlayer;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private MediaSource videoSource;
+ private static CacheDataSourceFactory cacheDataSourceFactory;
+ private static final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
+ private static final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
+
+ private AtomicBoolean isProgressLoopRunning = new AtomicBoolean();
+ private Handler progressLoop;
+ private Runnable progressUpdate;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Repeat
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private RepeatMode currentRepeatMode = RepeatMode.REPEAT_DISABLED;
+
+ public enum RepeatMode {
+ REPEAT_DISABLED,
+ REPEAT_ONE,
+ REPEAT_ALL
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Views
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private View rootView;
+
+ private AspectRatioFrameLayout aspectRatioFrameLayout;
+ private SurfaceView surfaceView;
+ private View surfaceForeground;
+
+ private View loadingPanel;
+ private ImageView endScreen;
+ private ImageView controlAnimationView;
+
+ private View controlsRoot;
+ private TextView currentDisplaySeek;
+
+ private View bottomControlsRoot;
+ private SeekBar playbackSeekBar;
+ private TextView playbackCurrentTime;
+ private TextView playbackEndTime;
+
+ private View topControlsRoot;
+ private TextView qualityTextView;
+ private ImageButton fullScreenButton;
+
+ private ValueAnimator controlViewAnimator;
+
+ private boolean isQualityPopupMenuVisible = false;
+ private boolean qualityChanged = false;
+ private int qualityPopupMenuGroupId = 69;
+ private PopupMenu qualityPopupMenu;
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ public AbstractPlayer(String debugTag, Context context) {
+ this.TAG = debugTag;
+ this.context = context;
+ this.progressLoop = new Handler();
+ this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+
+ if (cacheDataSourceFactory == null) {
+ DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getPackageName()), bandwidthMeter);
+ File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
+ if (!cacheDir.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ cacheDir.mkdir();
+ }
+
+ Log.d(TAG, "buildMediaSource: cacheDir = " + cacheDir.getAbsolutePath());
+ SimpleCache simpleCache = new SimpleCache(cacheDir, new LeastRecentlyUsedCacheEvictor(64 * 1024 * 1024L));
+ cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory, CacheDataSource.FLAG_BLOCK_ON_CACHE, 512 * 1024);
+ }
+ }
+
+ public void setup(View rootView) {
+ initViews(rootView);
+ initListeners();
+ if (simpleExoPlayer == null) initPlayer();
+ else {
+ simpleExoPlayer.addListener(this);
+ simpleExoPlayer.setVideoListener(this);
+ simpleExoPlayer.setVideoSurfaceView(surfaceView);
+ }
+ }
+
+ public void initViews(View rootView) {
+ this.rootView = rootView;
+ this.aspectRatioFrameLayout = (AspectRatioFrameLayout) rootView.findViewById(R.id.aspectRatioLayout);
+ this.surfaceView = (SurfaceView) rootView.findViewById(R.id.surfaceView);
+ this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground);
+ this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
+ this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
+ this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
+ this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot);
+ this.currentDisplaySeek = (TextView) rootView.findViewById(R.id.currentDisplaySeek);
+ this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
+ this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
+ this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
+ this.bottomControlsRoot = rootView.findViewById(R.id.bottomControls);
+ this.topControlsRoot = rootView.findViewById(R.id.topControls);
+ this.qualityTextView = (TextView) rootView.findViewById(R.id.qualityTextView);
+ this.fullScreenButton = (ImageButton) rootView.findViewById(R.id.fullScreenButton);
+
+ //this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
+ this.playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
+
+ this.qualityPopupMenu = new PopupMenu(context, qualityTextView);
+
+ ((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);
+
+ }
+
+ public void initListeners() {
+ progressUpdate = new Runnable() {
+ @Override
+ public void run() {
+ //if(DEBUG) Log.d(TAG, "progressUpdate run() called");
+ onUpdateProgress((int) simpleExoPlayer.getCurrentPosition(), (int) simpleExoPlayer.getDuration(), simpleExoPlayer.getBufferedPercentage());
+ if (isProgressLoopRunning.get()) progressLoop.postDelayed(this, 100);
+ }
+ };
+
+ playbackSeekBar.setOnSeekBarChangeListener(this);
+ fullScreenButton.setOnClickListener(this);
+ qualityTextView.setOnClickListener(this);
+ }
+
+ public void initPlayer() {
+ if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
+
+ AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
+ DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
+ DefaultLoadControl loadControl = new DefaultLoadControl();
+
+ simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(context, defaultTrackSelector, loadControl);
+ simpleExoPlayer.addListener(this);
+ simpleExoPlayer.setVideoListener(this);
+ simpleExoPlayer.setVideoSurfaceView(surfaceView);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void handleIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
+ if (intent == null) return;
+
+ selectedIndexStream = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
+
+ Serializable serializable = intent.getSerializableExtra(VIDEO_STREAMS_LIST);
+
+ if (serializable instanceof ArrayList) videoStreamsList = (ArrayList) serializable;
+ if (serializable instanceof Vector) videoStreamsList = new ArrayList<>((List) serializable);
+
+ videoUrl = intent.getStringExtra(VIDEO_URL);
+ videoTitle = intent.getStringExtra(VIDEO_TITLE);
+ videoStartPos = intent.getIntExtra(START_POSITION, -1);
+ channelName = intent.getStringExtra(CHANNEL_NAME);
+ startedFromNewPipe = intent.getBooleanExtra(STARTED_FROM_NEWPIPE, true);
+ try {
+ videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ playVideo(getSelectedStreamUri(), true);
+ }
+
+ public void playVideo(Uri videoURI, boolean autoPlay) {
+ if (DEBUG) Log.d(TAG, "playVideo() called with: videoURI = [" + videoURI + "], autoPlay = [" + autoPlay + "]");
+
+ if (videoURI == null || simpleExoPlayer == null) {
+ onError();
+ return;
+ }
+
+ changeState(STATE_LOADING);
+ isPrepared = false;
+ qualityChanged = false;
+
+ qualityPopupMenu.getMenu().removeGroup(qualityPopupMenuGroupId);
+ buildQualityMenu(qualityPopupMenu);
+
+ videoSource = buildMediaSource(videoURI, MediaFormat.getSuffixById(videoStreamsList.get(selectedIndexStream).format));
+
+ if (simpleExoPlayer.getPlaybackState() != ExoPlayer.STATE_IDLE) simpleExoPlayer.stop();
+ if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
+ simpleExoPlayer.prepare(videoSource);
+ simpleExoPlayer.setPlayWhenReady(autoPlay);
+ }
+
+ public void destroy() {
+ if (DEBUG) Log.d(TAG, "destroy() called");
+ if (simpleExoPlayer != null) {
+ simpleExoPlayer.stop();
+ simpleExoPlayer.release();
+ }
+ if (progressLoop != null) stopProgressLoop();
+ }
+
+ private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
+ if (DEBUG) Log.d(TAG, "buildMediaSource() called with: uri = [" + uri + "], overrideExtension = [" + overrideExtension + "]");
+ int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
+ switch (type) {
+ case C.TYPE_SS:
+ return new SsMediaSource(uri, cacheDataSourceFactory, new DefaultSsChunkSource.Factory(cacheDataSourceFactory), null, null);
+ case C.TYPE_DASH:
+ return new DashMediaSource(uri, cacheDataSourceFactory, new DefaultDashChunkSource.Factory(cacheDataSourceFactory), null, null);
+ case C.TYPE_HLS:
+ return new HlsMediaSource(uri, cacheDataSourceFactory, null, null);
+ case C.TYPE_OTHER:
+ return new ExtractorMediaSource(uri, cacheDataSourceFactory, extractorsFactory, null, null);
+ default: {
+ throw new IllegalStateException("Unsupported type: " + type);
+ }
+ }
+ }
+
+ public void buildQualityMenu(PopupMenu popupMenu) {
+ for (int i = 0; i < videoStreamsList.size(); i++) {
+ VideoStream videoStream = videoStreamsList.get(i);
+ popupMenu.getMenu().add(qualityPopupMenuGroupId, i, Menu.NONE, MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
+ }
+ qualityTextView.setText(videoStreamsList.get(selectedIndexStream).resolution);
+ popupMenu.setOnMenuItemClickListener(this);
+ popupMenu.setOnDismissListener(this);
+
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // States Implementation
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void changeState(int state) {
+ if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
+ currentState = state;
+ switch (state) {
+ case STATE_LOADING:
+ onLoading();
+ break;
+ case STATE_PLAYING:
+ onPlaying();
+ break;
+ case STATE_BUFFERING:
+ onBuffering();
+ break;
+ case STATE_PAUSED:
+ onPaused();
+ break;
+ case STATE_PAUSED_SEEK:
+ onPausedSeek();
+ break;
+ case STATE_COMPLETED:
+ onCompleted();
+ break;
+ }
+ }
+
+ @Override
+ public void onLoading() {
+ if (DEBUG) Log.d(TAG, "onLoading() called");
+
+ if (!isProgressLoopRunning.get()) startProgressLoop();
+
+ showAndAnimateControl(-1, true);
+ playbackSeekBar.setEnabled(true);
+ playbackSeekBar.setProgress(0);
+
+ // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
+
+ animateView(endScreen, false, 0, 0);
+ animateView(controlsRoot, false, 0, 0);
+ loadingPanel.setBackgroundColor(Color.BLACK);
+ animateView(loadingPanel, true, 0, 0);
+ animateView(surfaceForeground, true, 100, 0);
+ }
+
+ @Override
+ public void onPlaying() {
+ if (DEBUG) Log.d(TAG, "onPlaying() called");
+ if (!isProgressLoopRunning.get()) startProgressLoop();
+ showAndAnimateControl(-1, true);
+ loadingPanel.setVisibility(View.GONE);
+ animateView(controlsRoot, false, 500, DEFAULT_CONTROLS_HIDE_TIME, true);
+ animateView(currentDisplaySeek, false, 200, 0);
+ }
+
+ @Override
+ public void onBuffering() {
+ if (DEBUG) Log.d(TAG, "onBuffering() called");
+ loadingPanel.setBackgroundColor(Color.TRANSPARENT);
+ animateView(loadingPanel, true, 500, 0);
+ animateView(controlsRoot, false, 0, 0, true);
+ }
+
+ @Override
+ public void onPaused() {
+ if (DEBUG) Log.d(TAG, "onPaused() called");
+ animateView(controlsRoot, true, 500, 100);
+ loadingPanel.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onPausedSeek() {
+ if (DEBUG) Log.d(TAG, "onPausedSeek() called");
+ showAndAnimateControl(-1, true);
+ }
+
+ @Override
+ public void onCompleted() {
+ if (DEBUG) Log.d(TAG, "onCompleted() called");
+
+ if (isProgressLoopRunning.get()) stopProgressLoop();
+
+ if (videoThumbnail != null) endScreen.setImageBitmap(videoThumbnail);
+ animateView(controlsRoot, true, 500, 0);
+ animateView(endScreen, true, 800, 0);
+ animateView(currentDisplaySeek, false, 200, 0);
+ loadingPanel.setVisibility(View.GONE);
+
+ playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
+ playbackSeekBar.setProgress(playbackSeekBar.getMax());
+ playbackSeekBar.setEnabled(false);
+ playbackEndTime.setText(getTimeString(playbackSeekBar.getMax()));
+ playbackCurrentTime.setText(playbackEndTime.getText());
+ // Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
+
+ animateView(surfaceForeground, true, 100, 0);
+
+ if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
+ changeState(STATE_LOADING);
+ getPlayer().seekTo(0);
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // ExoPlayer Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onTimelineChanged(Timeline timeline, Object manifest) {
+
+ }
+
+ @Override
+ public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
+
+ }
+
+ @Override
+ public void onLoadingChanged(boolean isLoading) {
+ if (DEBUG) Log.d(TAG, "onLoadingChanged() called with: isLoading = [" + isLoading + "]");
+
+ if (!isLoading && getCurrentState() == STATE_PAUSED && isProgressLoopRunning.get()) stopProgressLoop();
+ else if (isLoading && !isProgressLoopRunning.get()) startProgressLoop();
+ }
+
+ @Override
+ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ if (DEBUG) Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
+ if (getCurrentState() == STATE_PAUSED_SEEK) {
+ if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek");
+ return;
+ }
+
+ switch (playbackState) {
+ case ExoPlayer.STATE_IDLE: // 1
+ isPrepared = false;
+ break;
+ case ExoPlayer.STATE_BUFFERING: // 2
+ if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING);
+ break;
+ case ExoPlayer.STATE_READY: //3
+ if (!isPrepared) {
+ isPrepared = true;
+ onPrepared(playWhenReady);
+ break;
+ }
+ if (currentState == STATE_PAUSED_SEEK) break;
+ changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
+ break;
+ case ExoPlayer.STATE_ENDED: // 4
+ changeState(STATE_COMPLETED);
+ isPrepared = false;
+ break;
+ }
+ }
+
+ @Override
+ public void onPlayerError(ExoPlaybackException error) {
+ if (DEBUG) Log.d(TAG, "onPlayerError() called with: error = [" + error + "]");
+ onError();
+ }
+
+ @Override
+ public void onPositionDiscontinuity() {
+ if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called");
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // ExoPlayer Video Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
+ if (DEBUG) {
+ Log.d(TAG, "onVideoSizeChanged() called with: width / height = [" + width + " / " + height + " = " + (((float) width) / height) + "], unappliedRotationDegrees = [" + unappliedRotationDegrees + "], pixelWidthHeightRatio = [" + pixelWidthHeightRatio + "]");
+ }
+ aspectRatioFrameLayout.setAspectRatio(((float) width) / height);
+ }
+
+ @Override
+ public void onRenderedFirstFrame() {
+ animateView(surfaceForeground, false, 100, 0);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // General Player
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public abstract void onError();
+
+ public void onPrepared(boolean playWhenReady) {
+ if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]");
+
+ if (videoStartPos > 0) {
+ playbackSeekBar.setProgress(videoStartPos);
+ playbackCurrentTime.setText(getTimeString(videoStartPos));
+ videoStartPos = -1;
+ }
+
+ playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
+ playbackEndTime.setText(getTimeString((int) simpleExoPlayer.getDuration()));
+
+ changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
+ }
+
+ public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
+ if (!isPrepared) return;
+ if (currentState != STATE_PAUSED) {
+ if (currentState != STATE_PAUSED_SEEK) playbackSeekBar.setProgress(currentProgress);
+ playbackCurrentTime.setText(getTimeString(currentProgress));
+ }
+ if (simpleExoPlayer.isLoading() || bufferPercent > 90) {
+ playbackSeekBar.setSecondaryProgress((int) (playbackSeekBar.getMax() * ((float) bufferPercent / 100)));
+ }
+ if (DEBUG && bufferPercent % 20 == 0) { //Limit log
+ Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
+ }
+ }
+
+ public void onUpdateThumbnail(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called with: intent = [" + intent + "]");
+ if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return;
+ videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
+ }
+
+ public void onVideoPlayPause() {
+ if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
+ if (currentState == STATE_COMPLETED) {
+ changeState(STATE_LOADING);
+ if (qualityChanged) playVideo(getSelectedStreamUri(), true);
+ simpleExoPlayer.seekTo(0);
+ return;
+ }
+ simpleExoPlayer.setPlayWhenReady(!isPlaying());
+ }
+
+ public void onFastRewind() {
+ if (DEBUG) Log.d(TAG, "onFastRewind() called");
+ seekBy(-FAST_FORWARD_REWIND_AMOUNT);
+ showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
+ animateView(controlsRoot, false, 100, 0);
+ }
+
+ public void onFastForward() {
+ if (DEBUG) Log.d(TAG, "onFastForward() called");
+ seekBy(FAST_FORWARD_REWIND_AMOUNT);
+ showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
+ animateView(controlsRoot, false, 100, 0);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // OnClick related
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onClick(View v) {
+ if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
+ if (v.getId() == fullScreenButton.getId()) {
+ onFullScreenButtonClicked();
+ } else if (v.getId() == qualityTextView.getId()) {
+ onQualitySelectorClicked();
+ }
+ }
+
+ /**
+ * Called when an item of the quality selector is selected
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ if (DEBUG) Log.d(TAG, "onMenuItemClick() called with: menuItem = [" + menuItem + "], menuItem.getItemId = [" + menuItem.getItemId() + "]");
+ if (selectedIndexStream == menuItem.getItemId()) return true;
+ setVideoStartPos((int) getPlayer().getCurrentPosition());
+
+ if (!(getCurrentState() == STATE_COMPLETED)) playVideo(Uri.parse(getVideoStreamsList().get(menuItem.getItemId()).url), wasPlaying);
+ else qualityChanged = true;
+
+ selectedIndexStream = menuItem.getItemId();
+ qualityTextView.setText(menuItem.getTitle());
+ return true;
+ }
+
+ /**
+ * Called when the quality selector is dismissed
+ */
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ if (DEBUG) Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]");
+ isQualityPopupMenuVisible = false;
+ qualityTextView.setText(videoStreamsList.get(selectedIndexStream).resolution);
+ }
+
+ public abstract void onFullScreenButtonClicked();
+
+ public void onQualitySelectorClicked() {
+ if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
+ qualityPopupMenu.show();
+ isQualityPopupMenuVisible = true;
+ animateView(getControlsRoot(), true, 300, 0);
+
+ VideoStream videoStream = videoStreamsList.get(selectedIndexStream);
+ qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
+ wasPlaying = isPlaying();
+ }
+
+ public void onRepeatClicked() {
+ if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
+ // TODO: implement repeat all when playlist is implemented
+
+ // Switch the modes between DISABLED and REPEAT_ONE, till playlist is implemented
+ setCurrentRepeatMode(getCurrentRepeatMode() == RepeatMode.REPEAT_DISABLED ?
+ RepeatMode.REPEAT_ONE :
+ RepeatMode.REPEAT_DISABLED);
+
+ if (DEBUG) Log.d(TAG, "onRepeatClicked() currentRepeatMode = " + getCurrentRepeatMode().name());
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // SeekBar Listener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (DEBUG && fromUser) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + progress + "]");
+ //if (fromUser) playbackCurrentTime.setText(getTimeString(progress));
+ if (fromUser) currentDisplaySeek.setText(getTimeString(progress));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
+ if (getCurrentState() != STATE_PAUSED_SEEK) changeState(STATE_PAUSED_SEEK);
+
+ wasPlaying = isPlaying();
+ if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
+
+ animateView(controlsRoot, true, 0, 0);
+ animateView(currentDisplaySeek, true, 300, 0);
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (DEBUG) Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]");
+
+ simpleExoPlayer.seekTo(seekBar.getProgress());
+ if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true);
+
+ playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
+ animateView(currentDisplaySeek, false, 200, 0);
+
+ if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING);
+ if (!isProgressLoopRunning.get()) startProgressLoop();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private static final StringBuilder stringBuilder = new StringBuilder();
+ private static final Formatter formatter = new Formatter(stringBuilder, Locale.getDefault());
+
+ public String getTimeString(int milliSeconds) {
+ long seconds = (milliSeconds % 60000L) / 1000L;
+ long minutes = (milliSeconds % 3600000L) / 60000L;
+ long hours = (milliSeconds % 86400000L) / 3600000L;
+ long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
+
+ stringBuilder.setLength(0);
+ return days > 0 ? formatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()
+ : hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
+ : formatter.format("%02d:%02d", minutes, seconds).toString();
+ }
+
+ public boolean isControlsVisible() {
+ return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
+ }
+
+ /**
+ * Show a animation, and depending on goneOnEnd, will stay on the screen or be gone
+ *
+ * @param drawableId the drawable that will be used to animate, pass -1 to clear any animation that is visible
+ * @param goneOnEnd will set the animation view to GONE on the end of the animation
+ */
+ public void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
+ if (DEBUG) Log.d(TAG, "showAndAnimateControl() called with: drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
+ if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
+ if (DEBUG) Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
+ controlViewAnimator.end();
+ }
+
+ if (drawableId == -1) {
+ if (controlAnimationView.getVisibility() == View.VISIBLE) {
+ controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
+ ).setDuration(300);
+ controlViewAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ controlAnimationView.setVisibility(View.GONE);
+ }
+ });
+ controlViewAnimator.start();
+ }
+ return;
+ }
+
+ float scaleFrom = goneOnEnd ? 1f : 1f, scaleTo = goneOnEnd ? 1.8f : 1.4f;
+ float alphaFrom = goneOnEnd ? 1f : 0f, alphaTo = goneOnEnd ? 0f : 1f;
+
+
+ controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(controlAnimationView,
+ PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
+ PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
+ );
+ controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
+ controlViewAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (goneOnEnd) controlAnimationView.setVisibility(View.GONE);
+ else controlAnimationView.setVisibility(View.VISIBLE);
+ }
+ });
+
+
+ controlAnimationView.setVisibility(View.VISIBLE);
+ controlAnimationView.setImageDrawable(ContextCompat.getDrawable(context, drawableId));
+ controlViewAnimator.start();
+ }
+
+ public void animateView(View view, boolean enterOrExit, long duration, long delay) {
+ animateView(view, enterOrExit, duration, delay, null, false);
+ }
+
+ public void animateView(View view, boolean enterOrExit, long duration, long delay, boolean hideUi) {
+ animateView(view, enterOrExit, duration, delay, null, hideUi);
+ }
+
+ public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
+ animateView(view, enterOrExit, duration, delay, execOnEnd, false);
+ }
+
+ /**
+ * Animate the view
+ *
+ * @param view view that will be animated
+ * @param enterOrExit true to enter, false to exit
+ * @param duration how long the animation will take, in milliseconds
+ * @param delay how long the animation will wait to start, in milliseconds
+ * @param execOnEnd runnable that will be executed when the animation ends
+ * @param hideUi need to hide ui when animation ends,
+ * just a helper for classes extending this
+ */
+ public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) {
+ if (DEBUG) {
+ Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]");
+ }
+ if (view.getVisibility() == View.VISIBLE && enterOrExit) {
+ if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.VISIBLE);
+ view.setAlpha(1f);
+ if (execOnEnd != null) execOnEnd.run();
+ return;
+ } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
+ if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.GONE);
+ view.setAlpha(0f);
+ if (execOnEnd != null) execOnEnd.run();
+ return;
+ }
+
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.VISIBLE);
+
+ if (view == controlsRoot) {
+ if (enterOrExit) {
+ view.animate().alpha(1f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ }).start();
+ } else {
+ view.animate().alpha(0f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ })
+ .start();
+ }
+ return;
+ }
+
+ if (enterOrExit) {
+ view.setAlpha(0f);
+ view.setScaleX(.8f);
+ view.setScaleY(.8f);
+ view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ }).start();
+ } else {
+ view.setAlpha(1f);
+ view.setScaleX(1f);
+ view.setScaleY(1f);
+ view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ })
+ .start();
+ }
+ }
+
+ private void seekBy(int milliSeconds) {
+ if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]");
+ if (simpleExoPlayer == null) return;
+ int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds);
+ simpleExoPlayer.seekTo(progress);
+ }
+
+ public boolean isPlaying() {
+ return simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_READY && simpleExoPlayer.getPlayWhenReady();
+ }
+
+ public boolean isQualityMenuVisible() {
+ return isQualityPopupMenuVisible;
+ }
+
+ private void startProgressLoop() {
+ progressLoop.removeCallbacksAndMessages(null);
+ isProgressLoopRunning.set(true);
+ progressLoop.post(progressUpdate);
+ }
+
+ private void stopProgressLoop() {
+ isProgressLoopRunning.set(false);
+ progressLoop.removeCallbacksAndMessages(null);
+ }
+
+ public void tryDeleteCacheFiles(Context context) {
+ File cacheDir = new File(context.getExternalCacheDir(), CACHE_FOLDER_NAME);
+
+ if (cacheDir.exists()) {
+ try {
+ if (cacheDir.isDirectory()) {
+ for (File file : cacheDir.listFiles()) {
+ try {
+ if (DEBUG) Log.d(TAG, "tryDeleteCacheFiles: " + file.getAbsolutePath() + " deleted = " + file.delete());
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Getters and Setters
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public SimpleExoPlayer getPlayer() {
+ return simpleExoPlayer;
+ }
+
+ public SharedPreferences getSharedPreferences() {
+ return sharedPreferences;
+ }
+
+ public AspectRatioFrameLayout getAspectRatioFrameLayout() {
+ return aspectRatioFrameLayout;
+ }
+
+ public SurfaceView getSurfaceView() {
+ return surfaceView;
+ }
+
+ public RepeatMode getCurrentRepeatMode() {
+ return currentRepeatMode;
+ }
+
+ public void setCurrentRepeatMode(RepeatMode mode) {
+ currentRepeatMode = mode;
+ }
+
+ public boolean wasPlaying() {
+ return wasPlaying;
+ }
+
+ public int getCurrentState() {
+ return currentState;
+ }
+
+ public Uri getSelectedStreamUri() {
+ return Uri.parse(videoStreamsList.get(selectedIndexStream).url);
+ }
+
+ public int getQualityPopupMenuGroupId() {
+ return qualityPopupMenuGroupId;
+ }
+
+ public String getVideoUrl() {
+ return videoUrl;
+ }
+
+ public void setVideoUrl(String videoUrl) {
+ this.videoUrl = videoUrl;
+ }
+
+ public int getVideoStartPos() {
+ return videoStartPos;
+ }
+
+ public void setVideoStartPos(int videoStartPos) {
+ this.videoStartPos = videoStartPos;
+ }
+
+ public String getVideoTitle() {
+ return videoTitle;
+ }
+
+ public void setVideoTitle(String videoTitle) {
+ this.videoTitle = videoTitle;
+ }
+
+ public Bitmap getVideoThumbnail() {
+ return videoThumbnail;
+ }
+
+ public void setVideoThumbnail(Bitmap videoThumbnail) {
+ this.videoThumbnail = videoThumbnail;
+ }
+
+ public String getChannelName() {
+ return channelName;
+ }
+
+ public void setChannelName(String channelName) {
+ this.channelName = channelName;
+ }
+
+ public int getSelectedIndexStream() {
+ return selectedIndexStream;
+ }
+
+ public void setSelectedIndexStream(int selectedIndexStream) {
+ this.selectedIndexStream = selectedIndexStream;
+ }
+
+ public ArrayList getVideoStreamsList() {
+ return videoStreamsList;
+ }
+
+ public void setVideoStreamsList(ArrayList videoStreamsList) {
+ this.videoStreamsList = videoStreamsList;
+ }
+
+ public boolean isStartedFromNewPipe() {
+ return startedFromNewPipe;
+ }
+
+ public void setStartedFromNewPipe(boolean startedFromNewPipe) {
+ this.startedFromNewPipe = startedFromNewPipe;
+ }
+
+ public View getRootView() {
+ return rootView;
+ }
+
+ public void setRootView(View rootView) {
+ this.rootView = rootView;
+ }
+
+ public View getLoadingPanel() {
+ return loadingPanel;
+ }
+
+ public ImageView getEndScreen() {
+ return endScreen;
+ }
+
+ public ImageView getControlAnimationView() {
+ return controlAnimationView;
+ }
+
+ public View getControlsRoot() {
+ return controlsRoot;
+ }
+
+ public View getBottomControlsRoot() {
+ return bottomControlsRoot;
+ }
+
+ public SeekBar getPlaybackSeekBar() {
+ return playbackSeekBar;
+ }
+
+ public TextView getPlaybackCurrentTime() {
+ return playbackCurrentTime;
+ }
+
+ public TextView getPlaybackEndTime() {
+ return playbackEndTime;
+ }
+
+ public View getTopControlsRoot() {
+ return topControlsRoot;
+ }
+
+ public TextView getQualityTextView() {
+ return qualityTextView;
+ }
+
+ public ImageButton getFullScreenButton() {
+ return fullScreenButton;
+ }
+
+ public PopupMenu getQualityPopupMenu() {
+ return qualityPopupMenu;
+ }
+
+ public View getSurfaceForeground() {
+ return surfaceForeground;
+ }
+
+ public TextView getCurrentDisplaySeek() {
+ return currentDisplaySeek;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
index d1c53d85a..88a5a914d 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -26,7 +26,6 @@ import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.util.NavStack;
import java.io.IOException;
@@ -343,7 +342,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
/*
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
- (R.drawable.ic_pause_white_24dp, "Pause", playPI).build();
+ (R.drawable.ic_pause_white, "Pause", playPI).build();
*/
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID,
@@ -465,7 +464,7 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
RemoteViews views = getContentView(), bigViews = getBigContentView();
int imageSrc;
if(isPlaying) {
- imageSrc = R.drawable.ic_pause_white_24dp;
+ imageSrc = R.drawable.ic_pause_white;
} else {
imageSrc = R.drawable.ic_play_circle_filled_white_24dp;
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
index c868bb722..3a7c5e085 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
@@ -1,220 +1,574 @@
package org.schabi.newpipe.player;
import android.app.Activity;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
+import android.widget.PopupMenu;
import android.widget.SeekBar;
-
-import com.devbrackets.android.exomedia.listener.OnCompletionListener;
-import com.devbrackets.android.exomedia.listener.OnPreparedListener;
-import com.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener;
-import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
-import com.devbrackets.android.exomedia.ui.widget.VideoControlsMobile;
+import android.widget.TextView;
+import android.widget.Toast;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.PermissionHelper;
+import org.schabi.newpipe.util.ThemeHelper;
-public class ExoPlayerActivity extends Activity implements OnPreparedListener, OnCompletionListener {
- private static final String TAG = "ExoPlayerActivity";
- private static final boolean DEBUG = false;
- private EMVideoView videoView;
- private CustomVideoControls videoControls;
+/**
+ * Activity Player implementing AbstractPlayer
+ *
+ * @author mauriciocolli
+ */
+public class ExoPlayerActivity extends Activity {
+ private static final String TAG = ".ExoPlayerActivity";
+ private static final boolean DEBUG = AbstractPlayer.DEBUG;
- public static final String VIDEO_TITLE = "video_title";
- public static final String CHANNEL_NAME = "channel_name";
- private String videoTitle = "";
- private volatile String channelName = "";
- private int lastPosition;
- private boolean isFinished;
+ private AudioManager audioManager;
+ private BroadcastReceiver broadcastReceiver;
+ private GestureDetector gestureDetector;
+
+ private final Runnable hideUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hideSystemUi();
+ }
+ };
+ private boolean activityPaused;
+
+ private AbstractPlayerImpl playerImpl;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Activity LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
@Override
- public void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+ ThemeHelper.setTheme(this, false);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ if (getIntent() == null) {
+ Toast.makeText(this, R.string.general_error, Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
setContentView(R.layout.activity_exo_player);
- videoView = (EMVideoView) findViewById(R.id.emVideoView);
+ playerImpl = new AbstractPlayerImpl();
+ playerImpl.setup(findViewById(android.R.id.content));
+ initReceiver();
+ playerImpl.handleIntent(getIntent());
}
@Override
- protected void onStart() {
- super.onStart();
- Intent intent = getIntent();
- videoTitle = intent.getStringExtra(VIDEO_TITLE);
- channelName = intent.getStringExtra(CHANNEL_NAME);
- videoView.setOnPreparedListener(this);
- videoView.setOnCompletionListener(this);
- videoView.setVideoURI(intent.getData());
-
- videoControls = new CustomVideoControls(this);
- videoControls.setTitle(videoTitle);
- videoControls.setSubTitle(channelName);
-
- //We don't need these button until the playlist or queue is implemented
- videoControls.setNextButtonRemoved(true);
- videoControls.setPreviousButtonRemoved(true);
-
- videoControls.setVisibilityListener(new VideoControlsVisibilityListener() {
- @Override
- public void onControlsShown() {
- if (DEBUG) Log.d(TAG, "------------ onControlsShown() called");
- showSystemUi();
- }
-
- @Override
- public void onControlsHidden() {
- if (DEBUG) Log.d(TAG, "------------ onControlsHidden() called");
- hideSystemUi();
- }
- });
- videoView.setControls(videoControls);
+ protected void onNewIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
+ super.onNewIntent(intent);
+ playerImpl.handleIntent(intent);
}
@Override
- public void onPrepared() {
- if (DEBUG) Log.d(TAG, "onPrepared() called");
- videoView.start();
+ public void onBackPressed() {
+ if (DEBUG) Log.d(TAG, "onBackPressed() called");
+ super.onBackPressed();
+ if (playerImpl.isStartedFromNewPipe()) NavStack.getInstance().openDetailActivity(this, playerImpl.getVideoUrl(), 0);
+ if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
}
@Override
- public void onCompletion() {
- if (DEBUG) Log.d(TAG, "onCompletion() called");
-// videoView.getVideoControls().setButtonListener();
- //videoView.restart();
- videoControls.setRewindButtonRemoved(true);
- videoControls.setFastForwardButtonRemoved(true);
- isFinished = true;
- videoControls.getSeekBar().setEnabled(false);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- videoView.stopPlayback();
- lastPosition = videoView.getCurrentPosition();
+ protected void onStop() {
+ super.onStop();
+ if (DEBUG) Log.d(TAG, "onStop() called");
+ activityPaused = true;
+ playerImpl.destroy();
+ playerImpl.setVideoStartPos((int) playerImpl.getPlayer().getCurrentPosition());
}
@Override
protected void onResume() {
super.onResume();
- if (lastPosition > 0) videoView.seekTo(lastPosition);
+ if (DEBUG) Log.d(TAG, "onResume() called");
+ if (activityPaused) {
+ //playerImpl.getPlayer().setPlayWhenReady(true);
+ playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
+ playerImpl.initPlayer();
+ playerImpl.playVideo(playerImpl.getSelectedStreamUri(), false);
+ activityPaused = false;
+ }
}
@Override
protected void onDestroy() {
super.onDestroy();
- videoView.stopPlayback();
+ if (DEBUG) Log.d(TAG, "onDestroy() called");
+ if (playerImpl != null) playerImpl.destroy();
+ if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void initReceiver() {
+ if (DEBUG) Log.d(TAG, "initReceiver() called");
+ broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
+ switch (intent.getAction()) {
+ case AbstractPlayer.ACTION_UPDATE_THUMB:
+ playerImpl.onUpdateThumbnail(intent);
+ break;
+ }
+ }
+ };
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
+ registerReceiver(broadcastReceiver, intentFilter);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
private void showSystemUi() {
if (DEBUG) Log.d(TAG, "showSystemUi() called");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ );
+ } else getWindow().getDecorView().setSystemUiVisibility(0);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- getWindow().getDecorView().setSystemUiVisibility(0);
}
private void hideSystemUi() {
if (DEBUG) Log.d(TAG, "hideSystemUi() called");
- if (android.os.Build.VERSION.SDK_INT >= 17) {
- getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
+ int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_IMMERSIVE
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) visibility |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ getWindow().getDecorView().setSystemUiVisibility(visibility);
}
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
- private class CustomVideoControls extends VideoControlsMobile {
- protected static final int FAST_FORWARD_REWIND_AMOUNT = 8000;
+ private void toggleOrientation() {
+ setRequestedOrientation(getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels
+ ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
+ }
- protected ImageButton fastForwardButton;
- protected ImageButton rewindButton;
+ ///////////////////////////////////////////////////////////////////////////
- public CustomVideoControls(Context context) {
- super(context);
+ @SuppressWarnings({"unused", "WeakerAccess"})
+ private class AbstractPlayerImpl extends AbstractPlayer {
+ private TextView titleTextView;
+ private TextView channelTextView;
+ private TextView volumeTextView;
+ private TextView brightnessTextView;
+ private ImageButton repeatButton;
+
+ private ImageButton screenRotationButton;
+ private ImageButton playPauseButton;
+
+ AbstractPlayerImpl() {
+ super("AbstractPlayerImpl" + ExoPlayerActivity.TAG, ExoPlayerActivity.this);
}
@Override
- protected int getLayoutResource() {
- return R.layout.exomedia_custom_controls;
+ public void initViews(View rootView) {
+ super.initViews(rootView);
+ this.titleTextView = (TextView) rootView.findViewById(R.id.titleTextView);
+ this.channelTextView = (TextView) rootView.findViewById(R.id.channelTextView);
+ this.volumeTextView = (TextView) rootView.findViewById(R.id.volumeTextView);
+ this.brightnessTextView = (TextView) rootView.findViewById(R.id.brightnessTextView);
+ this.repeatButton = (ImageButton) rootView.findViewById(R.id.repeatButton);
+
+ this.screenRotationButton = (ImageButton) rootView.findViewById(R.id.screenRotationButton);
+ this.playPauseButton = (ImageButton) rootView.findViewById(R.id.playPauseButton);
+
+ // Due to a bug on lower API, lets set the alpha instead of using a drawable
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) repeatButton.setImageAlpha(77);
+ else { //noinspection deprecation
+ repeatButton.setAlpha(77);
+ }
+
}
@Override
- protected void retrieveViews() {
- super.retrieveViews();
- rewindButton = (ImageButton) findViewById(R.id.exomedia_controls_frewind_btn);
- fastForwardButton = (ImageButton) findViewById(R.id.exomedia_controls_fforward_btn);
+ public void initListeners() {
+ super.initListeners();
+
+ MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
+ gestureDetector = new GestureDetector(context, listener);
+ gestureDetector.setIsLongpressEnabled(false);
+ playerImpl.getRootView().setOnTouchListener(listener);
+
+ repeatButton.setOnClickListener(this);
+ playPauseButton.setOnClickListener(this);
+ screenRotationButton.setOnClickListener(this);
}
@Override
- protected void registerListeners() {
- super.registerListeners();
- rewindButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onRewindClicked();
- }
- });
- fastForwardButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onFastForwardClicked();
- }
- });
- }
-
- public boolean onFastForwardClicked() {
- if (videoView == null) return false;
-
- int newPosition = videoView.getCurrentPosition() + FAST_FORWARD_REWIND_AMOUNT;
- if (newPosition > seekBar.getMax()) newPosition = seekBar.getMax();
-
- performSeek(newPosition);
- return true;
- }
-
- public boolean onRewindClicked() {
- if (videoView == null) return false;
-
- int newPosition = videoView.getCurrentPosition() - FAST_FORWARD_REWIND_AMOUNT;
- if (newPosition < 0) newPosition = 0;
-
- performSeek(newPosition);
- return true;
+ public void handleIntent(Intent intent) {
+ super.handleIntent(intent);
+ titleTextView.setText(getVideoTitle());
+ channelTextView.setText(getChannelName());
}
@Override
- public void setFastForwardButtonRemoved(boolean removed) {
- fastForwardButton.setVisibility(removed ? View.GONE : View.VISIBLE);
+ public void playVideo(Uri videoURI, boolean autoPlay) {
+ super.playVideo(videoURI, autoPlay);
+ playPauseButton.setImageResource(autoPlay ? R.drawable.ic_pause_white : R.drawable.ic_play_arrow_white);
}
@Override
- public void setRewindButtonRemoved(boolean removed) {
- rewindButton.setVisibility(removed ? View.GONE : View.VISIBLE);
+ public void onFullScreenButtonClicked() {
+ if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
+ if (playerImpl.getPlayer() == null) return;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && !PermissionHelper.checkSystemAlertWindowPermission(ExoPlayerActivity.this)) {
+ Toast.makeText(ExoPlayerActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ Intent i = new Intent(ExoPlayerActivity.this, PopupVideoPlayer.class);
+ i.putExtra(AbstractPlayer.VIDEO_TITLE, getVideoTitle())
+ .putExtra(AbstractPlayer.CHANNEL_NAME, getChannelName())
+ .putExtra(AbstractPlayer.VIDEO_URL, getVideoUrl())
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, getSelectedIndexStream())
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, getVideoStreamsList())
+ .putExtra(AbstractPlayer.START_POSITION, ((int) getPlayer().getCurrentPosition()));
+ context.startService(i);
+ ((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
+ if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
+ ExoPlayerActivity.this.finish();
}
@Override
- protected void onPlayPauseClick() {
- super.onPlayPauseClick();
- if (videoView == null) return;
- if (DEBUG) Log.d(TAG, "onPlayPauseClick() called" + videoView.getDuration() + " position= " + videoView.getCurrentPosition());
- if (isFinished) {
- videoView.restart();
- setRewindButtonRemoved(false);
- setFastForwardButtonRemoved(false);
- isFinished = false;
- seekBar.setEnabled(true);
+ @SuppressWarnings("deprecation")
+ public void onRepeatClicked() {
+ super.onRepeatClicked();
+ if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
+ switch (getCurrentRepeatMode()) {
+ case REPEAT_DISABLED:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) repeatButton.setImageAlpha(77);
+ else repeatButton.setAlpha(77);
+
+ break;
+ case REPEAT_ONE:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) repeatButton.setImageAlpha(255);
+ else repeatButton.setAlpha(255);
+
+ break;
+ case REPEAT_ALL:
+ // Waiting :)
+ break;
}
}
- private void performSeek(int newPosition) {
- internalListener.onSeekEnded(newPosition);
+ @Override
+ public void onClick(View v) {
+ super.onClick(v);
+ if (v.getId() == repeatButton.getId()) onRepeatClicked();
+ else if (v.getId() == playPauseButton.getId()) onVideoPlayPause();
+ else if (v.getId() == screenRotationButton.getId()) onScreenRotationClicked();
+
+ if (getCurrentState() != STATE_COMPLETED) {
+ animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() {
+ @Override
+ public void run() {
+ if (getCurrentState() == STATE_PLAYING && !playerImpl.isQualityMenuVisible()) {
+ animateView(playerImpl.getControlsRoot(), false, 300, DEFAULT_CONTROLS_HIDE_TIME, true);
+ }
+ }
+ }, false);
+ }
}
- public SeekBar getSeekBar() {
- return seekBar;
+ private void onScreenRotationClicked() {
+ if (DEBUG) Log.d(TAG, "onScreenRotationClicked() called");
+ toggleOrientation();
+ }
+
+ @Override
+ public void onVideoPlayPause() {
+ super.onVideoPlayPause();
+ if (getPlayer().getPlayWhenReady()) {
+ animateView(playPauseButton, false, 80, 0, new Runnable() {
+ @Override
+ public void run() {
+ playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ animateView(playPauseButton, true, 200, 0);
+ }
+ });
+ } else {
+ animateView(playPauseButton, false, 80, 0, new Runnable() {
+ @Override
+ public void run() {
+ playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
+ animateView(playPauseButton, true, 200, 0);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ super.onStopTrackingTouch(seekBar);
+ if (playerImpl.wasPlaying()) {
+ hideSystemUi();
+ playerImpl.getControlsRoot().setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ super.onDismiss(menu);
+ if (isPlaying()) animateView(getControlsRoot(), false, 500, 0, true);
+ }
+
+ @Override
+ public void onError() {
+ Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // States
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onLoading() {
+ super.onLoading();
+ playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ }
+
+ @Override
+ public void onPaused() {
+ super.onPaused();
+ animateView(playPauseButton, true, 100, 0);
+ showSystemUi();
+ }
+
+ @Override
+ public void onPausedSeek() {
+ super.onPausedSeek();
+ animateView(playPauseButton, false, 100, 0);
+ }
+
+ @Override
+ public void onPlaying() {
+ super.onPlaying();
+ animateView(playPauseButton, true, 500, 0);
+ }
+
+ @Override
+ public void onCompleted() {
+ if (getCurrentRepeatMode() == RepeatMode.REPEAT_ONE) {
+ playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ } else {
+ showSystemUi();
+ animateView(playPauseButton, false, 0, 0, new Runnable() {
+ @Override
+ public void run() {
+ playPauseButton.setImageResource(R.drawable.ic_replay_white);
+ animateView(playPauseButton, true, 300, 0);
+ }
+ });
+ }
+ super.onCompleted();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void animateView(View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) {
+ //if (execOnEnd == null) playerImpl.setDefaultAnimationEnd(hideUiRunnable);
+
+ if (hideUi && execOnEnd != null) {
+ Runnable combinedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ execOnEnd.run();
+ hideUiRunnable.run();
+ }
+ };
+ super.animateView(view, enterOrExit, duration, delay, combinedRunnable, true);
+ } else super.animateView(view, enterOrExit, duration, delay, hideUi ? hideUiRunnable : execOnEnd, hideUi);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Getters
+ ///////////////////////////////////////////////////////////////////////////
+
+ public TextView getTitleTextView() {
+ return titleTextView;
+ }
+
+ public TextView getChannelTextView() {
+ return channelTextView;
+ }
+
+ public TextView getVolumeTextView() {
+ return volumeTextView;
+ }
+
+ public TextView getBrightnessTextView() {
+ return brightnessTextView;
+ }
+
+ public ImageButton getRepeatButton() {
+ return repeatButton;
+ }
+
+ public ImageButton getPlayPauseButton() {
+ return playPauseButton;
}
}
-}
+
+ private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
+ private boolean isMoving;
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
+ if (!playerImpl.isPlaying()) return false;
+ if (e.getX() > playerImpl.getRootView().getWidth() / 2) playerImpl.onFastForward();
+ else playerImpl.onFastRewind();
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
+ if (playerImpl.getCurrentState() != StateInterface.STATE_PLAYING) return true;
+
+ if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true);
+ else {
+ playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
+ @Override
+ public void run() {
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
+ }
+ });
+ showSystemUi();
+ }
+ return true;
+ }
+
+ private final float stepsBrightness = 21, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
+ private float currentBrightness = .5f;
+
+ private int currentVolume, maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+ private final String brightnessUnicode = new String(Character.toChars(0x2600));
+ // private final String volumeUnicode = new String(Character.toChars(0x1F50A));
+ private final String volumeUnicode = new String(Character.toChars(0x1F508));
+
+
+ private final int MOVEMENT_THRESHOLD = 40;
+ private final int eventsThreshold = 3;
+ private boolean triggered = false;
+ private int eventsNum;
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && false) Log.d(TAG, "ExoPlayerActivity.onScroll = " +
+ ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
+ ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
+ ", distanceXy = [" + distanceX + ", " + distanceY + "]");
+ float abs = Math.abs(e2.getY() - e1.getY());
+ if (!triggered) {
+ triggered = abs > MOVEMENT_THRESHOLD;
+ return false;
+ }
+
+ if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == StateInterface.STATE_COMPLETED) return false;
+ isMoving = true;
+// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
+ boolean up = distanceY > 0;
+
+
+ if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
+ currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + (up ? 1 : -1);
+ if (currentVolume >= maxVolume) currentVolume = maxVolume;
+ if (currentVolume <= 0) currentVolume = 0;
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
+
+ if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
+ playerImpl.getVolumeTextView().setText(volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%");
+
+ if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), true, 200, 0);
+ if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);
+ } else {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ currentBrightness += up ? stepBrightness : -stepBrightness;
+ if (currentBrightness >= 1f) currentBrightness = 1f;
+ if (currentBrightness <= minBrightness) currentBrightness = minBrightness;
+
+ lp.screenBrightness = currentBrightness;
+ getWindow().setAttributes(lp);
+ if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness);
+ int brightnessNormalized = Math.round(currentBrightness * 100);
+
+ playerImpl.getBrightnessTextView().setText(brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%");
+
+ if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), true, 200, 0);
+ if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
+ }
+ return true;
+ }
+
+ private void onScrollEnd() {
+ if (DEBUG) Log.d(TAG, "onScrollEnd() called");
+ triggered = false;
+ eventsNum = 0;
+ /* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
+ if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/
+ if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200);
+ if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
+
+ if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ gestureDetector.onTouchEvent(event);
+ if (event.getAction() == MotionEvent.ACTION_UP && isMoving) {
+ isMoving = false;
+ onScrollEnd();
+ }
+ return true;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java
index f13e73b49..49da537ac 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayVideoActivity.java
@@ -5,9 +5,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
-import android.graphics.drawable.Drawable;
-import android.media.MediaPlayer;
import android.media.AudioManager;
+import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -28,7 +27,6 @@ import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.VideoView;
-import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
/**
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index e396ac1b6..661cb1632 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -1,10 +1,5 @@
package org.schabi.newpipe.player;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -13,17 +8,15 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.PixelFormat;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
-import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
@@ -31,17 +24,10 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
+import android.widget.PopupMenu;
import android.widget.RemoteViews;
-import android.widget.SeekBar;
import android.widget.Toast;
-import com.devbrackets.android.exomedia.listener.OnCompletionListener;
-import com.devbrackets.android.exomedia.listener.OnErrorListener;
-import com.devbrackets.android.exomedia.listener.OnPreparedListener;
-import com.devbrackets.android.exomedia.listener.OnSeekCompletionListener;
-import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
-import com.devbrackets.android.exomedia.util.Repeater;
-import com.devbrackets.android.exomedia.util.TimeFormatUtil;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
@@ -56,210 +42,204 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
-import org.schabi.newpipe.player.popup.PopupViewHolder;
-import org.schabi.newpipe.player.popup.StateInterface;
import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.ThemeHelper;
import java.io.IOException;
+import java.util.ArrayList;
-public class PopupVideoPlayer extends Service implements StateInterface {
+/**
+ * Service Popup Player implementing AbstractPlayer
+ *
+ * @author mauriciocolli
+ */
+public class PopupVideoPlayer extends Service {
private static final String TAG = ".PopupVideoPlayer";
- private static final boolean DEBUG = false;
- private static int CURRENT_STATE = -1;
+ private static final boolean DEBUG = AbstractPlayer.DEBUG;
private static final int NOTIFICATION_ID = 40028922;
- protected static final int FAST_FORWARD_REWIND_AMOUNT = 10000; // 10 Seconds
- protected static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
+ public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
+ public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
+ public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
+ public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
private BroadcastReceiver broadcastReceiver;
- private InternalListener internalListener;
private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector;
- private ValueAnimator controlViewAnimator;
- private PopupViewHolder viewHolder;
- private EMVideoView emVideoView;
private float screenWidth, screenHeight;
private float popupWidth, popupHeight;
- private float currentPopupHeight = 200;
+ private float currentPopupHeight = 110.0f * Resources.getSystem().getDisplayMetrics().density;
//private float minimumHeight = 100; // TODO: Use it when implementing the resize of the popup
- public static final String VIDEO_URL = "video_url";
- public static final String STREAM_URL = "stream_url";
- public static final String VIDEO_TITLE = "video_title";
- public static final String CHANNEL_NAME = "channel_name";
-
+ private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
private NotificationManager notificationManager;
private NotificationCompat.Builder notBuilder;
private RemoteViews notRemoteView;
- private Uri streamUri;
- private String videoUrl = "";
- private String videoTitle = "";
- private volatile String channelName = "";
private ImageLoader imageLoader = ImageLoader.getInstance();
- private DisplayImageOptions displayImageOptions =
- new DisplayImageOptions.Builder().cacheInMemory(true).build();
- private volatile Bitmap videoThumbnail;
+ private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
- private Repeater progressPollRepeater = new Repeater();
- private SharedPreferences sharedPreferences;
+ private AbstractPlayerImpl playerImpl;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Service LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate() {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
- internalListener = new InternalListener();
- viewHolder = new PopupViewHolder(null);
- progressPollRepeater.setRepeatListener(internalListener);
- progressPollRepeater.setRepeaterDelay(500);
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
initReceiver();
+
+ playerImpl = new AbstractPlayerImpl();
+ ThemeHelper.setTheme(this, false);
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public int onStartCommand(final Intent intent, int flags, int startId) {
+ if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
+ if (playerImpl.getPlayer() == null) initPopup();
+ if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
+
+ if (imageLoader != null) imageLoader.clearMemoryCache();
+ if (intent.getStringExtra(NavStack.URL) != null) {
+ playerImpl.setStartedFromNewPipe(false);
+ Thread fetcher = new Thread(new FetcherRunnable(intent));
+ fetcher.start();
+ } else {
+ playerImpl.setStartedFromNewPipe(true);
+ playerImpl.handleIntent(intent);
+ }
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ updateScreenSize();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy() called");
+ stopForeground(true);
+ if (playerImpl != null) {
+ playerImpl.destroy();
+ if (playerImpl.getRootView() != null) windowManager.removeView(playerImpl.getRootView());
+ }
+ if (imageLoader != null) imageLoader.clearMemoryCache();
+ if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
+ if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
private void initReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (DEBUG)
- Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
+ if (DEBUG) Log.d(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
switch (intent.getAction()) {
- case InternalListener.ACTION_CLOSE:
- internalListener.onVideoClose();
+ case ACTION_CLOSE:
+ onVideoClose();
break;
- case InternalListener.ACTION_PLAY_PAUSE:
- internalListener.onVideoPlayPause();
+ case ACTION_PLAY_PAUSE:
+ playerImpl.onVideoPlayPause();
break;
- case InternalListener.ACTION_OPEN_DETAIL:
- internalListener.onOpenDetail(PopupVideoPlayer.this, videoUrl);
+ case ACTION_OPEN_DETAIL:
+ onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl());
break;
- case InternalListener.ACTION_UPDATE_THUMB:
- internalListener.onUpdateThumbnail(intent);
+ case ACTION_REPEAT:
+ playerImpl.onRepeatClicked();
+ break;
+ case AbstractPlayer.ACTION_UPDATE_THUMB:
+ playerImpl.onUpdateThumbnail(intent);
break;
}
}
};
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(InternalListener.ACTION_CLOSE);
- intentFilter.addAction(InternalListener.ACTION_PLAY_PAUSE);
- intentFilter.addAction(InternalListener.ACTION_OPEN_DETAIL);
- intentFilter.addAction(InternalListener.ACTION_UPDATE_THUMB);
+ intentFilter.addAction(ACTION_CLOSE);
+ intentFilter.addAction(ACTION_PLAY_PAUSE);
+ intentFilter.addAction(ACTION_OPEN_DETAIL);
+ intentFilter.addAction(ACTION_REPEAT);
+ intentFilter.addAction(AbstractPlayer.ACTION_UPDATE_THUMB);
registerReceiver(broadcastReceiver, intentFilter);
}
- @SuppressLint({"RtlHardcoded"})
+ @SuppressLint("RtlHardcoded")
private void initPopup() {
if (DEBUG) Log.d(TAG, "initPopup() called");
View rootView = View.inflate(this, R.layout.player_popup, null);
- viewHolder = new PopupViewHolder(rootView);
- viewHolder.getPlaybackSeekBar().setOnSeekBarChangeListener(internalListener);
- emVideoView = viewHolder.getVideoView();
- emVideoView.setOnPreparedListener(internalListener);
- emVideoView.setOnCompletionListener(internalListener);
- emVideoView.setOnErrorListener(internalListener);
- emVideoView.setOnSeekCompletionListener(internalListener);
+ playerImpl.setup(rootView);
+
+ updateScreenSize();
windowLayoutParams = new WindowManager.LayoutParams(
(int) getMinimumVideoWidth(currentPopupHeight), (int) currentPopupHeight,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
+
windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
gestureDetector = new GestureDetector(this, listener);
gestureDetector.setIsLongpressEnabled(false);
rootView.setOnTouchListener(listener);
- updateScreenSize();
-
+ playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width);
+ playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height);
windowManager.addView(rootView, windowLayoutParams);
}
- @Override
- public int onStartCommand(final Intent intent, int flags, int startId) {
- if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
- if (emVideoView == null) initPopup();
-
- if (intent.getStringExtra(NavStack.URL) != null) {
- Thread fetcher = new Thread(new FetcherRunnable(intent));
- fetcher.start();
- } else {
- if (imageLoader != null) imageLoader.clearMemoryCache();
- streamUri = Uri.parse(intent.getStringExtra(STREAM_URL));
- videoUrl = intent.getStringExtra(VIDEO_URL);
- videoTitle = intent.getStringExtra(VIDEO_TITLE);
- channelName = intent.getStringExtra(CHANNEL_NAME);
- try {
- videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
- } catch (Exception e) {
- e.printStackTrace();
- }
- playVideo(streamUri);
- }
- return START_NOT_STICKY;
- }
-
- private float getMinimumVideoWidth(float height) {
- float width = height * (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
- if (DEBUG) Log.d(TAG, "getMinimumVideoWidth() called with: height = [" + height + "], returned: " + width);
- return width;
- }
-
- private void updateScreenSize() {
- DisplayMetrics metrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(metrics);
-
- screenWidth = metrics.widthPixels;
- screenHeight = metrics.heightPixels;
- if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight);
- }
-
- private void seekBy(int milliSeconds) {
- if (emVideoView == null) return;
- int progress = emVideoView.getCurrentPosition() + milliSeconds;
- emVideoView.seekTo(progress);
- }
-
- private void playVideo(Uri videoURI) {
- if (DEBUG) Log.d(TAG, "playVideo() called with: streamUri = [" + streamUri + "]");
-
- changeState(STATE_LOADING);
-
- windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
- windowManager.updateViewLayout(viewHolder.getRootView(), windowLayoutParams);
-
- if (videoURI == null || emVideoView == null || viewHolder.getRootView() == null) {
- Toast.makeText(this, "Failed to play this video", Toast.LENGTH_SHORT).show();
- stopSelf();
- return;
- }
- if (emVideoView.isPlaying()) emVideoView.stopPlayback();
- emVideoView.setVideoURI(videoURI);
-
- notBuilder = createNotification();
- startForeground(NOTIFICATION_ID, notBuilder.build());
- notificationManager.notify(NOTIFICATION_ID, this.notBuilder.build());
- }
+ /*//////////////////////////////////////////////////////////////////////////
+ // Notification
+ //////////////////////////////////////////////////////////////////////////*/
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_popup_notification);
- if (videoThumbnail != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
+
+ if (playerImpl.getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getVideoThumbnail());
else notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
+ notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
+ notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getChannelName());
+
notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(InternalListener.ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationStop,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(InternalListener.ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
- notRemoteView.setTextViewText(R.id.notificationSongName, videoTitle);
- notRemoteView.setTextViewText(R.id.notificationArtist, channelName);
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_CLOSE), PendingIntent.FLAG_UPDATE_CURRENT));
notRemoteView.setOnClickPendingIntent(R.id.notificationContent,
- PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(InternalListener.ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_OPEN_DETAIL), PendingIntent.FLAG_UPDATE_CURRENT));
+ notRemoteView.setOnClickPendingIntent(R.id.notificationRepeat,
+ PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_REPEAT), PendingIntent.FLAG_UPDATE_CURRENT));
+
+ switch (playerImpl.getCurrentRepeatMode()) {
+ case REPEAT_DISABLED:
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
+ break;
+ case REPEAT_ONE:
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
+ break;
+ case REPEAT_ALL:
+ // Waiting :)
+ break;
+ }
return new NotificationCompat.Builder(this)
.setOngoing(true)
- .setSmallIcon(R.drawable.ic_play_arrow_white_48dp)
+ .setSmallIcon(R.drawable.ic_play_arrow_white)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notRemoteView);
}
@@ -276,384 +256,175 @@ public class PopupVideoPlayer extends Service implements StateInterface {
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
- /**
- * Show a animation, and depending on goneOnEnd, will stay on the screen or be gone
- *
- * @param drawableId the drawable that will be used to animate, pass -1 to clear any animation that is visible
- * @param goneOnEnd will set the animation view to GONE on the end of the animation
- */
- private void showAndAnimateControl(final int drawableId, final boolean goneOnEnd) {
- if (DEBUG) Log.d(TAG, "showAndAnimateControl() called with: drawableId = [" + drawableId + "], goneOnEnd = [" + goneOnEnd + "]");
- if (controlViewAnimator != null && controlViewAnimator.isRunning()) {
- if (DEBUG) Log.d(TAG, "showAndAnimateControl: controlViewAnimator.isRunning");
- controlViewAnimator.end();
- }
- if (drawableId == -1) {
- if (viewHolder.getControlAnimationView().getVisibility() == View.VISIBLE) {
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(viewHolder.getControlAnimationView(),
- PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f),
- PropertyValuesHolder.ofFloat(View.SCALE_X, 1.4f, 1f),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, 1.4f, 1f)
- ).setDuration(300);
- controlViewAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- viewHolder.getControlAnimationView().setVisibility(View.GONE);
- }
- });
- controlViewAnimator.start();
- }
- return;
- }
+ /*//////////////////////////////////////////////////////////////////////////
+ // Misc
+ //////////////////////////////////////////////////////////////////////////*/
- float scaleFrom = goneOnEnd ? 1f : 1f, scaleTo = goneOnEnd ? 1.8f : 1.4f;
- float alphaFrom = goneOnEnd ? 1f : 0f, alphaTo = goneOnEnd ? 0f : 1f;
-
-
- controlViewAnimator = ObjectAnimator.ofPropertyValuesHolder(viewHolder.getControlAnimationView(),
- PropertyValuesHolder.ofFloat(View.ALPHA, alphaFrom, alphaTo),
- PropertyValuesHolder.ofFloat(View.SCALE_X, scaleFrom, scaleTo),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleFrom, scaleTo)
- );
- controlViewAnimator.setDuration(goneOnEnd ? 1000 : 500);
- controlViewAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (goneOnEnd) viewHolder.getControlAnimationView().setVisibility(View.GONE);
- else viewHolder.getControlAnimationView().setVisibility(View.VISIBLE);
- }
- });
-
-
- viewHolder.getControlAnimationView().setVisibility(View.VISIBLE);
- viewHolder.getControlAnimationView().setImageDrawable(ContextCompat.getDrawable(PopupVideoPlayer.this, drawableId));
- controlViewAnimator.start();
+ public void onVideoClose() {
+ if (DEBUG) Log.d(TAG, "onVideoClose() called");
+ stopSelf();
}
- /**
- * Animate the view
- *
- * @param enterOrExit true to enter, false to exit
- * @param duration how long the animation will take, in milliseconds
- * @param delay how long the animation will wait to start, in milliseconds
- */
- private void animateView(final View view, final boolean enterOrExit, long duration, long delay) {
- if (DEBUG) Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "]");
- if (view.getVisibility() == View.VISIBLE && enterOrExit) {
- if (DEBUG) Log.d(TAG, "animateLoadingPanel() > view.getVisibility() == View.VISIBLE && enterOrExit");
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
- return;
- }
-
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
-
- if (view == viewHolder.getControlsRoot()) {
- if (enterOrExit) {
- view.setAlpha(0f);
- view.animate().alpha(1f).setDuration(duration).setStartDelay(delay).setListener(null).start();
- } else {
- view.setAlpha(1f);
- view.animate().alpha(0f)
- .setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.GONE);
- }
- })
- .start();
- }
- return;
- }
-
- if (enterOrExit) {
- view.setAlpha(0f);
- view.setScaleX(.8f);
- view.setScaleY(.8f);
- view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay).setListener(null).start();
- } else {
- view.setAlpha(1f);
- view.setScaleX(1f);
- view.setScaleY(1f);
- view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.GONE);
- }
- })
- .start();
- }
+ public void onOpenDetail(Context context, String videoUrl) {
+ if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
+ Intent i = new Intent(context, VideoItemDetailActivity.class);
+ i.putExtra(NavStack.SERVICE_ID, 0)
+ .putExtra(NavStack.URL, videoUrl)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(i);
+ //NavStack.getInstance().openDetailActivity(context, videoUrl, 0);
}
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- updateScreenSize();
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private float getMinimumVideoWidth(float height) {
+ float width = height * (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
+ if (DEBUG) Log.d(TAG, "getMinimumVideoWidth() called with: height = [" + height + "], returned: " + width);
+ return width;
}
- @Override
- public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy() called");
- stopForeground(true);
- if (emVideoView != null) emVideoView.stopPlayback();
- if (imageLoader != null) imageLoader.clearMemoryCache();
- if (viewHolder.getRootView() != null) windowManager.removeView(viewHolder.getRootView());
- if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
- if (progressPollRepeater != null) {
- progressPollRepeater.stop();
- progressPollRepeater.setRepeatListener(null);
- }
- if (broadcastReceiver != null) unregisterReceiver(broadcastReceiver);
- }
+ private void updateScreenSize() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(metrics);
- @Override
- public IBinder onBind(Intent intent) {
- return null;
+ screenWidth = metrics.widthPixels;
+ screenHeight = metrics.heightPixels;
+ if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight);
}
- ///////////////////////////////////////////////////////////////////////////
- // States Implementation
///////////////////////////////////////////////////////////////////////////
- @Override
- public void changeState(int state) {
- if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
- CURRENT_STATE = state;
- switch (state) {
- case STATE_LOADING:
- onLoading();
- break;
- case STATE_PLAYING:
- onPlaying();
- break;
- case STATE_PAUSED:
- onPaused();
- break;
- case STATE_PAUSED_SEEK:
- onPausedSeek();
- break;
- case STATE_COMPLETED:
- onCompleted();
- break;
+ private class AbstractPlayerImpl extends AbstractPlayer {
+ AbstractPlayerImpl() {
+ super("AbstractPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
}
- }
-
- @Override
- public void onLoading() {
- if (DEBUG) Log.d(TAG, "onLoading() called");
- updateNotification(R.drawable.ic_play_arrow_white_48dp);
-
- showAndAnimateControl(-1, true);
- viewHolder.getPlaybackSeekBar().setEnabled(true);
- viewHolder.getPlaybackSeekBar().setProgress(0);
- viewHolder.getLoadingPanel().setBackgroundColor(Color.BLACK);
- animateView(viewHolder.getLoadingPanel(), true, 500, 0);
- viewHolder.getEndScreen().setVisibility(View.GONE);
- viewHolder.getControlsRoot().setVisibility(View.GONE);
- }
-
- @Override
- public void onPlaying() {
- if (DEBUG) Log.d(TAG, "onPlaying() called");
- updateNotification(R.drawable.ic_pause_white_24dp);
-
- showAndAnimateControl(-1, true);
- viewHolder.getLoadingPanel().setVisibility(View.GONE);
- animateView(viewHolder.getControlsRoot(), false, 500, DEFAULT_CONTROLS_HIDE_TIME);
- }
-
- @Override
- public void onPaused() {
- if (DEBUG) Log.d(TAG, "onPaused() called");
- updateNotification(R.drawable.ic_play_arrow_white_48dp);
-
- showAndAnimateControl(R.drawable.ic_play_arrow_white_48dp, false);
- animateView(viewHolder.getControlsRoot(), true, 500, 100);
- viewHolder.getLoadingPanel().setVisibility(View.GONE);
- }
-
- @Override
- public void onPausedSeek() {
- if (DEBUG) Log.d(TAG, "onPausedSeek() called");
- updateNotification(R.drawable.ic_play_arrow_white_48dp);
-
- showAndAnimateControl(-1, true);
- viewHolder.getLoadingPanel().setBackgroundColor(Color.TRANSPARENT);
- animateView(viewHolder.getLoadingPanel(), true, 300, 0);
- }
-
- @Override
- public void onCompleted() {
- if (DEBUG) Log.d(TAG, "onCompleted() called");
- updateNotification(R.drawable.ic_replay_white);
- showAndAnimateControl(R.drawable.ic_replay_white, false);
- animateView(viewHolder.getControlsRoot(), true, 500, 0);
- animateView(viewHolder.getEndScreen(), true, 200, 0);
- viewHolder.getLoadingPanel().setVisibility(View.GONE);
- viewHolder.getPlaybackSeekBar().setEnabled(false);
- viewHolder.getPlaybackCurrentTime().setText(viewHolder.getPlaybackEndTime().getText());
- if (videoThumbnail != null) viewHolder.getEndScreen().setImageBitmap(videoThumbnail);
- }
-
- /**
- * This class joins all the necessary listeners
- */
- @SuppressWarnings({"WeakerAccess"})
- public class InternalListener implements SeekBar.OnSeekBarChangeListener, OnPreparedListener, OnSeekCompletionListener, OnCompletionListener, OnErrorListener, Repeater.RepeatListener {
- public static final String ACTION_CLOSE = "org.schabi.newpipe.player.PopupVideoPlayer.CLOSE";
- public static final String ACTION_PLAY_PAUSE = "org.schabi.newpipe.player.PopupVideoPlayer.PLAY_PAUSE";
- public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
- public static final String ACTION_UPDATE_THUMB = "org.schabi.newpipe.player.PopupVideoPlayer.UPDATE_THUMBNAIL";
@Override
- public void onPrepared() {
- if (DEBUG) Log.d(TAG, "onPrepared() called");
- viewHolder.getPlaybackSeekBar().setMax(emVideoView.getDuration());
- viewHolder.getPlaybackEndTime().setText(TimeFormatUtil.formatMs(emVideoView.getDuration()));
+ public void playVideo(Uri videoURI, boolean autoPlay) {
+ super.playVideo(videoURI, autoPlay);
- changeState(STATE_PLAYING);
- progressPollRepeater.start();
- emVideoView.start();
+ windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
+ windowManager.updateViewLayout(getRootView(), windowLayoutParams);
+ notBuilder = createNotification();
+ startForeground(NOTIFICATION_ID, notBuilder.build());
+ notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
- public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
- if (viewHolder.isControlsVisible() && CURRENT_STATE != STATE_PAUSED_SEEK) {
- viewHolder.getPlaybackSeekBar().setProgress(currentProgress);
- viewHolder.getPlaybackCurrentTime().setText(TimeFormatUtil.formatMs(currentProgress));
- viewHolder.getPlaybackSeekBar().setSecondaryProgress((int) (viewHolder.getPlaybackSeekBar().getMax() * ((float) bufferPercent / 100)));
+ @Override
+ public void onFullScreenButtonClicked() {
+ if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
+ Intent intent;
+ //if (getSharedPreferences().getBoolean(getResources().getString(R.string.use_exoplayer_key), false)) {
+ // TODO: Remove this check when ExoPlayer is the default
+ // For now just disable the non-exoplayer player
+ //noinspection ConstantConditions,ConstantIfStatement
+ if (true) {
+ intent = new Intent(PopupVideoPlayer.this, ExoPlayerActivity.class)
+ .putExtra(AbstractPlayer.VIDEO_TITLE, getVideoTitle())
+ .putExtra(AbstractPlayer.VIDEO_URL, getVideoUrl())
+ .putExtra(AbstractPlayer.CHANNEL_NAME, getChannelName())
+ .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, getSelectedIndexStream())
+ .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, getVideoStreamsList())
+ .putExtra(AbstractPlayer.START_POSITION, ((int) getPlayer().getCurrentPosition()));
+ if (!playerImpl.isStartedFromNewPipe()) intent.putExtra(AbstractPlayer.STARTED_FROM_NEWPIPE, false);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ } else {
+ intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
+ .putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
+ .putExtra(PlayVideoActivity.STREAM_URL, getSelectedStreamUri().toString())
+ .putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
+ .putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
- if (DEBUG && bufferPercent % 10 == 0) { //Limit log
- Log.d(TAG, "updateProgress() called with: isVisible = " + viewHolder.isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]");
+ context.startActivity(intent);
+ stopSelf();
+ }
+
+ @Override
+ public void onRepeatClicked() {
+ super.onRepeatClicked();
+ switch (getCurrentRepeatMode()) {
+ case REPEAT_DISABLED:
+ // Drawable didn't work on low API :/
+ //notRemoteView.setImageViewResource(R.id.notificationRepeat, R.drawable.ic_repeat_disabled_white);
+ // Set the icon to 30% opacity - 255 (max) * .3
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 77);
+ break;
+ case REPEAT_ONE:
+ notRemoteView.setInt(R.id.notificationRepeat, setAlphaMethodName, 255);
+ break;
+ case REPEAT_ALL:
+ // Waiting :)
+ break;
}
- }
-
- public void onOpenDetail(Context context, String videoUrl) {
- if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
- Intent i = new Intent(context, VideoItemDetailActivity.class);
- i.putExtra(NavStack.SERVICE_ID, 0);
- i.putExtra(NavStack.URL, videoUrl);
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(i);
- //NavStack.getInstance().openDetailActivity(context, videoUrl, 0);
- }
-
- public void onUpdateThumbnail(Intent intent) {
- if (DEBUG) Log.d(TAG, "onUpdateThumbnail() called");
- if (!intent.getStringExtra(VIDEO_URL).equals(videoUrl)) return;
- videoThumbnail = ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail;
- if (videoThumbnail != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
updateNotification(-1);
}
- public void onVideoClose() {
- if (DEBUG) Log.d(TAG, "onVideoClose() called");
+ @Override
+ public void onUpdateThumbnail(Intent intent) {
+ super.onUpdateThumbnail(intent);
+ if (getVideoThumbnail() != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, getVideoThumbnail());
+ updateNotification(-1);
+ }
+
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ super.onDismiss(menu);
+ if (isPlaying()) animateView(getControlsRoot(), false, 500, 0);
+ }
+
+ @Override
+ public void onError() {
+ Toast.makeText(context, "Failed to play this video", Toast.LENGTH_SHORT).show();
stopSelf();
}
- public void onVideoPlayPause() {
- if (DEBUG) Log.d(TAG, "onVideoPlayPause() called");
- if (CURRENT_STATE == STATE_COMPLETED) {
- changeState(STATE_LOADING);
- emVideoView.restart();
- return;
- }
- if (emVideoView.isPlaying()) {
- emVideoView.pause();
- progressPollRepeater.stop();
- internalListener.onRepeat();
- changeState(STATE_PAUSED);
- } else {
- emVideoView.start();
- progressPollRepeater.start();
- changeState(STATE_PLAYING);
- }
- }
+ /*//////////////////////////////////////////////////////////////////////////
+ // States
+ //////////////////////////////////////////////////////////////////////////*/
- public void onFastRewind() {
- if (DEBUG) Log.d(TAG, "onFastRewind() called");
- seekBy(-FAST_FORWARD_REWIND_AMOUNT);
- internalListener.onRepeat();
- changeState(STATE_PAUSED_SEEK);
-
- showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
- }
-
- public void onFastForward() {
- if (DEBUG) Log.d(TAG, "onFastForward() called");
- seekBy(FAST_FORWARD_REWIND_AMOUNT);
- internalListener.onRepeat();
- changeState(STATE_PAUSED_SEEK);
-
- showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
+ @Override
+ public void onLoading() {
+ super.onLoading();
+ updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
- public void onSeekComplete() {
- if (DEBUG) Log.d(TAG, "onSeekComplete() called");
-
- if (!emVideoView.isPlaying()) emVideoView.start();
- changeState(STATE_PLAYING);
- /*if (emVideoView.isPlaying()) changeState(STATE_PLAYING);
- else changeState(STATE_PAUSED);*/
+ public void onPlaying() {
+ super.onPlaying();
+ updateNotification(R.drawable.ic_pause_white);
}
@Override
- public void onCompletion() {
- if (DEBUG) Log.d(TAG, "onCompletion() called");
- changeState(STATE_COMPLETED);
- progressPollRepeater.stop();
+ public void onBuffering() {
+ super.onBuffering();
+ updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
- public boolean onError() {
- if (DEBUG) Log.d(TAG, "onError() called");
- stopSelf();
- return true;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // SeekBar Listener
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (DEBUG) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + progress + "], fromUser = [" + fromUser + "]");
- viewHolder.getPlaybackCurrentTime().setText(TimeFormatUtil.formatMs(progress));
+ public void onPaused() {
+ super.onPaused();
+ updateNotification(R.drawable.ic_play_arrow_white);
+ showAndAnimateControl(R.drawable.ic_play_arrow_white, false);
}
@Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
-
- changeState(STATE_PAUSED_SEEK);
- if (emVideoView.isPlaying()) emVideoView.pause();
- animateView(viewHolder.getControlsRoot(), true, 300, 0);
- viewHolder.getControlsRoot().setAlpha(1f);
+ public void onPausedSeek() {
+ super.onPausedSeek();
+ updateNotification(R.drawable.ic_play_arrow_white);
}
@Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (DEBUG) Log.d(TAG, "onProgressChanged() called with: seekBar = [" + seekBar + "], progress = [" + seekBar.getProgress() + "]");
- emVideoView.seekTo(seekBar.getProgress());
-
+ public void onCompleted() {
+ super.onCompleted();
+ updateNotification(R.drawable.ic_replay_white);
+ showAndAnimateControl(R.drawable.ic_replay_white, false);
}
- ///////////////////////////////////////////////////////////////////////////
- // Repeater Listener
- ///////////////////////////////////////////////////////////////////////////
-
- /**
- * Don't mistake this with anything related to the player itself, it's the {@link Repeater.RepeatListener#onRepeat}
- * It's used for pool the progress of the video
- */
- @Override
- public void onRepeat() {
- onUpdateProgress(emVideoView.getCurrentPosition(), emVideoView.getDuration(), emVideoView.getBufferPercentage());
- }
}
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
@@ -663,42 +434,33 @@ public class PopupVideoPlayer extends Service implements StateInterface {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
- if (!emVideoView.isPlaying()) return false;
- if (e.getX() > popupWidth / 2) internalListener.onFastForward();
- else internalListener.onFastRewind();
+ if (!playerImpl.isPlaying()) return false;
+ if (e.getX() > popupWidth / 2) playerImpl.onFastForward();
+ else playerImpl.onFastRewind();
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
- if (emVideoView == null) return false;
- internalListener.onVideoPlayPause();
+ if (playerImpl.getPlayer() == null) return false;
+ playerImpl.onVideoPlayPause();
return true;
}
-
@Override
public boolean onDown(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
initialPopupX = windowLayoutParams.x;
initialPopupY = windowLayoutParams.y;
- popupWidth = viewHolder.getRootView().getWidth();
- popupHeight = viewHolder.getRootView().getHeight();
+ popupWidth = playerImpl.getRootView().getWidth();
+ popupHeight = playerImpl.getRootView().getHeight();
return false;
}
- @Override
- public void onShowPress(MotionEvent e) {
- if (DEBUG) Log.d(TAG, "onShowPress() called with: e = [" + e + "]");
- /*viewHolder.getControlsRoot().animate().setListener(null).cancel();
- viewHolder.getControlsRoot().setAlpha(1f);
- viewHolder.getControlsRoot().setVisibility(View.VISIBLE);*/
- animateView(viewHolder.getControlsRoot(), true, 200, 0);
- }
-
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ if (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f) playerImpl.animateView(playerImpl.getControlsRoot(), true, 30, 0);
isMoving = true;
float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX);
float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY);
@@ -712,20 +474,21 @@ public class PopupVideoPlayer extends Service implements StateInterface {
windowLayoutParams.x = (int) posX;
windowLayoutParams.y = (int) posY;
- if (DEBUG) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && false) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]" +
", posXy = [" + posX + ", " + posY + "]" +
", popupWh rootView.get wh = [" + popupWidth + " x " + popupHeight + "]");
- windowManager.updateViewLayout(viewHolder.getRootView(), windowLayoutParams);
+ windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
return true;
}
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
- if (viewHolder.isControlsVisible() && CURRENT_STATE == STATE_PLAYING) {
- animateView(viewHolder.getControlsRoot(), false, 300, DEFAULT_CONTROLS_HIDE_TIME);
+ if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == StateInterface.STATE_PLAYING) {
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, AbstractPlayer.DEFAULT_CONTROLS_HIDE_TIME);
}
}
@@ -763,48 +526,56 @@ public class PopupVideoPlayer extends Service implements StateInterface {
if (service == null) return;
streamExtractor = service.getExtractorInstance(intent.getStringExtra(NavStack.URL));
StreamInfo info = StreamInfo.getVideoInfo(streamExtractor);
- String defaultResolution = sharedPreferences.getString(
+ String defaultResolution = playerImpl.getSharedPreferences().getString(
getResources().getString(R.string.default_resolution_key),
getResources().getString(R.string.default_resolution_value));
- String chosen = "", secondary = "", fallback = "";
+ VideoStream chosen = null, secondary = null, fallback = null;
+ playerImpl.setVideoStreamsList(info.video_streams instanceof ArrayList
+ ? (ArrayList) info.video_streams
+ : new ArrayList<>(info.video_streams));
+
for (VideoStream item : info.video_streams) {
if (DEBUG && printStreams) {
- Log.d(TAG, "StreamExtractor: current Item"
+ Log.d(TAG, "FetcherRunnable.StreamExtractor: current Item"
+ ", item.resolution = " + item.resolution
+ ", item.format = " + item.format
+ ", item.url = " + item.url);
}
if (defaultResolution.equals(item.resolution)) {
if (item.format == MediaFormat.MPEG_4.id) {
- chosen = item.url;
- if (DEBUG)
- Log.d(TAG, "StreamExtractor: CHOSEN item"
- + ", item.resolution = " + item.resolution
- + ", item.format = " + item.format
- + ", item.url = " + item.url);
- } else if (item.format == 2) secondary = item.url;
- else fallback = item.url;
-
+ chosen = item;
+ if (DEBUG) Log.d(TAG, "FetcherRunnable.StreamExtractor: CHOSEN item, item.resolution = " + item.resolution + ", item.format = " + item.format + ", item.url = " + item.url);
+ } else if (item.format == 2) secondary = item;
+ else fallback = item;
}
}
- if (!chosen.trim().isEmpty()) streamUri = Uri.parse(chosen);
- else if (!secondary.trim().isEmpty()) streamUri = Uri.parse(secondary);
- else if (!fallback.trim().isEmpty()) streamUri = Uri.parse(fallback);
- else streamUri = Uri.parse(info.video_streams.get(0).url);
- if (DEBUG && printStreams) Log.d(TAG, "StreamExtractor: chosen = " + chosen
+ int selectedIndexStream;
+
+ if (chosen != null) selectedIndexStream = info.video_streams.indexOf(chosen);
+ else if (secondary != null) selectedIndexStream = info.video_streams.indexOf(secondary);
+ else if (fallback != null) selectedIndexStream = info.video_streams.indexOf(fallback);
+ else selectedIndexStream = 0;
+
+ playerImpl.setSelectedIndexStream(selectedIndexStream);
+
+ if (DEBUG && printStreams) Log.d(TAG, "FetcherRunnable.StreamExtractor: chosen = " + chosen
+ "\n, secondary = " + secondary
+ "\n, fallback = " + fallback
+ "\n, info.video_streams.get(0).url = " + info.video_streams.get(0).url);
- videoUrl = info.webpage_url;
- videoTitle = info.title;
- channelName = info.uploader;
+
+ playerImpl.setVideoUrl(info.webpage_url);
+ playerImpl.setVideoTitle(info.title);
+ playerImpl.setChannelName(info.uploader);
+ if (info.start_position > 0) playerImpl.setVideoStartPos(info.start_position * 1000);
+ else playerImpl.setVideoStartPos(-1);
+
mainHandler.post(new Runnable() {
@Override
public void run() {
- playVideo(streamUri);
+ playerImpl.playVideo(playerImpl.getSelectedStreamUri(), true);
}
});
imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() {
@@ -813,9 +584,10 @@ public class PopupVideoPlayer extends Service implements StateInterface {
mainHandler.post(new Runnable() {
@Override
public void run() {
- videoThumbnail = loadedImage;
- if (videoThumbnail != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, videoThumbnail);
+ playerImpl.setVideoThumbnail(loadedImage);
+ if (loadedImage != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
updateNotification(-1);
+ ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = loadedImage;
}
});
}
@@ -841,4 +613,5 @@ public class PopupVideoPlayer extends Service implements StateInterface {
}
}
}
-}
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/player/popup/StateInterface.java b/app/src/main/java/org/schabi/newpipe/player/StateInterface.java
similarity index 71%
rename from app/src/main/java/org/schabi/newpipe/player/popup/StateInterface.java
rename to app/src/main/java/org/schabi/newpipe/player/StateInterface.java
index 94ea41470..7b3681ab8 100644
--- a/app/src/main/java/org/schabi/newpipe/player/popup/StateInterface.java
+++ b/app/src/main/java/org/schabi/newpipe/player/StateInterface.java
@@ -1,8 +1,9 @@
-package org.schabi.newpipe.player.popup;
+package org.schabi.newpipe.player;
public interface StateInterface {
int STATE_LOADING = 123;
- int STATE_PLAYING = 125;
+ int STATE_PLAYING = 124;
+ int STATE_BUFFERING = 125;
int STATE_PAUSED = 126;
int STATE_PAUSED_SEEK = 127;
int STATE_COMPLETED = 128;
@@ -11,6 +12,7 @@ public interface StateInterface {
void onLoading();
void onPlaying();
+ void onBuffering();
void onPaused();
void onPausedSeek();
void onCompleted();
diff --git a/app/src/main/java/org/schabi/newpipe/player/popup/PopupViewHolder.java b/app/src/main/java/org/schabi/newpipe/player/popup/PopupViewHolder.java
deleted file mode 100644
index 22895668e..000000000
--- a/app/src/main/java/org/schabi/newpipe/player/popup/PopupViewHolder.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.schabi.newpipe.player.popup;
-
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.os.Build;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
-
-import org.schabi.newpipe.R;
-
-public class PopupViewHolder {
- private View rootView;
- private EMVideoView videoView;
- private View loadingPanel;
- private ImageView endScreen;
- private ImageView controlAnimationView;
- private LinearLayout controlsRoot;
- private SeekBar playbackSeekBar;
- private TextView playbackCurrentTime;
- private TextView playbackEndTime;
-
- public PopupViewHolder(View rootView) {
- if (rootView == null) return;
- this.rootView = rootView;
- this.videoView = (EMVideoView) rootView.findViewById(R.id.popupVideoView);
- this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
- this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
- this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
- this.controlsRoot = (LinearLayout) rootView.findViewById(R.id.playbackControlRoot);
- this.playbackSeekBar = (SeekBar) rootView.findViewById(R.id.playbackSeekBar);
- this.playbackCurrentTime = (TextView) rootView.findViewById(R.id.playbackCurrentTime);
- this.playbackEndTime = (TextView) rootView.findViewById(R.id.playbackEndTime);
- doModifications();
- }
-
- private void doModifications() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
- playbackSeekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY);
- }
-
- public boolean isControlsVisible() {
- return controlsRoot != null && controlsRoot.getVisibility() == View.VISIBLE;
- }
-
- public boolean isVisible(View view) {
- return view != null && view.getVisibility() == View.VISIBLE;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // GETTERS
- ///////////////////////////////////////////////////////////////////////////
-
- public View getRootView() {
- return rootView;
- }
-
- public EMVideoView getVideoView() {
- return videoView;
- }
-
- public View getLoadingPanel() {
- return loadingPanel;
- }
-
- public ImageView getEndScreen() {
- return endScreen;
- }
-
- public ImageView getControlAnimationView() {
- return controlAnimationView;
- }
-
- public LinearLayout getControlsRoot() {
- return controlsRoot;
- }
-
- public SeekBar getPlaybackSeekBar() {
- return playbackSeekBar;
- }
-
- public TextView getPlaybackCurrentTime() {
- return playbackCurrentTime;
- }
-
- public TextView getPlaybackEndTime() {
- return playbackEndTime;
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
index 6bf261e14..cb3869c01 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
@@ -19,17 +19,14 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
-import org.schabi.newpipe.ChannelActivity;
+import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.info_list.InfoItemBuilder;
-import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.info_list.InfoListAdapter;
+import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack;
import java.util.EnumSet;
diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png
rename to app/src/main/res/drawable-hdpi/ic_close_white.png
diff --git a/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png
deleted file mode 100644
index ceb1a1eeb..000000000
Binary files a/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-hdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..159bea7fd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-hdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..9b8131124
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_pause_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
rename to app/src/main/res/drawable-hdpi/ic_pause_white.png
diff --git a/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 4d2ea05c4..000000000
Binary files a/app/src/main/res/drawable-hdpi/ic_pause_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-hdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-hdpi/ic_repeat_white.png b/app/src/main/res/drawable-hdpi/ic_repeat_white.png
new file mode 100644
index 000000000..5de7a2951
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_screen_rotation_white.png b/app/src/main/res/drawable-hdpi/ic_screen_rotation_white.png
new file mode 100644
index 000000000..b81f22246
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_screen_rotation_white.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_screen_rotation_white_24dp.png
deleted file mode 100644
index fa2c881e0..000000000
Binary files a/app/src/main/res/drawable-hdpi/ic_screen_rotation_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_white.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png
rename to app/src/main/res/drawable-mdpi/ic_close_white.png
diff --git a/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png
deleted file mode 100644
index af7f8288d..000000000
Binary files a/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-mdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..364bad0b8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-mdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..4423c7ce9
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_pause_white.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
rename to app/src/main/res/drawable-mdpi/ic_pause_white.png
diff --git a/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 2272d478c..000000000
Binary files a/app/src/main/res/drawable-mdpi/ic_pause_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-mdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-mdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-mdpi/ic_repeat_white.png b/app/src/main/res/drawable-mdpi/ic_repeat_white.png
new file mode 100644
index 000000000..ad8b8c0df
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_screen_rotation_white.png b/app/src/main/res/drawable-mdpi/ic_screen_rotation_white.png
new file mode 100644
index 000000000..bb36eef34
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_screen_rotation_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_screen_rotation_white_24dp.png
deleted file mode 100644
index b6acecf34..000000000
Binary files a/app/src/main/res/drawable-mdpi/ic_screen_rotation_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_close_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png
rename to app/src/main/res/drawable-xhdpi/ic_close_white.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..ef360fe40
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-xhdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..c1dcfb290
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_pause_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxxhdpi/ic_pause_white_24dp.png
rename to app/src/main/res/drawable-xhdpi/ic_pause_white.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-xhdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xhdpi/ic_repeat_white.png
new file mode 100644
index 000000000..c13d00242
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white.png b/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white.png
new file mode 100644
index 000000000..449b6725f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white_24dp.png
deleted file mode 100644
index 9092249af..000000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white.png b/app/src/main/res/drawable-xxhdpi/ic_close_white.png
new file mode 100644
index 000000000..4927bc242
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..b7f4133fd
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..a0a1b4d4f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_white.png b/app/src/main/res/drawable-xxhdpi/ic_pause_white.png
new file mode 100644
index 000000000..3ea7e03e5
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-xxhdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png
new file mode 100644
index 000000000..bf7607966
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white.png b/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white.png
new file mode 100644
index 000000000..a160572a4
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white_24dp.png
deleted file mode 100644
index cc0fa87f7..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white.png b/app/src/main/res/drawable-xxxhdpi/ic_close_white.png
new file mode 100644
index 000000000..1ab231275
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_close_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_white.png b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_white.png
new file mode 100644
index 000000000..b47b3f8bd
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_white.png b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_white.png
new file mode 100644
index 000000000..ea9f18ae6
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_fullscreen_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_white.png b/app/src/main/res/drawable-xxxhdpi/ic_pause_white.png
new file mode 100644
index 000000000..76482b1fd
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pause_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png
similarity index 100%
rename from app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png
rename to app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white.png
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png
new file mode 100644
index 000000000..a59db47ee
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white.png b/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white.png
new file mode 100644
index 000000000..3cde2bfef
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white_24dp.png
deleted file mode 100644
index d67b21ae8..000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white_24dp.png and /dev/null differ
diff --git a/app/src/main/res/drawable/popup_controls_bg.xml b/app/src/main/res/drawable/player_controls_bg.xml
similarity index 69%
rename from app/src/main/res/drawable/popup_controls_bg.xml
rename to app/src/main/res/drawable/player_controls_bg.xml
index d04812bd8..9c9468112 100644
--- a/app/src/main/res/drawable/popup_controls_bg.xml
+++ b/app/src/main/res/drawable/player_controls_bg.xml
@@ -1,8 +1,7 @@
diff --git a/app/src/main/res/drawable/player_top_controls_bg.xml b/app/src/main/res/drawable/player_top_controls_bg.xml
new file mode 100644
index 000000000..b7cdecc87
--- /dev/null
+++ b/app/src/main/res/drawable/player_top_controls_bg.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_exo_player.xml b/app/src/main/res/layout/activity_exo_player.xml
index 5653532ec..b0e80b584 100644
--- a/app/src/main/res/layout/activity_exo_player.xml
+++ b/app/src/main/res/layout/activity_exo_player.xml
@@ -1,15 +1,318 @@
-
-
+
-
+ android:layout_height="match_parent"
+ android:layout_gravity="center">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_channel.xml b/app/src/main/res/layout/content_channel.xml
deleted file mode 100644
index 79a41a628..000000000
--- a/app/src/main/res/layout/content_channel.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/exomedia_custom_controls.xml b/app/src/main/res/layout/exomedia_custom_controls.xml
deleted file mode 100644
index dedaf7908..000000000
--- a/app/src/main/res/layout/exomedia_custom_controls.xml
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_notification.xml b/app/src/main/res/layout/player_notification.xml
index 43ac993ca..22a60418b 100644
--- a/app/src/main/res/layout/player_notification.xml
+++ b/app/src/main/res/layout/player_notification.xml
@@ -65,7 +65,7 @@
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
- android:src="@drawable/ic_pause_white_24dp" />
+ android:src="@drawable/ic_pause_white" />
+ android:src="@drawable/ic_close_white" />
diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml
index 0fb6a8eb2..4a81d2ca3 100644
--- a/app/src/main/res/layout/player_notification_expanded.xml
+++ b/app/src/main/res/layout/player_notification_expanded.xml
@@ -58,7 +58,7 @@
android:background="#00ffffff"
android:clickable="true"
android:scaleType="fitXY"
- android:src="@drawable/ic_close_white_24dp" />
+ android:src="@drawable/ic_close_white" />
diff --git a/app/src/main/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml
index 6d1860408..a3b2b80b6 100644
--- a/app/src/main/res/layout/player_popup.xml
+++ b/app/src/main/res/layout/player_popup.xml
@@ -4,18 +4,29 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@android:color/black"
android:gravity="center">
-
+ android:layout_gravity="center">
-
+
+
+
+
+
+ tools:ignore="ContentDescription"
+ tools:visibility="visible"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:orientation="horizontal"
+ android:weightSum="5">
+
+ tools:ignore="ContentDescription"
+ tools:visibility="visible"/>
-
-
-
-
-
-
-
-
-
-
+ tools:ignore="RtlHardcoded"
+ tools:text="1:06:29"
+ tools:visibility="visible"/>
+ tools:text="a long, long, long, long, long title"/>
+ tools:text="a long, long artist"/>
+
+
+ android:padding="5dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_close_white"
+ tools:ignore="ContentDescription,RtlHardcoded"/>
\ No newline at end of file
diff --git a/app/src/main/res/menu/video_player.xml b/app/src/main/res/menu/video_player.xml
index 87aaccee8..c79217adc 100644
--- a/app/src/main/res/menu/video_player.xml
+++ b/app/src/main/res/menu/video_player.xml
@@ -5,5 +5,5 @@
+ android:icon="@drawable/ic_screen_rotation_white"/>
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 7e2e2d82e..e10406726 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -1,27 +1,5 @@
- Navigate home
- %1$s, %2$s
- %1$s, %2$s, %3$s
- Navigate up
- More options
- Done
- See all
- Choose an app
- OFF
- ON
- Search…
- Clear query
- Search query
- Search
- Submit query
- Voice search
- Share with
- Share with %s
- Collapse
- 999+
- "بدء تشغيل الفيديو تلقائيًا عندما يتم فتحه من تطبيق أخر."
- التشغيل التلقائي
مشغل NewPipe في الخلفية
جاري التشغيل في الخلفية
إلغاء
@@ -87,31 +65,4 @@
خطأ
لا يمكن تحليل الموقع.
لا يمكن فك تشفير توقيع رابط الفيديو.
- NewPipe
- android.support.design.widget.AppBarLayout$ScrollingViewBehavior
- autoplay_through_intent
- %1$s - NewPipe
- https://www.c3s.cc/
- %1$d / %2$d
- default_audio_format
- m4a
- en
- default_resolution_preference
- 360p
- 0
- download_path_audio
- download_path
- https://f-droid.org/repository/browse/?fdfilter=Kore&fdid=org.xbmc.kore
- https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc
- search_language
- settings_category_appearance
- settings_category_other
- settings_category_video_audio
- show_next_video
- show_play_with_kodi
- الثيمات
- NewPipe
- use_external_audio_player
- use_external_video_player
- use_tor
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 9a0f3690e..95164d622 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -22,8 +22,6 @@
Zadejte umístění pro stažené audio soubory.
Umístění pro stažené audio
- Automatické přehrávání skrze Intent
- Automaticky přehrávat video, jestliže je volané z jiné aplikace.
Výchozí rozlišení
Přehrát pomocí Kodi
Aplikace Kore nenalezena. Nainstalovat Kore?
@@ -159,143 +157,6 @@
Aktivita kanálu
Hlášení uživatele
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
Podrobné
Nelze zjistit dekodéry zařízení
Nelze doložit dekodér
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index d5330ad9e..01e415eaa 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -16,16 +16,12 @@
Browser
Rotation
Einstellungen
- Externen Player benutzen
Downloadverzeichnis für Videos
Verzeichnis in dem heruntergeladene Videos gespeichert werden.
Downloadverzeichnis für Videos eingeben
- Automatisches Abspielen durch Intent
- Startet ein Video automatisch wenn es von einer anderen App aufgerufen wurde.
Standardauflösung
Mit Kodi abspielen
Kore App wurde nicht gefunden. Möchten sie Kore jetzt installieren?
- Kore installieren
Zeige \"Mit Kodi abspielen\" Option
Zeigt eine Option an, über die man Videos mit dem Kodi Mediacenter abspielen kann.
Audio
@@ -169,143 +165,6 @@
Beschädigte URL oder Internet nicht erreichbar
Vorgeschlagener Dateiname
Kanalaktivität
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
reCAPTCHA
Schwarz
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 638feaef9..1ee429ac5 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -28,8 +28,6 @@
Διαδρομή για αποθήκευση αρχείων ήχου
Εισάγετε διαδρομή για λήψη αρχείων ήχου.
- Αυτόματη αναπαραγωγή μέσω Intent
- Αυτόματη αναπαραγωγή video όταν καλείται από άλλη εφαρμογή.
Προεπιλεγμένη ανάλυση
Αναπαραγωγή με το Kodi
Η εφαρμογή Kore δεν βρέθηκε. Εγκατάσταση;
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 50089bb9c..cb852e61a 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -48,7 +48,6 @@
Ŝatoj
Malŝatoj
Uzi la programon Tor
- Ludi aŭtomate per Intent
Neniu elsendlflua ludilo trovita. Ĉu instali la aplikaĵon VLC?
La aplikaĵo Kore ne estas trovita. Ĉu instali la aplikaĵon Kore?
Montri la sekvan videon kaj similajn videojn
@@ -67,7 +66,6 @@
Montri opcion por ludi videon per la aplikaĵo Kodi.
Dosierujo por konservi elŝutitajn videojn.
Dosierujo por konservi elŝutitan muzikon
- Ludi videon aŭtomate kiam ĝi estas vokita de alia aplikaĵo.
Elektu lokon por konservi elŝutitajn videojn
Elektu lokon por konservi elŝutitan muzikon.
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 3288e898a..ec667aaaa 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -16,16 +16,12 @@
Seleccionar navegador
rotación
Ajustes
- Usar reproductor externo
Ruta de descarga de vídeo
Ruta para almacenar los vídeos descargados.
Introducir directorio de descargas para vídeos
- Intentar reproducción automática
- Reproducir vídeos automáticamente cuando se llamen desde otra aplicación.
Resolución por defecto
Reproducir con Kodi
Aplicación Kore no encontrada. ¿Instalar Kore?
- Instalar Kore
Mostrar opción \"Reproducir con Kodi\"
Muestra una opción para reproducir el vídeo con Kodi Media Center.
Audio
@@ -159,143 +155,6 @@
Actividad del canal
Ajustes
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
Oscuro
Todo
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 5f0a9f5f7..bfedaf4d6 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -19,7 +19,6 @@
Lehenetsitako bereizmena
Kodirekin erreproduzitu
Kore aplikazioa ez da aurkitu. Kore beharrezkoa da Kodi multimedia zentroarekin bideoak erreproduzitzeko.
- Kore instalatu
\"Kodirekin erreproduzitu\" aukera erakutsi
Kodi multimedia zentroarekin bideoa erreproduzitzeko aukera erakusten du.
Audioa
@@ -48,8 +47,6 @@
"Orrialdea bilatu: "
Kanpoko bideo erreproduzitzailea erabili
Kanpoko audio erreproduzitzailea erabili
- Intent bidez automatikoki erreproduzitu
- Bideoa automatikoki hasten du beste aplikazio batetik deitu denean.
Atzeko planoan erreproduzitzen
Ukitu bilaketa hasteko
Audioa deskargatzeko kokapena
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index cad6350ff..b9f368548 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,6 +1,5 @@
- Lire automatiquement une vidéo lorsqu’elle a été appelée depuis une autre application.
Annuler
Choisir un navigateur
Définition par défaut
@@ -10,11 +9,9 @@
Entrer le chemin de téléchargement des vidéos
Chemin de stockage des vidéos téléchargées.
Installer
- Installer Kore
L’application Kore est introuvable. Installer Kore ?
Aucun lecteur de flux réseau trouvé. Installer VLC ?
Ouvrir dans le navigateur
- Lecture automatique via Intent
Lire avec Kodi
rotation
Rechercher
@@ -26,7 +23,6 @@
Afficher l’option « Lire avec Kodi »
Paramètres
Ajoutée le %1$s
- Utiliser un lecteur externe
%1$s vues
Audio
Format audio par défaut
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index e161f511e..6078fa29c 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -16,16 +16,12 @@
Válasszon böngészőt
forgatás
Beállítások
- Külső lejátszó használata
Videófájlok letöltési helye
Útvonal a letöltött videók tárolásához.
Adja meg a videófájlok letöltési helyét
- Automatikus lejátszás Intent-en keresztül
- Videó automatikus lejátszása külső alkalmazással való megnyitás esetén.
Alapértelmezett felbontás
Lejátszás Kodi-val
A Kore alkalmazás nem található. Feltelepíti a Kore lejátszót?
- Kore telepítése
\"Lejátszás Kodi-val\" opció mutatása
Mutat egy opciót a videók Kodi médiaközponttal való lejátszására.
Hang
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
index 8f9dfc41f..2c14f099f 100644
--- a/app/src/main/res/values-id/strings.xml
+++ b/app/src/main/res/values-id/strings.xml
@@ -151,143 +151,6 @@
Laporan pengguna
Thread
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
reCAPTCHA
Rintangan reCAPTCHA
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 302dfd7f1..30f171f22 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -16,16 +16,12 @@
Scegli browser
rotazione
Impostazioni
- Usa un riproduttore video esterno
Cartella dei video scaricati
Percorso dove memorizzare i video scaricati.
Inserisci il percorso per i download
- Auto riproduzione attraverso internet
- Avvia automaticamente un video quando è stato chiamato da un\'altra applicazione.
Risoluzione predefinita
Riproduci con Kodi
L\'applicazione Kore non è stata trovata. Kore è necessario per riprodurre video con Kodi media center. Vorresti installarlo?
- Installa Kore
Mostra l\'opzione \"Riproduci con Kodi\"
Mostra un opzione per riprodurre un video attraverso Kodi media center.
Audio
@@ -39,8 +35,6 @@
Video simili
Lingua preferita per i contenuti
Video e Audio
- INFO
- ETC
Anteprima video
Anteprima video
@@ -164,143 +158,6 @@
Impostazioni
ChannelActivity
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
reCAPTCHA
Sfida reCAPTCHA
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 9fb0d33e1..17c9f8aad 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -15,16 +15,12 @@
ブラウザーを選択
回転
設定
- 外部プレーヤーを使用する
動画を保存する場所
動画を保存する位置
動画を保存する場所を入力して下さい
- インテントで自動再生
- 他のアプリケーションから呼び出されたとき、自動的に動画再生を開始します。
基本解像度
Kodi で再生
Koreが見つかりません。Kore を入手しますか。
- Kore をインストール
\"Kodi で再生\" 設定を表示
Kodi メディアセンター経由で動画を再生するための設定を表示します
音楽
@@ -162,143 +158,6 @@
アプリ/UI がクラッシュしました
何:\\n提案:\\nコンテンツ言語:\\nサービス:\\nGMT 時間:\\nパッケージ:\\nバージョン:\\nOS バージョン:\\nグローバル IP 範囲:
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
reCAPTCHA
reCAPTCHA の要求
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 50709b3b3..855db8f26 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -16,16 +16,12 @@
브라우저 선택
회전
설정
- 외부 플레이어 사용
비디오 다운로드 위치
다운로드된 비디오가 저장될 경로를 선택하세요.
비디오 다운로드 경로 입력
- 인텐트로 경유할 경우 자동 재생
- 다른 앱으로부터 호출되었을 경우에 비디오를 자동으로 재생합니다.
기본 해상도
Kodi로 재생
Kore 앱이 발견되지 않았습니다. Kore를 설치할까요?
- Kore 설치
\"Kodi로 재생\" 옵션 표시
비디오를 Kodi media center를 사용해 재생하는 옵션을 표시합니다.
오디오
@@ -39,8 +35,6 @@
유사한 비디오
선호하는 컨텐츠 언어
비디오 & 오디오
- 정보
- 기타
비디오 미리보기 썸네일
비디오 미리보기 썸네일
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 6315b61fd..f5befe193 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -28,7 +28,6 @@
Sti å lagre nedlastet lyd i.
Skriv inn nedlastingssti for lydfiler.
- Automatisk avspilling av video når det blir forespurt fra et annet program.
Forvalgt oppløsning
Spill av med Kodi
Kore-programmet ble ikke funnet. Installer Kore?
@@ -65,7 +64,6 @@
Kan ikke opprette nedlastingsmappe \'%1$s\'
Opprettet nedlastingsmappen \'%1$s\'
-Automatisk avspilling med Intent
Trykk for å komme i gang
Automatisk avspilling når forespurt av et annet program
Automatisk avspilling av video når NewPipe blir forespurt av et annet program.
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 1d1e8a4f0..1e0d80673 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -16,16 +16,12 @@
Kies een browser
rotatie
Instellingen
- Gebruik externe speler
Downloadlocatie voor video\'s
Locatie om gedownloade video\'s in op te slaan.
Voer downloadlocatie in voor video\'s
- Speel automatisch via Intent
- Speel een video automatisch af indien geopend vanuit een andere app.
Standaardresolutie
Afspelen met Kodi
Kore-app niet gevonden. Kore installeren?
- Installeer Kore
Toon \"Afspelen met Kodi\"-optie
Toont een optie om een video op een Kodi media center af te spelen.
Audio
@@ -157,143 +153,6 @@
ChannelActivity
Instellingen
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
Zwart
reCAPTCHA
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index fcdf32240..84394a32f 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -21,12 +21,9 @@
Descarga de vídeos
Local para guardar os vídeos descarregados.
Digite o caminho para os vídeos
- Reproduzir através de Intent
- Reproduzir automaticamente o vídeo se invocado por outra aplicação.
Resolução padrão
Reproduzir com Kodi
Aplicação não encontrada. Instalar o Kore?
- Instalar o Kore
Mostrar opção \"Reproduzir com Kodi\"
Mostra uma opção para reproduzir o vídeo com o Kodi.
Áudio
@@ -40,8 +37,6 @@
Vídeos similares
Idioma preferencial do conteúdo
Vídeo e áudio
- Informações
- Outras
Miniatura de vídeos
Miniatura de vídeos
@@ -158,143 +153,6 @@
Aplicação encerrada
O quê:\\nPedido:\\nIdioma do conteúdo:\\nServiço:\\nHora GMT:\\nPacote:\\nVersão:\\nVersão do SO:\\nIP global:
Atividade do canal
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
Abrir no modo “popup“
Preto
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 3a6a64f86..6c28f910b 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -28,8 +28,6 @@
Locul în care se vor stoca fișierele audio descărcate
Introduceți calea de descărcare pentru fișierele audio.
- Redare automată intenționată
- Redați automat un videoclip atunci când este chemat din altă aplicație.
Rezoluție implicită
Redați folosind Kodi
Aplicația Kore nu a fost găsită. Instalați Kore?
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 288b1137a..dcb8221a7 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -16,16 +16,12 @@
Выбрать браузер
поворот
Настройки
- Использовать внешний проигрыватель
Место для загрузок
Папка для хранения загруженных видео
Введите путь к папке для загрузки видео
- Автопроигрывание через интернет
- Автоматически воспроизводить видео, открытое через другое приложение
Разрешение по-умолчанию
Воспроизвести с помощью Kodi
Приложение Kore не наидено. Установить Kore?
- Установить Kore
Показывать опцию \"Воспроизвести с помощью Kodi\"
Показать опцию воспроизведения видео через Kodi media center
Аудио
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index ec90d8727..0ace30a7e 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -29,8 +29,6 @@
Vložte cestu kam sa budú ukladať zvukové súbory.
Cesta kam sa bude ukladať prevzaté audio
- Automatické prehrávanie na vyžiadanie
- Automatický prehrá video na žiadosť z inej aplikácie.
Štandardné rozlíšenie
Prehrať cez Kodi
Aplikácia Kore nenajdená. Inštalovať Kore?
@@ -155,143 +153,6 @@
Aktivita kanálov
Nastavenia
- "
- Materiál je metaforou.
-
-
-
- Materiál metafora je zjednocujúci teórie racionálne priestoru a systém pohybu.
- Materiál je zakotvená v taktilné skutočnosti, inšpirované štúdiom papiera a atramentu, ale napriek tomu
- technologicky pokročilé a otvorené fantáziu a mágiu.
-
- Povrchy a hrany materiálu poskytujú vizuálnu pokyny, ktoré sú zakotvené v realite.
- Použitie známych hmatových vlastností, pomáha používateľom rýchlo pochopiť affordances. napriek tomu
- Pružnosť materiálu vytvára nové affordances že nadradené tie fyzické
- svet bez toho, aby porušenie pravidiel fyziky.
-
- Základy svetla, povrchu a pohyb sú kľúčom k dopravovanie, ako sa objekty pohybovať,
- komunikovať, a existujú v priestore a vo vzťahu k sebe navzájom. Realistické svetelné šou
- švy, rozdeľuje priestor, a indikuje pohyblivých častí.
-
-
-
- Bold, kreslený, úmyselné.
-
-
-
- Foundational prvky tlače na dizajn typografie, rošty, priestor, mierka, farby,
- a používanie metafor riadiť vizuálne procedúry. Tieto prvky robiť oveľa viac, než potešiť
- oko. Vytvárajú hierarchie, zmysel a zameranie. výber farieb úmyselné, od okraja k okraju
- snímok, rozsiahle typografie a úmyselné biely priestor vytvoriť tučný a grafický
- rozhranie, ktoré ponorí užívateľov v zážitku.
-
- Dôraz na užívateľských akcií činí základné funkcie okamžite zrejmé a poskytuje
- waypointy pre užívateľov.
-
-
-
- Motion poskytuje význam.
-
-
-
- Návrh rešpektuje a posilňuje užívateľa ako hnacia sila. Primárne akcie užívateľa sú
- inflexné body, ktoré iniciujú pohyb, transformáciu celej konštrukcie.
-
- Celá akcia prebieha v jedinom prostredí. Objekty sú prezentované užívateľovi bez toho,
- prelomenie kontinuity skúseností, aj keď ich transformáciu a reorganizáciu.
-
- Pohyb je zmysluplné a vhodné, slúžiace sústrediť pozornosť a zachovanie kontinuity.
- Spätná väzba je jemné, ale napriek tomu jasné. Prechody sú ef fi točné ešte koherentné.
-
-
-
- 3D svet.
-
-
-
- Materiál prostredie je 3D priestore, čo znamená, že všetky objekty majú X, Y, a Z
- rozmery. Os je kolmo zarovnaný k rovine zobrazenie, s
- kladná os rozširuje smerom k divákovi. Každý list materiálu, umiestnené v jednom
- poloha pozdĺž osi a má štandardnú hrúbku 1DP.
-
- Na webe, os sa používa pre vrstvenie a nie pre perspektívy. 3D svet
- emuloval tým, že manipuluje os y.
-
-
-
- Svetlo a tieň.
-
-
-
- V hmotnom prostredí, virtuálne svetlá osvetľujú scénu. Kľúčové svetla vytvárajú
- smerové tiene, zatiaľ čo okolité svetlo vytvára mäkké tiene zo všetkých strán.
-
- Tiene v hmotnom prostredí sú obsadené týmito dvoma svetelnými zdrojmi. v Android
- vývoj, dochádza tiene, keď sú svetelné zdroje blokované plechového materiálu, na
- Rôzne pozície pozdĺž osi. Na webe tiene sú znázornené manipuláciou
- Iba os y. Nasledujúci príklad ukazuje kartu s výškou 6dp.
-
-
-
- Odpočíva nadmorskú výšku.
-
-
-
- Všetky významné objekty, bez ohľadu na ich veľkosť, majú výšku odpočíva, alebo predvolené výšku
- to nič nemení. V prípade, že objekt sa mení výšku, mal by sa vrátiť do svojej pokojovej
- nadmorskej výšky čo najskôr.
-
-
-
- Component výškach.
-
-
-
- Pokojová nadmorská výška pre typ komponentu je konzistentné naprieč aplikáciami (napr FAB elevácie
- nelíši od 6dp v jednej aplikácii na 16dp v inej aplikácii).
-
- Zložky môžu mať rôzne pokojovej výšky medzi platformami, v závislosti od hĺbky
- životného prostredia (napr televízor má väčšiu hĺbku, ako mobilný telefón alebo plochy).
-
-
-
- Citlivé nadmorská výška a dynamické elevácie offsety.
-
-
-
- Niektoré typy komponentov majú citlivejší výšku, čo znamená, že meniť výšku v odozve
- na vstup používateľa (napríklad normálny priebeh, sa zameral, a lisované), alebo systémové udalosti. tieto elevácie
- Zmeny sa vykonávali dôsledne používať dynamické výškové posuny.
-
- Dynamické výškové posuny sú cieľom nadmorská výška, ktorá zložka sa pohybuje smerom, relatívna
- do pokojového stavu súčasti. Zaisťujú, že zmeny elevácie sú v súlade
- naprieč akcií a typov komponentov. Napríklad všetky súčasti, ktoré vlek na lise majú
- rovnaká zmena prevýšenie vo vzťahu k ich prevýšenie odpočinku.
-
- Akonáhle je vstupná udalosť dokončenie alebo zrušená, bude zložka vráti do svojej pokojovej
- nadmorská výška.
-
-
-
- Vyhnúť sa rušenie elevácie.
-
-
-
- Zložky s citlivými nadmorských výškach môže stretnúť s ďalšími komponentmi, ako sa pohybovať medzi
- ich odpočinku vyvýšeniny a dynamické výškové odsadenie. Vzhľadom k tomu, materiál nemôže prejsť
- prostredníctvom iného materiálu, komponenty nenarušovať spolu navzájom nejakom množstvo ciest,
- či už na úrovni jednotlivých komponentov alebo s použitím kompletné rozloženie aplikácie.
-
- Na úrovni komponentov môžu zložky presunúť alebo odstrániť skôr, než spôsobia rušenie.
- Napríklad tlačidlo plávajúce akcie (FAB) môžu zmiznúť alebo presunúť mimo obrazovku pred
- Užívateľ zdvihne kartu, alebo sa môže pohybovať, ak sa objaví snackbar.
-
- Na úrovni Usporiadanie, riešenie rozvrhnutie aplikácie, aby sa minimalizovalo príležitostí k rušeniu.
- Napríklad, umiestnenie FAB na jednej strane prúdu niekoľkých kariet, takže FAB nebude zasahovať
- ak sa užívateľ pokúsi vyzdvihnúť jednu z kariet.
-
-
- "
reCAPTCHA
Výzva reCAPTCHA
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 6ee3e2f9a..653575e6e 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -24,7 +24,6 @@
Privzeta ločljivost
Predvajaj s Kodi
Programa Kore ni mogoče najti. Ali želite program namestiti?
- Namesti program Kore
Pokaži možnost \"Predvajaj s Kodi\"
Privzet zapis zvoka
Zvok
@@ -40,10 +39,7 @@
Sličica predogleda videa
Sličica predogleda videa
Sličica pošiljalnika
- Samodejno predvajanje prek vmesnika Intent
- Začne samodejno predvajanje videa, ko je zagnan prek drugega programa.
Pokaže možnost predvajanja videa preko predstavnega središča Kodi.
- Drugo
Všeč mi je
Ni mi všeč
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index bc8098a95..933733ba4 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -16,16 +16,12 @@
Отвори помоћу
ротација
Поставке
- Користи спољашњи плејер
Одредиште преузимања за видео
Путања за упис преузетих видеа.
Унесите путању за преузимање видеа
- Аутопуштање преко интента
- Аутоматско пуштање видеа по позиву из друге апликације.
Подразумевана резолуција
Пусти помоћу Кодија
Апликација Кор (Kore) није нађена. Инсталирати Кор?
- Инсталирај Кор
Прикажи „Пусти помоћу Кодија“
Приказ опције за пуштање видеа у Коди медија центру.
Аудио
@@ -164,143 +160,6 @@
Активност канала
Поставке
- "
- Material is the metaphor.
-
-
-
- A material metaphor is the unifying theory of a rationalized space and a system of motion.
- The material is grounded in tactile reality, inspired by the study of paper and ink, yet
- technologically advanced and open to imagination and magic.
-
- Surfaces and edges of the material provide visual cues that are grounded in reality. The
- use of familiar tactile attributes helps users quickly understand affordances. Yet the
- flexibility of the material creates new affordances that supercede those in the physical
- world, without breaking the rules of physics.
-
- The fundamentals of light, surface, and movement are key to conveying how objects move,
- interact, and exist in space and in relation to each other. Realistic lighting shows
- seams, divides space, and indicates moving parts.
-
-
-
- Bold, graphic, intentional.
-
-
-
- The foundational elements of print based design typography, grids, space, scale, color,
- and use of imagery guide visual treatments. These elements do far more than please the
- eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge
- imagery, large scale typography, and intentional white space create a bold and graphic
- interface that immerse the user in the experience.
-
- An emphasis on user actions makes core functionality immediately apparent and provides
- waypoints for the user.
-
-
-
- Motion provides meaning.
-
-
-
- Motion respects and reinforces the user as the prime mover. Primary user actions are
- inflection points that initiate motion, transforming the whole design.
-
- All action takes place in a single environment. Objects are presented to the user without
- breaking the continuity of experience even as they transform and reorganize.
-
- Motion is meaningful and appropriate, serving to focus attention and maintain continuity.
- Feedback is subtle yet clear. Transitions are efficient yet coherent.
-
-
-
- 3D world.
-
-
-
- The material environment is a 3D space, which means all objects have x, y, and z
- dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the
- positive z-axis extending towards the viewer. Every sheet of material occupies a single
- position along the z-axis and has a standard 1dp thickness.
-
- On the web, the z-axis is used for layering and not for perspective. The 3D world is
- emulated by manipulating the y-axis.
-
-
-
- Light and shadow.
-
-
-
- Within the material environment, virtual lights illuminate the scene. Key lights create
- directional shadows, while ambient light creates soft shadows from all angles.
-
- Shadows in the material environment are cast by these two light sources. In Android
- development, shadows occur when light sources are blocked by sheets of material at
- various positions along the z-axis. On the web, shadows are depicted by manipulating the
- y-axis only. The following example shows the card with a height of 6dp.
-
-
-
- Resting elevation.
-
-
-
- All material objects, regardless of size, have a resting elevation, or default elevation
- that does not change. If an object changes elevation, it should return to its resting
- elevation as soon as possible.
-
-
-
- Component elevations.
-
-
-
- The resting elevation for a component type is consistent across apps (e.g., FAB elevation
- does not vary from 6dp in one app to 16dp in another app).
-
- Components may have different resting elevations across platforms, depending on the depth
- of the environment (e.g., TV has a greater depth than mobile or desktop).
-
-
-
- Responsive elevation and dynamic elevation offsets.
-
-
-
- Some component types have responsive elevation, meaning they change elevation in response
- to user input (e.g., normal, focused, and pressed) or system events. These elevation
- changes are consistently implemented using dynamic elevation offsets.
-
- Dynamic elevation offsets are the goal elevation that a component moves towards, relative
- to the component’s resting state. They ensure that elevation changes are consistent
- across actions and component types. For example, all components that lift on press have
- the same elevation change relative to their resting elevation.
-
- Once the input event is completed or cancelled, the component will return to its resting
- elevation.
-
-
-
- Avoiding elevation interference.
-
-
-
- Components with responsive elevations may encounter other components as they move between
- their resting elevations and dynamic elevation offsets. Because material cannot pass
- through other material, components avoid interfering with one another any number of ways,
- whether on a per component basis or using the entire app layout.
-
- On a component level, components can move or be removed before they cause interference.
- For example, a floating action button (FAB) can disappear or move off screen before a
- user picks up a card, or it can move if a snackbar appears.
-
- On the layout level, design your app layout to minimize opportunities for interference.
- For example, position the FAB to one side of stream of a cards so the FAB won’t interfere
- when a user tries to pick up one of cards.
-
-
- "
Стопка
reCAPTCHA стопка
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index a0e17bb4a..83fd8a182 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -143,7 +143,6 @@
Lütfen bir kullanılabilir indirme dizini seçin.
-
Ayarlar
İndirme menüsü kurulamıyor.
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 70ce224d2..56429e262 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -23,8 +23,6 @@
视频下载路径
存放已下载视频的路径。
输入视频的下载路径
- 刻意自动播放
- 当另一个程式发出要求时自动播放视频。
默认分辨率
用 Kodi 播放
找不到 Kore 应用程式,您要安装 Kore 吗?
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 225a7014c..2ff8465b2 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -18,8 +18,6 @@
影片下載路徑
存放已下載影片的路徑。
輸入影片的下載路徑
- 刻意自動播放
- 當另一個程式發出要求時自動播放影片。
預設解析度
用 Kodi 播放
找不到 Kore 應用程式,您要安裝 Kore 嗎?
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 12049e0a2..c7cb3a9c4 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -22,8 +22,6 @@
影片下載路徑
存放已下載影片的路徑。
輸入影片的下載路徑
- 刻意自動播放
- 當另一個程式發出要求時自動播放影片。
預設解析度
用 Kodi 播放
顯示以 Kodi 媒體中心播放影片的選項。
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index bb2823cb9..99dd0f390 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -11,7 +11,7 @@
use_external_video_player
use_external_audio_player
autoplay_through_intent
- use_exoplayer
+ use_oldplayer
default_resolution_preference
360p
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 263c6bc14..9e992ec18 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -25,6 +25,7 @@
Settings
Use external video player
Use external audio player
+ NewPipe Popup mode
Video download path
Path to store downloaded videos in.
@@ -75,6 +76,7 @@
Other
%1$s - NewPipe
Playing in background
+ Playing in popup mode
https://www.c3s.cc/
Play
Content
@@ -150,8 +152,8 @@
Unable to query device decoders
Unable to instantiate decoder %1$s
Permission to access storage was denied
- Use ExoPlayer
- Experimental
+ Use old player
+ Old build in Mediaframework player.
videos
subscriber
Subscribe
@@ -195,95 +197,6 @@
MD5
SHA1
ChannelActivity
-
- "Material is the metaphor.\n\n"
-
- "A material metaphor is the unifying theory of a rationalized space and a system of motion."
- "The material is grounded in tactile reality, inspired by the study of paper and ink, yet "
- "technologically advanced and open to imagination and magic.\n"
- "Surfaces and edges of the material provide visual cues that are grounded in reality. The "
- "use of familiar tactile attributes helps users quickly understand affordances. Yet the "
- "flexibility of the material creates new affordances that supercede those in the physical "
- "world, without breaking the rules of physics.\n"
- "The fundamentals of light, surface, and movement are key to conveying how objects move, "
- "interact, and exist in space and in relation to each other. Realistic lighting shows "
- "seams, divides space, and indicates moving parts.\n\n"
-
- "Bold, graphic, intentional.\n\n"
-
- "The foundational elements of print based design typography, grids, space, scale, color, "
- "and use of imagery guide visual treatments. These elements do far more than please the "
- "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
- "imagery, large scale typography, and intentional white space create a bold and graphic "
- "interface that immerse the user in the experience.\n"
- "An emphasis on user actions makes core functionality immediately apparent and provides "
- "waypoints for the user.\n\n"
-
- "Motion provides meaning.\n\n"
-
- "Motion respects and reinforces the user as the prime mover. Primary user actions are "
- "inflection points that initiate motion, transforming the whole design.\n"
- "All action takes place in a single environment. Objects are presented to the user without "
- "breaking the continuity of experience even as they transform and reorganize.\n"
- "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
- "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
-
- "3D world.\n\n"
-
- "The material environment is a 3D space, which means all objects have x, y, and z "
- "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the "
- "positive z-axis extending towards the viewer. Every sheet of material occupies a single "
- "position along the z-axis and has a standard 1dp thickness.\n"
- "On the web, the z-axis is used for layering and not for perspective. The 3D world is "
- "emulated by manipulating the y-axis.\n\n"
-
- "Light and shadow.\n\n"
-
- "Within the material environment, virtual lights illuminate the scene. Key lights create "
- "directional shadows, while ambient light creates soft shadows from all angles.\n"
- "Shadows in the material environment are cast by these two light sources. In Android "
- "development, shadows occur when light sources are blocked by sheets of material at "
- "various positions along the z-axis. On the web, shadows are depicted by manipulating the "
- "y-axis only. The following example shows the card with a height of 6dp.\n\n"
-
- "Resting elevation.\n\n"
-
- "All material objects, regardless of size, have a resting elevation, or default elevation "
- "that does not change. If an object changes elevation, it should return to its resting "
- "elevation as soon as possible.\n\n"
-
- "Component elevations.\n\n"
-
- "The resting elevation for a component type is consistent across apps (e.g., FAB elevation "
- "does not vary from 6dp in one app to 16dp in another app).\n"
- "Components may have different resting elevations across platforms, depending on the depth "
- "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n"
-
- "Responsive elevation and dynamic elevation offsets.\n\n"
-
- "Some component types have responsive elevation, meaning they change elevation in response "
- "to user input (e.g., normal, focused, and pressed) or system events. These elevation "
- "changes are consistently implemented using dynamic elevation offsets.\n"
- "Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
- "to the component’s resting state. They ensure that elevation changes are consistent "
- "across actions and component types. For example, all components that lift on press have "
- "the same elevation change relative to their resting elevation.\n"
- "Once the input event is completed or cancelled, the component will return to its resting "
- "elevation.\n\n"
-
- "Avoiding elevation interference.\n\n"
-
- "Components with responsive elevations may encounter other components as they move between "
- "their resting elevations and dynamic elevation offsets. Because material cannot pass "
- "through other material, components avoid interfering with one another any number of ways, "
- "whether on a per component basis or using the entire app layout.\n"
- "On a component level, components can move or be removed before they cause interference. "
- "For example, a floating action button (FAB) can disappear or move off screen before a "
- "user picks up a card, or it can move if a snackbar appears.\n"
- "On the layout level, design your app layout to minimize opportunities for interference. "
- "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere "
- "when a user tries to pick up one of cards.\n\n"
-
Settings
reCAPTCHA
reCAPTCHA Challenge
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 606da4eb0..06d5eb0e9 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -2,18 +2,11 @@
-
-
-
-