diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 8081dcad7..d48994d0f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -35,7 +35,9 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.DisplayMetrics; import android.util.Log; +import android.util.TypedValue; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -48,6 +50,8 @@ import android.widget.TextView; import android.widget.Toast; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.SubtitleView; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -61,7 +65,6 @@ import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; -import org.schabi.newpipe.util.PopupMenuIconHacker; import org.schabi.newpipe.util.ThemeHelper; import java.util.List; @@ -194,7 +197,6 @@ public final class MainVideoPlayer extends Activity { super.onConfigurationChanged(newConfig); if (playerImpl.isSomePopupMenuVisible()) { - playerImpl.moreOptionsPopupMenu.dismiss(); playerImpl.getQualityPopupMenu().dismiss(); playerImpl.getPlaybackSpeedPopupMenu().dismiss(); } @@ -301,8 +303,11 @@ public final class MainVideoPlayer extends Activity { private boolean queueVisible; private ImageButton moreOptionsButton; - public int moreOptionsPopupMenuGroupId = 89; - public PopupMenu moreOptionsPopupMenu; + private ImageButton toggleOrientationButton; + private ImageButton switchPopupButton; + private ImageButton switchBackgroundButton; + + private View secondaryControls; VideoPlayerImpl(final Context context) { super("VideoPlayerImpl" + MainVideoPlayer.TAG, context); @@ -322,9 +327,12 @@ public final class MainVideoPlayer extends Activity { this.playPauseButton = rootView.findViewById(R.id.playPauseButton); this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton); this.playNextButton = rootView.findViewById(R.id.playNextButton); + this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton); - this.moreOptionsPopupMenu = new PopupMenu(context, moreOptionsButton); - buildMoreOptionsMenu(); + this.secondaryControls = rootView.findViewById(R.id.secondaryControls); + this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation); + this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground); + this.switchPopupButton = rootView.findViewById(R.id.switchPopup); titleTextView.setSelected(true); channelTextView.setSelected(true); @@ -332,6 +340,24 @@ public final class MainVideoPlayer extends Activity { getRootView().setKeepScreenOn(true); } + @Override + protected void setupSubtitleView(@NonNull SubtitleView view, + @NonNull String captionSizeKey) { + final float captionRatioInverse; + if (captionSizeKey.equals(getString(R.string.smaller_caption_size_key))) { + captionRatioInverse = 22f; + } else if (captionSizeKey.equals(getString(R.string.larger_caption_size_key))) { + captionRatioInverse = 18f; + } else { + captionRatioInverse = 20f; + } + + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); + view.setFixedTextSize(TypedValue.COMPLEX_UNIT_PX, + (float) minimumLength / captionRatioInverse); + } + @Override public void initListeners() { super.initListeners(); @@ -348,7 +374,11 @@ public final class MainVideoPlayer extends Activity { playPauseButton.setOnClickListener(this); playPreviousButton.setOnClickListener(this); playNextButton.setOnClickListener(this); + moreOptionsButton.setOnClickListener(this); + toggleOrientationButton.setOnClickListener(this); + switchBackgroundButton.setOnClickListener(this); + switchPopupButton.setOnClickListener(this); } /*////////////////////////////////////////////////////////////////////////// @@ -464,6 +494,16 @@ public final class MainVideoPlayer extends Activity { return; } else if (v.getId() == moreOptionsButton.getId()) { onMoreOptionsClicked(); + + } else if (v.getId() == toggleOrientationButton.getId()) { + onScreenRotationClicked(); + + } else if (v.getId() == switchPopupButton.getId()) { + onFullScreenButtonClicked(); + + } else if (v.getId() == switchBackgroundButton.getId()) { + onPlayBackgroundButtonClicked(); + } if (getCurrentState() != STATE_COMPLETED) { @@ -497,8 +537,15 @@ public final class MainVideoPlayer extends Activity { private void onMoreOptionsClicked() { if (DEBUG) Log.d(TAG, "onMoreOptionsClicked() called"); - moreOptionsPopupMenu.show(); - isSomePopupMenuVisible = true; + if (secondaryControls.getVisibility() == View.VISIBLE) { + moreOptionsButton.setImageDrawable(getResources().getDrawable( + R.drawable.ic_expand_more_white_24dp)); + animateView(secondaryControls, false, 200); + } else { + moreOptionsButton.setImageDrawable(getResources().getDrawable( + R.drawable.ic_expand_less_white_24dp)); + animateView(secondaryControls, true, 200); + } showControls(300); } @@ -522,6 +569,18 @@ public final class MainVideoPlayer extends Activity { if (isPlaying()) hideControls(300, 0); } + @Override + protected int nextResizeMode(int currentResizeMode) { + switch (currentResizeMode) { + case AspectRatioFrameLayout.RESIZE_MODE_FIT: + return AspectRatioFrameLayout.RESIZE_MODE_FILL; + case AspectRatioFrameLayout.RESIZE_MODE_FILL: + return AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + default: + return AspectRatioFrameLayout.RESIZE_MODE_FIT; + } + } + @Override protected int getDefaultResolutionIndex(final List sortedVideos) { return ListHelper.getDefaultResolutionIndex(context, sortedVideos); @@ -637,42 +696,6 @@ public final class MainVideoPlayer extends Activity { setShuffleButton(shuffleButton, playQueue.isShuffled()); } - private void buildMoreOptionsMenu() { - this.moreOptionsPopupMenu.getMenuInflater().inflate(R.menu.menu_videooptions, - moreOptionsPopupMenu.getMenu()); - - moreOptionsPopupMenu.setOnMenuItemClickListener(menuItem -> { - switch (menuItem.getItemId()) { - case R.id.toggleOrientation: - onScreenRotationClicked(); - break; - case R.id.switchPopup: - onFullScreenButtonClicked(); - break; - case R.id.switchBackground: - onPlayBackgroundButtonClicked(); - break; - } - return false; - }); - - try { - PopupMenuIconHacker.setShowPopupIcon(moreOptionsPopupMenu); - } catch (Exception e) { - e.printStackTrace(); - } - - // fix icon theme - if(ThemeHelper.isLightThemeSelected(MainVideoPlayer.this)) { - moreOptionsPopupMenu.getMenu() - .findItem(R.id.toggleOrientation) - .setIcon(R.drawable.ic_screen_rotation_black_24dp); - moreOptionsPopupMenu.getMenu() - .findItem(R.id.switchPopup) - .setIcon(R.drawable.ic_fullscreen_exit_black_24dp); - } - } - private void buildQueue() { queueLayout = findViewById(R.id.playQueuePanel); 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 c3803f0d5..f4e7a0d6a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -49,8 +49,11 @@ import android.widget.RemoteViews; import android.widget.SeekBar; import android.widget.TextView; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.SubtitleView; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; @@ -88,6 +91,8 @@ public final class PopupVideoPlayer extends Service { private static final String POPUP_SAVED_X = "popup_saved_x"; private static final String POPUP_SAVED_Y = "popup_saved_y"; + private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300; + private WindowManager windowManager; private WindowManager.LayoutParams windowLayoutParams; private GestureDetector gestureDetector; @@ -358,10 +363,12 @@ public final class PopupVideoPlayer extends Service { /////////////////////////////////////////////////////////////////////////// - protected class VideoPlayerImpl extends VideoPlayer { + protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener { private TextView resizingIndicator; private ImageButton fullScreenButton; + private View extraOptionsView; + @Override public void handleIntent(Intent intent) { super.handleIntent(intent); @@ -380,6 +387,29 @@ public final class PopupVideoPlayer extends Service { resizingIndicator = rootView.findViewById(R.id.resizing_indicator); fullScreenButton = rootView.findViewById(R.id.fullScreenButton); fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked()); + + extraOptionsView = rootView.findViewById(R.id.extraOptionsView); + rootView.addOnLayoutChangeListener(this); + } + + @Override + protected void setupSubtitleView(@NonNull SubtitleView view, + @NonNull String captionSizeKey) { + float captionRatio = SubtitleView.DEFAULT_TEXT_SIZE_FRACTION; + if (captionSizeKey.equals(getString(R.string.smaller_caption_size_key))) { + captionRatio *= 0.9; + } else if (captionSizeKey.equals(getString(R.string.larger_caption_size_key))) { + captionRatio *= 1.1; + } + view.setFractionalTextSize(captionRatio); + } + + @Override + public void onLayoutChange(final View view, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + float widthDp = Math.abs(right - left) / getResources().getDisplayMetrics().density; + final int visibility = widthDp > MINIMUM_SHOW_EXTRA_WIDTH_DP ? View.VISIBLE : View.GONE; + extraOptionsView.setVisibility(visibility); } @Override @@ -438,6 +468,15 @@ public final class PopupVideoPlayer extends Service { if (isPlaying()) hideControls(500, 0); } + @Override + protected int nextResizeMode(int resizeMode) { + if (resizeMode == AspectRatioFrameLayout.RESIZE_MODE_FILL) { + return AspectRatioFrameLayout.RESIZE_MODE_FIT; + } else { + return AspectRatioFrameLayout.RESIZE_MODE_FILL; + } + } + @Override public void onStopTrackingTouch(SeekBar seekBar) { super.onStopTrackingTouch(seekBar); @@ -642,8 +681,8 @@ public final class PopupVideoPlayer extends Service { //////////////////////////////////////////////////////////////////////////*/ /*package-private*/ void enableVideoRenderer(final boolean enable) { - final int videoRendererIndex = getVideoRendererIndex(); - if (trackSelector != null && videoRendererIndex != -1) { + final int videoRendererIndex = getRendererIndex(C.TRACK_TYPE_VIDEO); + if (trackSelector != null && videoRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setRendererDisabled(videoRendererIndex, !enable); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 5399ff047..a0bc7223f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -29,8 +29,10 @@ import android.content.Intent; 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.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; @@ -46,17 +48,25 @@ import android.widget.SeekBar; import android.widget.TextView; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; +import com.google.android.exoplayer2.source.SingleSampleMediaSource; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.SubtitleView; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.Subtitles; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ListHelper; @@ -64,6 +74,8 @@ import org.schabi.newpipe.util.ListHelper; import java.util.ArrayList; import java.util.List; +import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT; +import static com.google.android.exoplayer2.C.TIME_UNSET; import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -88,6 +100,7 @@ public abstract class VideoPlayer extends BasePlayer // Player //////////////////////////////////////////////////////////////////////////*/ + protected static final int RENDERER_UNAVAILABLE = -1; public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds private ArrayList availableStreams; @@ -123,6 +136,11 @@ public abstract class VideoPlayer extends BasePlayer private View topControlsRoot; private TextView qualityTextView; + private SubtitleView subtitleView; + + private TextView resizeView; + private TextView captionTextView; + private ValueAnimator controlViewAnimator; private Handler controlsVisibilityHandler = new Handler(); @@ -133,6 +151,9 @@ public abstract class VideoPlayer extends BasePlayer private int playbackSpeedPopupMenuGroupId = 79; private PopupMenu playbackSpeedPopupMenu; + private int captionPopupMenuGroupId = 89; + private PopupMenu captionPopupMenu; + /////////////////////////////////////////////////////////////////////////// public VideoPlayer(String debugTag, Context context) { @@ -164,6 +185,17 @@ public abstract class VideoPlayer extends BasePlayer this.topControlsRoot = rootView.findViewById(R.id.topControls); this.qualityTextView = rootView.findViewById(R.id.qualityTextView); + this.subtitleView = rootView.findViewById(R.id.subtitleView); + final String captionSizeKey = PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.caption_size_key), + context.getString(R.string.caption_size_default)); + setupSubtitleView(subtitleView, captionSizeKey); + + this.resizeView = rootView.findViewById(R.id.resizeTextView); + resizeView.setText(PlayerHelper.resizeTypeOf(context, aspectRatioFrameLayout.getResizeMode())); + + this.captionTextView = rootView.findViewById(R.id.captionTextView); + //this.aspectRatioFrameLayout.setAspectRatio(16.0f / 9.0f); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) @@ -172,25 +204,37 @@ public abstract class VideoPlayer extends BasePlayer this.qualityPopupMenu = new PopupMenu(context, qualityTextView); this.playbackSpeedPopupMenu = new PopupMenu(context, playbackSpeedTextView); + this.captionPopupMenu = new PopupMenu(context, captionTextView); - ((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)).getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY); - + ((ProgressBar) this.loadingPanel.findViewById(R.id.progressBarLoadingPanel)) + .getIndeterminateDrawable().setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY); } + protected abstract void setupSubtitleView(@NonNull SubtitleView view, + @NonNull String captionSizeKey); + @Override public void initListeners() { super.initListeners(); playbackSeekBar.setOnSeekBarChangeListener(this); playbackSpeedTextView.setOnClickListener(this); qualityTextView.setOnClickListener(this); + captionTextView.setOnClickListener(this); + resizeView.setOnClickListener(this); } @Override public void initPlayer() { super.initPlayer(); + + // Setup video view simpleExoPlayer.setVideoSurfaceView(surfaceView); simpleExoPlayer.addVideoListener(this); + // Setup subtitle view + simpleExoPlayer.addTextOutput(cues -> subtitleView.onCues(cues)); + + // Setup audio session with onboard equalizer if (Build.VERSION.SDK_INT >= 21) { trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)); } @@ -236,6 +280,38 @@ public abstract class VideoPlayer extends BasePlayer playbackSpeedPopupMenu.setOnDismissListener(this); } + private void buildCaptionMenu(final List availableLanguages) { + if (captionPopupMenu == null) return; + captionPopupMenu.getMenu().removeGroup(captionPopupMenuGroupId); + + // Add option for turning off caption + MenuItem captionOffItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId, + 0, Menu.NONE, R.string.caption_none); + captionOffItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); + if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) { + trackSelector.setRendererDisabled(textRendererIndex, true); + } + return true; + }); + + // Add all available captions + for (int i = 0; i < availableLanguages.size(); i++) { + final String captionLanguage = availableLanguages.get(i); + MenuItem captionItem = captionPopupMenu.getMenu().add(captionPopupMenuGroupId, + i + 1, Menu.NONE, captionLanguage); + captionItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT); + if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) { + trackSelector.setParameters(trackSelector.getParameters() + .withPreferredTextLanguage(captionLanguage)); + trackSelector.setRendererDisabled(textRendererIndex, false); + } + return true; + }); + } + captionPopupMenu.setOnDismissListener(this); + } /*////////////////////////////////////////////////////////////////////////// // Playback Listener //////////////////////////////////////////////////////////////////////////*/ @@ -251,7 +327,8 @@ public abstract class VideoPlayer extends BasePlayer playbackSpeedTextView.setVisibility(View.GONE); if (info != null) { - final List videos = ListHelper.getSortedStreamVideosList(context, info.video_streams, info.video_only_streams, false); + final List videos = ListHelper.getSortedStreamVideosList(context, + info.video_streams, info.video_only_streams, false); availableStreams = new ArrayList<>(videos); if (playbackQuality == null) { selectedStreamIndex = getDefaultResolutionIndex(videos); @@ -280,13 +357,39 @@ public abstract class VideoPlayer extends BasePlayer if (index < 0 || index >= videos.size()) return null; final VideoStream video = videos.get(index); - final MediaSource streamSource = buildMediaSource(video.getUrl(), MediaFormat.getSuffixById(video.format)); - final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams); - if (!video.isVideoOnly || audio == null) return streamSource; + List mediaSources = new ArrayList<>(); + // Create video stream source + final MediaSource streamSource = buildMediaSource(video.getUrl(), + MediaFormat.getSuffixById(video.getFormatId())); + mediaSources.add(streamSource); - // Merge with audio stream in case if video does not contain audio - final MediaSource audioSource = buildMediaSource(audio.getUrl(), MediaFormat.getSuffixById(audio.format)); - return new MergingMediaSource(streamSource, audioSource); + // Create optional audio stream source + final AudioStream audio = ListHelper.getHighestQualityAudio(info.audio_streams); + if (video.isVideoOnly && audio != null) { + // Merge with audio stream in case if video does not contain audio + final MediaSource audioSource = buildMediaSource(audio.getUrl(), + MediaFormat.getSuffixById(audio.getFormatId())); + mediaSources.add(audioSource); + } + + // Create subtitle sources + for (final Subtitles subtitle : info.getSubtitles()) { + final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType()); + if (mimeType == null) continue; + + final Format textFormat = Format.createTextSampleFormat(null, mimeType, + SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(subtitle)); + final MediaSource textSource = new SingleSampleMediaSource( + Uri.parse(subtitle.getURL()), cacheDataSourceFactory, textFormat, TIME_UNSET); + mediaSources.add(textSource); + } + + if (mediaSources.size() == 1) { + return mediaSources.get(0); + } else { + return new MergingMediaSource(mediaSources.toArray( + new MediaSource[mediaSources.size()])); + } } /*////////////////////////////////////////////////////////////////////////// @@ -364,6 +467,12 @@ public abstract class VideoPlayer extends BasePlayer // ExoPlayer Video Listener //////////////////////////////////////////////////////////////////////////*/ + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + super.onTracksChanged(trackGroups, trackSelections); + onTextTrackUpdate(); + } + @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (DEBUG) { @@ -377,6 +486,57 @@ public abstract class VideoPlayer extends BasePlayer animateView(surfaceForeground, false, 100); } + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer Track Updates + //////////////////////////////////////////////////////////////////////////*/ + + private void onTextTrackUpdate() { + final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT); + + if (captionTextView == null) return; + if (trackSelector == null || trackSelector.getCurrentMappedTrackInfo() == null || + textRenderer == RENDERER_UNAVAILABLE) { + captionTextView.setVisibility(View.GONE); + return; + } + + final TrackGroupArray textTracks = trackSelector.getCurrentMappedTrackInfo() + .getTrackGroups(textRenderer); + + // Extract all loaded languages + List availableLanguages = new ArrayList<>(textTracks.length); + for (int i = 0; i < textTracks.length; i++) { + final TrackGroup textTrack = textTracks.get(i); + if (textTrack.length > 0 && textTrack.getFormat(0) != null) { + availableLanguages.add(textTrack.getFormat(0).language); + } + } + + // Normalize mismatching language strings + final String preferredLanguage = trackSelector.getParameters().preferredTextLanguage; + // Because ExoPlayer normalizes the preferred language string but not the text track + // language strings, some preferred language string will have the language name in lowercase + String formattedPreferredLanguage = null; + if (preferredLanguage != null) { + for (final String language : availableLanguages) { + if (language.compareToIgnoreCase(preferredLanguage) == 0) { + formattedPreferredLanguage = language; + break; + } + } + } + + // Build UI + buildCaptionMenu(availableLanguages); + if (trackSelector.getRendererDisabled(textRenderer) || formattedPreferredLanguage == null || + !availableLanguages.contains(formattedPreferredLanguage)) { + captionTextView.setText(R.string.caption_none); + } else { + captionTextView.setText(formattedPreferredLanguage); + } + captionTextView.setVisibility(availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + /*////////////////////////////////////////////////////////////////////////// // General Player //////////////////////////////////////////////////////////////////////////*/ @@ -453,6 +613,10 @@ public abstract class VideoPlayer extends BasePlayer onQualitySelectorClicked(); } else if (v.getId() == playbackSpeedTextView.getId()) { onPlaybackSpeedClicked(); + } else if (v.getId() == resizeView.getId()) { + onResizeClicked(); + } else if (v.getId() == captionTextView.getId()) { + onCaptionClicked(); } } @@ -516,6 +680,23 @@ public abstract class VideoPlayer extends BasePlayer showControls(300); } + private void onCaptionClicked() { + if (DEBUG) Log.d(TAG, "onCaptionClicked() called"); + captionPopupMenu.show(); + isSomePopupMenuVisible = true; + showControls(300); + } + + private void onResizeClicked() { + if (getAspectRatioFrameLayout() != null && context != null) { + final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode(); + final int newResizeMode = nextResizeMode(currentResizeMode); + getAspectRatioFrameLayout().setResizeMode(newResizeMode); + getResizeView().setText(PlayerHelper.resizeTypeOf(context, newResizeMode)); + } + } + + protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode); /*////////////////////////////////////////////////////////////////////////// // SeekBar Listener //////////////////////////////////////////////////////////////////////////*/ @@ -557,16 +738,16 @@ public abstract class VideoPlayer extends BasePlayer // Utils //////////////////////////////////////////////////////////////////////////*/ - public int getVideoRendererIndex() { - if (simpleExoPlayer == null) return -1; + public int getRendererIndex(final int trackIndex) { + if (simpleExoPlayer == null) return RENDERER_UNAVAILABLE; for (int t = 0; t < simpleExoPlayer.getRendererCount(); t++) { - if (simpleExoPlayer.getRendererType(t) == C.TRACK_TYPE_VIDEO) { + if (simpleExoPlayer.getRendererType(t) == trackIndex) { return t; } } - return -1; + return RENDERER_UNAVAILABLE; } public boolean isControlsVisible() { @@ -755,4 +936,15 @@ public abstract class VideoPlayer extends BasePlayer return currentDisplaySeek; } + public SubtitleView getSubtitleView() { + return subtitleView; + } + + public TextView getResizeView() { + return resizeView; + } + + public TextView getCaptionTextView() { + return captionTextView; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 40063ba40..476838f13 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -5,7 +5,12 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.util.MimeTypes; + import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.Subtitles; +import org.schabi.newpipe.extractor.stream.SubtitlesFormat; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -14,6 +19,12 @@ import java.util.Locale; import javax.annotation.Nonnull; +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FILL; +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT; +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT; +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH; +import static com.google.android.exoplayer2.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + public class PlayerHelper { private PlayerHelper() {} @@ -46,6 +57,30 @@ public class PlayerHelper { return pitchFormatter.format(pitch); } + public static String mimeTypesOf(final SubtitlesFormat format) { + switch (format) { + case VTT: return MimeTypes.TEXT_VTT; + case TTML: return MimeTypes.APPLICATION_TTML; + default: throw new IllegalArgumentException("Unrecognized mime type: " + format.name()); + } + } + + @NonNull + public static String captionLanguageOf(@NonNull final Subtitles subtitles) { + final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale()); + return displayName + (subtitles.isAutoGenerated() ? " (auto-generated)" : ""); + } + + public static String resizeTypeOf(@NonNull final Context context, + @AspectRatioFrameLayout.ResizeMode final int resizeMode) { + switch (resizeMode) { + case RESIZE_MODE_FIT: return context.getResources().getString(R.string.resize_fit); + case RESIZE_MODE_FILL: return context.getResources().getString(R.string.resize_fill); + case RESIZE_MODE_ZOOM: return context.getResources().getString(R.string.resize_zoom); + default: throw new IllegalArgumentException("Unrecognized resize mode: " + resizeMode); + } + } + public static boolean isResumeAfterAudioFocusGain(@NonNull final Context context) { return isResumeAfterAudioFocusGain(context, false); } diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index 36222f5bc..80c67974f 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -30,6 +30,12 @@ + @@ -225,12 +234,12 @@ android:layout_marginLeft="2dp" android:layout_marginRight="2dp" android:layout_toLeftOf="@+id/moreOptionsButton" - android:background="#00ffffff" android:clickable="true" android:focusable="true" android:padding="5dp" android:scaleType="fitXY" android:src="@drawable/list" + android:background="?attr/selectableItemBackground" tools:ignore="ContentDescription,RtlHardcoded"/> + + + + + + + + + + + + - + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" /> diff --git a/app/src/main/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml index 299b3b110..4f6711437 100644 --- a/app/src/main/res/layout/player_popup.xml +++ b/app/src/main/res/layout/player_popup.xml @@ -28,6 +28,10 @@ android:layout_height="match_parent" android:background="@android:color/black"/> + @@ -70,6 +74,7 @@ android:padding="5dp" android:textColor="@android:color/white" android:textStyle="bold" + android:background="?attr/selectableItemBackground" tools:ignore="RtlHardcoded,RtlSymmetry" tools:text="1080p60"/> @@ -79,24 +84,61 @@ android:layout_height="30dp" android:layout_toRightOf="@+id/qualityTextView" android:gravity="center" - android:padding="6dp" + android:padding="5dp" android:textColor="@android:color/white" android:textStyle="bold" + android:background="?attr/selectableItemBackground" tools:ignore="RelativeOverlap,RtlHardcoded,RtlSymmetry" tools:text="1.75x"/> + + + + + + + - diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 1ac8e68cd..c66b5b43f 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -100,6 +100,25 @@ @string/black_theme_title + + caption_size_key + @string/normal_caption_size_key + + smaller_caption_size + normal_caption_size + larger_caption_size + + + @string/smaller_caption_font_size + @string/normal_caption_font_size + @string/larger_caption_font_size + + + @string/smaller_caption_size_key + @string/normal_caption_size_key + @string/larger_caption_size_key + + show_search_suggestions show_play_with_kodi diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23453c89d..c2923a529 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -397,4 +397,17 @@ Added to playlist Playlist thumbnail changed Failed to delete playlist + + + No Caption + + FIT + FILL + ZOOM + + Caption Font Size + Smaller Font + Normal Font + Larger Font + diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index 58b08a284..3ffbd9d81 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -21,4 +21,12 @@ android:key="@string/show_hold_to_append_key" android:title="@string/show_hold_to_append_title" android:summary="@string/show_hold_to_append_summary"/> + +