mirror of
https://codeberg.org/tom79/Fedilab.git
synced 2024-12-21 16:20:03 +02:00
Prepare Media3
This commit is contained in:
parent
596c546cce
commit
c778c71306
108 changed files with 1993 additions and 73 deletions
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<RecyclerView.ViewHolder>
|
|||
* @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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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;
|
||||
|
|
|
@ -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<Integer> SUPPORTED_TRACK_TYPES =
|
||||
|
|
|
@ -65,14 +65,14 @@
|
|||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/media_video"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
app:use_controller="false" />
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlayerControlView
|
||||
<androidx.media3.ui.PlayerControlView
|
||||
android:id="@+id/controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/media_video"
|
||||
app:shutter_background_color="@color/transparent"
|
||||
app:surface_type="texture_view"
|
||||
|
|
1
doubletapplayerview/.gitignore
vendored
Normal file
1
doubletapplayerview/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
38
doubletapplayerview/build.gradle
Normal file
38
doubletapplayerview/build.gradle
Normal file
|
@ -0,0 +1,38 @@
|
|||
apply plugin: 'com.android.library'
|
||||
android {
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
namespace 'com.github.vkay94.dtpv'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation "androidx.core:core-ktx:1.12.0"
|
||||
|
||||
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.constraintlayout:constraintlayout:2.1.4"
|
||||
}
|
0
doubletapplayerview/consumer-rules.pro
Normal file
0
doubletapplayerview/consumer-rules.pro
Normal file
21
doubletapplayerview/proguard-rules.pro
vendored
Normal file
21
doubletapplayerview/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
2
doubletapplayerview/src/main/AndroidManifest.xml
Normal file
2
doubletapplayerview/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.vkay94.dtpv" />
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() { }
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3,2 L22,12 L3,22 Z" />
|
||||
|
||||
</vector>
|
30
doubletapplayerview/src/main/res/layout/yt_overlay.xml
Normal file
30
doubletapplayerview/src/main/res/layout/yt_overlay.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/root_constraint_layout"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.github.vkay94.dtpv.youtube.views.CircleClipTapView
|
||||
android:id="@+id/circle_clip_tap_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="false"
|
||||
android:focusable="false" />
|
||||
|
||||
<com.github.vkay94.dtpv.youtube.views.SecondsView
|
||||
android:id="@+id/seconds_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_default="percent"
|
||||
app:layout_constraintWidth_percent="0.5"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
48
doubletapplayerview/src/main/res/layout/yt_seconds_view.xml
Normal file
48
doubletapplayerview/src/main/res/layout/yt_seconds_view.xml
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="ContentDescription">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/triangle_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_play_triangle"
|
||||
tools:alpha="0.18" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_play_triangle"
|
||||
tools:alpha="0.5" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_play_triangle"
|
||||
tools:alpha="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/tv_seconds"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="4dp"
|
||||
tools:text="20 Sekunden" />
|
||||
|
||||
</LinearLayout>
|
6
doubletapplayerview/src/main/res/values-af/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-af/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekondes</item>
|
||||
<item quantity="one">%d sekonde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-am/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-am/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d ሰከንዶች</item>
|
||||
<item quantity="one">%d ሰከንድ</item>
|
||||
</plurals>
|
||||
</resources>
|
10
doubletapplayerview/src/main/res/values-ar/plurals.xml
Normal file
10
doubletapplayerview/src/main/res/values-ar/plurals.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d ثانية</item>
|
||||
<item quantity="zero">%d ثانية</item>
|
||||
<item quantity="one">ثانية واحدة (%d)</item>
|
||||
<item quantity="two">ثانيتان (%d)</item>
|
||||
<item quantity="few">%d ثوانٍ</item>
|
||||
<item quantity="many">%d ثانية</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-az/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-az/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d saniyə</item>
|
||||
<item quantity="one">%d saniyə</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekundi</item>
|
||||
<item quantity="one">%d sekunda</item>
|
||||
<item quantity="few">%d sekunde</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-be/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-be/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунды</item>
|
||||
<item quantity="one">%d секунда</item>
|
||||
<item quantity="few">%d секунды</item>
|
||||
<item quantity="many">%d секунд</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-bg/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-bg/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунди</item>
|
||||
<item quantity="one">%d секунда</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-bn/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-bn/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d সেকেন্ড</item>
|
||||
<item quantity="one">%d সেকেন্ড</item>
|
||||
</plurals>
|
||||
</resources>
|
7
doubletapplayerview/src/main/res/values-bs/plurals.xml
Normal file
7
doubletapplayerview/src/main/res/values-bs/plurals.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d s</item>
|
||||
<item quantity="one">%d s</item>
|
||||
<item quantity="few">%d s</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ca/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ca/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segons</item>
|
||||
<item quantity="one">%d segon</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-cs/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-cs/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekund</item>
|
||||
<item quantity="one">%d sekunda</item>
|
||||
<item quantity="few">%d sekundy</item>
|
||||
<item quantity="many">%d sekundy</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-da/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-da/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekunder</item>
|
||||
<item quantity="one">%d sekund</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-de/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-de/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d Sekunden</item>
|
||||
<item quantity="one">%d Sekunde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-el/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-el/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d δευτερόλεπτα</item>
|
||||
<item quantity="one">%d δευτερόλεπτο</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d seconds</item>
|
||||
<item quantity="one">%d second</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d seconds</item>
|
||||
<item quantity="one">%d second</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segundos</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-es/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-es/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segundos</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-et/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-et/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekundit</item>
|
||||
<item quantity="one">%d sekund</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-eu/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-eu/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segundo</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-fa/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-fa/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d ثانیه</item>
|
||||
<item quantity="one">%d ثانیه</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-fi/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-fi/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekuntia</item>
|
||||
<item quantity="one">%d sekunti</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d secondes</item>
|
||||
<item quantity="one">%d seconde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-fr/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-fr/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d secondes</item>
|
||||
<item quantity="one">%d seconde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-gl/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-gl/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segundos</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-gu/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-gu/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d સેકન્ડ</item>
|
||||
<item quantity="one">%d સેકન્ડ</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-hi/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-hi/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d सेकंड</item>
|
||||
<item quantity="one">%d सेकंड</item>
|
||||
</plurals>
|
||||
</resources>
|
7
doubletapplayerview/src/main/res/values-hr/plurals.xml
Normal file
7
doubletapplayerview/src/main/res/values-hr/plurals.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekundi</item>
|
||||
<item quantity="one">%d sekunda</item>
|
||||
<item quantity="few">%d sekunde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-hu/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-hu/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d másodperc</item>
|
||||
<item quantity="one">%d másodperc</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-hy/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-hy/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d վայրկյան</item>
|
||||
<item quantity="one">%d վայրկյան</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-in/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-in/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d detik</item>
|
||||
<item quantity="one">%d detik</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-is/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-is/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekúndur</item>
|
||||
<item quantity="one">%d sekúnda</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-it/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-it/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d secondi</item>
|
||||
<item quantity="one">%d secondo</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-iw/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-iw/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d שניות</item>
|
||||
<item quantity="one">שנייה אחת (%d)</item>
|
||||
<item quantity="two">%d שניות</item>
|
||||
<item quantity="many">%d שניות</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ja/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ja/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d秒</item>
|
||||
<item quantity="one">%d秒</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ka/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ka/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d წამი</item>
|
||||
<item quantity="one">%d წამი</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-kk/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-kk/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунд</item>
|
||||
<item quantity="one">%d секунд</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-km/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-km/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d វិនាទី</item>
|
||||
<item quantity="one">%d វិនាទី</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-kn/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-kn/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d ಸೆಕೆಂಡ್ಗಳು</item>
|
||||
<item quantity="one">%d ಸೆಕೆಂಡ್ಗಳು</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ko/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ko/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d초</item>
|
||||
<item quantity="one">%d초</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ky/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ky/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунд</item>
|
||||
<item quantity="one">%d секунд</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-lo/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-lo/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d ວິນາທີ</item>
|
||||
<item quantity="one">%d ວິນາທີ</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-lt/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-lt/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekundžių</item>
|
||||
<item quantity="one">%d sekundė</item>
|
||||
<item quantity="few">%d sekundės</item>
|
||||
<item quantity="many">%d sekundės</item>
|
||||
</plurals>
|
||||
</resources>
|
7
doubletapplayerview/src/main/res/values-lv/plurals.xml
Normal file
7
doubletapplayerview/src/main/res/values-lv/plurals.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekundes</item>
|
||||
<item quantity="zero">%d sekundes</item>
|
||||
<item quantity="one">%d sekunde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-mk/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-mk/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунди</item>
|
||||
<item quantity="one">%d секунда</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ml/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ml/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d സെക്കൻഡ്</item>
|
||||
<item quantity="one">%d സെക്കൻഡ്</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-mn/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-mn/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунд</item>
|
||||
<item quantity="one">%d секунд</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-mr/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-mr/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d सेकंद</item>
|
||||
<item quantity="one">%d सेकंद</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ms/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ms/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d saat</item>
|
||||
<item quantity="one">%d saat</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-my/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-my/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d စက္ကန့်</item>
|
||||
<item quantity="one">%d စက္ကန့်</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-nb/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-nb/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekunder</item>
|
||||
<item quantity="one">%d sekund</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ne/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ne/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d सेकेन्ड</item>
|
||||
<item quantity="one">%d सेकेन्ड</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-nl/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-nl/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d seconden</item>
|
||||
<item quantity="one">%d seconde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-pa/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-pa/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d ਸਕਿੰਟ</item>
|
||||
<item quantity="one">%d ਸਕਿੰਟ</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-pl/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-pl/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekundy</item>
|
||||
<item quantity="one">%d sekunda</item>
|
||||
<item quantity="few">%d sekundy</item>
|
||||
<item quantity="many">%d sekund</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segundos</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d segundos</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
7
doubletapplayerview/src/main/res/values-ro/plurals.xml
Normal file
7
doubletapplayerview/src/main/res/values-ro/plurals.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d de secunde</item>
|
||||
<item quantity="one">%d secundă</item>
|
||||
<item quantity="few">%d secunde</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-ru/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-ru/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунды</item>
|
||||
<item quantity="one">%d секунда</item>
|
||||
<item quantity="few">%d секунды</item>
|
||||
<item quantity="many">%d секунд</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-si/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-si/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">තත්පර %dක්</item>
|
||||
<item quantity="one">තත්පර %dක්</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-sk/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-sk/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekúnd</item>
|
||||
<item quantity="one">%d sekunda</item>
|
||||
<item quantity="few">%d sekundy</item>
|
||||
<item quantity="many">%d sekundy</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-sl/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-sl/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekund</item>
|
||||
<item quantity="one">%d sekunda</item>
|
||||
<item quantity="two">%d sekundi</item>
|
||||
<item quantity="few">%d sekunde</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-sq/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-sq/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekonda</item>
|
||||
<item quantity="one">%d sekondë</item>
|
||||
</plurals>
|
||||
</resources>
|
7
doubletapplayerview/src/main/res/values-sr/plurals.xml
Normal file
7
doubletapplayerview/src/main/res/values-sr/plurals.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунди</item>
|
||||
<item quantity="one">%d секунда</item>
|
||||
<item quantity="few">%d секунде</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-sv/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-sv/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d sekunder</item>
|
||||
<item quantity="one">%d sekund</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-sw/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-sw/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">Sekunde %d</item>
|
||||
<item quantity="one">Sekunde %d</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ta/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ta/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d விநாடிகள்</item>
|
||||
<item quantity="one">%d விநாடி</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-te/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-te/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d సెకన్లు</item>
|
||||
<item quantity="one">%d సెకను</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-th/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-th/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d วินาที</item>
|
||||
<item quantity="one">%d วินาที</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-tl/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-tl/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d na segundo</item>
|
||||
<item quantity="one">%d segundo</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-tr/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-tr/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d saniye</item>
|
||||
<item quantity="one">%d saniye</item>
|
||||
</plurals>
|
||||
</resources>
|
8
doubletapplayerview/src/main/res/values-uk/plurals.xml
Normal file
8
doubletapplayerview/src/main/res/values-uk/plurals.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d секунди</item>
|
||||
<item quantity="one">%d секунда</item>
|
||||
<item quantity="few">%d секунди</item>
|
||||
<item quantity="many">%d секунд</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-ur/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-ur/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d سیکنڈز</item>
|
||||
<item quantity="one">%d سیکنڈ</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-uz/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-uz/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d soniya</item>
|
||||
<item quantity="one">%d soniya</item>
|
||||
</plurals>
|
||||
</resources>
|
6
doubletapplayerview/src/main/res/values-vi/plurals.xml
Normal file
6
doubletapplayerview/src/main/res/values-vi/plurals.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d giây</item>
|
||||
<item quantity="one">%d giây</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<plurals name="quick_seek_x_second">
|
||||
<item quantity="other">%d 秒</item>
|
||||
<item quantity="one">%d 秒</item>
|
||||
</plurals>
|
||||
</resources>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue