From c778c71306a474a340d56c29de0296e3e2e20d76 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 20 Jan 2024 18:10:05 +0100 Subject: [PATCH] Prepare Media3 --- app/build.gradle | 11 +- .../activities/BasePeertubeActivity.java | 4 +- .../helper/CacheDataSourceFactory.java | 22 +- .../mastodon/ui/drawer/StatusAdapter.java | 17 +- .../ui/drawer/StatusDirectMessageAdapter.java | 15 +- .../ui/fragment/media/FragmentMedia.java | 16 +- .../peertube/activities/PeertubeActivity.java | 45 +- .../peertube/helper/TrackSelectionDialog.java | 16 +- .../mastodon/layout/fragment_slide_media.xml | 4 +- .../layouts/mastodon/layout/layout_media.xml | 2 +- doubletapplayerview/.gitignore | 1 + doubletapplayerview/build.gradle | 38 ++ doubletapplayerview/consumer-rules.pro | 0 doubletapplayerview/proguard-rules.pro | 21 + .../src/main/AndroidManifest.xml | 2 + .../github/vkay94/dtpv/DoubleTapPlayerView.kt | 222 ++++++++ .../vkay94/dtpv/PlayerDoubleTapListener.java | 37 ++ .../com/github/vkay94/dtpv/SeekListener.kt | 13 + .../vkay94/dtpv/youtube/YouTubeOverlay.kt | 509 ++++++++++++++++++ .../dtpv/youtube/views/CircleClipTapView.kt | 221 ++++++++ .../dtpv/youtube/views/YouTubeSecondsView.kt | 201 +++++++ .../main/res/drawable/ic_play_triangle.xml | 11 + .../src/main/res/layout/yt_overlay.xml | 30 ++ .../src/main/res/layout/yt_seconds_view.xml | 48 ++ .../src/main/res/values-af/plurals.xml | 6 + .../src/main/res/values-am/plurals.xml | 6 + .../src/main/res/values-ar/plurals.xml | 10 + .../src/main/res/values-az/plurals.xml | 6 + .../src/main/res/values-b+sr+Latn/plurals.xml | 7 + .../src/main/res/values-be/plurals.xml | 8 + .../src/main/res/values-bg/plurals.xml | 6 + .../src/main/res/values-bn/plurals.xml | 6 + .../src/main/res/values-bs/plurals.xml | 7 + .../src/main/res/values-ca/plurals.xml | 6 + .../src/main/res/values-cs/plurals.xml | 8 + .../src/main/res/values-da/plurals.xml | 6 + .../src/main/res/values-de/plurals.xml | 6 + .../src/main/res/values-el/plurals.xml | 6 + .../src/main/res/values-en-rGB/plurals.xml | 6 + .../src/main/res/values-en-rIN/plurals.xml | 6 + .../src/main/res/values-es-rUS/plurals.xml | 6 + .../src/main/res/values-es/plurals.xml | 6 + .../src/main/res/values-et/plurals.xml | 6 + .../src/main/res/values-eu/plurals.xml | 6 + .../src/main/res/values-fa/plurals.xml | 6 + .../src/main/res/values-fi/plurals.xml | 6 + .../src/main/res/values-fr-rCA/plurals.xml | 6 + .../src/main/res/values-fr/plurals.xml | 6 + .../src/main/res/values-gl/plurals.xml | 6 + .../src/main/res/values-gu/plurals.xml | 6 + .../src/main/res/values-hi/plurals.xml | 6 + .../src/main/res/values-hr/plurals.xml | 7 + .../src/main/res/values-hu/plurals.xml | 6 + .../src/main/res/values-hy/plurals.xml | 6 + .../src/main/res/values-in/plurals.xml | 6 + .../src/main/res/values-is/plurals.xml | 6 + .../src/main/res/values-it/plurals.xml | 6 + .../src/main/res/values-iw/plurals.xml | 8 + .../src/main/res/values-ja/plurals.xml | 6 + .../src/main/res/values-ka/plurals.xml | 6 + .../src/main/res/values-kk/plurals.xml | 6 + .../src/main/res/values-km/plurals.xml | 6 + .../src/main/res/values-kn/plurals.xml | 6 + .../src/main/res/values-ko/plurals.xml | 6 + .../src/main/res/values-ky/plurals.xml | 6 + .../src/main/res/values-lo/plurals.xml | 6 + .../src/main/res/values-lt/plurals.xml | 8 + .../src/main/res/values-lv/plurals.xml | 7 + .../src/main/res/values-mk/plurals.xml | 6 + .../src/main/res/values-ml/plurals.xml | 6 + .../src/main/res/values-mn/plurals.xml | 6 + .../src/main/res/values-mr/plurals.xml | 6 + .../src/main/res/values-ms/plurals.xml | 6 + .../src/main/res/values-my/plurals.xml | 6 + .../src/main/res/values-nb/plurals.xml | 6 + .../src/main/res/values-ne/plurals.xml | 6 + .../src/main/res/values-nl/plurals.xml | 6 + .../src/main/res/values-pa/plurals.xml | 6 + .../src/main/res/values-pl/plurals.xml | 8 + .../src/main/res/values-pt-rBR/plurals.xml | 6 + .../src/main/res/values-pt-rPT/plurals.xml | 6 + .../src/main/res/values-ro/plurals.xml | 7 + .../src/main/res/values-ru/plurals.xml | 8 + .../src/main/res/values-si/plurals.xml | 6 + .../src/main/res/values-sk/plurals.xml | 8 + .../src/main/res/values-sl/plurals.xml | 8 + .../src/main/res/values-sq/plurals.xml | 6 + .../src/main/res/values-sr/plurals.xml | 7 + .../src/main/res/values-sv/plurals.xml | 6 + .../src/main/res/values-sw/plurals.xml | 6 + .../src/main/res/values-ta/plurals.xml | 6 + .../src/main/res/values-te/plurals.xml | 6 + .../src/main/res/values-th/plurals.xml | 6 + .../src/main/res/values-tl/plurals.xml | 6 + .../src/main/res/values-tr/plurals.xml | 6 + .../src/main/res/values-uk/plurals.xml | 8 + .../src/main/res/values-ur/plurals.xml | 6 + .../src/main/res/values-uz/plurals.xml | 6 + .../src/main/res/values-vi/plurals.xml | 6 + .../src/main/res/values-zh-rCN/plurals.xml | 6 + .../src/main/res/values-zh-rHK/plurals.xml | 6 + .../src/main/res/values-zh-rTW/plurals.xml | 6 + .../src/main/res/values-zu/plurals.xml | 6 + .../src/main/res/values/dtpv.xml | 8 + .../src/main/res/values/plurals.xml | 7 + .../src/main/res/values/public.xml | 16 + .../src/main/res/values/yt_overlay.xml | 26 + settings.gradle | 1 + 108 files changed, 1993 insertions(+), 73 deletions(-) create mode 100644 doubletapplayerview/.gitignore create mode 100644 doubletapplayerview/build.gradle create mode 100644 doubletapplayerview/consumer-rules.pro create mode 100644 doubletapplayerview/proguard-rules.pro create mode 100644 doubletapplayerview/src/main/AndroidManifest.xml create mode 100644 doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt create mode 100644 doubletapplayerview/src/main/java/com/github/vkay94/dtpv/PlayerDoubleTapListener.java create mode 100644 doubletapplayerview/src/main/java/com/github/vkay94/dtpv/SeekListener.kt create mode 100644 doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt create mode 100644 doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/CircleClipTapView.kt create mode 100644 doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt create mode 100644 doubletapplayerview/src/main/res/drawable/ic_play_triangle.xml create mode 100644 doubletapplayerview/src/main/res/layout/yt_overlay.xml create mode 100644 doubletapplayerview/src/main/res/layout/yt_seconds_view.xml create mode 100644 doubletapplayerview/src/main/res/values-af/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-am/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ar/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-az/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-b+sr+Latn/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-be/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-bg/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-bn/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-bs/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ca/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-cs/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-da/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-de/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-el/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-en-rGB/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-en-rIN/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-es-rUS/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-es/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-et/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-eu/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-fa/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-fi/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-fr-rCA/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-fr/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-gl/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-gu/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-hi/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-hr/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-hu/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-hy/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-in/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-is/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-it/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-iw/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ja/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ka/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-kk/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-km/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-kn/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ko/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ky/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-lo/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-lt/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-lv/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-mk/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ml/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-mn/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-mr/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ms/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-my/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-nb/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ne/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-nl/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-pa/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-pl/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-pt-rBR/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-pt-rPT/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ro/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ru/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-si/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-sk/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-sl/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-sq/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-sr/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-sv/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-sw/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ta/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-te/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-th/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-tl/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-tr/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-uk/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-ur/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-uz/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-vi/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-zh-rCN/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-zh-rHK/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-zh-rTW/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values-zu/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values/dtpv.xml create mode 100644 doubletapplayerview/src/main/res/values/plurals.xml create mode 100644 doubletapplayerview/src/main/res/values/public.xml create mode 100644 doubletapplayerview/src/main/res/values/yt_overlay.xml diff --git a/app/build.gradle b/app/build.gradle index 268a76383..4917a13eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -132,14 +132,18 @@ dependencies { implementation project(path: ':sparkbutton') implementation project(path: ':colorPicker') implementation project(path: ':mathjaxandroid') - + implementation project(path: ':doubletapplayerview') implementation 'com.burhanrashid52:photoeditor:1.5.1' implementation("com.vanniktech:android-image-cropper:4.3.3") annotationProcessor "com.github.bumptech.glide:compiler:4.12.0" implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.23.0' - implementation 'com.google.android.exoplayer:exoplayer:2.19.1' + implementation "androidx.media3:media3-exoplayer:1.2.1" + implementation "androidx.media3:media3-exoplayer-dash:1.2.1" + implementation "androidx.media3:media3-ui:1.2.1" + + implementation "androidx.viewpager2:viewpager2:1.0.0" implementation 'com.github.piasy:rxandroidaudio:1.7.0' implementation 'com.github.piasy:AudioProcessor:1.7.0' @@ -177,14 +181,13 @@ dependencies { implementation 'androidx.browser:browser:1.7.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'com.github.amoskorir:avatarimagegenerator:1.5.0' - implementation 'com.google.android.exoplayer:extension-mediasession:2.19.1' + implementation "com.github.mabbas007:TagsEditText:1.0.5" implementation "net.gotev:uploadservice:4.9.2" implementation "net.gotev:uploadservice-okhttp:4.9.2" implementation 'androidx.media:media:1.7.0' implementation 'com.github.mancj:MaterialSearchBar:0.8.5' - implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.0' implementation 'io.noties.markwon:core:4.6.2' diff --git a/app/src/fdroid/java/app/fedilab/android/activities/BasePeertubeActivity.java b/app/src/fdroid/java/app/fedilab/android/activities/BasePeertubeActivity.java index 1edddce2f..d190f88fa 100644 --- a/app/src/fdroid/java/app/fedilab/android/activities/BasePeertubeActivity.java +++ b/app/src/fdroid/java/app/fedilab/android/activities/BasePeertubeActivity.java @@ -28,9 +28,9 @@ import android.view.View; import android.webkit.MimeTypeMap; import androidx.appcompat.app.AlertDialog; +import androidx.media3.common.Player; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.jetbrains.annotations.NotNull; @@ -53,7 +53,7 @@ public class BasePeertubeActivity extends BaseBarActivity { protected ActivityPeertubeBinding binding; protected VideoData.Video peertube; - protected ExoPlayer player; + protected Player player; protected String videoURL; protected String subtitlesStr; diff --git a/app/src/main/java/app/fedilab/android/mastodon/helper/CacheDataSourceFactory.java b/app/src/main/java/app/fedilab/android/mastodon/helper/CacheDataSourceFactory.java index 3f74b906d..ddfceadfd 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/helper/CacheDataSourceFactory.java +++ b/app/src/main/java/app/fedilab/android/mastodon/helper/CacheDataSourceFactory.java @@ -18,24 +18,24 @@ import android.content.Context; import android.content.SharedPreferences; import androidx.annotation.NonNull; +import androidx.media3.database.ExoDatabaseProvider; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import androidx.preference.PreferenceManager; -import com.google.android.exoplayer2.database.ExoDatabaseProvider; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; -import com.google.android.exoplayer2.upstream.FileDataSource; -import com.google.android.exoplayer2.upstream.cache.CacheDataSink; -import com.google.android.exoplayer2.upstream.cache.CacheDataSource; -import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; -import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSourceFactory; +import androidx.media3.datasource.DefaultHttpDataSource; +import androidx.media3.datasource.FileDataSource; +import androidx.media3.datasource.cache.CacheDataSink; +import androidx.media3.datasource.cache.CacheDataSource; +import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor; +import androidx.media3.datasource.cache.SimpleCache; import java.io.File; import app.fedilab.android.R; - +@androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public class CacheDataSourceFactory implements DataSource.Factory { private static SimpleCache sDownloadCache; diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java index 465ebae8f..d9203d93e 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java @@ -76,6 +76,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.LinearLayoutCompat; @@ -88,6 +89,14 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.ProgressiveMediaSource; +import androidx.media3.ui.PlayerView; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -97,13 +106,6 @@ import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.request.RequestOptions; import com.github.stom79.mytransl.MyTransL; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.ui.PlayerView; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.smarteist.autoimageslider.SliderAnimations; import com.smarteist.autoimageslider.SliderView; @@ -390,6 +392,7 @@ public class StatusAdapter extends RecyclerView.Adapter * @param status {@link Status} */ @SuppressLint("ClickableViewAccessibility") + @androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public static void statusManagement(Context context, StatusesVM statusesVM, SearchVM searchVM, diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java index 7407e84ab..d96d4d429 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusDirectMessageAdapter.java @@ -49,17 +49,18 @@ import androidx.core.content.ContextCompat; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; +import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.ProgressiveMediaSource; +import androidx.media3.ui.PlayerView; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.RequestBuilder; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.ui.PlayerView; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSource; + import org.jetbrains.annotations.NotNull; diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMedia.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMedia.java index 6740a6d8e..030efd46d 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMedia.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMedia.java @@ -33,18 +33,18 @@ import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.MediaItem; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.Player; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.ProgressiveMediaSource; import androidx.preference.PreferenceManager; import com.bumptech.glide.Glide; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.r0adkll.slidr.Slidr; import com.r0adkll.slidr.model.SlidrConfig; import com.r0adkll.slidr.model.SlidrInterface; @@ -248,7 +248,7 @@ public class FragmentMedia extends Fragment { } } - + @androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private void loadVideo(String url, String type) { if (binding == null || !isAdded() || getActivity() == null || url == null) { return; diff --git a/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java index 0fd8dd487..66034c619 100644 --- a/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java +++ b/app/src/main/java/app/fedilab/android/peertube/activities/PeertubeActivity.java @@ -14,7 +14,8 @@ package app.fedilab.android.peertube.activities; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ -import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; + +import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.BaseMainActivity.currentInstance; import static app.fedilab.android.BaseMainActivity.currentToken; @@ -85,34 +86,30 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.Format; +import androidx.media3.common.MediaItem; +import androidx.media3.common.MimeTypes; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; +import androidx.media3.common.VideoSize; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.MergingMediaSource; +import androidx.media3.exoplayer.source.ProgressiveMediaSource; +import androidx.media3.exoplayer.source.SingleSampleMediaSource; +import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection; +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.trackselection.TrackSelector; +import androidx.media3.ui.AspectRatioFrameLayout; +import androidx.media3.ui.PlayerControlView; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.github.vkay94.dtpv.youtube.YouTubeOverlay; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.PlaybackException; -import com.google.android.exoplayer2.PlaybackParameters; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; -import com.google.android.exoplayer2.source.MergingMediaSource; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.source.SingleSampleMediaSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelector; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.ui.DefaultTimeBar; -import com.google.android.exoplayer2.ui.PlayerControlView; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSource; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.video.VideoSize; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; @@ -168,7 +165,7 @@ import app.fedilab.android.peertube.webview.MastalabWebChromeClient; import app.fedilab.android.peertube.webview.MastalabWebViewClient; import es.dmoral.toasty.Toasty; - +@androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public class PeertubeActivity extends BasePeertubeActivity implements CommentListAdapter.AllCommentRemoved, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction, Player.Listener { public static String video_id; diff --git a/app/src/main/java/app/fedilab/android/peertube/helper/TrackSelectionDialog.java b/app/src/main/java/app/fedilab/android/peertube/helper/TrackSelectionDialog.java index eb7e2e2e2..6090d123c 100644 --- a/app/src/main/java/app/fedilab/android/peertube/helper/TrackSelectionDialog.java +++ b/app/src/main/java/app/fedilab/android/peertube/helper/TrackSelectionDialog.java @@ -14,14 +14,14 @@ import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; +import androidx.media3.common.C; +import androidx.media3.common.Player; +import androidx.media3.common.TrackGroup; +import androidx.media3.common.TrackSelectionOverride; +import androidx.media3.common.TrackSelectionParameters; +import androidx.media3.common.Tracks; +import androidx.media3.ui.TrackSelectionView; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Tracks; -import com.google.android.exoplayer2.source.TrackGroup; -import com.google.android.exoplayer2.trackselection.TrackSelectionOverride; -import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; -import com.google.android.exoplayer2.ui.TrackSelectionView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.common.collect.ImmutableList; @@ -31,12 +31,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import app.fedilab.android.R; import app.fedilab.android.databinding.TrackSelectionDialogBinding; /** * Dialog to select tracks. */ +@androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public final class TrackSelectionDialog extends DialogFragment { public static final ImmutableList SUPPORTED_TRACK_TYPES = diff --git a/app/src/main/res/layouts/mastodon/layout/fragment_slide_media.xml b/app/src/main/res/layouts/mastodon/layout/fragment_slide_media.xml index 2fc855328..d9dc542ab 100644 --- a/app/src/main/res/layouts/mastodon/layout/fragment_slide_media.xml +++ b/app/src/main/res/layouts/mastodon/layout/fragment_slide_media.xml @@ -65,14 +65,14 @@ android:layout_height="match_parent" android:visibility="gone"> - - - diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt new file mode 100644 index 000000000..a3374d90c --- /dev/null +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt @@ -0,0 +1,222 @@ +package com.github.vkay94.dtpv + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.util.AttributeSet +import android.util.Log +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import androidx.core.view.GestureDetectorCompat +import androidx.media3.ui.PlayerView + + +/** + * Custom player class for Double-Tapping listening + */ +open class DoubleTapPlayerView @JvmOverloads constructor( + context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : PlayerView(context!!, attrs, defStyleAttr) { + + private val gestureDetector: GestureDetectorCompat + private val gestureListener: DoubleTapGestureListener = DoubleTapGestureListener(rootView) + + private var controller: PlayerDoubleTapListener? = null + get() = gestureListener.controls + set(value) { + gestureListener.controls = value + field = value + } + + private var controllerRef: Int = -1 + + init { + gestureDetector = GestureDetectorCompat(context, gestureListener) + + // Check whether controller is set through XML + attrs?.let { + val a = context?.obtainStyledAttributes(attrs, R.styleable.DoubleTapPlayerView, 0,0) + controllerRef = a?.getResourceId(R.styleable.DoubleTapPlayerView_dtpv_controller, -1) ?: -1 + + a?.recycle() + } + } + + /** + * If this field is set to `true` this view will handle double tapping, otherwise it will + * handle touches the same way as the original [PlayerView][com.google.android.exoplayer2.ui.PlayerView] does + */ + var isDoubleTapEnabled = true + + /** + * Time window a double tap is active, so a followed tap is calling a gesture detector + * method instead of normal tap (see [PlayerView.onTouchEvent]) + */ + var doubleTapDelay: Long = 700 + get() = gestureListener.doubleTapDelay + set(value) { + gestureListener.doubleTapDelay = value + field = value + } + + /** + * Sets the [PlayerDoubleTapListener] which handles the gesture callbacks. + * + * Primarily used for [YouTubeOverlay][com.github.vkay94.dtpv.youtube.YouTubeOverlay] + */ + fun controller(controller: PlayerDoubleTapListener) = apply { this.controller = controller } + + /** + * Returns the current state of double tapping. + */ + fun isInDoubleTapMode(): Boolean = gestureListener.isDoubleTapping + + /** + * Resets the timeout to keep in double tap mode. + * + * Called once in [PlayerDoubleTapListener.onDoubleTapStarted]. Needs to be called + * from outside if the double tap is customized / overridden to detect ongoing taps + */ + fun keepInDoubleTapMode() { + gestureListener.keepInDoubleTapMode() + } + + /** + * Cancels double tap mode instantly by calling [PlayerDoubleTapListener.onDoubleTapFinished] + */ + fun cancelInDoubleTapMode() { + gestureListener.cancelInDoubleTapMode() + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(ev: MotionEvent): Boolean { + if (isDoubleTapEnabled) { + gestureDetector.onTouchEvent(ev) + + // Do not trigger original behavior when double tapping + // otherwise the controller would show/hide - it would flack + return true + } + return super.onTouchEvent(ev) + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + // If the PlayerView is set by XML then call the corresponding setter method + if (controllerRef != -1) { + try { + val view = (this.parent as View).findViewById(controllerRef) as View + if (view is PlayerDoubleTapListener) { + controller(view) + } + } catch (e: Exception) { + e.printStackTrace() + Log.e("DoubleTapPlayerView", + "controllerRef is either invalid or not PlayerDoubleTapListener: ${e.message}") + } + } + + } + + /** + * Gesture Listener for double tapping + * + * For more information which methods are called in certain situations look for + * [GestureDetector.onTouchEvent][android.view.GestureDetector.onTouchEvent], + * especially for ACTION_DOWN and ACTION_UP + */ + private class DoubleTapGestureListener(private val rootView: View) : GestureDetector.SimpleOnGestureListener() { + + private val mHandler = Handler(Looper.getMainLooper()) + private val mRunnable = Runnable { + if (DEBUG) Log.d(TAG, "Runnable called") + isDoubleTapping = false + controls?.onDoubleTapFinished() + } + + var controls: PlayerDoubleTapListener? = null + var isDoubleTapping = false + var doubleTapDelay: Long = 650 + + /** + * Resets the timeout to keep in double tap mode. + * + * Called once in [PlayerDoubleTapListener.onDoubleTapStarted]. Needs to be called + * from outside if the double tap is customized / overridden to detect ongoing taps + */ + fun keepInDoubleTapMode() { + isDoubleTapping = true + mHandler.removeCallbacks(mRunnable) + mHandler.postDelayed(mRunnable, doubleTapDelay) + } + + /** + * Cancels double tap mode instantly by calling [PlayerDoubleTapListener.onDoubleTapFinished] + */ + fun cancelInDoubleTapMode() { + mHandler.removeCallbacks(mRunnable) + isDoubleTapping = false + controls?.onDoubleTapFinished() + } + + override fun onDown(e: MotionEvent): Boolean { + // Used to override the other methods + if (isDoubleTapping) { + controls?.onDoubleTapProgressDown(e.x, e.y) + return true + } + return super.onDown(e) + } + + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (isDoubleTapping) { + if (DEBUG) Log.d(TAG, "onSingleTapUp: isDoubleTapping = true") + controls?.onDoubleTapProgressUp(e.x, e.y) + return true + } + return super.onSingleTapUp(e) + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + // Ignore this event if double tapping is still active + // Return true needed because this method is also called if you tap e.g. three times + // in a row, therefore the controller would appear since the original behavior is + // to hide and show on single tap + if (isDoubleTapping) return true + if (DEBUG) Log.d(TAG, "onSingleTapConfirmed: isDoubleTap = false") + return rootView.performClick() + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + // First tap (ACTION_DOWN) of both taps + if (DEBUG) Log.d(TAG, "onDoubleTap") + if (!isDoubleTapping) { + isDoubleTapping = true + keepInDoubleTapMode() + controls?.onDoubleTapStarted(e.x, e.y) + } + return true + } + + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + // Second tap (ACTION_UP) of both taps + if (e.actionMasked == MotionEvent.ACTION_UP && isDoubleTapping) { + if (DEBUG) Log.d( + TAG, + "onDoubleTapEvent, ACTION_UP" + ) + controls?.onDoubleTapProgressUp(e.x, e.y) + return true + } + return super.onDoubleTapEvent(e) + } + + companion object { + private const val TAG = ".DTGListener" + private var DEBUG = true + } + } +} \ No newline at end of file diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/PlayerDoubleTapListener.java b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/PlayerDoubleTapListener.java new file mode 100644 index 000000000..cdfaaa233 --- /dev/null +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/PlayerDoubleTapListener.java @@ -0,0 +1,37 @@ +package com.github.vkay94.dtpv; + +public interface PlayerDoubleTapListener { + + /** + * Called when double tapping starts, after double tap gesture + * + * @param posX x tap position on the root view + * @param posY y tap position on the root view + */ + default void onDoubleTapStarted(float posX, float posY) { } + + /** + * Called for each ongoing tap (also single tap) (MotionEvent#ACTION_DOWN) + * when double tap started and still in double tap mode defined + * by {@link DoubleTapPlayerView#getDoubleTapDelay()} + * + * @param posX x tap position on the root view + * @param posY y tap position on the root view + */ + default void onDoubleTapProgressDown(float posX, float posY) { } + + /** + * Called for each ongoing tap (also single tap) (MotionEvent#ACTION_UP} + * when double tap started and still in double tap mode defined + * by {@link DoubleTapPlayerView#getDoubleTapDelay()} + * + * @param posX x tap position on the root view + * @param posY y tap position on the root view + */ + default void onDoubleTapProgressUp(float posX, float posY) { } + + /** + * Called when {@link DoubleTapPlayerView#getDoubleTapDelay()} is over + */ + default void onDoubleTapFinished() { } +} \ No newline at end of file diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/SeekListener.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/SeekListener.kt new file mode 100644 index 000000000..b2b6339d5 --- /dev/null +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/SeekListener.kt @@ -0,0 +1,13 @@ +package com.github.vkay94.dtpv + +interface SeekListener { + /** + * Called when video start reached during rewinding + */ + fun onVideoStartReached() {} + + /** + * Called when video end reached during forwarding + */ + fun onVideoEndReached() {} +} \ No newline at end of file diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt new file mode 100644 index 000000000..9db99e562 --- /dev/null +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/YouTubeOverlay.kt @@ -0,0 +1,509 @@ +package com.github.vkay94.dtpv.youtube + +import android.content.Context +import android.media.session.PlaybackState +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.annotation.* +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.ContextCompat +import androidx.core.widget.TextViewCompat +import androidx.media3.common.Player +import com.github.vkay94.dtpv.DoubleTapPlayerView +import com.github.vkay94.dtpv.PlayerDoubleTapListener +import com.github.vkay94.dtpv.R +import com.github.vkay94.dtpv.SeekListener +import com.github.vkay94.dtpv.youtube.views.CircleClipTapView +import com.github.vkay94.dtpv.youtube.views.SecondsView + + +/** + * Overlay for [DoubleTapPlayerView] to create a similar UI/UX experience like the official + * YouTube Android app. + * + * The overlay has the typical YouTube scaling circle animation and provides some configurations + * which can't be accomplished with the regular Android Ripple (I didn't find any options in the + * documentation ...). + */ +class YouTubeOverlay(context: Context, private val attrs: AttributeSet?) : + ConstraintLayout(context, attrs), PlayerDoubleTapListener { + + private var rootLayout: ConstraintLayout + private var secondsView: SecondsView + private var circleClipTapView: CircleClipTapView + + constructor(context: Context) : this(context, null) { + // Hide overlay initially when added programmatically + this.visibility = View.INVISIBLE + } + + private var playerViewRef: Int = -1 + + // Player behaviors + private var playerView: DoubleTapPlayerView? = null + private var player: Player? = null + + init { + LayoutInflater.from(context).inflate(R.layout.yt_overlay, this, true) + + rootLayout = findViewById(R.id.root_constraint_layout) + secondsView = findViewById(R.id.seconds_view) + circleClipTapView = findViewById(R.id.circle_clip_tap_view) + + // Initialize UI components + initializeAttributes() + secondsView.isForward = true + changeConstraints(true) + + // This code snippet is executed when the circle scale animation is finished + circleClipTapView.performAtEnd = { + performListener?.onAnimationEnd() + + secondsView.visibility = View.INVISIBLE + secondsView.seconds = 0 + secondsView.stop() + } + } + + /** + * Sets all optional XML attributes and defaults + */ + private fun initializeAttributes() { + if (attrs != null) { + val a = context.obtainStyledAttributes(attrs, + R.styleable.YouTubeOverlay, 0, 0) + + // PlayerView => see onAttachToWindow + playerViewRef = a.getResourceId(R.styleable.YouTubeOverlay_yt_playerView, -1) + + // Durations + animationDuration = a.getInt( + R.styleable.YouTubeOverlay_yt_animationDuration, 650).toLong() + + seekSeconds = a.getInt( + R.styleable.YouTubeOverlay_yt_seekSeconds, 10) + + iconAnimationDuration = a.getInt( + R.styleable.YouTubeOverlay_yt_iconAnimationDuration, 750).toLong() + + // Arc size + arcSize = a.getDimensionPixelSize( + R.styleable.YouTubeOverlay_yt_arcSize, + context.resources.getDimensionPixelSize(R.dimen.dtpv_yt_arc_size)).toFloat() + + // Colors + tapCircleColor = a.getColor( + R.styleable.YouTubeOverlay_yt_tapCircleColor, + ContextCompat.getColor(context, R.color.dtpv_yt_tap_circle_color) + ) + + circleBackgroundColor = a.getColor( + R.styleable.YouTubeOverlay_yt_backgroundCircleColor, + ContextCompat.getColor(context, R.color.dtpv_yt_background_circle_color) + ) + + // Seconds TextAppearance + textAppearance = a.getResourceId( + R.styleable.YouTubeOverlay_yt_textAppearance, + R.style.YTOSecondsTextAppearance) + + // Seconds icon + icon = a.getResourceId( + R.styleable.YouTubeOverlay_yt_icon, + R.drawable.ic_play_triangle + ) + + a.recycle() + + } else { + // Set defaults + arcSize = context.resources.getDimensionPixelSize(R.dimen.dtpv_yt_arc_size).toFloat() + tapCircleColor = ContextCompat.getColor(context, R.color.dtpv_yt_tap_circle_color) + circleBackgroundColor = ContextCompat.getColor(context, R.color.dtpv_yt_background_circle_color) + animationDuration = 650 + iconAnimationDuration = 750 + seekSeconds = 10 + textAppearance = R.style.YTOSecondsTextAppearance + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + // If the PlayerView is set by XML then call the corresponding setter method + if (playerViewRef != -1) + playerView((this.parent as View).findViewById(playerViewRef) as DoubleTapPlayerView) + } + + /** + * Obligatory call if playerView is not set via XML! + * + * Links the DoubleTapPlayerView to this view for recognizing the tapped position. + * + * @param playerView PlayerView which triggers the event + */ + fun playerView(playerView: DoubleTapPlayerView) = apply { + this.playerView = playerView + } + + /** + * Obligatory call! Needs to be called whenever the Player changes. + * + * Performs seekTo-calls on the ExoPlayer's Player instance. + * + * @param player PlayerView which triggers the event + */ + fun player(player: Player) = apply { + this.player = player + } + + /* + Properties + */ + + private var seekListener: SeekListener? = null + + /** + * Optional: Sets a listener to observe whether double tap reached the start / end of the video + */ + fun seekListener(listener: SeekListener) = apply { + seekListener = listener + } + + private var performListener: PerformListener? = null + + /** + * Sets a listener to execute some code before and after the animation + * (for example UI changes (hide and show views etc.)) + */ + fun performListener(listener: PerformListener) = apply { + performListener = listener + } + + /** + * Forward / rewind duration on a tap in seconds. + */ + var seekSeconds: Int = 0 + private set + + fun seekSeconds(seconds: Int) = apply { + seekSeconds = seconds + } + + /** + * Color of the scaling circle on touch feedback. + */ + var tapCircleColor: Int + get() = circleClipTapView.circleColor + private set(value) { + circleClipTapView.circleColor = value + } + + fun tapCircleColorRes(@ColorRes resId: Int) = apply { + tapCircleColor = ContextCompat.getColor(context, resId) + } + + fun tapCircleColorInt(@ColorInt color: Int) = apply { + tapCircleColor = color + } + + /** + * Color of the clipped background circle + */ + var circleBackgroundColor: Int + get() = circleClipTapView.circleBackgroundColor + private set(value) { + circleClipTapView.circleBackgroundColor = value + } + + fun circleBackgroundColorRes(@ColorRes resId: Int) = apply { + circleBackgroundColor = ContextCompat.getColor(context, resId) + } + + fun circleBackgroundColorInt(@ColorInt color: Int) = apply { + circleBackgroundColor = color + } + + /** + * Duration of the circle scaling animation / speed in milliseconds. + * The overlay keeps visible until the animation finishes. + */ + var animationDuration: Long + get() = circleClipTapView.animationDuration + private set(value) { + circleClipTapView.animationDuration = value + } + + fun animationDuration(duration: Long) = apply { + animationDuration = duration + } + + /** + * Size of the arc which will be clipped from the background circle. + * The greater the value the more roundish the shape becomes + */ + var arcSize: Float + get() = circleClipTapView.arcSize + internal set(value) { + circleClipTapView.arcSize = value + } + + fun arcSize(@DimenRes resId: Int) = apply { + arcSize = context.resources.getDimension(resId) + } + + fun arcSize(px: Float) = apply { + arcSize = px + } + + /** + * Duration the icon animation (fade in + fade out) for a full cycle in milliseconds. + */ + var iconAnimationDuration: Long = 750 + get() = secondsView.cycleDuration + private set(value) { + secondsView.cycleDuration = value + field = value + } + + fun iconAnimationDuration(duration: Long) = apply { + iconAnimationDuration = duration + } + + /** + * One of the three forward icons which will be animated above the seconds indicator. + * The rewind icon will be the 180° mirrored version. + * + * Keep in mind that padding on the left and right of the drawable will be rendered which + * could result in additional space between the three icons. + */ + @DrawableRes + var icon: Int = 0 + get() = secondsView.icon + private set(value) { + secondsView.stop() + secondsView.icon = value + field = value + } + + fun icon(@DrawableRes resId: Int) = apply { + icon = resId + } + + /** + * Text appearance of the *xx seconds* text. + */ + @StyleRes + var textAppearance: Int = 0 + private set(value) { + TextViewCompat.setTextAppearance(secondsView.textView, value) + field = value + } + + fun textAppearance(@StyleRes resId: Int) = apply { + textAppearance = resId + } + + /** + * TextView view for *xx seconds*. + * + * In case of you'd like to change some specific attributes of the TextView in runtime. + */ + val secondsTextView: TextView + get() = secondsView.textView + + override fun onDoubleTapStarted(posX: Float, posY: Float) { + if (player == null || playerView == null) + return + + if (performListener?.shouldForward(player!!, playerView!!, posX) == null) + return + } + + override fun onDoubleTapProgressUp(posX: Float, posY: Float) { + + // Check first whether forwarding/rewinding is "valid" + if (player == null || playerView == null) return + + val shouldForward = performListener?.shouldForward(player!!, playerView!!, posX) + + // YouTube behavior: show overlay on MOTION_UP + // But check whether the first double tap is in invalid area + if (this.visibility != View.VISIBLE) { + if (shouldForward != null) { + performListener?.onAnimationStart() + secondsView.visibility = View.VISIBLE + secondsView.start() + } else + return + } + + when (shouldForward) { + false -> { + + // First time tap or switched + if (secondsView.isForward) { + changeConstraints(false) + secondsView.apply { + isForward = false + seconds = 0 + } + } + + // Cancel ripple and start new without triggering overlay disappearance + // (resetting instead of ending) + circleClipTapView.resetAnimation { + circleClipTapView.updatePosition(posX, posY) + } + rewinding() + } + true -> { + + // First time tap or switched + if (!secondsView.isForward) { + changeConstraints(true) + secondsView.apply { + isForward = true + seconds = 0 + } + } + + // Cancel ripple and start new without triggering overlay disappearance + // (resetting instead of ending) + circleClipTapView.resetAnimation { + circleClipTapView.updatePosition(posX, posY) + } + forwarding() + } + else -> { + // Middle area tapped: do nothing + // + // playerView?.cancelInDoubleTapMode() + // circle_clip_tap_view.endAnimation() + // triangle_seconds_view.stop() + } + } + } + + /** + * Seeks the video to desired position. + * Calls interface functions when start reached ([SeekListener.onVideoStartReached]) + * or when end reached ([SeekListener.onVideoEndReached]) + * + * @param newPosition desired position + */ + private fun seekToPosition(newPosition: Long?) { + if (newPosition == null) return + + // Start of the video reached + if (newPosition <= 0) { + player?.seekTo(0) + + seekListener?.onVideoStartReached() + return + } + + // End of the video reached + player?.duration?.let { total -> + if (newPosition >= total) { + player?.seekTo(total) + + seekListener?.onVideoEndReached() + return + } + } + + // Otherwise + playerView?.keepInDoubleTapMode() + player?.seekTo(newPosition) + } + + private fun forwarding() { + secondsView.seconds += seekSeconds + seekToPosition(player?.currentPosition?.plus(seekSeconds * 1000)) + } + + private fun rewinding() { + secondsView.seconds += seekSeconds + seekToPosition(player?.currentPosition?.minus(seekSeconds * 1000)) + } + + private fun changeConstraints(forward: Boolean) { + val constraintSet = ConstraintSet() + with(constraintSet) { + clone(rootLayout) + if (forward) { + clear(secondsView.id, ConstraintSet.START) + connect(secondsView.id, ConstraintSet.END, + ConstraintSet.PARENT_ID, ConstraintSet.END) + } else { + clear(secondsView.id, ConstraintSet.END) + connect(secondsView.id, ConstraintSet.START, + ConstraintSet.PARENT_ID, ConstraintSet.START) + } + secondsView.start() + applyTo(rootLayout) + } + } + + interface PerformListener { + /** + * Called when the overlay is not visible and onDoubleTapProgressUp event occurred. + * Visibility of the overlay should be set to VISIBLE within this interface method. + */ + fun onAnimationStart() + + /** + * Called when the circle animation is finished. + * Visibility of the overlay should be set to GONE within this interface method. + */ + fun onAnimationEnd() + + /** + * Determines whether the player should forward, rewind or skip this tap by doing + * nothing / ignoring. Is called for each tap. + * + * By overriding this method you can check for self-defined conditions whether showing the + * overlay and rewinding/forwarding (e.g. if the media source valid) or skip it. + * + * In the following you see the default conditions for each action (if there is no media + * to play ([PlaybackState.STATE_NONE]), an error occurred ([PlaybackState.STATE_ERROR]) + * or the media is stopped ([PlaybackState.STATE_STOPPED]) the tap will be ignored in any + * case): + * + * + * | Action | Current position | Screen width portion | + * |---------|---------------------------|----------------------| + * | rewind | greater than 500 ms | 0% to 35% | + * | forward | less than total duration | 65% to 100% | + * | ignore | ------------ | between 35% and 65% | + * + * @param player Current [Player] + * @param playerView [PlayerView] which accepts the taps + * @param posX Position of the tap on the x-axis + * + * @return `true` to forward, `false` to rewind or `null` to ignore. + */ + fun shouldForward(player: Player, playerView: DoubleTapPlayerView, posX: Float): Boolean? { + + if (player.playbackState == PlaybackState.STATE_ERROR || + player.playbackState == PlaybackState.STATE_NONE || + player.playbackState == PlaybackState.STATE_STOPPED) { + + playerView.cancelInDoubleTapMode() + return null + } + + if (player.currentPosition > 500 && posX < playerView.width * 0.35) + return false + + if (player.currentPosition < player.duration && posX > playerView.width * 0.65) + return true + + return null + } + } +} diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/CircleClipTapView.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/CircleClipTapView.kt new file mode 100644 index 000000000..53d84e4ff --- /dev/null +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/CircleClipTapView.kt @@ -0,0 +1,221 @@ +package com.github.vkay94.dtpv.youtube.views + +import android.animation.Animator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.github.vkay94.dtpv.R + +/** + * View class + * + * Draws a arc shape and provides a circle scaling animation. + * Used by [YouTubeOverlay][com.github.vkay94.dtpv.youtube.YouTubeOverlay]. + */ +internal class CircleClipTapView(context: Context?, attrs: AttributeSet) : + View(context, attrs) { + + private var backgroundPaint = Paint() + private var circlePaint = Paint() + + private var widthPx = 0 + private var heightPx = 0 + + // Background + + private var shapePath = Path() + private var isLeft = true + + // Circle + + private var cX = 0f + private var cY = 0f + + private var currentRadius = 0f + private var minRadius: Int = 0 + private var maxRadius: Int = 0 + + // Animation + + private var valueAnimator: ValueAnimator? = null + private var forceReset = false + + init { + requireNotNull(context) { "Context is null." } + + backgroundPaint.apply { + style = Paint.Style.FILL + isAntiAlias = true + color = ContextCompat.getColor(context, R.color.dtpv_yt_background_circle_color) + } + + circlePaint.apply { + style = Paint.Style.FILL + isAntiAlias = true + color = ContextCompat.getColor(context, R.color.dtpv_yt_tap_circle_color) + } + + // Pre-configuations depending on device display metrics + val dm = context.resources.displayMetrics + + widthPx = dm.widthPixels + heightPx = dm.heightPixels + + minRadius = (30f * dm.density).toInt() + maxRadius = (400f * dm.density).toInt() + + updatePathShape() + + valueAnimator = getCircleAnimator() + } + + var performAtEnd: () -> Unit = { } + + /* + Getter and setter + */ + + var arcSize: Float = 80f + set(value) { + field = value + updatePathShape() + } + + var circleBackgroundColor: Int + get() = backgroundPaint.color + set(value) { + backgroundPaint.color = value + } + + var circleColor: Int + get() = circlePaint.color + set(value) { + circlePaint.color = value + } + + var animationDuration: Long + get() = valueAnimator?.duration ?: 650 + set(value) { + getCircleAnimator().duration = value + } + + /* + Methods + */ + + /* + Circle + */ + + fun updatePosition(x: Float, y: Float) { + cX = x + cY = y + + val newIsLeft = x <= resources.displayMetrics.widthPixels / 2 + if (isLeft != newIsLeft) { + isLeft = newIsLeft + updatePathShape() + } + } + + private fun invalidateWithCurrentRadius(factor: Float) { + currentRadius = minRadius + ((maxRadius - minRadius) * factor) + invalidate() + } + + /* + Background + */ + + private fun updatePathShape() { + val halfWidth = widthPx * 0.5f + + shapePath.reset() +// shapePath.fillType = Path.FillType.WINDING + + val w = if (isLeft) 0f else widthPx.toFloat() + val f = if (isLeft) 1 else -1 + + shapePath.moveTo(w, 0f) + shapePath.lineTo(f * (halfWidth - arcSize) + w, 0f) + shapePath.quadTo( + f * (halfWidth + arcSize) + w, + heightPx.toFloat() / 2, + f * (halfWidth - arcSize) + w, + heightPx.toFloat() + ) + shapePath.lineTo(w, heightPx.toFloat()) + + shapePath.close() + invalidate() + } + + /* + Animation + */ + + private fun getCircleAnimator(): ValueAnimator { + if (valueAnimator == null) { + valueAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = animationDuration +// interpolator = LinearInterpolator() + addUpdateListener { + invalidateWithCurrentRadius(it.animatedValue as Float) + } + + addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator?) { + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + if (!forceReset) performAtEnd() + } + + override fun onAnimationRepeat(animation: Animator?) {} + override fun onAnimationCancel(animation: Animator?) {} + }) + } + } + return valueAnimator!! + } + + fun resetAnimation(body: () -> Unit) { + forceReset = true + getCircleAnimator().end() + body() + forceReset = false + getCircleAnimator().start() + } + + fun endAnimation() { + getCircleAnimator().end() + } + + /* + Others: Drawing and Measurements + */ + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + widthPx = w + heightPx = h + updatePathShape() + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + + // Background + canvas?.clipPath(shapePath) + canvas?.drawPath(shapePath, backgroundPaint) + + // Circle + canvas?.drawCircle(cX, cY, currentRadius, circlePaint) + } +} \ No newline at end of file diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt new file mode 100644 index 000000000..6e4c1f339 --- /dev/null +++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/youtube/views/YouTubeSecondsView.kt @@ -0,0 +1,201 @@ +package com.github.vkay94.dtpv.youtube.views + +import android.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart +import com.github.vkay94.dtpv.R + +/** + * Layout group which handles the icon animation while forwarding and rewinding. + * + * Since it's based on view's alpha the fading effect is more fluid (more YouTube-like) than + * using static drawables, especially when [cycleDuration] is low. + * + * Used by [YouTubeOverlay][com.github.vkay94.dtpv.youtube.YouTubeOverlay]. + */ +class SecondsView(context: Context, attrs: AttributeSet?) : + ConstraintLayout(context, attrs) { + + private var trianglesContainer: LinearLayout + private var secondsTextView: TextView + private var icon1: ImageView + private var icon2: ImageView + private var icon3: ImageView + + init { + LayoutInflater.from(context).inflate(R.layout.yt_seconds_view, this, true) + + trianglesContainer = findViewById(R.id.triangle_container) + secondsTextView = findViewById(R.id.tv_seconds) + icon1 = findViewById(R.id.icon_1) + icon2 = findViewById(R.id.icon_2) + icon3 = findViewById(R.id.icon_3) + } + + /** + * Defines the duration for a full cycle of the triangle animation. + * Each animation step takes 20% of it. + */ + var cycleDuration: Long = 750L + set(value) { + firstAnimator.duration = value / 5 + secondAnimator.duration = value / 5 + thirdAnimator.duration = value / 5 + fourthAnimator.duration = value / 5 + fifthAnimator.duration = value / 5 + field = value + } + + /** + * Sets the `TextView`'s seconds text according to the device`s language. + */ + var seconds: Int = 0 + set(value) { + secondsTextView.text = context.resources.getQuantityString( + R.plurals.quick_seek_x_second, value, value + ) + field = value + } + + /** + * Mirrors the triangles depending on what kind of type should be used (forward/rewind). + */ + var isForward: Boolean = true + set(value) { + trianglesContainer.rotation = if (value) 0f else 180f + field = value + } + + val textView: TextView + get() = secondsTextView + + @DrawableRes + var icon: Int = R.drawable.ic_play_triangle + set(value) { + if (value > 0) { + icon1.setImageResource(value) + icon2.setImageResource(value) + icon3.setImageResource(value) + } + field = value + } + + /** + * Starts the triangle animation + */ + fun start() { + stop() + firstAnimator.start() + } + + /** + * Stops the triangle animation + */ + fun stop() { + firstAnimator.cancel() + secondAnimator.cancel() + thirdAnimator.cancel() + fourthAnimator.cancel() + fifthAnimator.cancel() + reset() + } + + private fun reset() { + icon1.alpha = 0f + icon2.alpha = 0f + icon3.alpha = 0f + } + + private val firstAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(0f, 1f).setDuration(cycleDuration / 5).apply { + doOnStart { + icon1.alpha = 0f + icon2.alpha = 0f + icon3.alpha = 0f + } + addUpdateListener { + icon1.alpha = (it.animatedValue as Float) + } + + doOnEnd { + secondAnimator.start() + } + } + } + + private val secondAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(0f, 1f).setDuration(cycleDuration / 5).apply { + doOnStart { + icon1.alpha = 1f + icon2.alpha = 0f + icon3.alpha = 0f + } + addUpdateListener { + icon2.alpha = (it.animatedValue as Float) + } + doOnEnd { + thirdAnimator.start() + } + } + } + + private val thirdAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(0f, 1f).setDuration(cycleDuration / 5).apply { + doOnStart { + icon1.alpha = 1f + icon2.alpha = 1f + icon3.alpha = 0f + } + addUpdateListener { + icon1.alpha = + 1f - icon3.alpha // or 1f - it (t3.alpha => all three stay a little longer together) + icon3.alpha = (it.animatedValue as Float) + } + doOnEnd { + fourthAnimator.start() + } + } + + } + + private val fourthAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(0f, 1f).setDuration(cycleDuration / 5).apply { + doOnStart { + icon1.alpha = 0f + icon2.alpha = 1f + icon3.alpha = 1f + } + addUpdateListener { + icon2.alpha = 1f - (it.animatedValue as Float) + } + doOnEnd { + fifthAnimator.start() + } + + } + } + + private val fifthAnimator: ValueAnimator by lazy { + ValueAnimator.ofFloat(0f, 1f).setDuration(cycleDuration / 5).apply { + doOnStart { + icon1.alpha = 0f + icon2.alpha = 0f + icon3.alpha = 1f + } + addUpdateListener { + icon3.alpha = 1f - (it.animatedValue as Float) + } + doOnEnd { + firstAnimator.start() + } + } + } +} \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/drawable/ic_play_triangle.xml b/doubletapplayerview/src/main/res/drawable/ic_play_triangle.xml new file mode 100644 index 000000000..1aee026db --- /dev/null +++ b/doubletapplayerview/src/main/res/drawable/ic_play_triangle.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/doubletapplayerview/src/main/res/layout/yt_overlay.xml b/doubletapplayerview/src/main/res/layout/yt_overlay.xml new file mode 100644 index 000000000..ca56f3c5e --- /dev/null +++ b/doubletapplayerview/src/main/res/layout/yt_overlay.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/layout/yt_seconds_view.xml b/doubletapplayerview/src/main/res/layout/yt_seconds_view.xml new file mode 100644 index 000000000..88d3c0ab0 --- /dev/null +++ b/doubletapplayerview/src/main/res/layout/yt_seconds_view.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-af/plurals.xml b/doubletapplayerview/src/main/res/values-af/plurals.xml new file mode 100644 index 000000000..277d313ca --- /dev/null +++ b/doubletapplayerview/src/main/res/values-af/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekondes + %d sekonde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-am/plurals.xml b/doubletapplayerview/src/main/res/values-am/plurals.xml new file mode 100644 index 000000000..f435422d7 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-am/plurals.xml @@ -0,0 +1,6 @@ + + + %d ሰከንዶች + %d ሰከንድ + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ar/plurals.xml b/doubletapplayerview/src/main/res/values-ar/plurals.xml new file mode 100644 index 000000000..1dc70d26c --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ar/plurals.xml @@ -0,0 +1,10 @@ + + + %d ثانية + %d ثانية + ثانية واحدة (%d) + ثانيتان (%d) + %d ثوانٍ + %d ثانية + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-az/plurals.xml b/doubletapplayerview/src/main/res/values-az/plurals.xml new file mode 100644 index 000000000..3d8282238 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-az/plurals.xml @@ -0,0 +1,6 @@ + + + %d saniyə + %d saniyə + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-b+sr+Latn/plurals.xml b/doubletapplayerview/src/main/res/values-b+sr+Latn/plurals.xml new file mode 100644 index 000000000..767192c58 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-b+sr+Latn/plurals.xml @@ -0,0 +1,7 @@ + + + %d sekundi + %d sekunda + %d sekunde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-be/plurals.xml b/doubletapplayerview/src/main/res/values-be/plurals.xml new file mode 100644 index 000000000..788f46a88 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-be/plurals.xml @@ -0,0 +1,8 @@ + + + %d секунды + %d секунда + %d секунды + %d секунд + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-bg/plurals.xml b/doubletapplayerview/src/main/res/values-bg/plurals.xml new file mode 100644 index 000000000..42fecc0bf --- /dev/null +++ b/doubletapplayerview/src/main/res/values-bg/plurals.xml @@ -0,0 +1,6 @@ + + + %d секунди + %d секунда + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-bn/plurals.xml b/doubletapplayerview/src/main/res/values-bn/plurals.xml new file mode 100644 index 000000000..bcbdc2668 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-bn/plurals.xml @@ -0,0 +1,6 @@ + + + %d সেকেন্ড + %d সেকেন্ড + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-bs/plurals.xml b/doubletapplayerview/src/main/res/values-bs/plurals.xml new file mode 100644 index 000000000..897c547b8 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-bs/plurals.xml @@ -0,0 +1,7 @@ + + + %d s + %d s + %d s + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ca/plurals.xml b/doubletapplayerview/src/main/res/values-ca/plurals.xml new file mode 100644 index 000000000..635a26643 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ca/plurals.xml @@ -0,0 +1,6 @@ + + + %d segons + %d segon + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-cs/plurals.xml b/doubletapplayerview/src/main/res/values-cs/plurals.xml new file mode 100644 index 000000000..902a450c0 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-cs/plurals.xml @@ -0,0 +1,8 @@ + + + %d sekund + %d sekunda + %d sekundy + %d sekundy + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-da/plurals.xml b/doubletapplayerview/src/main/res/values-da/plurals.xml new file mode 100644 index 000000000..8fa3643a2 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-da/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekunder + %d sekund + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-de/plurals.xml b/doubletapplayerview/src/main/res/values-de/plurals.xml new file mode 100644 index 000000000..af650df28 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-de/plurals.xml @@ -0,0 +1,6 @@ + + + %d Sekunden + %d Sekunde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-el/plurals.xml b/doubletapplayerview/src/main/res/values-el/plurals.xml new file mode 100644 index 000000000..586e10813 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-el/plurals.xml @@ -0,0 +1,6 @@ + + + %d δευτερόλεπτα + %d δευτερόλεπτο + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-en-rGB/plurals.xml b/doubletapplayerview/src/main/res/values-en-rGB/plurals.xml new file mode 100644 index 000000000..43b3a2c3f --- /dev/null +++ b/doubletapplayerview/src/main/res/values-en-rGB/plurals.xml @@ -0,0 +1,6 @@ + + + %d seconds + %d second + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-en-rIN/plurals.xml b/doubletapplayerview/src/main/res/values-en-rIN/plurals.xml new file mode 100644 index 000000000..43b3a2c3f --- /dev/null +++ b/doubletapplayerview/src/main/res/values-en-rIN/plurals.xml @@ -0,0 +1,6 @@ + + + %d seconds + %d second + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-es-rUS/plurals.xml b/doubletapplayerview/src/main/res/values-es-rUS/plurals.xml new file mode 100644 index 000000000..706757b20 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-es-rUS/plurals.xml @@ -0,0 +1,6 @@ + + + %d segundos + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-es/plurals.xml b/doubletapplayerview/src/main/res/values-es/plurals.xml new file mode 100644 index 000000000..706757b20 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-es/plurals.xml @@ -0,0 +1,6 @@ + + + %d segundos + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-et/plurals.xml b/doubletapplayerview/src/main/res/values-et/plurals.xml new file mode 100644 index 000000000..52c87acb3 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-et/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekundit + %d sekund + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-eu/plurals.xml b/doubletapplayerview/src/main/res/values-eu/plurals.xml new file mode 100644 index 000000000..e1a0845c8 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-eu/plurals.xml @@ -0,0 +1,6 @@ + + + %d segundo + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-fa/plurals.xml b/doubletapplayerview/src/main/res/values-fa/plurals.xml new file mode 100644 index 000000000..d0bf84a54 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-fa/plurals.xml @@ -0,0 +1,6 @@ + + + %d ثانیه + %d ثانیه + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-fi/plurals.xml b/doubletapplayerview/src/main/res/values-fi/plurals.xml new file mode 100644 index 000000000..261c11c32 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-fi/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekuntia + %d sekunti + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-fr-rCA/plurals.xml b/doubletapplayerview/src/main/res/values-fr-rCA/plurals.xml new file mode 100644 index 000000000..7bf19751e --- /dev/null +++ b/doubletapplayerview/src/main/res/values-fr-rCA/plurals.xml @@ -0,0 +1,6 @@ + + + %d secondes + %d seconde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-fr/plurals.xml b/doubletapplayerview/src/main/res/values-fr/plurals.xml new file mode 100644 index 000000000..7bf19751e --- /dev/null +++ b/doubletapplayerview/src/main/res/values-fr/plurals.xml @@ -0,0 +1,6 @@ + + + %d secondes + %d seconde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-gl/plurals.xml b/doubletapplayerview/src/main/res/values-gl/plurals.xml new file mode 100644 index 000000000..706757b20 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-gl/plurals.xml @@ -0,0 +1,6 @@ + + + %d segundos + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-gu/plurals.xml b/doubletapplayerview/src/main/res/values-gu/plurals.xml new file mode 100644 index 000000000..ec6605017 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-gu/plurals.xml @@ -0,0 +1,6 @@ + + + %d સેકન્ડ + %d સેકન્ડ + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-hi/plurals.xml b/doubletapplayerview/src/main/res/values-hi/plurals.xml new file mode 100644 index 000000000..969b3dc81 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-hi/plurals.xml @@ -0,0 +1,6 @@ + + + %d सेकंड + %d सेकंड + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-hr/plurals.xml b/doubletapplayerview/src/main/res/values-hr/plurals.xml new file mode 100644 index 000000000..767192c58 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-hr/plurals.xml @@ -0,0 +1,7 @@ + + + %d sekundi + %d sekunda + %d sekunde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-hu/plurals.xml b/doubletapplayerview/src/main/res/values-hu/plurals.xml new file mode 100644 index 000000000..df3f9b6d5 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-hu/plurals.xml @@ -0,0 +1,6 @@ + + + %d másodperc + %d másodperc + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-hy/plurals.xml b/doubletapplayerview/src/main/res/values-hy/plurals.xml new file mode 100644 index 000000000..a4b9e33e3 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-hy/plurals.xml @@ -0,0 +1,6 @@ + + + %d վայրկյան + %d վայրկյան + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-in/plurals.xml b/doubletapplayerview/src/main/res/values-in/plurals.xml new file mode 100644 index 000000000..210cf9d6c --- /dev/null +++ b/doubletapplayerview/src/main/res/values-in/plurals.xml @@ -0,0 +1,6 @@ + + + %d detik + %d detik + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-is/plurals.xml b/doubletapplayerview/src/main/res/values-is/plurals.xml new file mode 100644 index 000000000..a48e77632 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-is/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekúndur + %d sekúnda + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-it/plurals.xml b/doubletapplayerview/src/main/res/values-it/plurals.xml new file mode 100644 index 000000000..e90c71044 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-it/plurals.xml @@ -0,0 +1,6 @@ + + + %d secondi + %d secondo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-iw/plurals.xml b/doubletapplayerview/src/main/res/values-iw/plurals.xml new file mode 100644 index 000000000..9c7f41429 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-iw/plurals.xml @@ -0,0 +1,8 @@ + + + %d שניות + שנייה אחת (%d) + %d שניות + %d שניות + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ja/plurals.xml b/doubletapplayerview/src/main/res/values-ja/plurals.xml new file mode 100644 index 000000000..aa7592329 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ja/plurals.xml @@ -0,0 +1,6 @@ + + + %d秒 + %d秒 + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ka/plurals.xml b/doubletapplayerview/src/main/res/values-ka/plurals.xml new file mode 100644 index 000000000..342b53031 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ka/plurals.xml @@ -0,0 +1,6 @@ + + + %d წამი + %d წამი + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-kk/plurals.xml b/doubletapplayerview/src/main/res/values-kk/plurals.xml new file mode 100644 index 000000000..8b1c9993b --- /dev/null +++ b/doubletapplayerview/src/main/res/values-kk/plurals.xml @@ -0,0 +1,6 @@ + + + %d секунд + %d секунд + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-km/plurals.xml b/doubletapplayerview/src/main/res/values-km/plurals.xml new file mode 100644 index 000000000..3b36a5562 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-km/plurals.xml @@ -0,0 +1,6 @@ + + + %d វិនាទី + %d វិនាទី + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-kn/plurals.xml b/doubletapplayerview/src/main/res/values-kn/plurals.xml new file mode 100644 index 000000000..2f6aa177d --- /dev/null +++ b/doubletapplayerview/src/main/res/values-kn/plurals.xml @@ -0,0 +1,6 @@ + + + %d ಸೆಕೆಂಡ್‌ಗಳು + %d ಸೆಕೆಂಡ್‌ಗಳು + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ko/plurals.xml b/doubletapplayerview/src/main/res/values-ko/plurals.xml new file mode 100644 index 000000000..58dc87dda --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ko/plurals.xml @@ -0,0 +1,6 @@ + + + %d초 + %d초 + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ky/plurals.xml b/doubletapplayerview/src/main/res/values-ky/plurals.xml new file mode 100644 index 000000000..8b1c9993b --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ky/plurals.xml @@ -0,0 +1,6 @@ + + + %d секунд + %d секунд + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-lo/plurals.xml b/doubletapplayerview/src/main/res/values-lo/plurals.xml new file mode 100644 index 000000000..b1008c308 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-lo/plurals.xml @@ -0,0 +1,6 @@ + + + %d ວິນາທີ + %d ວິນາທີ + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-lt/plurals.xml b/doubletapplayerview/src/main/res/values-lt/plurals.xml new file mode 100644 index 000000000..23267e557 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-lt/plurals.xml @@ -0,0 +1,8 @@ + + + %d sekundžių + %d sekundė + %d sekundės + %d sekundės + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-lv/plurals.xml b/doubletapplayerview/src/main/res/values-lv/plurals.xml new file mode 100644 index 000000000..b7c129243 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-lv/plurals.xml @@ -0,0 +1,7 @@ + + + %d sekundes + %d sekundes + %d sekunde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-mk/plurals.xml b/doubletapplayerview/src/main/res/values-mk/plurals.xml new file mode 100644 index 000000000..42fecc0bf --- /dev/null +++ b/doubletapplayerview/src/main/res/values-mk/plurals.xml @@ -0,0 +1,6 @@ + + + %d секунди + %d секунда + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ml/plurals.xml b/doubletapplayerview/src/main/res/values-ml/plurals.xml new file mode 100644 index 000000000..293dc5ad8 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ml/plurals.xml @@ -0,0 +1,6 @@ + + + %d സെക്കൻഡ് + %d സെക്കൻഡ് + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-mn/plurals.xml b/doubletapplayerview/src/main/res/values-mn/plurals.xml new file mode 100644 index 000000000..8b1c9993b --- /dev/null +++ b/doubletapplayerview/src/main/res/values-mn/plurals.xml @@ -0,0 +1,6 @@ + + + %d секунд + %d секунд + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-mr/plurals.xml b/doubletapplayerview/src/main/res/values-mr/plurals.xml new file mode 100644 index 000000000..07c8d086c --- /dev/null +++ b/doubletapplayerview/src/main/res/values-mr/plurals.xml @@ -0,0 +1,6 @@ + + + %d सेकंद + %d सेकंद + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ms/plurals.xml b/doubletapplayerview/src/main/res/values-ms/plurals.xml new file mode 100644 index 000000000..eba204046 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ms/plurals.xml @@ -0,0 +1,6 @@ + + + %d saat + %d saat + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-my/plurals.xml b/doubletapplayerview/src/main/res/values-my/plurals.xml new file mode 100644 index 000000000..bc82c58df --- /dev/null +++ b/doubletapplayerview/src/main/res/values-my/plurals.xml @@ -0,0 +1,6 @@ + + + %d စက္ကန့် + %d စက္ကန့် + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-nb/plurals.xml b/doubletapplayerview/src/main/res/values-nb/plurals.xml new file mode 100644 index 000000000..8fa3643a2 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-nb/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekunder + %d sekund + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ne/plurals.xml b/doubletapplayerview/src/main/res/values-ne/plurals.xml new file mode 100644 index 000000000..128c6cd7e --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ne/plurals.xml @@ -0,0 +1,6 @@ + + + %d सेकेन्ड + %d सेकेन्ड + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-nl/plurals.xml b/doubletapplayerview/src/main/res/values-nl/plurals.xml new file mode 100644 index 000000000..f670f2064 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-nl/plurals.xml @@ -0,0 +1,6 @@ + + + %d seconden + %d seconde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-pa/plurals.xml b/doubletapplayerview/src/main/res/values-pa/plurals.xml new file mode 100644 index 000000000..0b2aa4a95 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-pa/plurals.xml @@ -0,0 +1,6 @@ + + + %d ਸਕਿੰਟ + %d ਸਕਿੰਟ + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-pl/plurals.xml b/doubletapplayerview/src/main/res/values-pl/plurals.xml new file mode 100644 index 000000000..b46bce408 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-pl/plurals.xml @@ -0,0 +1,8 @@ + + + %d sekundy + %d sekunda + %d sekundy + %d sekund + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-pt-rBR/plurals.xml b/doubletapplayerview/src/main/res/values-pt-rBR/plurals.xml new file mode 100644 index 000000000..a405d3199 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-pt-rBR/plurals.xml @@ -0,0 +1,6 @@ + + + %d segundos + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-pt-rPT/plurals.xml b/doubletapplayerview/src/main/res/values-pt-rPT/plurals.xml new file mode 100644 index 000000000..a405d3199 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-pt-rPT/plurals.xml @@ -0,0 +1,6 @@ + + + %d segundos + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ro/plurals.xml b/doubletapplayerview/src/main/res/values-ro/plurals.xml new file mode 100644 index 000000000..7ef47f65d --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ro/plurals.xml @@ -0,0 +1,7 @@ + + + %d de secunde + %d secundă + %d secunde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ru/plurals.xml b/doubletapplayerview/src/main/res/values-ru/plurals.xml new file mode 100644 index 000000000..b6e263ffd --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ru/plurals.xml @@ -0,0 +1,8 @@ + + + %d секунды + %d секунда + %d секунды + %d секунд + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-si/plurals.xml b/doubletapplayerview/src/main/res/values-si/plurals.xml new file mode 100644 index 000000000..82df6075f --- /dev/null +++ b/doubletapplayerview/src/main/res/values-si/plurals.xml @@ -0,0 +1,6 @@ + + + තත්පර %dක් + තත්පර %dක් + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-sk/plurals.xml b/doubletapplayerview/src/main/res/values-sk/plurals.xml new file mode 100644 index 000000000..92c4bf706 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-sk/plurals.xml @@ -0,0 +1,8 @@ + + + %d sekúnd + %d sekunda + %d sekundy + %d sekundy + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-sl/plurals.xml b/doubletapplayerview/src/main/res/values-sl/plurals.xml new file mode 100644 index 000000000..89f5f5187 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-sl/plurals.xml @@ -0,0 +1,8 @@ + + + %d sekund + %d sekunda + %d sekundi + %d sekunde + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-sq/plurals.xml b/doubletapplayerview/src/main/res/values-sq/plurals.xml new file mode 100644 index 000000000..3e7ac358d --- /dev/null +++ b/doubletapplayerview/src/main/res/values-sq/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekonda + %d sekondë + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-sr/plurals.xml b/doubletapplayerview/src/main/res/values-sr/plurals.xml new file mode 100644 index 000000000..b51d400b7 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-sr/plurals.xml @@ -0,0 +1,7 @@ + + + %d секунди + %d секунда + %d секунде + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-sv/plurals.xml b/doubletapplayerview/src/main/res/values-sv/plurals.xml new file mode 100644 index 000000000..8fa3643a2 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-sv/plurals.xml @@ -0,0 +1,6 @@ + + + %d sekunder + %d sekund + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-sw/plurals.xml b/doubletapplayerview/src/main/res/values-sw/plurals.xml new file mode 100644 index 000000000..f3b740d5b --- /dev/null +++ b/doubletapplayerview/src/main/res/values-sw/plurals.xml @@ -0,0 +1,6 @@ + + + Sekunde %d + Sekunde %d + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ta/plurals.xml b/doubletapplayerview/src/main/res/values-ta/plurals.xml new file mode 100644 index 000000000..22e5e1061 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ta/plurals.xml @@ -0,0 +1,6 @@ + + + %d விநாடிகள் + %d விநாடி + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-te/plurals.xml b/doubletapplayerview/src/main/res/values-te/plurals.xml new file mode 100644 index 000000000..0c22eb18a --- /dev/null +++ b/doubletapplayerview/src/main/res/values-te/plurals.xml @@ -0,0 +1,6 @@ + + + %d సెకన్లు + %d సెకను + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-th/plurals.xml b/doubletapplayerview/src/main/res/values-th/plurals.xml new file mode 100644 index 000000000..0a878d65e --- /dev/null +++ b/doubletapplayerview/src/main/res/values-th/plurals.xml @@ -0,0 +1,6 @@ + + + %d วินาที + %d วินาที + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-tl/plurals.xml b/doubletapplayerview/src/main/res/values-tl/plurals.xml new file mode 100644 index 000000000..020f62987 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-tl/plurals.xml @@ -0,0 +1,6 @@ + + + %d na segundo + %d segundo + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-tr/plurals.xml b/doubletapplayerview/src/main/res/values-tr/plurals.xml new file mode 100644 index 000000000..028244d7d --- /dev/null +++ b/doubletapplayerview/src/main/res/values-tr/plurals.xml @@ -0,0 +1,6 @@ + + + %d saniye + %d saniye + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-uk/plurals.xml b/doubletapplayerview/src/main/res/values-uk/plurals.xml new file mode 100644 index 000000000..d7ce533bd --- /dev/null +++ b/doubletapplayerview/src/main/res/values-uk/plurals.xml @@ -0,0 +1,8 @@ + + + %d секунди + %d секунда + %d секунди + %d секунд + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-ur/plurals.xml b/doubletapplayerview/src/main/res/values-ur/plurals.xml new file mode 100644 index 000000000..c0a9f2c87 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-ur/plurals.xml @@ -0,0 +1,6 @@ + + + %d سیکنڈز + %d سیکنڈ + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-uz/plurals.xml b/doubletapplayerview/src/main/res/values-uz/plurals.xml new file mode 100644 index 000000000..aa48deace --- /dev/null +++ b/doubletapplayerview/src/main/res/values-uz/plurals.xml @@ -0,0 +1,6 @@ + + + %d soniya + %d soniya + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-vi/plurals.xml b/doubletapplayerview/src/main/res/values-vi/plurals.xml new file mode 100644 index 000000000..6de2191b2 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-vi/plurals.xml @@ -0,0 +1,6 @@ + + + %d giây + %d giây + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-zh-rCN/plurals.xml b/doubletapplayerview/src/main/res/values-zh-rCN/plurals.xml new file mode 100644 index 000000000..48443b151 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-zh-rCN/plurals.xml @@ -0,0 +1,6 @@ + + + %d 秒 + %d 秒 + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-zh-rHK/plurals.xml b/doubletapplayerview/src/main/res/values-zh-rHK/plurals.xml new file mode 100644 index 000000000..48443b151 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-zh-rHK/plurals.xml @@ -0,0 +1,6 @@ + + + %d 秒 + %d 秒 + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-zh-rTW/plurals.xml b/doubletapplayerview/src/main/res/values-zh-rTW/plurals.xml new file mode 100644 index 000000000..48443b151 --- /dev/null +++ b/doubletapplayerview/src/main/res/values-zh-rTW/plurals.xml @@ -0,0 +1,6 @@ + + + %d 秒 + %d 秒 + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values-zu/plurals.xml b/doubletapplayerview/src/main/res/values-zu/plurals.xml new file mode 100644 index 000000000..d20ee654b --- /dev/null +++ b/doubletapplayerview/src/main/res/values-zu/plurals.xml @@ -0,0 +1,6 @@ + + + %d amasekhondi + %d amasekhondi + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values/dtpv.xml b/doubletapplayerview/src/main/res/values/dtpv.xml new file mode 100644 index 000000000..141a0dba4 --- /dev/null +++ b/doubletapplayerview/src/main/res/values/dtpv.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values/plurals.xml b/doubletapplayerview/src/main/res/values/plurals.xml new file mode 100644 index 000000000..acccc7b4a --- /dev/null +++ b/doubletapplayerview/src/main/res/values/plurals.xml @@ -0,0 +1,7 @@ + + + %d seconds + %d second + %d seconds + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values/public.xml b/doubletapplayerview/src/main/res/values/public.xml new file mode 100644 index 000000000..ed8500e12 --- /dev/null +++ b/doubletapplayerview/src/main/res/values/public.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doubletapplayerview/src/main/res/values/yt_overlay.xml b/doubletapplayerview/src/main/res/values/yt_overlay.xml new file mode 100644 index 000000000..40f5adae3 --- /dev/null +++ b/doubletapplayerview/src/main/res/values/yt_overlay.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + #18FFFFFF + #20EEEEEE + 40dp + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 907710f87..1730e30da 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,3 +6,4 @@ include ':ratethisapp' include ':sparkbutton' include ':mathjaxandroid' include ':colorPicker' +include ':doubletapplayerview'