diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 201a65180c..752a2242f8 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -139,6 +139,9 @@ dependencies { implementation "com.android.support:customtabs:${supportVersion}" implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.google.android.exoplayer:exoplayer-core:2.9.0' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:3.10.0' //noinspection GradleDependency implementation 'com.j256.ormlite:ormlite-core:4.48' diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index ff794195ce..7ccdd2508b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -100,6 +100,7 @@ public String getKey() { public static final OptionsSetting imageAutoLoadNetwork; public static final OptionsSetting videoAutoLoadNetwork; public static final BooleanSetting videoOpenExternal; + public static final BooleanSetting videoUseExoplayer; public static final BooleanSetting textOnly; public static final BooleanSetting videoErrorIgnore; public static final OptionsSetting boardViewMode; @@ -174,6 +175,7 @@ public String getKey() { imageAutoLoadNetwork = new OptionsSetting<>(p, "preference_image_auto_load_network", MediaAutoLoadMode.class, MediaAutoLoadMode.WIFI); videoAutoLoadNetwork = new OptionsSetting<>(p, "preference_video_auto_load_network", MediaAutoLoadMode.class, MediaAutoLoadMode.WIFI); videoOpenExternal = new BooleanSetting(p, "preference_video_external", false); + videoUseExoplayer = new BooleanSetting(p, "preference_video_exoplayer", true); textOnly = new BooleanSetting(p, "preference_text_only", false); videoErrorIgnore = new BooleanSetting(p, "preference_video_error_ignore", false); boardViewMode = new OptionsSetting<>(p, "preference_board_view_mode", PostViewMode.class, PostViewMode.LIST); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java index ec6253cca0..11834b41e6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java @@ -109,6 +109,10 @@ private void populatePreferences() { R.string.setting_video_open_external, R.string.setting_video_open_external_description)); + media.add(new BooleanSettingView(this, ChanSettings.videoUseExoplayer, + R.string.setting_video_exoplayer, + R.string.setting_video_exoplayer_description)); + media.add(new BooleanSettingView(this, ChanSettings.shareUrl, R.string.setting_share_url, R.string.setting_share_url_description)); 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 8b5a63f602..80d9c195de 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 @@ -36,12 +36,23 @@ import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.ImageLoader.ImageContainer; import com.davemorrissey.labs.subscaleview.ImageSource; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.util.Util; import org.floens.chan.R; import org.floens.chan.core.cache.FileCache; import org.floens.chan.core.cache.FileCacheDownloader; import org.floens.chan.core.cache.FileCacheListener; import org.floens.chan.core.cache.FileCacheProvider; +import org.floens.chan.core.di.UserAgentProvider; import org.floens.chan.core.model.PostImage; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.utils.AndroidUtils; @@ -70,11 +81,13 @@ public enum Mode { @Inject ImageLoader imageLoader; + @Inject + UserAgentProvider userAgent; + private ImageView playView; private PostImage postImage; private Callback callback; - private Mode mode = Mode.UNLOADED; private boolean hasContent = false; @@ -84,8 +97,10 @@ public enum Mode { private FileCacheDownloader videoRequest; private VideoView videoView; + private PlayerView exoVideoView; private boolean videoError = false; private MediaPlayer mediaPlayer; + private SimpleExoPlayer exoPlayer; public MultiImageView(Context context) { this(context, null); @@ -166,9 +181,15 @@ public CustomScaleImageView findScaleImageView() { } public void setVolume(boolean muted) { - if (mediaPlayer != null) { - final float volume = muted ? 0f : 1f; - mediaPlayer.setVolume(volume, volume); + final float volume = muted ? 0f : 1f; + if (ChanSettings.videoUseExoplayer.get()) { + if (exoPlayer != null) { + exoPlayer.getAudioComponent().setVolume(volume); + } + } else { + if (mediaPlayer != null) { + mediaPlayer.setVolume(volume, volume); + } } } @@ -391,6 +412,21 @@ private void setVideoFile(final File file) { AndroidUtils.openIntent(intent); onModeLoaded(Mode.MOVIE, videoView); + } else if (ChanSettings.videoUseExoplayer.get()) { + exoVideoView = new PlayerView(getContext()); + exoPlayer = ExoPlayerFactory.newSimpleInstance(getContext()); + exoVideoView.setPlayer(exoPlayer); + DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(), + Util.getUserAgent(getContext(), userAgent.getUserAgent())); + MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory) + .createMediaSource(android.net.Uri.fromFile(file)); + + exoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL); //Repeat forever + + exoPlayer.prepare(videoSource); + callback.onVideoLoaded(this, hasMediaPlayerAudioTracks(exoPlayer)); + addView(exoVideoView); + exoPlayer.setPlayWhenReady(true); } else { Context proxyContext = new NoMusicServiceCommandContext(getContext()); @@ -451,6 +487,10 @@ private boolean hasMediaPlayerAudioTracks(MediaPlayer mediaPlayer) { } } + private boolean hasMediaPlayerAudioTracks(ExoPlayer mediaPlayer) { + return mediaPlayer.getAudioComponent() != null; + } + private void onVideoError() { if (!videoError) { videoError = true; @@ -463,6 +503,10 @@ private void cleanupVideo(VideoView videoView) { mediaPlayer = null; } + private void cleanupVideo(PlayerView videoView) { + videoView.getPlayer().release(); + } + private void setBitImageFileInternal(File file, boolean tiling, final Mode forMode) { final CustomScaleImageView image = new CustomScaleImageView(getContext()); image.setImage(ImageSource.uri(file.getAbsolutePath()).tiling(tiling)); @@ -535,6 +579,8 @@ private void onModeLoaded(Mode mode, View view) { if (child != view) { if (child instanceof VideoView) { cleanupVideo((VideoView) child); + } else if (child instanceof PlayerView) { + cleanupVideo((PlayerView) child); } removeViewAt(i); diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 7ad5bfd81c..1b2e9716aa 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -508,6 +508,8 @@ If disabled, save the image with the filename the uploader assigned." If a video has audio, mute it by default. Play videos with external player Play videos in an external media player app + Enable new ExoPlayer + Use the new ExoPlayer for internal media player Share URL to image Share the URL to the image instead of the image itself Reveal image spoilers