Merge pull request #2506 from ByteHamster/picture-in-picture
Added Picure in picture
This commit is contained in:
commit
a9e269b3bf
|
@ -222,7 +222,8 @@
|
|||
|
||||
<activity
|
||||
android:name=".activity.VideoplayerActivity"
|
||||
android:configChanges="keyboardHidden|orientation"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:screenOrientation="sensorLandscape">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
|
|
|
@ -46,6 +46,7 @@ import de.danoeh.antennapod.core.util.Flavors;
|
|||
import de.danoeh.antennapod.core.util.ShareUtils;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.core.util.Supplier;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
|
@ -225,10 +226,12 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
if (controller != null) {
|
||||
controller.reinitServiceIfPaused();
|
||||
controller.pause();
|
||||
}
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
@ -379,6 +382,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
|
|||
} else {
|
||||
startActivity(intent);
|
||||
}
|
||||
finish();
|
||||
return true;
|
||||
} else {
|
||||
if (media != null) {
|
||||
|
|
|
@ -10,27 +10,31 @@ import android.support.v4.view.WindowCompat;
|
|||
import android.support.v7.app.ActionBar;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
import de.danoeh.antennapod.view.AspectRatioVideoView;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Activity for playing video files.
|
||||
*/
|
||||
|
@ -52,6 +56,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
private LinearLayout videoOverlay;
|
||||
private AspectRatioVideoView videoview;
|
||||
private ProgressBar progressIndicator;
|
||||
private FrameLayout videoframe;
|
||||
|
||||
@Override
|
||||
protected void chooseTheme() {
|
||||
|
@ -96,11 +101,28 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
videoControlsHider.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserLeaveHint () {
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this) && UserPreferences.getVideoBackgroundBehavior()
|
||||
== UserPreferences.VideoBackgroundBehavior.PICTURE_IN_PICTURE) {
|
||||
compatEnterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
|
||||
controller.pause();
|
||||
}
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
@ -135,20 +157,23 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
controls = (LinearLayout) findViewById(R.id.controls);
|
||||
videoOverlay = (LinearLayout) findViewById(R.id.overlay);
|
||||
videoview = (AspectRatioVideoView) findViewById(R.id.videoview);
|
||||
videoframe = (FrameLayout) findViewById(R.id.videoframe);
|
||||
progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);
|
||||
videoview.getHolder().addCallback(surfaceHolderCallback);
|
||||
videoview.setOnTouchListener(onVideoviewTouched);
|
||||
videoframe.setOnTouchListener(onVideoviewTouched);
|
||||
videoOverlay.setOnTouchListener((view, motionEvent) -> true); // To suppress touches directly below the slider
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
videoOverlay.setFitsSystemWindows(true);
|
||||
}
|
||||
|
||||
setupVideoControlsToggler();
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
videoframe.getViewTreeObserver().addOnGlobalLayoutListener(() ->
|
||||
videoview.setAvailableSize(videoframe.getWidth(), videoframe.getHeight()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -176,6 +201,9 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
|
||||
private final View.OnTouchListener onVideoviewTouched = (v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
return true;
|
||||
}
|
||||
videoControlsHider.stop();
|
||||
toggleVideoControlsVisibility();
|
||||
if (videoControlsShowing) {
|
||||
|
@ -260,7 +288,9 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.d(TAG, "Videosurface was destroyed");
|
||||
videoSurfaceCreated = false;
|
||||
if (controller != null && !destroyingDueToReload) {
|
||||
if (controller != null && !destroyingDueToReload
|
||||
&& UserPreferences.getVideoBackgroundBehavior()
|
||||
!= UserPreferences.VideoBackgroundBehavior.CONTINUE_PLAYING) {
|
||||
controller.notifyVideoSurfaceAbandoned();
|
||||
}
|
||||
}
|
||||
|
@ -269,6 +299,13 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
|
||||
@Override
|
||||
protected void onReloadNotification(int notificationCode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && PictureInPictureUtil.isInPictureInPictureMode(this)) {
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO
|
||||
|| notificationCode == PlaybackService.EXTRA_CODE_CAST) {
|
||||
finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
|
||||
Log.d(TAG, "ReloadNotification received, switching to Audioplayer now");
|
||||
destroyingDueToReload = true;
|
||||
|
@ -313,28 +350,31 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
videoOverlay.startAnimation(animation);
|
||||
controls.startAnimation(animation);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private void hideVideoControls() {
|
||||
private void hideVideoControls(boolean showAnimation) {
|
||||
if (showAnimation) {
|
||||
final Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out);
|
||||
if (animation != null) {
|
||||
videoOverlay.startAnimation(animation);
|
||||
controls.startAnimation(animation);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
}
|
||||
int videoviewFlag = (Build.VERSION.SDK_INT >= 16) ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0;
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | videoviewFlag);
|
||||
videoOverlay.setFitsSystemWindows(true);
|
||||
}
|
||||
|
||||
videoOverlay.setVisibility(View.GONE);
|
||||
controls.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void hideVideoControls() {
|
||||
hideVideoControls(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return R.layout.videoplayer_activity;
|
||||
|
@ -350,6 +390,32 @@ public class VideoplayerActivity extends MediaplayerActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
if (PictureInPictureUtil.supportsPictureInPicture(this)) {
|
||||
menu.findItem(R.id.player_go_to_picture_in_picture).setVisible(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.player_go_to_picture_in_picture) {
|
||||
compatEnterPictureInPicture();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void compatEnterPictureInPicture() {
|
||||
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls(false);
|
||||
enterPictureInPictureMode();
|
||||
}
|
||||
}
|
||||
|
||||
private static class VideoControlsHider extends Handler {
|
||||
|
||||
private static final int DELAY = 2500;
|
||||
|
|
|
@ -3,6 +3,7 @@ package de.danoeh.antennapod.config;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import android.os.Build;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.AudioplayerActivity;
|
||||
import de.danoeh.antennapod.activity.CastplayerActivity;
|
||||
|
@ -18,7 +19,11 @@ public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks {
|
|||
return new Intent(context, CastplayerActivity.class);
|
||||
}
|
||||
if (mediaType == MediaType.VIDEO) {
|
||||
return new Intent(context, VideoplayerActivity.class);
|
||||
Intent i = new Intent(context, VideoplayerActivity.class);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
}
|
||||
return i;
|
||||
} else {
|
||||
return new Intent(context, AudioplayerActivity.class);
|
||||
}
|
||||
|
|
|
@ -38,8 +38,10 @@ import android.widget.Toast;
|
|||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import com.afollestad.materialdialogs.prefs.MaterialListPreference;
|
||||
import de.danoeh.antennapod.activity.ImportExportActivity;
|
||||
import de.danoeh.antennapod.activity.OpmlImportFromPathActivity;
|
||||
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -426,6 +428,11 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
|
|||
return false;
|
||||
}
|
||||
);
|
||||
if (!PictureInPictureUtil.supportsPictureInPicture(activity)) {
|
||||
MaterialListPreference behaviour = (MaterialListPreference) ui.findPreference(UserPreferences.PREF_VIDEO_BEHAVIOR);
|
||||
behaviour.setEntries(R.array.video_background_behavior_options_without_pip);
|
||||
behaviour.setEntryValues(R.array.video_background_behavior_values_without_pip);
|
||||
}
|
||||
ui.findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> {
|
||||
ProxyDialog dialog = new ProxyDialog(ui.getActivity());
|
||||
dialog.createDialog().show();
|
||||
|
|
|
@ -25,6 +25,8 @@ public class AspectRatioVideoView extends VideoView {
|
|||
|
||||
private int mVideoWidth;
|
||||
private int mVideoHeight;
|
||||
private float mAvailableWidth = -1;
|
||||
private float mAvailableHeight = -1;
|
||||
|
||||
public AspectRatioVideoView(Context context) {
|
||||
this(context, null);
|
||||
|
@ -48,8 +50,13 @@ public class AspectRatioVideoView extends VideoView {
|
|||
return;
|
||||
}
|
||||
|
||||
float heightRatio = (float) mVideoHeight / (float) getHeight();
|
||||
float widthRatio = (float) mVideoWidth / (float) getWidth();
|
||||
if (mAvailableWidth < 0 || mAvailableHeight < 0) {
|
||||
mAvailableWidth = getWidth();
|
||||
mAvailableHeight = getHeight();
|
||||
}
|
||||
|
||||
float heightRatio = (float) mVideoHeight / mAvailableHeight;
|
||||
float widthRatio = (float) mVideoWidth / mAvailableWidth;
|
||||
|
||||
int scaledHeight;
|
||||
int scaledWidth;
|
||||
|
@ -94,4 +101,15 @@ public class AspectRatioVideoView extends VideoView {
|
|||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum size that the view might expand to
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public void setAvailableSize(float width, float height) {
|
||||
mAvailableWidth = width;
|
||||
mAvailableHeight = height;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/videoframe">
|
||||
|
||||
<de.danoeh.antennapod.view.AspectRatioVideoView
|
||||
android:id="@+id/videoview"
|
||||
|
@ -103,6 +104,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@+id/txtvLength"
|
||||
android:layout_toRightOf="@+id/txtvPosition"
|
||||
android:layout_centerInParent="true"
|
||||
android:max="500" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -41,6 +41,14 @@
|
|||
android:title="@string/visit_website_label"
|
||||
android:visible="false">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/player_go_to_picture_in_picture"
|
||||
custom:showAsAction="collapseActionView"
|
||||
android:title="@string/player_go_to_picture_in_picture"
|
||||
android:visible="false">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/share_item"
|
||||
android:icon="?attr/social_share"
|
||||
|
|
|
@ -171,6 +171,14 @@
|
|||
android:key="prefResumeAfterCall"
|
||||
android:summary="@string/pref_resumeAfterCall_sum"
|
||||
android:title="@string/pref_resumeAfterCall_title"/>
|
||||
<com.afollestad.materialdialogs.prefs.MaterialListPreference
|
||||
android:defaultValue="stop"
|
||||
android:entries="@array/video_background_behavior_options"
|
||||
android:entryValues="@array/video_background_behavior_values"
|
||||
android:key="prefVideoBehavior"
|
||||
android:summary="@string/pref_videoBehavior_sum"
|
||||
android:title="@string/pref_videoBehavior_title"
|
||||
app:useStockLayout="true"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/network_pref">
|
||||
|
|
|
@ -74,6 +74,7 @@ public class UserPreferences {
|
|||
private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
|
||||
private static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
|
||||
private static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall";
|
||||
public static final String PREF_VIDEO_BEHAVIOR = "prefVideoBehavior";
|
||||
|
||||
// Network
|
||||
private static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded";
|
||||
|
@ -661,6 +662,14 @@ public class UserPreferences {
|
|||
.apply();
|
||||
}
|
||||
|
||||
public static VideoBackgroundBehavior getVideoBackgroundBehavior() {
|
||||
switch (prefs.getString(PREF_VIDEO_BEHAVIOR, "stop")) {
|
||||
case "stop": return VideoBackgroundBehavior.STOP;
|
||||
case "pip": return VideoBackgroundBehavior.PICTURE_IN_PICTURE;
|
||||
case "continue": return VideoBackgroundBehavior.CONTINUE_PLAYING;
|
||||
default: return VideoBackgroundBehavior.STOP;
|
||||
}
|
||||
}
|
||||
|
||||
public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() {
|
||||
int cleanupValue = Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
|
||||
|
@ -839,4 +848,8 @@ public class UserPreferences {
|
|||
public static boolean isCastEnabled() {
|
||||
return prefs.getBoolean(PREF_CAST_ENABLED, false);
|
||||
}
|
||||
|
||||
public enum VideoBackgroundBehavior {
|
||||
STOP, PICTURE_IN_PICTURE, CONTINUE_PLAYING
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package de.danoeh.antennapod.core.util.gui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
public class PictureInPictureUtil {
|
||||
private PictureInPictureUtil() {
|
||||
}
|
||||
|
||||
public static boolean supportsPictureInPicture(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
PackageManager packageManager = activity.getPackageManager();
|
||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInPictureInPictureMode(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && supportsPictureInPicture(activity)) {
|
||||
return activity.isInPictureInPictureMode();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -224,4 +224,26 @@
|
|||
<item>@string/fast_forward_label</item>
|
||||
<item>@string/skip_episode_label</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_background_behavior_options">
|
||||
<item>@string/stop_playback</item>
|
||||
<item>@string/player_go_to_picture_in_picture</item>
|
||||
<item>@string/continue_playback</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_background_behavior_values">
|
||||
<item>stop</item>
|
||||
<item>pip</item>
|
||||
<item>continue</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_background_behavior_options_without_pip">
|
||||
<item>@string/stop_playback</item>
|
||||
<item>@string/continue_playback</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_background_behavior_values_without_pip">
|
||||
<item>stop</item>
|
||||
<item>continue</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
|
|
@ -237,6 +237,7 @@
|
|||
<string name="no_media_playing_label">No media playing</string>
|
||||
<string name="position_default_label" translate="false">00:00:00</string>
|
||||
<string name="player_buffering_msg">Buffering</string>
|
||||
<string name="player_go_to_picture_in_picture">Picture-in-picture mode</string>
|
||||
<string name="playbackservice_notification_title">Playing podcast</string>
|
||||
<string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string>
|
||||
|
||||
|
@ -440,6 +441,10 @@
|
|||
<string name="pref_cast_message_free_flavor">Chromecast requires third party proprietary libraries that are disabled in this version of AntennaPod</string>
|
||||
<string name="pref_enqueue_downloaded_title">Enqueue Downloaded</string>
|
||||
<string name="pref_enqueue_downloaded_summary">Add downloaded episodes to the queue</string>
|
||||
<string name="pref_videoBehavior_title">Video behavior</string>
|
||||
<string name="pref_videoBehavior_sum">Behavior when leaving video playback</string>
|
||||
<string name="stop_playback">Stop playback</string>
|
||||
<string name="continue_playback">Continue playback</string>
|
||||
|
||||
<!-- Auto-Flattr dialog -->
|
||||
<string name="auto_flattr_enable">Enable automatic flattring</string>
|
||||
|
|
Loading…
Reference in New Issue