├── .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 | 9 | 10 | 14 | 15 | 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 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules/audiotrimmer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 27 | 28 | 29 | 30 | 31 | 32 | 41 | 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 | ![Screenshot_20210930-113440 (1)](https://user-images.githubusercontent.com/5465207/135402381-2205dc66-3be4-4ff1-b261-e35c8af32911.jpg) 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 | } --------------------------------------------------------------------------------