├── .gitignore
├── .gradle
├── 6.1.1
│ ├── executionHistory
│ │ └── executionHistory.lock
│ ├── fileChanges
│ │ └── last-build.bin
│ ├── fileHashes
│ │ ├── fileHashes.bin
│ │ └── fileHashes.lock
│ └── gc.properties
├── buildOutputCleanup
│ ├── buildOutputCleanup.lock
│ ├── cache.properties
│ └── outputFiles.bin
├── checksums
│ ├── checksums.lock
│ ├── md5-checksums.bin
│ └── sha1-checksums.bin
└── vcs-1
│ └── gc.properties
├── .idea
├── $CACHE_FILE$
├── .gitignore
├── caches
│ └── build_file_checksums.ser
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── libraries
│ ├── Gradle__androidx_activity_activity_1_0_0_aar.xml
│ ├── Gradle__androidx_annotation_annotation_1_1_0.xml
│ ├── Gradle__androidx_appcompat_appcompat_1_2_0_aar.xml
│ ├── Gradle__androidx_appcompat_appcompat_resources_1_2_0_aar.xml
│ ├── Gradle__androidx_arch_core_core_common_2_1_0.xml
│ ├── Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml
│ ├── Gradle__androidx_collection_collection_1_1_0.xml
│ ├── Gradle__androidx_constraintlayout_constraintlayout_1_1_3_aar.xml
│ ├── Gradle__androidx_constraintlayout_constraintlayout_solver_1_1_3.xml
│ ├── Gradle__androidx_core_core_1_3_2_aar.xml
│ ├── Gradle__androidx_core_core_ktx_1_3_2_aar.xml
│ ├── Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml
│ ├── Gradle__androidx_customview_customview_1_0_0_aar.xml
│ ├── Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml
│ ├── Gradle__androidx_fragment_fragment_1_1_0_aar.xml
│ ├── Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml
│ ├── Gradle__androidx_lifecycle_lifecycle_common_2_1_0.xml
│ ├── Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml
│ ├── Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml
│ ├── Gradle__androidx_lifecycle_lifecycle_runtime_2_1_0_aar.xml
│ ├── Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml
│ ├── Gradle__androidx_loader_loader_1_0_0_aar.xml
│ ├── Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml
│ ├── Gradle__androidx_test_core_1_3_0_aar.xml
│ ├── Gradle__androidx_test_espresso_espresso_core_3_3_0_aar.xml
│ ├── Gradle__androidx_test_espresso_espresso_idling_resource_3_3_0_aar.xml
│ ├── Gradle__androidx_test_ext_junit_1_1_2_aar.xml
│ ├── Gradle__androidx_test_monitor_1_3_0_aar.xml
│ ├── Gradle__androidx_test_runner_1_3_0_aar.xml
│ ├── Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml
│ ├── Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml
│ ├── Gradle__androidx_versionedparcelable_versionedparcelable_1_1_0_aar.xml
│ └── Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml
├── misc.xml
├── modules.xml
├── modules
│ └── audiotrimmer.iml
└── vcs.xml
├── LICENSE
├── README.md
├── build.gradle
├── consumer-rules.pro
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── local.properties
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── pro
│ └── audiotrimmer
│ └── ExampleInstrumentedTest.kt
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── pro
│ │ └── audiotrimmer
│ │ ├── AudioTrimmerContract.kt
│ │ ├── AudioTrimmerPresenter.kt
│ │ ├── AudioTrimmerView.kt
│ │ ├── Injection.kt
│ │ ├── SeekBarOnProgressChanged.kt
│ │ ├── Utils.kt
│ │ ├── WaveGravity.kt
│ │ ├── WaveformSeekBar.kt
│ │ ├── exception
│ │ ├── InvalidInputException.kt
│ │ └── SampleDataException.kt
│ │ └── slidingwindow
│ │ └── SlidingWindowView.kt
└── res
│ ├── drawable-v23
│ └── seek_handle.xml
│ ├── drawable
│ ├── seek_handle.xml
│ ├── text_bg.xml
│ ├── transparent_seek.xml
│ ├── trimmer_left_bar.xml
│ └── trimmer_right_bar.xml
│ ├── layout
│ └── layout_audio_trimmer.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ └── strings.xml
└── test
└── java
└── com
└── pro
└── audiotrimmer
└── ExampleUnitTest.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/.gradle/6.1.1/executionHistory/executionHistory.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/6.1.1/executionHistory/executionHistory.lock
--------------------------------------------------------------------------------
/.gradle/6.1.1/fileChanges/last-build.bin:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gradle/6.1.1/fileHashes/fileHashes.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/6.1.1/fileHashes/fileHashes.bin
--------------------------------------------------------------------------------
/.gradle/6.1.1/fileHashes/fileHashes.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/6.1.1/fileHashes/fileHashes.lock
--------------------------------------------------------------------------------
/.gradle/6.1.1/gc.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/6.1.1/gc.properties
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/buildOutputCleanup.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/buildOutputCleanup/buildOutputCleanup.lock
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/cache.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 22 11:39:42 IST 2021
2 | gradle.version=6.1.1
3 |
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/outputFiles.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/buildOutputCleanup/outputFiles.bin
--------------------------------------------------------------------------------
/.gradle/checksums/checksums.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/checksums/checksums.lock
--------------------------------------------------------------------------------
/.gradle/checksums/md5-checksums.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/checksums/md5-checksums.bin
--------------------------------------------------------------------------------
/.gradle/checksums/sha1-checksums.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/checksums/sha1-checksums.bin
--------------------------------------------------------------------------------
/.gradle/vcs-1/gc.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.gradle/vcs-1/gc.properties
--------------------------------------------------------------------------------
/.idea/$CACHE_FILE$:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Android
10 |
11 |
12 | ComplianceLintAndroid
13 |
14 |
15 | CorrectnessLintAndroid
16 |
17 |
18 | Kotlin
19 |
20 |
21 | LintAndroid
22 |
23 |
24 | PerformanceLintAndroid
25 |
26 |
27 | Style issuesKotlin
28 |
29 |
30 | UsabilityLintAndroid
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_annotation_annotation_1_1_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_2_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_2_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_constraintlayout_constraintlayout_1_1_3_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_constraintlayout_constraintlayout_solver_1_1_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_core_core_1_3_2_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_core_core_ktx_1_3_2_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_1_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_1_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_test_core_1_3_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_test_espresso_espresso_core_3_3_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_test_espresso_espresso_idling_resource_3_3_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_test_ext_junit_1_1_2_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_test_monitor_1_3_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_test_runner_1_3_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/modules/audiotrimmer.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | generateDebugSources
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Pro gupta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AudioTrimmer
2 | Audio trimming library for Android (No processing)
3 |
4 | This library contains only the UI and logic for the trimmer and not the actual processing of the files . You can use FFmpeg or any other libraries for that.
5 | Add this as a library module in your project to use the View.
6 |
7 | 
8 |
9 |
10 | A sample app is yet to be added but here is the basic implementation of the trimmer as a view.
11 |
12 | Example to use with xml -
13 |
14 | ```
15 |
20 |
33 |
34 | ```
35 |
36 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.3.72"
4 | repositories {
5 | jcenter()
6 | mavenCentral()
7 | google()
8 | }
9 | dependencies {
10 | classpath "com.android.tools.build:gradle:4.0.1"
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.+'
13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
14 | }
15 | }
16 | apply plugin: 'com.android.library'
17 | apply plugin: 'kotlin-android'
18 | apply plugin: 'kotlin-android-extensions'
19 |
20 | android {
21 | compileSdkVersion 31
22 |
23 | defaultConfig {
24 | minSdkVersion 24
25 | targetSdkVersion 31
26 | versionCode 1
27 | versionName "1.0"
28 |
29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
30 | consumerProguardFiles "consumer-rules.pro"
31 | }
32 |
33 | buildTypes {
34 | release {
35 | minifyEnabled false
36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
37 | }
38 | }
39 | }
40 |
41 | dependencies {
42 | implementation fileTree(dir: "libs", include: ["*.jar"])
43 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.5.31"
44 | implementation 'androidx.core:core-ktx:1.6.0'
45 | implementation 'androidx.appcompat:appcompat:1.3.1'
46 | testImplementation 'junit:junit:4.12'
47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
49 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
50 | implementation "org.jetbrains.anko:anko-commons:0.10.0"
51 | implementation 'com.intuit.sdp:sdp-android:1.0.6'
52 | implementation 'com.intuit.ssp:ssp-android:1.0.6'
53 | }
54 |
55 | repositories {
56 | google()
57 | }
58 |
--------------------------------------------------------------------------------
/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/consumer-rules.pro
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pro-softs/AudioTrimmer/89c5c435041cb4098becfe797d983a83912452a7/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 03 05:26:34 IST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file must *NOT* be checked into Version Control Systems,
2 | # as it contains information specific to your local configuration.
3 | #
4 | # Location of the SDK. This is only used by Gradle.
5 | # For customization when using a Version Control System, please read the
6 | # header note.
7 | #Fri Jan 22 11:05:35 IST 2021
8 | sdk.dir=/Users/****/Library/Android/sdk
9 |
--------------------------------------------------------------------------------
/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/src/androidTest/java/com/pro/audiotrimmer/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.pro.audiotrimmer.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/AudioTrimmerContract.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import java.io.File
4 |
5 | internal interface AudioTrimmerContract {
6 | interface View {
7 | fun getSlidingWindowWidth(): Int
8 | fun setupSlidingWindow()
9 | fun setAudioProgress(progress: Float)
10 | fun setAudioSamples(samples: ShortArray)
11 | fun setTotalAudioLength(videoLength: Long)
12 |
13 | fun setStartEndTimer(left: Long, right: Long)
14 | }
15 |
16 | interface Presenter {
17 | fun onViewAttached(view: View)
18 | fun onViewDetached()
19 |
20 | fun setAudio(audio: File)
21 | fun setAudioSamples(samples: ShortArray)
22 | fun setMaxDuration(millis: Long)
23 | fun setMinDuration(millis: Long)
24 | fun setAudioProgress(millis: Long)
25 | fun setOnSelectedRangeChangedListener(listener: AudioTrimmerView.OnSelectedRangeChangedListener)
26 |
27 | fun setInitialStartEndTimer(start: Long, end: Long)
28 |
29 | fun isValidState(): Boolean
30 | fun show()
31 |
32 | fun getStringForTime(timeMs: Long): String?
33 | }
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/AudioTrimmerPresenter.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import com.pro.audiotrimmer.slidingwindow.SlidingWindowView
6 | import java.io.File
7 | import java.util.*
8 | import kotlin.math.min
9 | import kotlin.math.roundToLong
10 |
11 | internal class AudioTrimmerPresenter : AudioTrimmerContract.Presenter,
12 | SlidingWindowView.Listener {
13 |
14 | private var view: AudioTrimmerContract.View? = null
15 |
16 | private var audio: File? = null
17 | private var maxDuration = 30_000L
18 | private var minDuration = 3_000L
19 |
20 | private var onSelectedRangeChangedListener: AudioTrimmerView.OnSelectedRangeChangedListener? = null
21 |
22 | private var audioLength = 0L
23 | private var audioWindowLength = 0L
24 |
25 | private var rawStartMillis = 0L
26 | private var rawEndMillis = 0L
27 | private var rawProgressMillis = 0L
28 |
29 | private val startMillis
30 | get() = min(rawStartMillis, audioLength)
31 | private val endMillis
32 | get() = min(rawEndMillis, audioLength)
33 | private val progressMillis
34 | get() = min(rawProgressMillis, audioLength)
35 |
36 | /* -------------------------------------------------------------------------------------------*/
37 | /* Presenter */
38 |
39 | override fun onViewAttached(view: AudioTrimmerContract.View) {
40 | this.view = view
41 | }
42 |
43 | override fun onViewDetached() {
44 | this.view = null
45 | }
46 |
47 | /* -------------------------------------------------------------------------------------------*/
48 | /* Builder */
49 | override fun setAudio(audio: File) {
50 | this.audio = audio
51 | }
52 |
53 | override fun setAudioSamples(samples: ShortArray) {
54 | view?.setAudioSamples(samples)
55 | }
56 |
57 | override fun setMaxDuration(millis: Long) {
58 | this.maxDuration = millis
59 | }
60 |
61 | override fun setMinDuration(millis: Long) {
62 | this.minDuration = millis
63 | }
64 |
65 | override fun setAudioProgress(millis: Long) {
66 | val progress = millis / audioWindowLength.toFloat()
67 | view?.setAudioProgress(progress)
68 | }
69 |
70 | override fun setOnSelectedRangeChangedListener(listener: AudioTrimmerView.OnSelectedRangeChangedListener) {
71 | this.onSelectedRangeChangedListener = listener
72 | }
73 |
74 | override fun isValidState(): Boolean {
75 | return audio != null
76 | && maxDuration > 0L
77 | && minDuration > 0L
78 | && maxDuration >= minDuration
79 | }
80 |
81 | override fun show() {
82 | if (!isValidState()) {
83 | return
84 | }
85 |
86 | val audio = this.audio ?: return
87 | audioLength = extractAudioLength(audio.path)
88 |
89 | if (audioLength < minDuration) {
90 | // TODO
91 | return
92 | }
93 |
94 | view?.setStartEndTimer(0, audioLength)
95 |
96 | audioWindowLength = min(
97 | audioLength,
98 | maxDuration
99 | )
100 |
101 | val windowWidth = view?.getSlidingWindowWidth() ?: return
102 |
103 | rawStartMillis = 0L
104 | rawEndMillis = audioWindowLength
105 | rawProgressMillis = 0L
106 |
107 | view?.setupSlidingWindow()
108 | view?.setTotalAudioLength(audioWindowLength)
109 |
110 | onSelectedRangeChangedListener?.onSelectRangeEnd(rawStartMillis, rawEndMillis)
111 | onSelectedRangeChangedListener?.onProgressEnd(rawProgressMillis)
112 | }
113 |
114 | override fun getStringForTime(timeMs: Long): String? {
115 | val totalSeconds = (timeMs + 500) / 1000
116 | val seconds = totalSeconds % 60
117 | val minutes = totalSeconds / 60 % 60
118 | val hours = totalSeconds / 3600
119 |
120 | return if (hours > 0) String.format(Locale.ENGLISH, "%d:%02d:%02d", hours, minutes, seconds)
121 | else String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds)
122 | }
123 |
124 | /* -------------------------------------------------------------------------------------------*/
125 | /* SlidingWindowView.Listener */
126 | override fun onDragRangeBarStart() {
127 | onSelectedRangeChangedListener?.onSelectRangeStart()
128 | }
129 |
130 | override fun onDragRangeBar(left: Float, right: Float): Boolean {
131 | calculateSelectedArea(left, right)
132 | val duration = rawEndMillis - rawStartMillis
133 |
134 | if (duration < minDuration) {
135 | return false
136 | }
137 |
138 | view?.setStartEndTimer(rawStartMillis, rawEndMillis)
139 |
140 |
141 | onSelectedRangeChangedListener?.onSelectRange(rawStartMillis, rawEndMillis)
142 |
143 | return true
144 | }
145 |
146 | override fun onDragRangeBarEnd(left: Float, right: Float) {
147 | calculateSelectedArea(left, right)
148 |
149 | onSelectedRangeChangedListener?.onSelectRangeEnd(rawStartMillis, rawEndMillis)
150 | }
151 |
152 | override fun setInitialStartEndTimer(start: Long, end: Long) {
153 | view?.setStartEndTimer(start, end)
154 | }
155 |
156 | override fun onProgressEnd(progress: Float) {
157 | calculateDraggedPosition(progress)
158 | onSelectedRangeChangedListener?.onProgressEnd(rawProgressMillis)
159 | }
160 |
161 | override fun onProgressStart() {
162 | onSelectedRangeChangedListener?.onProgressStart()
163 | }
164 |
165 | override fun onDragProgressBar(progress: Float): Boolean {
166 | calculateDraggedPosition(progress)
167 | onSelectedRangeChangedListener?.onDragProgressBar(rawProgressMillis)
168 |
169 | return true
170 | }
171 |
172 | /* -------------------------------------------------------------------------------------------*/
173 | /* Internal helpers */
174 | private fun calculateSelectedArea(left: Float, right: Float) {
175 | rawStartMillis = (left * audioWindowLength).roundToLong()
176 | rawEndMillis = (right * audioWindowLength).roundToLong()
177 | }
178 |
179 | private fun calculateDraggedPosition(progress: Float) {
180 | rawProgressMillis = (progress * audioWindowLength).roundToLong()
181 | }
182 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/AudioTrimmerView.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.util.AttributeSet
6 | import android.util.Log
7 | import androidx.annotation.ColorInt
8 | import androidx.annotation.DrawableRes
9 | import androidx.constraintlayout.widget.ConstraintLayout
10 | import com.pro.audiotrimmer.databinding.LayoutAudioTrimmerBinding
11 | import com.pro.audiotrimmer.slidingwindow.SlidingWindowView
12 |
13 | import java.io.File
14 | import java.util.*
15 | import kotlin.math.roundToInt
16 |
17 | class AudioTrimmerView @JvmOverloads constructor(
18 | context: Context,
19 | val attrs: AttributeSet? = null,
20 | defStyle: Int = 0
21 | ) : ConstraintLayout(context, attrs, defStyle), AudioTrimmerContract.View {
22 |
23 | private var formatBuilder: java.lang.StringBuilder? = null
24 | private var formatter: Formatter? = null
25 | private var isWavesInit = false
26 |
27 | @DrawableRes
28 | private var leftBarRes: Int = R.drawable.trimmer_left_bar
29 | @DrawableRes
30 | private var rightBarRes: Int = R.drawable.trimmer_right_bar
31 | @DrawableRes
32 | private var thumbRes: Int = R.drawable.seek_handle
33 |
34 | private var barWidth: Float = dpToPx(context, 10f)
35 | private var thumbWidth: Float = dpToPx(context, 1f)
36 | private var borderWidth: Float = 0f
37 |
38 | @ColorInt
39 | private var borderColor: Int = Color.parseColor("#222730")
40 | @ColorInt
41 | private var overlayColor: Int = Color.argb(120, 183, 191, 207)
42 |
43 | private var presenter: AudioTrimmerContract.Presenter? = null
44 |
45 | private lateinit var binding: LayoutAudioTrimmerBinding
46 |
47 | /* -------------------------------------------------------------------------------------------*/
48 | /* Initialize */
49 | init {
50 | inflate(context, R.layout.layout_audio_trimmer, this)
51 | }
52 |
53 |
54 | override fun onFinishInflate() {
55 | super.onFinishInflate()
56 | this.binding = LayoutAudioTrimmerBinding.bind(this)
57 | obtainAttributes(attrs)
58 | initViews()
59 | }
60 |
61 | private fun obtainAttributes(attrs: AttributeSet?) {
62 | attrs ?: return
63 |
64 | val array = resources.obtainAttributes(attrs, R.styleable.AudioTrimmerView)
65 | try {
66 | leftBarRes = array.getResourceId(R.styleable.AudioTrimmerView_atv_window_left_bar, leftBarRes)
67 | binding.slidingWindowView.leftBarRes = leftBarRes
68 |
69 | rightBarRes = array.getResourceId(R.styleable.AudioTrimmerView_atv_window_right_bar, rightBarRes)
70 | binding.slidingWindowView.rightBarRes = rightBarRes
71 |
72 | thumbRes = array.getResourceId(R.styleable.AudioTrimmerView_atv_window_thumb, thumbRes)
73 | binding.slidingWindowView.sliderThumbRes = thumbRes
74 |
75 | barWidth = array.getDimension(R.styleable.AudioTrimmerView_atv_window_bar_width, barWidth)
76 | binding.slidingWindowView.barWidth = barWidth
77 |
78 | thumbWidth = array.getDimension(R.styleable.AudioTrimmerView_atv_window_bar_width, thumbWidth)
79 | binding.slidingWindowView.sliderWidth = thumbWidth
80 |
81 | borderWidth = array.getDimension(R.styleable.AudioTrimmerView_atv_window_border_width, borderWidth)
82 | binding.slidingWindowView.borderWidth = borderWidth
83 |
84 | borderColor = array.getColor(R.styleable.AudioTrimmerView_atv_window_border_color, borderColor)
85 | binding.slidingWindowView.borderColor = borderColor
86 |
87 | overlayColor = array.getColor(R.styleable.AudioTrimmerView_atv_overlay_color, overlayColor)
88 | binding.slidingWindowView.overlayColor = overlayColor
89 | } finally {
90 | array.recycle()
91 | }
92 | }
93 |
94 | private fun initViews() {
95 | if(!isWavesInit) {
96 | binding.waveFormView.sample = shortArrayOf(5, 5)
97 | binding.waveFormView.isEnabled = false
98 | isWavesInit = true
99 | }
100 | }
101 |
102 | /* -------------------------------------------------------------------------------------------*/
103 | /* Attach / Detach */
104 | override fun onAttachedToWindow() {
105 | super.onAttachedToWindow()
106 | presenter = obtainAudioTrimmerPresenter()
107 | .apply { onViewAttached(this@AudioTrimmerView) }
108 | onPresenterCreated()
109 | }
110 |
111 | override fun onDetachedFromWindow() {
112 | super.onDetachedFromWindow()
113 | presenter?.onViewDetached()
114 | presenter = null
115 | }
116 |
117 | private fun onPresenterCreated() {
118 | presenter?.let {
119 | binding.slidingWindowView.listener = presenter as SlidingWindowView.Listener
120 | }
121 | }
122 |
123 | /* -------------------------------------------------------------------------------------------*/
124 | /* Public APIs */
125 | fun setAudio(audio: File): AudioTrimmerView {
126 | presenter?.setAudio(audio)
127 |
128 | return this
129 | }
130 |
131 | fun setInitialTimes(start: Long, end: Long) {
132 | setStartEndTimer(start, end)
133 | }
134 |
135 | fun setMaxDuration(millis: Long): AudioTrimmerView {
136 | presenter?.setMaxDuration(millis)
137 | return this
138 | }
139 |
140 | fun setAudioProgress(progress: Long) {
141 | presenter?.setAudioProgress(progress)
142 | }
143 |
144 | fun setMinDuration(millis: Long): AudioTrimmerView {
145 | presenter?.setMinDuration(millis)
146 | return this
147 | }
148 |
149 | fun setOnSelectedRangeChangedListener(listener: OnSelectedRangeChangedListener): AudioTrimmerView {
150 | presenter?.setOnSelectedRangeChangedListener(listener)
151 | return this
152 | }
153 |
154 | fun setExtraDragSpace(spaceInPx: Float): AudioTrimmerView {
155 | binding.slidingWindowView.extraDragSpace = spaceInPx
156 | return this
157 | }
158 |
159 | fun show() {
160 | presenter?.show()
161 | }
162 |
163 | /* -------------------------------------------------------------------------------------------*/
164 | /* VideoTrimmerContract.View */
165 | override fun getSlidingWindowWidth(): Int {
166 | val screenWidth = resources.displayMetrics.widthPixels
167 | val margin = dpToPx(context, 11f)
168 | return screenWidth - 2 * (margin + barWidth).roundToInt()
169 | }
170 |
171 | override fun setupSlidingWindow() {
172 | binding.slidingWindowView.reset()
173 | }
174 |
175 | override fun setAudioProgress(progress: Float) {
176 | binding.slidingWindowView.setThumbPosition(progress)
177 | }
178 |
179 | override fun setAudioSamples(samples: ShortArray) {
180 | for (i in samples.indices) {
181 | samples[i] = (samples[i] % dpToPx(context, 10F)).toInt().toShort()
182 | }
183 | binding.waveFormView.sample = samples
184 | isWavesInit = true
185 | }
186 |
187 | override fun setTotalAudioLength(videoLength: Long) {
188 | binding.slidingWindowView.setTotalDuration(videoLength)
189 | }
190 |
191 | override fun setStartEndTimer(left: Long, right: Long) {
192 | "${context.getString(R.string.total)} ${presenter?.getStringForTime(right)}".also { binding.endTime.text = it }
193 | }
194 |
195 | /* -------------------------------------------------------------------------------------------*/
196 | /* Listener */
197 | interface OnSelectedRangeChangedListener {
198 | fun onSelectRangeStart()
199 | fun onSelectRange(startMillis: Long, endMillis: Long)
200 | fun onSelectRangeEnd(startMillis: Long, endMillis: Long)
201 | fun onProgressStart()
202 | fun onProgressEnd(millis: Long)
203 | fun onDragProgressBar(millis: Long)
204 | }
205 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/Injection.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | internal fun obtainAudioTrimmerPresenter() = AudioTrimmerPresenter()
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/SeekBarOnProgressChanged.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | interface SeekBarOnProgressChanged {
4 |
5 | fun onProgressChanged(waveformSeekBar: WaveformSeekBar, progress: Int, fromUser: Boolean)
6 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import android.content.Context
4 | import android.media.MediaMetadataRetriever
5 |
6 | internal fun dpToPx(context: Context, dp: Float): Float {
7 | val density = context.resources.displayMetrics.density
8 | return dp * density
9 | }
10 |
11 | internal fun extractAudioLength(audioPath: String): Long {
12 | val retriever = try {
13 | MediaMetadataRetriever()
14 | .apply { setDataSource(audioPath) }
15 | } catch (e: IllegalArgumentException) {
16 | return 0L
17 | }
18 |
19 | val length = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
20 | retriever.release()
21 |
22 | return length?.toLong() ?: 0L
23 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/WaveGravity.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | enum class WaveGravity {
4 | TOP,
5 | CENTER,
6 | BOTTOM
7 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/WaveformSeekBar.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import android.util.AttributeSet
7 | import android.view.MotionEvent
8 | import android.view.View
9 | import android.view.ViewConfiguration
10 | import kotlin.math.abs
11 |
12 | class WaveformSeekBar : View {
13 | private var mCanvasWidth = 0
14 | private var mCanvasHeight = 0
15 |
16 | private val mWavePaint = Paint(Paint.ANTI_ALIAS_FLAG)
17 | private val mWaveRect = RectF()
18 | private val mProgressCanvas = Canvas()
19 | private var mMaxValue = dpToPx(context, 2f).toInt().toShort()
20 | private var mTouchDownX = 0F
21 | private var mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
22 |
23 | constructor(context: Context?) : super(context){
24 | init(null)
25 | }
26 |
27 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){
28 | init(attrs)
29 | }
30 |
31 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
32 | init(attrs)
33 | }
34 |
35 | private fun init(attrs: AttributeSet?){
36 |
37 | val ta = context.obtainStyledAttributes(attrs, R.styleable.WaveformSeekBar)
38 |
39 | waveWidth = ta.getDimension(R.styleable.WaveformSeekBar_wave_width,waveWidth)
40 | waveGap = ta.getDimension(R.styleable.WaveformSeekBar_wave_gap,waveGap)
41 | waveCornerRadius = ta.getDimension(R.styleable.WaveformSeekBar_wave_corner_radius,waveCornerRadius)
42 | waveMinHeight = ta.getDimension(R.styleable.WaveformSeekBar_wave_min_height,waveMinHeight)
43 | waveBackgroundColor = ta.getColor(R.styleable.WaveformSeekBar_wave_background_color,waveBackgroundColor)
44 | waveProgressColor = ta.getColor(R.styleable.WaveformSeekBar_wave_progress_color,waveProgressColor)
45 | progress = ta.getInteger(R.styleable.WaveformSeekBar_wave_progress,progress)
46 | val gravity = ta.getString(R.styleable.WaveformSeekBar_wave_gravity)
47 | waveGravity = when(gravity){
48 | "1" -> WaveGravity.TOP
49 | "2" -> WaveGravity.CENTER
50 | else -> WaveGravity.BOTTOM
51 | }
52 |
53 | ta.recycle()
54 | }
55 |
56 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
57 | super.onSizeChanged(w, h, oldw, oldh)
58 | mCanvasWidth = w
59 | mCanvasHeight = h
60 | }
61 |
62 | @SuppressLint("DrawAllocation")
63 | override fun onDraw(canvas: Canvas) {
64 |
65 | super.onDraw(canvas)
66 | if (sample == null || sample!!.isEmpty())
67 | return
68 |
69 | mMaxValue = sample!!.maxOrNull()!!
70 | val step = (getAvailableWith() / (waveGap+waveWidth))/sample!!.size
71 |
72 | var i = 0F
73 | var lastWaveRight = paddingLeft.toFloat()
74 | while ( i < sample!!.size){
75 |
76 | var waveHeight = getAvailableHeight() * (sample!![i.toInt()].toFloat() / mMaxValue)
77 | if(waveHeight < waveMinHeight)
78 | waveHeight = waveMinHeight
79 |
80 | val top : Float = when(waveGravity){
81 | WaveGravity.TOP -> paddingTop.toFloat()
82 | WaveGravity.CENTER -> paddingTop+getAvailableHeight()/2F - waveHeight/2F
83 | WaveGravity.BOTTOM -> mCanvasHeight - paddingBottom - waveHeight
84 | }
85 |
86 | mWaveRect.set(lastWaveRight, top, lastWaveRight+waveWidth, top + waveHeight)
87 |
88 | when {
89 | mWaveRect.contains(getAvailableWith()*progress/100F, mWaveRect.centerY()) -> {
90 | var bitHeight = mWaveRect.height().toInt()
91 | if (bitHeight <= 0)
92 | bitHeight = waveWidth.toInt()
93 |
94 | val bitmap = Bitmap.createBitmap(getAvailableWith(),bitHeight , Bitmap.Config.ARGB_8888)
95 | mProgressCanvas.setBitmap(bitmap)
96 |
97 | val fillWidth = (getAvailableWith()*progress/100F)
98 |
99 | mWavePaint.color = waveProgressColor
100 | mProgressCanvas.drawRect(0F,0F,fillWidth,mWaveRect.bottom,mWavePaint)
101 |
102 | mWavePaint.color = waveBackgroundColor
103 | mProgressCanvas.drawRect(fillWidth,0F,getAvailableWith().toFloat(),mWaveRect.bottom,mWavePaint)
104 |
105 | val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
106 | mWavePaint.shader = shader
107 | }
108 | mWaveRect.right <= getAvailableWith()*progress/100F -> {
109 | mWavePaint.color = waveProgressColor
110 | mWavePaint.shader = null
111 | }
112 | else -> {
113 | mWavePaint.color = waveBackgroundColor
114 | mWavePaint.shader = null
115 | }
116 | }
117 |
118 | canvas.drawRoundRect(mWaveRect,waveCornerRadius,waveCornerRadius,mWavePaint)
119 |
120 | lastWaveRight = mWaveRect.right+waveGap
121 |
122 | if (lastWaveRight+waveWidth > getAvailableWith()+paddingLeft)
123 | break
124 |
125 | i += 1 / step
126 | }
127 | }
128 |
129 | override fun onTouchEvent(event: MotionEvent?): Boolean {
130 | if (!isEnabled)
131 | return false
132 |
133 | when(event!!.action){
134 | MotionEvent.ACTION_DOWN ->{
135 | if (isParentScrolling())
136 | mTouchDownX = event.x
137 | else
138 | updateProgress(event)
139 | }
140 | MotionEvent.ACTION_MOVE ->{
141 | updateProgress(event)
142 | }
143 | MotionEvent.ACTION_UP ->{
144 | if (abs(event.x - mTouchDownX) > mScaledTouchSlop)
145 | updateProgress(event)
146 |
147 | performClick()
148 | }
149 | }
150 | return true
151 | }
152 |
153 | private fun isParentScrolling() : Boolean{
154 | var parent = parent as View
155 | val root = rootView
156 |
157 | while (true){
158 | when {
159 | parent.canScrollHorizontally(1) -> return true
160 | parent.canScrollHorizontally(-1) -> return true
161 | parent.canScrollVertically(1) -> return true
162 | parent.canScrollVertically(-1) -> return true
163 | }
164 |
165 | if (parent == root)
166 | return false
167 |
168 | parent = parent.parent as View
169 |
170 | }
171 | }
172 |
173 | private fun updateProgress(event: MotionEvent?){
174 |
175 | progress = (100*event!!.x/getAvailableWith()).toInt()
176 | invalidate()
177 |
178 | if (onProgressChanged != null)
179 | onProgressChanged!!.onProgressChanged(this,progress,true)
180 | }
181 |
182 | override fun performClick(): Boolean {
183 | super.performClick()
184 | return true
185 | }
186 |
187 | private fun getAvailableWith() = mCanvasWidth-paddingLeft-paddingRight
188 | private fun getAvailableHeight() = mCanvasHeight-paddingTop-paddingBottom
189 |
190 | var onProgressChanged : SeekBarOnProgressChanged? = null
191 |
192 | var sample: ShortArray? = null
193 | set(value){
194 | field = value
195 | invalidate()
196 | }
197 |
198 | var progress : Int = 0
199 | set(value) {
200 | field = value
201 | invalidate()
202 |
203 | if (onProgressChanged != null)
204 | onProgressChanged!!.onProgressChanged(this,progress,false)
205 | }
206 |
207 | var waveBackgroundColor : Int = Color.BLACK
208 | set(value) {
209 | field = value
210 | invalidate()
211 | }
212 |
213 | var waveProgressColor : Int = Color.BLACK
214 | set(value) {
215 | field = value
216 | invalidate()
217 | }
218 |
219 | var waveGap : Float = dpToPx(context,2f)
220 | set(value) {
221 | field = value
222 | invalidate()
223 | }
224 |
225 | var waveWidth : Float = dpToPx(context,5f)
226 | set(value) {
227 | field = value
228 | invalidate()
229 | }
230 |
231 | var waveMinHeight : Float = waveWidth
232 | set(value) {
233 | field = value
234 | invalidate()
235 | }
236 |
237 | var waveCornerRadius : Float = dpToPx(context,2f)
238 | set(value) {
239 | field = value
240 | invalidate()
241 | }
242 |
243 | var waveGravity : WaveGravity = WaveGravity.CENTER
244 | set(value) {
245 | field = value
246 | invalidate()
247 | }
248 | }
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/exception/InvalidInputException.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer.exception
2 |
3 | class InvalidInputException(message: String) : Exception(message)
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/exception/SampleDataException.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer.exception
2 |
3 | class SampleDataException : Exception("Set the sample data by using WaveformSeekBar.setSample method.")
--------------------------------------------------------------------------------
/src/main/java/com/pro/audiotrimmer/slidingwindow/SlidingWindowView.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer.slidingwindow
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.util.AttributeSet
6 | import android.view.MotionEvent
7 | import android.view.MotionEvent.*
8 | import android.view.View
9 | import androidx.annotation.ColorInt
10 | import androidx.annotation.DrawableRes
11 | import androidx.core.content.ContextCompat
12 | import com.pro.audiotrimmer.dpToPx
13 | import java.util.*
14 | import kotlin.math.max
15 | import kotlin.math.min
16 | import kotlin.math.roundToInt
17 |
18 | internal class SlidingWindowView @JvmOverloads constructor(
19 | context: Context,
20 | attrs: AttributeSet? = null,
21 | defStyle: Int = 0
22 | ) : View(context, attrs, defStyle) {
23 |
24 | private val HOLD_LEFT_BAR = 0
25 | private val HOLD_RIGHT_BAR = 1
26 | private val HOLD_NOTHING = 2
27 | private val HOLD_THUMB = 3
28 |
29 | private fun getTextFromTime(timeMs: Int): String {
30 | val totalSeconds = (timeMs + 500) / 1000
31 | val seconds = totalSeconds % 60
32 | val minutes = totalSeconds / 60 % 60
33 | val hours = totalSeconds / 3600
34 | val milli = (timeMs % 1000) / 10
35 |
36 | return if (hours > 0) String.format(
37 | Locale.ENGLISH,
38 | "%d:%02d:%02d:%02d",
39 | hours,
40 | minutes,
41 | seconds,
42 | milli
43 | )
44 | else String.format(Locale.ENGLISH, "%02d:%02d:%02d", minutes, seconds, milli)
45 | }
46 |
47 | @DrawableRes
48 | var leftBarRes: Int = 0
49 | set(value) {
50 | field = value
51 | invalidate()
52 | }
53 |
54 | @DrawableRes
55 | var rightBarRes: Int = 0
56 | set(value) {
57 | field = value
58 | invalidate()
59 | }
60 |
61 | @DrawableRes
62 | var sliderThumbRes: Int = 0
63 | set(value) {
64 | field = value
65 | invalidate()
66 | }
67 |
68 | var barWidth: Float = 0f
69 | set(value) {
70 | field = value
71 | invalidate()
72 | }
73 |
74 | var sliderWidth: Float = 0f
75 | set(value) {
76 | field = value
77 | invalidate()
78 | }
79 |
80 | var borderWidth: Float = 0f
81 | set(value) {
82 | field = value
83 | invalidate()
84 | }
85 |
86 | @ColorInt
87 | var borderColor: Int = 0
88 | set(value) {
89 | field = value
90 | invalidate()
91 | }
92 |
93 | @ColorInt
94 | var overlayColor: Int = 0
95 | set(value) {
96 | field = value
97 | invalidate()
98 | }
99 |
100 | var listener: Listener? = null
101 | var extraDragSpace: Float = 0f
102 |
103 | private val borderPaint: Paint = Paint().apply { isAntiAlias = true }
104 | private val overlayPaint: Paint = Paint().apply { isAntiAlias = true }
105 | private val textPaint: Paint = Paint().apply { isAntiAlias = true }
106 | private val textBgPaint: Paint = Paint().apply { isAntiAlias = true }
107 |
108 | private var leftBarX = -1f // Left-Top
109 | private var rightBarX = -1f // Left-Top
110 | private var thumbX = -1f // Left-Top
111 |
112 | private var leftBarXPercentage = -1f
113 | private var rightBarXPercentage = -1f
114 | private var thumbXPercentage = -1f
115 |
116 | private var leftMilli = 0
117 | private var rightMilli = 0
118 | private var audioWindowLength = 0L
119 |
120 | private var hold = HOLD_NOTHING
121 |
122 | /* -------------------------------------------------------------------------------------------*/
123 | /* Public APIs */
124 | fun setBarPositions(leftPercentage: Float, rightPercentage: Float, thumbPercentage: Float) {
125 | this.leftBarXPercentage = leftPercentage
126 | this.rightBarXPercentage = rightPercentage
127 | this.thumbXPercentage = thumbPercentage
128 |
129 | postInvalidate()
130 | }
131 |
132 |
133 | fun setTotalDuration(videoLength: Long) {
134 | audioWindowLength = videoLength
135 | }
136 |
137 | fun setThumbPosition(thumbPercentage: Float) {
138 | this.thumbXPercentage = thumbPercentage
139 |
140 | postInvalidate()
141 | }
142 |
143 | fun reset() {
144 | leftBarX = -1f
145 | rightBarX = -1f
146 | leftBarXPercentage = -1f
147 | rightBarXPercentage = -1f
148 | thumbXPercentage = -1f
149 |
150 | invalidate()
151 | }
152 |
153 | /* -------------------------------------------------------------------------------------------*/
154 | /* Draw */
155 | override fun onDraw(canvas: Canvas) {
156 | super.onDraw(canvas)
157 |
158 | if (leftBarXPercentage >= 0f && rightBarXPercentage > 0f) {
159 | restoreBarPositions()
160 | }
161 |
162 | if(thumbXPercentage > 0f) {
163 | restoreThumb()
164 | }
165 |
166 | if (leftBarX < 0) {
167 | leftBarX = 0f
168 | }
169 |
170 | if(thumbX < barWidth) {
171 | thumbX = barWidth
172 | }
173 |
174 | if (rightBarX < 0) {
175 | rightBarX = width - barWidth
176 | }
177 |
178 | if(thumbX < leftBarX) {
179 | thumbX = leftBarX
180 | }
181 |
182 | if(thumbX > rightBarX) {
183 | thumbX = rightBarX
184 | }
185 |
186 | drawBorder(canvas)
187 | drawLeftBar(canvas)
188 | drawRightBar(canvas)
189 | drawOverlay(canvas)
190 |
191 | if(hold != HOLD_LEFT_BAR && hold != HOLD_RIGHT_BAR)
192 | drawSliderThumb(canvas)
193 | }
194 |
195 | private fun drawLeftBar(canvas: Canvas) {
196 | ContextCompat.getDrawable(context, leftBarRes)?.apply {
197 | setBounds(
198 | 0, dpToPx(context, 0f).roundToInt(), barWidth.roundToInt(), height
199 | )
200 |
201 | textPaint.color = Color.WHITE
202 | textPaint.textSize = 30f
203 | textPaint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
204 | textBgPaint.color = Color.parseColor("#33000000")
205 |
206 | canvas.save()
207 |
208 | canvas.translate(leftBarX, 0f)
209 |
210 | if(hold == HOLD_LEFT_BAR) {
211 | val leftR = if(leftBarX > dpToPx(context, 27f)) dpToPx(context, -27f) else 0f
212 |
213 | val rect = RectF(
214 | leftR,
215 | dpToPx(context, -30f), dpToPx(context, 55f), dpToPx(context, -5f)
216 | )
217 |
218 | canvas.drawRoundRect(rect, 25f, 25f, textBgPaint)
219 | val text = getTextFromTime(leftMilli)
220 | val textWidth: Float = textPaint.measureText(text)
221 |
222 | canvas.drawText(text, rect.centerX() - (textWidth / 2), rect.centerY()+5, textPaint)
223 | }
224 |
225 | draw(canvas)
226 | canvas.restore()
227 | }
228 | }
229 |
230 | private fun drawSliderThumb(canvas: Canvas) {
231 | ContextCompat.getDrawable(context, sliderThumbRes)?.apply {
232 | setBounds(
233 | 0, dpToPx(context, -10f).toInt(), sliderWidth.roundToInt(),
234 | (height + dpToPx(context, 10f)).roundToInt()
235 | )
236 |
237 | canvas.save()
238 |
239 | canvas.translate(thumbX, 0f)
240 | draw(canvas)
241 |
242 | canvas.restore()
243 | }
244 | }
245 |
246 | private fun drawRightBar(canvas: Canvas) {
247 | ContextCompat.getDrawable(context, rightBarRes)?.apply {
248 | setBounds(
249 | 0, dpToPx(context, 0f).roundToInt(), barWidth.roundToInt(), height
250 | )
251 |
252 | textPaint.color = Color.WHITE
253 | textBgPaint.color = Color.parseColor("#33000000")
254 |
255 | canvas.save()
256 | canvas.translate(rightBarX, 0f)
257 |
258 | if(hold == HOLD_RIGHT_BAR) {
259 | val test = ((width - barWidth) - rightBarX > dpToPx(context, 27f))
260 |
261 | val leftR = if(test) dpToPx(context, -27f) else dpToPx(context, -55f)
262 | val rightR = if(test) dpToPx(context, 55f) else barWidth
263 |
264 | val rect = RectF(
265 | leftR,
266 | dpToPx(context, -30f), rightR, dpToPx(context, -5f)
267 | )
268 |
269 | canvas.drawRoundRect(rect, 25f, 25f, textBgPaint)
270 | val text = getTextFromTime(rightMilli)
271 | val textWidth: Float = textPaint.measureText(text)
272 |
273 | canvas.drawText(text, rect.centerX() - (textWidth / 2), rect.centerY()+5, textPaint)
274 | }
275 |
276 | draw(canvas)
277 | canvas.restore()
278 | }
279 | }
280 |
281 | private fun drawBorder(canvas: Canvas) {
282 | borderPaint.strokeWidth = borderWidth
283 | borderPaint.color = borderColor
284 |
285 | val fromX = leftBarX + barWidth - 1
286 | val toX = rightBarX + 1
287 |
288 | drawTopBorder(canvas, fromX, toX)
289 | drawBottomBorder(canvas, fromX, toX)
290 | }
291 |
292 | private fun drawTopBorder(canvas: Canvas, fromX: Float, toX: Float) {
293 | val y = dpToPx(context, 0f).roundToInt() + borderWidth / 2f
294 | canvas.drawLine(fromX, y, toX, y, borderPaint)
295 | }
296 |
297 | private fun drawBottomBorder(canvas: Canvas, fromX: Float, toX: Float) {
298 | val y = height - dpToPx(context, 0f).roundToInt() - (borderWidth / 2f)
299 | canvas.drawLine(fromX, y, toX, y, borderPaint)
300 | }
301 |
302 | private fun drawOverlay(canvas: Canvas) {
303 | overlayPaint.color = overlayColor
304 |
305 | // Left side overlay
306 | if (leftBarX > barWidth) {
307 | canvas.drawRect(
308 | barWidth,
309 | dpToPx(context, 0f).roundToInt().toFloat(),
310 | leftBarX,
311 | height - dpToPx(context, 0f).roundToInt() - borderWidth,
312 | overlayPaint
313 | )
314 | }
315 |
316 | // Right side overlay
317 | if (rightBarX < width - 2 * barWidth) {
318 | canvas.drawRect(
319 | rightBarX + barWidth,
320 | dpToPx(context, 0f).roundToInt().toFloat(),
321 | width - barWidth,
322 | height - dpToPx(context, 0f).roundToInt() - borderWidth,
323 | overlayPaint
324 | )
325 | }
326 | }
327 |
328 | /* -------------------------------------------------------------------------------------------*/
329 | /* OnTouch */
330 | override fun onTouchEvent(event: MotionEvent): Boolean {
331 | return when (event.action) {
332 | ACTION_DOWN -> onDown(event.x, event.y)
333 | ACTION_MOVE -> onMove(event.x, event.y)
334 | ACTION_UP -> onUp(event.x, event.y)
335 | else -> super.onTouchEvent(event)
336 | }
337 | }
338 |
339 | private fun onDown(x: Float, y: Float): Boolean {
340 | hold = when {
341 | isLeftBarTouched(x, y) -> HOLD_LEFT_BAR
342 | isRightBarTouched(x, y) -> HOLD_RIGHT_BAR
343 | isThumbTouched(x, y) -> HOLD_THUMB
344 | else -> HOLD_NOTHING
345 | }
346 |
347 | when (hold) {
348 | HOLD_LEFT_BAR, HOLD_RIGHT_BAR ->
349 | listener?.onDragRangeBarStart()
350 | HOLD_THUMB ->
351 | listener?.onProgressStart()
352 | }
353 |
354 | return hold != HOLD_NOTHING
355 | }
356 |
357 | private fun onMove(x: Float, y: Float): Boolean {
358 | when (hold) {
359 | HOLD_LEFT_BAR -> moveLeftBar(x, y)
360 | HOLD_RIGHT_BAR -> moveRightBar(x, y)
361 | HOLD_THUMB -> moveSlider(x, y)
362 | else -> return false
363 | }
364 |
365 | return true
366 | }
367 |
368 | private fun onUp(x: Float, y: Float): Boolean {
369 | when (hold) {
370 | HOLD_LEFT_BAR, HOLD_RIGHT_BAR -> {
371 | val percentage = calculateXPercentage(leftBarX, rightBarX)
372 | listener?.onDragRangeBarEnd(percentage[0], percentage[1])
373 | }
374 | HOLD_THUMB -> {
375 | val percentage = calculateThumbXPercentage(thumbX)
376 | listener?.onProgressEnd(percentage)
377 | }
378 | }
379 |
380 | hold = HOLD_NOTHING
381 | return true
382 | }
383 |
384 | /* -------------------------------------------------------------------------------------------*/
385 | /* Internal helpers */
386 | private fun isLeftBarTouched(x: Float, y: Float): Boolean {
387 | return x in (leftBarX - extraDragSpace)..(leftBarX + barWidth + extraDragSpace)
388 | && y in 0f..height.toFloat()
389 | }
390 |
391 | private fun isRightBarTouched(x: Float, y: Float): Boolean {
392 | return x in (rightBarX - extraDragSpace)..(rightBarX + barWidth + extraDragSpace)
393 | && y in 0f..height.toFloat()
394 | }
395 |
396 | private fun isThumbTouched(x: Float, y: Float): Boolean {
397 | return !isLeftBarTouched(x, y) && !isRightBarTouched(x, y)
398 | }
399 |
400 | private fun moveLeftBar(x: Float, y: Float) {
401 | var predictedLeftBarX = x - (barWidth / 2f)
402 | predictedLeftBarX = max(predictedLeftBarX, 0f)
403 | predictedLeftBarX = min(predictedLeftBarX, rightBarX - barWidth - 1)
404 |
405 | val percentage = calculateXPercentage(predictedLeftBarX, rightBarX)
406 | if (listener?.onDragRangeBar(percentage[0], percentage[1]) != false) {
407 | leftBarX = predictedLeftBarX
408 | leftMilli = (percentage[0] * audioWindowLength).toInt()
409 |
410 | postInvalidate()
411 | }
412 | }
413 |
414 | private fun moveRightBar(x: Float, y: Float) {
415 | var predictedRightBarX = x - (barWidth / 2f)
416 | predictedRightBarX = max(predictedRightBarX, leftBarX + barWidth + 1)
417 | predictedRightBarX = min(predictedRightBarX, width.toFloat() - barWidth)
418 |
419 | val percentage = calculateXPercentage(leftBarX, predictedRightBarX)
420 | if (listener?.onDragRangeBar(percentage[0], percentage[1]) != false) {
421 | rightBarX = predictedRightBarX
422 | rightMilli = (percentage[1] * audioWindowLength).toInt()
423 |
424 | postInvalidate()
425 | }
426 | }
427 |
428 | private fun moveSlider(x: Float, y: Float) {
429 | var predictedSliderX = x - (sliderWidth / 2f)
430 | predictedSliderX = max(predictedSliderX, leftBarX + barWidth)
431 | predictedSliderX = min(predictedSliderX, rightBarX - barWidth)
432 |
433 | val percentage = calculateThumbXPercentage(predictedSliderX)
434 | if (listener?.onDragProgressBar(percentage) != false) {
435 | thumbX = predictedSliderX
436 | postInvalidate()
437 | }
438 | }
439 |
440 | private fun calculateXPercentage(leftBarX: Float, rightBarX: Float): FloatArray {
441 | val totalLength = width - barWidth
442 | val left = leftBarX / totalLength
443 | val right = rightBarX / totalLength
444 | return floatArrayOf(left, right)
445 | }
446 |
447 | private fun calculateThumbXPercentage(thumbLeftX: Float): Float {
448 | val totalLength = width - barWidth
449 |
450 | return thumbLeftX / totalLength
451 | }
452 |
453 | private fun restoreBarPositions() {
454 | val totalLength = width - barWidth
455 |
456 | this.leftBarX = leftBarXPercentage * totalLength
457 | this.rightBarX = rightBarXPercentage * totalLength
458 |
459 | this.leftBarXPercentage = -1f
460 | this.rightBarXPercentage = -1f
461 | }
462 |
463 | private fun restoreThumb() {
464 | val totalLength = width - barWidth
465 |
466 | this.thumbX = thumbXPercentage * totalLength
467 |
468 | this.thumbXPercentage = -1f
469 | }
470 |
471 | /* -------------------------------------------------------------------------------------------*/
472 | /* Listener */
473 | interface Listener {
474 | fun onDragRangeBarStart()
475 | fun onDragRangeBar(left: Float, right: Float): Boolean
476 | fun onDragRangeBarEnd(left: Float, right: Float)
477 |
478 |
479 | fun onProgressEnd(percentage: Float)
480 | fun onProgressStart()
481 | fun onDragProgressBar(percentage: Float): Boolean
482 | }
483 | }
--------------------------------------------------------------------------------
/src/main/res/drawable-v23/seek_handle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/src/main/res/drawable/seek_handle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/src/main/res/drawable/text_bg.xml:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
5 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/res/drawable/transparent_seek.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/trimmer_left_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
10 |
13 |
14 |
15 | -
16 |
17 |
18 |
21 |
22 |
23 |
24 | -
25 |
26 |
27 |
30 |
31 |
32 |
33 | -
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/res/drawable/trimmer_right_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
10 |
13 |
14 |
15 |
16 | -
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_audio_trimmer.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
26 |
46 |
47 |
61 |
62 |
70 |
--------------------------------------------------------------------------------
/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #fe5f55
4 |
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | lib
3 | TOTAL:
4 |
5 |
--------------------------------------------------------------------------------
/src/test/java/com/pro/audiotrimmer/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.pro.audiotrimmer
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------