diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java index e3907227..353e7348 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java @@ -54,6 +54,8 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager. private boolean viewPagerVisible = false; private boolean changeViewsOnInTransitionEnd = false; + private boolean muted = true; + public ImageViewerPresenter(Callback callback) { this.callback = callback; inject(this); @@ -106,6 +108,12 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager. callback.showProgress(false); } + public void onVolumeClicked() { + muted = !muted; + callback.showVolumeMenuItem(true, muted); + callback.setVolume(getCurrentPostImage(), muted); + } + public List getAllPostImages() { return images; } @@ -188,6 +196,9 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager. callback.showProgress(progress.get(selectedPosition) >= 0f); callback.onLoadProgress(progress.get(selectedPosition)); + + // If it has audio, we'll know after it is loaded. + callback.showVolumeMenuItem(false, true); } // Called from either a page swipe caused a lowres image to the center or an @@ -307,6 +318,17 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager. callback.onVideoError(multiImageView); } + @Override + public void onVideoLoaded(MultiImageView multiImageView, boolean hasAudio) { + PostImage currentPostImage = getCurrentPostImage(); + if (multiImageView.getPostImage() == currentPostImage) { + if (hasAudio) { + callback.showVolumeMenuItem(true, muted); + callback.setVolume(currentPostImage, muted); + } + } + } + private boolean imageAutoLoad(PostImage postImage) { // Auto load the image when it is cached return fileCache.exists(postImage.imageUrl.toString()) || shouldLoadForNetworkType(ChanSettings.imageAutoLoadNetwork.get()); @@ -361,6 +383,8 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager. void setImageMode(PostImage postImage, MultiImageView.Mode mode); + void setVolume(PostImage postImage, boolean muted); + void setTitle(PostImage postImage, int index, int count, boolean spoiler); void scrollToImage(PostImage postImage); @@ -372,5 +396,7 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager. void onLoadProgress(float progress); void onVideoError(MultiImageView multiImageView); + + void showVolumeMenuItem(boolean show, boolean muted); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java index c567ce42..28143e22 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java @@ -91,6 +91,12 @@ public class ImageViewerAdapter extends ViewPagerAdapter { } } + public void setVolume(PostImage postImage, boolean muted) { + // It must be loaded, or the user is not able to click the menu item. + MultiImageView view = find(postImage); + view.setVolume(muted); + } + public MultiImageView.Mode getMode(PostImage postImage) { MultiImageView view = find(postImage); if (view == null) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java index 68684ce7..a3a74068 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java @@ -39,6 +39,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.widget.CheckBox; +import android.widget.ImageView; import android.widget.Toast; import com.android.volley.VolleyError; @@ -85,6 +86,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres private static final float TRANSITION_FINAL_ALPHA = 0.85f; private static final int GO_POST_ID = 1; + private static final int VOLUME_ID = 3; private static final int SAVE_ID = 2; private static final int OPEN_BROWSER_ID = 103; private static final int SHARE_ID = 104; @@ -108,6 +110,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres private LoadingBar loadingBar; private ToolbarMenuItem overflowMenuItem; + private ToolbarMenuItem volumeMenuItem; public ImageViewerController(Context context, Toolbar toolbar) { super(context); @@ -129,6 +132,9 @@ public class ImageViewerController extends Controller implements ImageViewerPres if (goPostCallback != null) { navigation.menu.addItem(new ToolbarMenuItem(context, this, GO_POST_ID, R.drawable.ic_subdirectory_arrow_left_white_24dp)); } + + volumeMenuItem = navigation.menu.addItem(new ToolbarMenuItem(context, + this, VOLUME_ID, R.drawable.ic_volume_off_white_24dp)); navigation.menu.addItem(new ToolbarMenuItem(context, this, SAVE_ID, R.drawable.ic_file_download_white_24dp)); List items = new ArrayList<>(); @@ -144,17 +150,16 @@ public class ImageViewerController extends Controller implements ImageViewerPres pager.addOnPageChangeListener(presenter); loadingBar = view.findViewById(R.id.loading_bar); + showVolumeMenuItem(false, true); + // Sanity check if (parentController.view.getWindowToken() == null) { throw new IllegalArgumentException("parentController.view not attached"); } - AndroidUtils.waitForLayout(parentController.view.getViewTreeObserver(), view, new AndroidUtils.OnMeasuredCallback() { - @Override - public boolean onMeasured(View view) { - presenter.onViewMeasured(); - return true; - } + AndroidUtils.waitForLayout(parentController.view.getViewTreeObserver(), view, view -> { + presenter.onViewMeasured(); + return true; }); } @@ -189,6 +194,9 @@ public class ImageViewerController extends Controller implements ImageViewerPres case SAVE_ID: saveShare(false, presenter.getCurrentPostImage()); break; + case VOLUME_ID: + presenter.onVolumeClicked(); + break; } } @@ -285,6 +293,11 @@ public class ImageViewerController extends Controller implements ImageViewerPres ((ImageViewerAdapter) pager.getAdapter()).setMode(postImage, mode); } + @Override + public void setVolume(PostImage postImage, boolean muted) { + ((ImageViewerAdapter) pager.getAdapter()).setVolume(postImage, muted); + } + public MultiImageView.Mode getImageMode(PostImage postImage) { return ((ImageViewerAdapter) pager.getAdapter()).getMode(postImage); } @@ -335,6 +348,14 @@ public class ImageViewerController extends Controller implements ImageViewerPres } } + @Override + public void showVolumeMenuItem(boolean show, boolean muted) { + ImageView view = volumeMenuItem.getView(); + view.setVisibility(show ? View.VISIBLE : View.GONE); + view.setImageResource(muted ? + R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp); + } + public void startPreviewInTransition(PostImage postImage) { ThumbnailView startImageView = getTransitionImageView(postImage); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java index 00222763..c48d418f 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java @@ -20,8 +20,10 @@ package org.floens.chan.ui.view; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; +import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; +import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -81,23 +83,19 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener private VideoView videoView; private boolean videoError = false; + private MediaPlayer mediaPlayer; public MultiImageView(Context context) { - super(context); - init(); + this(context, null); } public MultiImageView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + this(context, attrs, 0); } - public MultiImageView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } + public MultiImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); - private void init() { inject(this); setOnClickListener(this); @@ -165,6 +163,13 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener return bigImage; } + public void setVolume(boolean muted) { + if (mediaPlayer != null) { + final float volume = muted ? 0f : 1f; + mediaPlayer.setVolume(volume, volume); + } + } + @Override public void onClick(View v) { callback.onTap(this); @@ -374,23 +379,24 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener videoView.setZOrderOnTop(true); videoView.setMediaController(new MediaController(getContext())); - addView(videoView, 0, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + videoView.setAudioFocusRequest(AudioManager.AUDIOFOCUS_NONE); + } - videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - mp.setLooping(true); - onModeLoaded(Mode.MOVIE, videoView); - } + addView(videoView, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER)); + + videoView.setOnPreparedListener(mp -> { + mediaPlayer = mp; + mp.setLooping(true); + mp.setVolume(0f, 0f); + onModeLoaded(Mode.MOVIE, videoView); + callback.onVideoLoaded(this, hasMediaPlayerAudioTracks(mp)); }); - videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - onVideoError(); + videoView.setOnErrorListener((mp, what, extra) -> { + onVideoError(); - return true; - } + return true; }); videoView.setVideoPath(file.getAbsolutePath()); @@ -404,6 +410,21 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener } } + private boolean hasMediaPlayerAudioTracks(MediaPlayer mediaPlayer) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + for (MediaPlayer.TrackInfo trackInfo : mediaPlayer.getTrackInfo()) { + if (trackInfo.getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) { + return true; + } + } + + return false; + } else { + // It'll just show the icon without doing anything. Remove when 4.0 is dropped. + return true; + } + } + private void onVideoError() { if (!videoError) { videoError = true; @@ -411,6 +432,11 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener } } + private void cleanupVideo(VideoView videoView) { + videoView.stopPlayback(); + mediaPlayer = null; + } + private void setBitImageFileInternal(File file, boolean tiling, final Mode forMode) { final CustomScaleImageView image = new CustomScaleImageView(getContext()); image.setImage(ImageSource.uri(file.getAbsolutePath()).tiling(tiling)); @@ -482,8 +508,7 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener if (child != playView) { if (child != view) { if (child instanceof VideoView) { - VideoView item = (VideoView) child; - item.stopPlayback(); + cleanupVideo((VideoView) child); } removeViewAt(i); @@ -511,6 +536,8 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener void onVideoError(MultiImageView multiImageView); + void onVideoLoaded(MultiImageView multiImageView, boolean hasAudio); + void onModeLoaded(MultiImageView multiImageView, Mode mode); } diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_volume_off_white_24dp.png b/Clover/app/src/main/res/drawable-hdpi/ic_volume_off_white_24dp.png new file mode 100644 index 00000000..ce0c2142 Binary files /dev/null and b/Clover/app/src/main/res/drawable-hdpi/ic_volume_off_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_volume_up_white_24dp.png b/Clover/app/src/main/res/drawable-hdpi/ic_volume_up_white_24dp.png new file mode 100644 index 00000000..57d78716 Binary files /dev/null and b/Clover/app/src/main/res/drawable-hdpi/ic_volume_up_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-mdpi/ic_volume_off_white_24dp.png b/Clover/app/src/main/res/drawable-mdpi/ic_volume_off_white_24dp.png new file mode 100644 index 00000000..4681ec14 Binary files /dev/null and b/Clover/app/src/main/res/drawable-mdpi/ic_volume_off_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-mdpi/ic_volume_up_white_24dp.png b/Clover/app/src/main/res/drawable-mdpi/ic_volume_up_white_24dp.png new file mode 100644 index 00000000..7cfd4c7b Binary files /dev/null and b/Clover/app/src/main/res/drawable-mdpi/ic_volume_up_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xhdpi/ic_volume_off_white_24dp.png b/Clover/app/src/main/res/drawable-xhdpi/ic_volume_off_white_24dp.png new file mode 100644 index 00000000..732a1c0f Binary files /dev/null and b/Clover/app/src/main/res/drawable-xhdpi/ic_volume_off_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xhdpi/ic_volume_up_white_24dp.png b/Clover/app/src/main/res/drawable-xhdpi/ic_volume_up_white_24dp.png new file mode 100644 index 00000000..2ed00343 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xhdpi/ic_volume_up_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_volume_off_white_24dp.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_volume_off_white_24dp.png new file mode 100644 index 00000000..474aae51 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/ic_volume_off_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_volume_up_white_24dp.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_volume_up_white_24dp.png new file mode 100644 index 00000000..2e751a40 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/ic_volume_up_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxxhdpi/ic_volume_off_white_24dp.png b/Clover/app/src/main/res/drawable-xxxhdpi/ic_volume_off_white_24dp.png new file mode 100644 index 00000000..df06b06b Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxxhdpi/ic_volume_off_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxxhdpi/ic_volume_up_white_24dp.png b/Clover/app/src/main/res/drawable-xxxhdpi/ic_volume_up_white_24dp.png new file mode 100644 index 00000000..82972b4e Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxxhdpi/ic_volume_up_white_24dp.png differ