├── .gitignore ├── .idea ├── assetWizardSettings.xml ├── codeStyles │ └── Project.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── vrgsoft │ │ └── videocrop │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── videcrop ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── arm │ │ └── ffmpeg │ └── x86 │ │ └── ffmpeg │ ├── java │ └── net │ │ └── vrgsoft │ │ └── videcrop │ │ ├── VideoCropActivity.java │ │ ├── cropview │ │ ├── util │ │ │ ├── AspectRatioUtil.java │ │ │ ├── HandleUtil.java │ │ │ ├── ImageViewUtil.java │ │ │ └── PaintUtil.java │ │ └── window │ │ │ ├── CropVideoView.java │ │ │ ├── CropView.java │ │ │ ├── edge │ │ │ ├── Edge.java │ │ │ └── EdgePair.java │ │ │ └── handle │ │ │ ├── CenterHandleHelper.java │ │ │ ├── CornerHandleHelper.java │ │ │ ├── Handle.java │ │ │ ├── HandleHelper.java │ │ │ ├── HorizontalHandleHelper.java │ │ │ └── VerticalHandleHelper.java │ │ ├── ffmpeg │ │ ├── CommandResult.java │ │ ├── CpuArch.java │ │ ├── CpuArchHelper.java │ │ ├── ExecuteBinaryResponseHandler.java │ │ ├── FFbinaryContextProvider.java │ │ ├── FFbinaryInterface.java │ │ ├── FFbinaryObserver.java │ │ ├── FFcommandExecuteAsyncTask.java │ │ ├── FFcommandExecuteResponseHandler.java │ │ ├── FFmpeg.java │ │ ├── FFtask.java │ │ ├── FileUtils.java │ │ ├── Log.java │ │ ├── ResponseHandler.java │ │ ├── ShellCommand.java │ │ └── Util.java │ │ ├── player │ │ └── VideoPlayer.java │ │ ├── util │ │ └── Utils.java │ │ └── view │ │ ├── ProgressView.java │ │ └── VideoSliceSeekBarH.java │ └── res │ ├── drawable-nodpi │ └── ic_thumb_3.png │ ├── drawable │ ├── bg_stroke.xml │ ├── cutter_dot.png │ ├── ic_aspect_ratio.xml │ ├── ic_check.xml │ ├── ic_crop.xml │ ├── ic_crop_16_9.xml │ ├── ic_crop_4_3.xml │ ├── ic_crop_custom.xml │ ├── ic_crop_landscape.xml │ ├── ic_crop_portrait.xml │ ├── ic_crop_square.xml │ ├── ic_pause.xml │ ├── ic_play.xml │ ├── ic_thumb.xml │ ├── ic_thumb_2.png │ └── progress_thumb.png │ ├── layout │ ├── activity_crop.xml │ ├── view_aspect_ratio_menu.xml │ └── view_crop.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── video.gif /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /.idea/assetWizardSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### [HIRE US](http://vrgsoft.net/) 2 | # VideoCrop 3 | Video cropping library with trimming and opportunity to choose different aspect ratio types

4 | 5 | # Usage 6 | *For a working implementation, Have a look at the Sample Project - app* 7 | 1. Include the library as local library project. 8 | ```gradle 9 | allprojects { 10 | repositories { 11 | maven { url 'https://jitpack.io' } 12 | } 13 | } 14 | dependencies { 15 | implementation 'com.github.VRGsoftUA:VideoCrop:1.0' 16 | } 17 | ``` 18 | 2. In code you need to start Activityfor result like so: 19 | ``` 20 | startActivityForResult(VideoCropActivity.createIntent(this, inputPath, outputPath), CROP_REQUEST); 21 | ``` 22 | 3. Then catch result in onActivityResult callback 23 | ```@Override 24 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 25 | if(requestCode == CROP_REQUEST && resultCode == RESULT_OK){ 26 | //crop successful 27 | } 28 | } 29 | ``` 30 | #### Contributing 31 | * Contributions are always welcome 32 | * If you want a feature and can code, feel free to fork and add the change yourself and make a pull request 33 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "net.vrgsoft.videocrop" 7 | minSdkVersion 17 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation fileTree(dir: 'libs', include: ['*.jar']) 22 | implementation "com.android.support:appcompat-v7:27.1.1" 23 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 24 | implementation project(":videcrop") 25 | } 26 | -------------------------------------------------------------------------------- /app/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 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/net/vrgsoft/videocrop/MainActivity.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videocrop; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import net.vrgsoft.videcrop.VideoCropActivity; 8 | 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | private static final int CROP_REQUEST = 200; 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | String inputPath = "/storage/emulated/0/1111.mp4"; 19 | String outputPath = "/storage/emulated/0/YDXJ08599.mp4"; 20 | startActivityForResult(VideoCropActivity.createIntent(this, inputPath, outputPath), CROP_REQUEST); 21 | } 22 | 23 | @Override 24 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 25 | if(requestCode == CROP_REQUEST && resultCode == RESULT_OK){ 26 | //crop successful 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VideoCrop 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.0-alpha05' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 12 12:34:38 EEST 2018 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-4.9-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':videcrop' 2 | -------------------------------------------------------------------------------- /videcrop/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /videcrop/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion "28.0.1" 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 17 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | 27 | implementation "com.android.support:appcompat-v7:27.1.1" 28 | implementation "com.android.support.constraint:constraint-layout:1.1.2" 29 | api "com.google.android.exoplayer:exoplayer-ui:2.8.0" 30 | api "com.google.android.exoplayer:exoplayer-core:2.8.0" 31 | } 32 | -------------------------------------------------------------------------------- /videcrop/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 22 | -------------------------------------------------------------------------------- /videcrop/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /videcrop/src/main/assets/arm/ffmpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/videcrop/src/main/assets/arm/ffmpeg -------------------------------------------------------------------------------- /videcrop/src/main/assets/x86/ffmpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/videcrop/src/main/assets/x86/ffmpeg -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/VideoCropActivity.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop; 2 | 3 | import android.Manifest; 4 | import android.animation.TimeInterpolator; 5 | import android.annotation.SuppressLint; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.content.res.Resources; 10 | import android.graphics.Rect; 11 | import android.media.MediaMetadataRetriever; 12 | import android.os.Bundle; 13 | import android.support.annotation.NonNull; 14 | import android.support.annotation.Nullable; 15 | import android.support.v4.app.ActivityCompat; 16 | import android.support.v4.content.ContextCompat; 17 | import android.support.v7.app.AppCompatActivity; 18 | import android.support.v7.widget.AppCompatImageView; 19 | import android.text.TextUtils; 20 | import android.util.Log; 21 | import android.view.View; 22 | import android.view.animation.AccelerateInterpolator; 23 | import android.view.animation.DecelerateInterpolator; 24 | import android.widget.TextView; 25 | import android.widget.Toast; 26 | 27 | import com.google.android.exoplayer2.util.Util; 28 | 29 | import net.vrgsoft.videcrop.cropview.window.CropVideoView; 30 | import net.vrgsoft.videcrop.ffmpeg.ExecuteBinaryResponseHandler; 31 | import net.vrgsoft.videcrop.ffmpeg.FFmpeg; 32 | import net.vrgsoft.videcrop.ffmpeg.FFtask; 33 | import net.vrgsoft.videcrop.player.VideoPlayer; 34 | import net.vrgsoft.videcrop.view.ProgressView; 35 | import net.vrgsoft.videcrop.view.VideoSliceSeekBarH; 36 | 37 | import java.io.File; 38 | import java.util.Formatter; 39 | import java.util.Locale; 40 | 41 | 42 | public class VideoCropActivity extends AppCompatActivity implements VideoPlayer.OnProgressUpdateListener, VideoSliceSeekBarH.SeekBarChangeListener { 43 | private static final String VIDEO_CROP_INPUT_PATH = "VIDEO_CROP_INPUT_PATH"; 44 | private static final String VIDEO_CROP_OUTPUT_PATH = "VIDEO_CROP_OUTPUT_PATH"; 45 | private static final int STORAGE_REQUEST = 100; 46 | 47 | private VideoPlayer mVideoPlayer; 48 | private StringBuilder formatBuilder; 49 | private Formatter formatter; 50 | 51 | private AppCompatImageView mIvPlay; 52 | private AppCompatImageView mIvAspectRatio; 53 | private AppCompatImageView mIvDone; 54 | private VideoSliceSeekBarH mTmbProgress; 55 | private CropVideoView mCropVideoView; 56 | private TextView mTvProgress; 57 | private TextView mTvDuration; 58 | private TextView mTvAspectCustom; 59 | private TextView mTvAspectSquare; 60 | private TextView mTvAspectPortrait; 61 | private TextView mTvAspectLandscape; 62 | private TextView mTvAspect4by3; 63 | private TextView mTvAspect16by9; 64 | private TextView mTvCropProgress; 65 | private View mAspectMenu; 66 | private ProgressView mProgressBar; 67 | 68 | private String inputPath; 69 | private String outputPath; 70 | private boolean isVideoPlaying = false; 71 | private boolean isAspectMenuShown = false; 72 | private FFtask mFFTask; 73 | private FFmpeg mFFMpeg; 74 | 75 | public static Intent createIntent(Context context, String inputPath, String outputPath) { 76 | Intent intent = new Intent(context, VideoCropActivity.class); 77 | intent.putExtra(VIDEO_CROP_INPUT_PATH, inputPath); 78 | intent.putExtra(VIDEO_CROP_OUTPUT_PATH, outputPath); 79 | return intent; 80 | } 81 | 82 | @Override 83 | protected void onCreate(@Nullable Bundle savedInstanceState) { 84 | super.onCreate(savedInstanceState); 85 | setContentView(R.layout.activity_crop); 86 | 87 | formatBuilder = new StringBuilder(); 88 | formatter = new Formatter(formatBuilder, Locale.getDefault()); 89 | 90 | inputPath = getIntent().getStringExtra(VIDEO_CROP_INPUT_PATH); 91 | outputPath = getIntent().getStringExtra(VIDEO_CROP_OUTPUT_PATH); 92 | 93 | if (TextUtils.isEmpty(inputPath) || TextUtils.isEmpty(outputPath)) { 94 | Toast.makeText(this, "input and output paths must be valid and not null", Toast.LENGTH_SHORT).show(); 95 | setResult(RESULT_CANCELED); 96 | finish(); 97 | } 98 | 99 | findViews(); 100 | initListeners(); 101 | 102 | requestStoragePermission(); 103 | } 104 | 105 | @Override 106 | public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { 107 | switch (requestCode) { 108 | case STORAGE_REQUEST: { 109 | if (grantResults.length > 0 110 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 111 | initPlayer(inputPath); 112 | } else { 113 | Toast.makeText(this, "You must grant a write storage permission to use this functionality", Toast.LENGTH_SHORT).show(); 114 | setResult(RESULT_CANCELED); 115 | finish(); 116 | } 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | protected void onStart() { 123 | super.onStart(); 124 | if (isVideoPlaying) { 125 | mVideoPlayer.play(true); 126 | } 127 | } 128 | 129 | @Override 130 | public void onStop() { 131 | super.onStop(); 132 | mVideoPlayer.play(false); 133 | } 134 | 135 | @Override 136 | public void onDestroy() { 137 | mVideoPlayer.release(); 138 | if (mFFTask != null && !mFFTask.isProcessCompleted()) { 139 | mFFTask.sendQuitSignal(); 140 | } 141 | if (mFFMpeg != null) { 142 | mFFMpeg.deleteFFmpegBin(); 143 | } 144 | super.onDestroy(); 145 | } 146 | 147 | @Override 148 | public void onFirstTimeUpdate(long duration, long currentPosition) { 149 | mTmbProgress.setSeekBarChangeListener(this); 150 | mTmbProgress.setMaxValue(duration); 151 | mTmbProgress.setLeftProgress(0); 152 | mTmbProgress.setRightProgress(duration); 153 | mTmbProgress.setProgressMinDiff(0); 154 | } 155 | 156 | @Override 157 | public void onProgressUpdate(long currentPosition, long duration, long bufferedPosition) { 158 | mTmbProgress.videoPlayingProgress(currentPosition); 159 | if (!mVideoPlayer.isPlaying() || currentPosition >= mTmbProgress.getRightProgress()) { 160 | if (mVideoPlayer.isPlaying()) { 161 | playPause(); 162 | } 163 | } 164 | 165 | mTmbProgress.setSliceBlocked(false); 166 | mTmbProgress.removeVideoStatusThumb(); 167 | 168 | // mTmbProgress.setPosition(currentPosition); 169 | // mTmbProgress.setBufferedPosition(bufferedPosition); 170 | // mTmbProgress.setDuration(duration); 171 | } 172 | 173 | private void findViews() { 174 | mCropVideoView = findViewById(R.id.cropVideoView); 175 | mIvPlay = findViewById(R.id.ivPlay); 176 | mIvAspectRatio = findViewById(R.id.ivAspectRatio); 177 | mIvDone = findViewById(R.id.ivDone); 178 | mTvProgress = findViewById(R.id.tvProgress); 179 | mTvDuration = findViewById(R.id.tvDuration); 180 | mTmbProgress = findViewById(R.id.tmbProgress); 181 | mAspectMenu = findViewById(R.id.aspectMenu); 182 | mTvAspectCustom = findViewById(R.id.tvAspectCustom); 183 | mTvAspectSquare = findViewById(R.id.tvAspectSquare); 184 | mTvAspectPortrait = findViewById(R.id.tvAspectPortrait); 185 | mTvAspectLandscape = findViewById(R.id.tvAspectLandscape); 186 | mTvAspect4by3 = findViewById(R.id.tvAspect4by3); 187 | mTvAspect16by9 = findViewById(R.id.tvAspect16by9); 188 | mProgressBar = findViewById(R.id.pbCropProgress); 189 | mTvCropProgress = findViewById(R.id.tvCropProgress); 190 | } 191 | 192 | private void initListeners() { 193 | mIvPlay.setOnClickListener(new View.OnClickListener() { 194 | @Override 195 | public void onClick(View v) { 196 | playPause(); 197 | } 198 | }); 199 | mIvAspectRatio.setOnClickListener(new View.OnClickListener() { 200 | @Override 201 | public void onClick(View v) { 202 | handleMenuVisibility(); 203 | } 204 | }); 205 | mTvAspectCustom.setOnClickListener(new View.OnClickListener() { 206 | @Override 207 | public void onClick(View v) { 208 | mCropVideoView.setFixedAspectRatio(false); 209 | handleMenuVisibility(); 210 | } 211 | }); 212 | mTvAspectSquare.setOnClickListener(new View.OnClickListener() { 213 | @Override 214 | public void onClick(View v) { 215 | mCropVideoView.setFixedAspectRatio(true); 216 | mCropVideoView.setAspectRatio(10, 10); 217 | handleMenuVisibility(); 218 | } 219 | }); 220 | mTvAspectPortrait.setOnClickListener(new View.OnClickListener() { 221 | @Override 222 | public void onClick(View v) { 223 | mCropVideoView.setFixedAspectRatio(true); 224 | mCropVideoView.setAspectRatio(8, 16); 225 | handleMenuVisibility(); 226 | } 227 | }); 228 | mTvAspectLandscape.setOnClickListener(new View.OnClickListener() { 229 | @Override 230 | public void onClick(View v) { 231 | mCropVideoView.setFixedAspectRatio(true); 232 | mCropVideoView.setAspectRatio(16, 8); 233 | handleMenuVisibility(); 234 | } 235 | }); 236 | mTvAspect4by3.setOnClickListener(new View.OnClickListener() { 237 | @Override 238 | public void onClick(View v) { 239 | mCropVideoView.setFixedAspectRatio(true); 240 | mCropVideoView.setAspectRatio(4, 3); 241 | handleMenuVisibility(); 242 | } 243 | }); 244 | mTvAspect16by9.setOnClickListener(new View.OnClickListener() { 245 | @Override 246 | public void onClick(View v) { 247 | mCropVideoView.setFixedAspectRatio(true); 248 | mCropVideoView.setAspectRatio(16, 9); 249 | handleMenuVisibility(); 250 | } 251 | }); 252 | mIvDone.setOnClickListener(new View.OnClickListener() { 253 | @Override 254 | public void onClick(View v) { 255 | handleCropStart(); 256 | } 257 | }); 258 | } 259 | 260 | private void playPause() { 261 | isVideoPlaying = !mVideoPlayer.isPlaying(); 262 | if (mVideoPlayer.isPlaying()) { 263 | mVideoPlayer.play(!mVideoPlayer.isPlaying()); 264 | mTmbProgress.setSliceBlocked(false); 265 | mTmbProgress.removeVideoStatusThumb(); 266 | mIvPlay.setImageResource(R.drawable.ic_play); 267 | return; 268 | } 269 | mVideoPlayer.seekTo(mTmbProgress.getLeftProgress()); 270 | mVideoPlayer.play(!mVideoPlayer.isPlaying()); 271 | mTmbProgress.videoPlayingProgress(mTmbProgress.getLeftProgress()); 272 | mIvPlay.setImageResource(R.drawable.ic_pause); 273 | } 274 | 275 | private void initPlayer(String uri) { 276 | if (!new File(uri).exists()) { 277 | Toast.makeText(this, "File doesn't exists", Toast.LENGTH_SHORT).show(); 278 | setResult(RESULT_CANCELED); 279 | finish(); 280 | return; 281 | } 282 | 283 | mVideoPlayer = new VideoPlayer(this); 284 | mCropVideoView.setPlayer(mVideoPlayer.getPlayer()); 285 | mVideoPlayer.initMediaSource(this, uri); 286 | mVideoPlayer.setUpdateListener(this); 287 | 288 | fetchVideoInfo(uri); 289 | } 290 | 291 | private void fetchVideoInfo(String uri) { 292 | MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 293 | retriever.setDataSource(new File(uri).getAbsolutePath()); 294 | int videoWidth = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); 295 | int videoHeight = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); 296 | int rotationDegrees = Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)); 297 | 298 | mCropVideoView.initBounds(videoWidth, videoHeight, rotationDegrees); 299 | } 300 | 301 | private void handleMenuVisibility() { 302 | isAspectMenuShown = !isAspectMenuShown; 303 | TimeInterpolator interpolator; 304 | if (isAspectMenuShown) { 305 | interpolator = new DecelerateInterpolator(); 306 | } else { 307 | interpolator = new AccelerateInterpolator(); 308 | } 309 | mAspectMenu.animate() 310 | .translationY(isAspectMenuShown ? 0 : Resources.getSystem().getDisplayMetrics().density * 400) 311 | .alpha(isAspectMenuShown ? 1 : 0) 312 | .setInterpolator(interpolator) 313 | .start(); 314 | } 315 | 316 | private void requestStoragePermission() { 317 | if (ContextCompat.checkSelfPermission(this, 318 | Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 319 | ActivityCompat.requestPermissions(this, 320 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_REQUEST); 321 | } else { 322 | initPlayer(inputPath); 323 | } 324 | } 325 | 326 | @SuppressLint("DefaultLocale") 327 | private void handleCropStart() { 328 | Rect cropRect = mCropVideoView.getCropRect(); 329 | long startCrop = mTmbProgress.getLeftProgress(); 330 | long durationCrop = mTmbProgress.getRightProgress() - mTmbProgress.getLeftProgress(); 331 | String start = Util.getStringForTime(formatBuilder, formatter, startCrop); 332 | String duration = Util.getStringForTime(formatBuilder, formatter, durationCrop); 333 | start += "." + startCrop % 1000; 334 | duration += "." + durationCrop % 1000; 335 | 336 | mFFMpeg = FFmpeg.getInstance(this); 337 | if (mFFMpeg.isSupported()) { 338 | String crop = String.format("crop=%d:%d:%d:%d:exact=0", cropRect.right, cropRect.bottom, cropRect.left, cropRect.top); 339 | String[] cmd = { 340 | "-y", 341 | "-ss", 342 | start, 343 | "-i", 344 | inputPath, 345 | "-t", 346 | duration, 347 | "-vf", 348 | crop, 349 | outputPath 350 | }; 351 | 352 | mFFTask = mFFMpeg.execute(cmd, new ExecuteBinaryResponseHandler() { 353 | @Override 354 | public void onSuccess(String message) { 355 | setResult(RESULT_OK); 356 | Log.e("onSuccess", message); 357 | finish(); 358 | } 359 | 360 | @Override 361 | public void onProgress(String message) { 362 | Log.e("onProgress", message); 363 | } 364 | 365 | @Override 366 | public void onFailure(String message) { 367 | Toast.makeText(VideoCropActivity.this, "Failed to crop!", Toast.LENGTH_SHORT).show(); 368 | Log.e("onFailure", message); 369 | } 370 | 371 | @Override 372 | public void onProgressPercent(float percent) { 373 | mProgressBar.setProgress((int) percent); 374 | mTvCropProgress.setText((int) percent + "%"); 375 | } 376 | 377 | @Override 378 | public void onStart() { 379 | mIvDone.setEnabled(false); 380 | mIvPlay.setEnabled(false); 381 | mProgressBar.setVisibility(View.VISIBLE); 382 | mProgressBar.setProgress(0); 383 | mTvCropProgress.setVisibility(View.VISIBLE); 384 | mTvCropProgress.setText("0%"); 385 | } 386 | 387 | @Override 388 | public void onFinish() { 389 | mIvDone.setEnabled(true); 390 | mIvPlay.setEnabled(true); 391 | mProgressBar.setVisibility(View.INVISIBLE); 392 | mProgressBar.setProgress(0); 393 | mTvCropProgress.setVisibility(View.INVISIBLE); 394 | mTvCropProgress.setText("0%"); 395 | Toast.makeText(VideoCropActivity.this, "FINISHED", Toast.LENGTH_SHORT).show(); 396 | } 397 | }, durationCrop * 1.0f / 1000); 398 | } 399 | } 400 | 401 | @Override 402 | public void seekBarValueChanged(long leftThumb, long rightThumb) { 403 | if (mTmbProgress.getSelectedThumb() == 1) { 404 | mVideoPlayer.seekTo(leftThumb); 405 | } 406 | 407 | mTvDuration.setText(Util.getStringForTime(formatBuilder, formatter, rightThumb)); 408 | mTvProgress.setText(Util.getStringForTime(formatBuilder, formatter, leftThumb)); 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/util/AspectRatioUtil.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.util; 2 | 3 | import android.graphics.Rect; 4 | 5 | public final class AspectRatioUtil { 6 | private AspectRatioUtil() { 7 | } 8 | 9 | public static float calculateAspectRatio(float left, float top, float right, float bottom) { 10 | float width = right - left; 11 | float height = bottom - top; 12 | return width / height; 13 | } 14 | 15 | public static float calculateAspectRatio(Rect rect) { 16 | return (float)rect.width() / (float)rect.height(); 17 | } 18 | 19 | public static float calculateLeft(float top, float right, float bottom, float targetAspectRatio) { 20 | float height = bottom - top; 21 | return right - targetAspectRatio * height; 22 | } 23 | 24 | public static float calculateTop(float left, float right, float bottom, float targetAspectRatio) { 25 | float width = right - left; 26 | return bottom - width / targetAspectRatio; 27 | } 28 | 29 | public static float calculateRight(float left, float top, float bottom, float targetAspectRatio) { 30 | float height = bottom - top; 31 | return targetAspectRatio * height + left; 32 | } 33 | 34 | public static float calculateBottom(float left, float top, float right, float targetAspectRatio) { 35 | float width = right - left; 36 | return width / targetAspectRatio + top; 37 | } 38 | 39 | public static float calculateWidth(float top, float bottom, float targetAspectRatio) { 40 | float height = bottom - top; 41 | return targetAspectRatio * height; 42 | } 43 | 44 | public static float calculateHeight(float left, float right, float targetAspectRatio) { 45 | float width = right - left; 46 | return width / targetAspectRatio; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/util/HandleUtil.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.util; 2 | 3 | import android.content.Context; 4 | import android.util.Pair; 5 | import android.util.TypedValue; 6 | 7 | import net.vrgsoft.videcrop.cropview.window.CropView; 8 | import net.vrgsoft.videcrop.cropview.window.handle.Handle; 9 | 10 | public final class HandleUtil { 11 | private static final int TARGET_RADIUS_DP = 24; 12 | 13 | private HandleUtil() { 14 | } 15 | 16 | public static float getTargetRadius(Context context) { 17 | float targetRadius = TypedValue.applyDimension(1, 24.0F, context.getResources().getDisplayMetrics()); 18 | return targetRadius; 19 | } 20 | 21 | public static Handle getPressedHandle(float x, float y, float left, float top, float right, float bottom, float targetRadius) { 22 | Handle pressedHandle = null; 23 | if (isInCornerTargetZone(x, y, left, top, targetRadius)) { 24 | pressedHandle = Handle.TOP_LEFT; 25 | } else if (isInCornerTargetZone(x, y, right, top, targetRadius)) { 26 | pressedHandle = Handle.TOP_RIGHT; 27 | } else if (isInCornerTargetZone(x, y, left, bottom, targetRadius)) { 28 | pressedHandle = Handle.BOTTOM_LEFT; 29 | } else if (isInCornerTargetZone(x, y, right, bottom, targetRadius)) { 30 | pressedHandle = Handle.BOTTOM_RIGHT; 31 | } else if (isInCenterTargetZone(x, y, left, top, right, bottom) && focusCenter()) { 32 | pressedHandle = Handle.CENTER; 33 | } else if (isInHorizontalTargetZone(x, y, left, right, top, targetRadius)) { 34 | pressedHandle = Handle.TOP; 35 | } else if (isInHorizontalTargetZone(x, y, left, right, bottom, targetRadius)) { 36 | pressedHandle = Handle.BOTTOM; 37 | } else if (isInVerticalTargetZone(x, y, left, top, bottom, targetRadius)) { 38 | pressedHandle = Handle.LEFT; 39 | } else if (isInVerticalTargetZone(x, y, right, top, bottom, targetRadius)) { 40 | pressedHandle = Handle.RIGHT; 41 | } else if (isInCenterTargetZone(x, y, left, top, right, bottom) && !focusCenter()) { 42 | pressedHandle = Handle.CENTER; 43 | } 44 | 45 | return pressedHandle; 46 | } 47 | 48 | public static Pair getOffset(Handle handle, float x, float y, float left, float top, float right, float bottom) { 49 | if (handle == null) { 50 | return null; 51 | } else { 52 | float touchOffsetX = 0.0F; 53 | float touchOffsetY = 0.0F; 54 | switch(handle) { 55 | case TOP_LEFT: 56 | touchOffsetX = left - x; 57 | touchOffsetY = top - y; 58 | break; 59 | case TOP_RIGHT: 60 | touchOffsetX = right - x; 61 | touchOffsetY = top - y; 62 | break; 63 | case BOTTOM_LEFT: 64 | touchOffsetX = left - x; 65 | touchOffsetY = bottom - y; 66 | break; 67 | case BOTTOM_RIGHT: 68 | touchOffsetX = right - x; 69 | touchOffsetY = bottom - y; 70 | break; 71 | case LEFT: 72 | touchOffsetX = left - x; 73 | touchOffsetY = 0.0F; 74 | break; 75 | case TOP: 76 | touchOffsetX = 0.0F; 77 | touchOffsetY = top - y; 78 | break; 79 | case RIGHT: 80 | touchOffsetX = right - x; 81 | touchOffsetY = 0.0F; 82 | break; 83 | case BOTTOM: 84 | touchOffsetX = 0.0F; 85 | touchOffsetY = bottom - y; 86 | break; 87 | case CENTER: 88 | float centerX = (right + left) / 2.0F; 89 | float centerY = (top + bottom) / 2.0F; 90 | touchOffsetX = centerX - x; 91 | touchOffsetY = centerY - y; 92 | } 93 | 94 | Pair result = new Pair(touchOffsetX, touchOffsetY); 95 | return result; 96 | } 97 | } 98 | 99 | private static boolean isInCornerTargetZone(float x, float y, float handleX, float handleY, float targetRadius) { 100 | return Math.abs(x - handleX) <= targetRadius && Math.abs(y - handleY) <= targetRadius; 101 | } 102 | 103 | private static boolean isInHorizontalTargetZone(float x, float y, float handleXStart, float handleXEnd, float handleY, float targetRadius) { 104 | return x > handleXStart && x < handleXEnd && Math.abs(y - handleY) <= targetRadius; 105 | } 106 | 107 | private static boolean isInVerticalTargetZone(float x, float y, float handleX, float handleYStart, float handleYEnd, float targetRadius) { 108 | return Math.abs(x - handleX) <= targetRadius && y > handleYStart && y < handleYEnd; 109 | } 110 | 111 | private static boolean isInCenterTargetZone(float x, float y, float left, float top, float right, float bottom) { 112 | return x > left && x < right && y > top && y < bottom; 113 | } 114 | 115 | private static boolean focusCenter() { 116 | return !CropView.showGuidelines(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/util/ImageViewUtil.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Rect; 5 | import android.view.View; 6 | 7 | public final class ImageViewUtil { 8 | private ImageViewUtil() { 9 | } 10 | 11 | public static Rect getBitmapRectCenterInside(Bitmap bitmap, View view) { 12 | int bitmapWidth = bitmap.getWidth(); 13 | int bitmapHeight = bitmap.getHeight(); 14 | int viewWidth = view.getWidth(); 15 | int viewHeight = view.getHeight(); 16 | return getBitmapRectCenterInsideHelper(bitmapWidth, bitmapHeight, viewWidth, viewHeight); 17 | } 18 | 19 | public static Rect getBitmapRectCenterInside(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) { 20 | return getBitmapRectCenterInsideHelper(bitmapWidth, bitmapHeight, viewWidth, viewHeight); 21 | } 22 | 23 | private static Rect getBitmapRectCenterInsideHelper(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) { 24 | double viewToBitmapWidthRatio = 1.0D / 0.0; 25 | double viewToBitmapHeightRatio = 1.0D / 0.0; 26 | if (viewWidth < bitmapWidth) { 27 | viewToBitmapWidthRatio = (double)viewWidth / (double)bitmapWidth; 28 | } 29 | 30 | if (viewHeight < bitmapHeight) { 31 | viewToBitmapHeightRatio = (double)viewHeight / (double)bitmapHeight; 32 | } 33 | 34 | double resultWidth; 35 | double resultHeight; 36 | if (viewToBitmapWidthRatio == 1.0D / 0.0 && viewToBitmapHeightRatio == 1.0D / 0.0) { 37 | resultHeight = (double)bitmapHeight; 38 | resultWidth = (double)bitmapWidth; 39 | } else if (viewToBitmapWidthRatio <= viewToBitmapHeightRatio) { 40 | resultWidth = (double)viewWidth; 41 | resultHeight = (double)bitmapHeight * resultWidth / (double)bitmapWidth; 42 | } else { 43 | resultHeight = (double)viewHeight; 44 | resultWidth = (double)bitmapWidth * resultHeight / (double)bitmapHeight; 45 | } 46 | 47 | int resultX; 48 | int resultY; 49 | if (resultWidth == (double)viewWidth) { 50 | resultX = 0; 51 | resultY = (int)Math.round(((double)viewHeight - resultHeight) / 2.0D); 52 | } else if (resultHeight == (double)viewHeight) { 53 | resultX = (int)Math.round(((double)viewWidth - resultWidth) / 2.0D); 54 | resultY = 0; 55 | } else { 56 | resultX = (int)Math.round(((double)viewWidth - resultWidth) / 2.0D); 57 | resultY = (int)Math.round(((double)viewHeight - resultHeight) / 2.0D); 58 | } 59 | 60 | Rect result = new Rect(resultX, resultY, resultX + (int)Math.ceil(resultWidth), resultY + (int)Math.ceil(resultHeight)); 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/util/PaintUtil.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.util; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.util.TypedValue; 7 | 8 | public final class PaintUtil { 9 | private static final int DEFAULT_CORNER_COLOR = -1; 10 | private static final String SEMI_TRANSPARENT = "#AAFFFFFF"; 11 | private static final String DEFAULT_BACKGROUND_COLOR_ID = "#B0000000"; 12 | private static final float DEFAULT_LINE_THICKNESS_DP = 3.0F; 13 | private static final float DEFAULT_CORNER_THICKNESS_DP = 5.0F; 14 | private static final float DEFAULT_GUIDELINE_THICKNESS_PX = 1.0F; 15 | 16 | private PaintUtil() { 17 | } 18 | 19 | public static Paint newBorderPaint(Context context) { 20 | float lineThicknessPx = TypedValue.applyDimension(1, 3.0F, context.getResources().getDisplayMetrics()); 21 | Paint borderPaint = new Paint(); 22 | borderPaint.setColor(Color.parseColor("#AAFFFFFF")); 23 | borderPaint.setStrokeWidth(lineThicknessPx); 24 | borderPaint.setStyle(Paint.Style.STROKE); 25 | return borderPaint; 26 | } 27 | 28 | public static Paint newGuidelinePaint() { 29 | Paint paint = new Paint(); 30 | paint.setColor(Color.parseColor("#AAFFFFFF")); 31 | paint.setStrokeWidth(1.0F); 32 | return paint; 33 | } 34 | 35 | public static Paint newBackgroundPaint(Context context) { 36 | Paint paint = new Paint(); 37 | paint.setColor(Color.parseColor("#B0000000")); 38 | return paint; 39 | } 40 | 41 | public static Paint newCornerPaint(Context context) { 42 | float lineThicknessPx = TypedValue.applyDimension(1, 5.0F, context.getResources().getDisplayMetrics()); 43 | Paint cornerPaint = new Paint(); 44 | cornerPaint.setColor(-1); 45 | cornerPaint.setStrokeWidth(lineThicknessPx); 46 | cornerPaint.setStyle(Paint.Style.STROKE); 47 | return cornerPaint; 48 | } 49 | 50 | public static float getCornerThickness() { 51 | return 5.0F; 52 | } 53 | 54 | public static float getLineThickness() { 55 | return 3.0F; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/CropVideoView.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Rect; 6 | import android.util.AttributeSet; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | 12 | import com.google.android.exoplayer2.SimpleExoPlayer; 13 | import com.google.android.exoplayer2.ui.PlayerView; 14 | 15 | import net.vrgsoft.videcrop.R; 16 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 17 | 18 | public class CropVideoView extends FrameLayout { 19 | private PlayerView mPlayerView; 20 | private CropView mCropView; 21 | private int mVideoWidth; 22 | private int mVideoHeight; 23 | private int mVideoRotationDegrees; 24 | private int mGuidelines = 1; 25 | private boolean mFixAspectRatio = false; 26 | private int mAspectRatioX = 1; 27 | private int mAspectRatioY = 1; 28 | 29 | public CropVideoView(Context context) { 30 | super(context); 31 | init(context); 32 | } 33 | 34 | public CropVideoView(Context context, AttributeSet attrs) { 35 | super(context, attrs); 36 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropVideoView, 0, 0); 37 | 38 | try { 39 | mGuidelines = ta.getInteger(R.styleable.CropVideoView_guidelines, 1); 40 | mFixAspectRatio = ta.getBoolean(R.styleable.CropVideoView_fixAspectRatio, false); 41 | mAspectRatioX = ta.getInteger(R.styleable.CropVideoView_aspectRatioX, 1); 42 | mAspectRatioY = ta.getInteger(R.styleable.CropVideoView_aspectRatioY, 1); 43 | } finally { 44 | ta.recycle(); 45 | } 46 | 47 | init(context); 48 | } 49 | 50 | private void init(Context context) { 51 | LayoutInflater inflater = LayoutInflater.from(context); 52 | View v = inflater.inflate(R.layout.view_crop, this, true); 53 | mPlayerView = v.findViewById(R.id.playerView); 54 | mCropView = v.findViewById(R.id.cropView); 55 | mCropView.setInitialAttributeValues(mGuidelines, mFixAspectRatio, mAspectRatioX, mAspectRatioY); 56 | } 57 | 58 | protected void onSizeChanged(int newWidth, int newHeight, int oldw, int oldh) { 59 | ViewGroup.LayoutParams lp = getLayoutParams(); 60 | if (mVideoRotationDegrees == 90 || mVideoRotationDegrees == 270) { 61 | if (mVideoWidth >= mVideoHeight) { 62 | lp.width = (int) (newHeight * (1.0f * mVideoHeight / mVideoWidth)); 63 | lp.height = newHeight; 64 | } else { 65 | lp.width = newWidth; 66 | lp.height = (int) (newWidth * (1.0f * mVideoWidth / mVideoHeight)); 67 | } 68 | } else { 69 | if (mVideoWidth >= mVideoHeight) { 70 | lp.width = newWidth; 71 | lp.height = (int) (newWidth * (1.0f * mVideoHeight / mVideoWidth)); 72 | } else { 73 | lp.width = (int) (newHeight * (1.0f * mVideoWidth / mVideoHeight)); 74 | lp.height = newHeight; 75 | } 76 | } 77 | 78 | setLayoutParams(lp); 79 | Rect rect = new Rect(0, 0, lp.width, lp.height); 80 | mCropView.setBitmapRect(rect); 81 | mCropView.resetCropOverlayView(); 82 | } 83 | 84 | public void setPlayer(SimpleExoPlayer player) { 85 | mPlayerView.setPlayer(player); 86 | mCropView.resetCropOverlayView(); 87 | } 88 | 89 | @Override 90 | protected void onDetachedFromWindow() { 91 | super.onDetachedFromWindow(); 92 | mPlayerView.setPlayer(null); 93 | } 94 | 95 | public Rect getCropRect() { 96 | float left = Edge.LEFT.getCoordinate(); 97 | float top = Edge.TOP.getCoordinate(); 98 | float right = Edge.RIGHT.getCoordinate(); 99 | float bottom = Edge.BOTTOM.getCoordinate(); 100 | Rect result = new Rect(); 101 | 102 | if (mVideoRotationDegrees == 90 || mVideoRotationDegrees == 270) { 103 | if (mVideoRotationDegrees == 90) { 104 | result.left = mVideoWidth - (int) (bottom * mVideoWidth / getHeight()); 105 | result.right = mVideoWidth - (int) (top * mVideoWidth / getHeight()); 106 | result.top = (int) (left * mVideoHeight / getWidth()); 107 | result.bottom = (int) (right * mVideoHeight / getWidth()); 108 | } else { 109 | result.left = (int) (top * mVideoWidth / getHeight()); 110 | result.right = (int) (bottom * mVideoWidth / getHeight()); 111 | result.top = mVideoHeight - (int) (right * mVideoHeight / getWidth()); 112 | result.bottom = mVideoHeight - (int) (left * mVideoHeight / getWidth()); 113 | } 114 | int realRight = result.right; 115 | result.right = result.bottom - result.top; 116 | result.bottom = realRight - result.left; 117 | } else { 118 | result.left = (int) (left * mVideoWidth / getWidth()); 119 | result.right = (int) (right * mVideoWidth / getWidth()); 120 | result.top = (int) (top * mVideoHeight / getHeight()); 121 | result.bottom = (int) (bottom * mVideoHeight / getHeight()); 122 | 123 | result.right = result.right - result.left; 124 | result.bottom = result.bottom - result.top; 125 | } 126 | 127 | return result; 128 | } 129 | 130 | public void setFixedAspectRatio(boolean fixAspectRatio) { 131 | mCropView.setFixedAspectRatio(fixAspectRatio); 132 | } 133 | 134 | public void setAspectRatio(int aspectRatioX, int aspectRatioY) { 135 | mAspectRatioX = aspectRatioX; 136 | mAspectRatioY = aspectRatioY; 137 | mCropView.setAspectRatioX(this.mAspectRatioX); 138 | mCropView.setAspectRatioY(this.mAspectRatioY); 139 | } 140 | 141 | public void initBounds(int videoWidth, int videoHeight, int rotationDegrees) { 142 | mVideoWidth = videoWidth; 143 | mVideoHeight = videoHeight; 144 | mVideoRotationDegrees = rotationDegrees; 145 | } 146 | 147 | // @Override 148 | // protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 149 | // int widthMode = MeasureSpec.getMode(widthMeasureSpec); 150 | // int widthSize = MeasureSpec.getSize(widthMeasureSpec); 151 | // int heightMode = MeasureSpec.getMode(heightMeasureSpec); 152 | // int heightSize = MeasureSpec.getSize(heightMeasureSpec); 153 | // 154 | // 155 | // } 156 | } 157 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/CropView.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.Rect; 7 | import android.util.AttributeSet; 8 | import android.util.DisplayMetrics; 9 | import android.util.Pair; 10 | import android.util.TypedValue; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | import net.vrgsoft.videcrop.cropview.util.AspectRatioUtil; 15 | import net.vrgsoft.videcrop.cropview.util.HandleUtil; 16 | import net.vrgsoft.videcrop.cropview.util.PaintUtil; 17 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 18 | import net.vrgsoft.videcrop.cropview.window.handle.Handle; 19 | 20 | public class CropView extends View { 21 | private static final int SNAP_RADIUS_DP = 6; 22 | private static final float DEFAULT_SHOW_GUIDELINES_LIMIT = 100.0F; 23 | private static final float DEFAULT_CORNER_THICKNESS_DP = PaintUtil.getCornerThickness(); 24 | private static final float DEFAULT_LINE_THICKNESS_DP = PaintUtil.getLineThickness(); 25 | private static final float DEFAULT_CORNER_OFFSET_DP; 26 | private static final float DEFAULT_CORNER_EXTENSION_DP; 27 | private static final float DEFAULT_CORNER_LENGTH_DP = 20.0F; 28 | private static final int GUIDELINES_OFF = 0; 29 | private static final int GUIDELINES_ON_TOUCH = 1; 30 | private static final int GUIDELINES_ON = 2; 31 | private Paint mBorderPaint; 32 | private Paint mGuidelinePaint; 33 | private Paint mCornerPaint; 34 | private Paint mBackgroundPaint; 35 | private Rect mBitmapRect; 36 | private float mHandleRadius; 37 | private float mSnapRadius; 38 | private Pair mTouchOffset; 39 | private Handle mPressedHandle; 40 | private boolean mFixAspectRatio = false; 41 | private int mAspectRatioX = 1; 42 | private int mAspectRatioY = 1; 43 | private float mTargetAspectRatio; 44 | private int mGuidelines; 45 | private boolean initializedCropWindow; 46 | private float mCornerExtension; 47 | private float mCornerOffset; 48 | private float mCornerLength; 49 | 50 | public CropView(Context context) { 51 | super(context); 52 | mTargetAspectRatio = (float)mAspectRatioX / (float)mAspectRatioY; 53 | initializedCropWindow = false; 54 | init(context); 55 | } 56 | 57 | public CropView(Context context, AttributeSet attrs) { 58 | super(context, attrs); 59 | mTargetAspectRatio = (float)mAspectRatioX / (float)mAspectRatioY; 60 | initializedCropWindow = false; 61 | init(context); 62 | } 63 | 64 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 65 | initCropWindow(mBitmapRect); 66 | } 67 | 68 | protected void onDraw(Canvas canvas) { 69 | super.onDraw(canvas); 70 | drawBackground(canvas, mBitmapRect); 71 | if (showGuidelines()) { 72 | if (mGuidelines == 2) { 73 | drawRuleOfThirdsGuidelines(canvas); 74 | } else if (mGuidelines == 1) { 75 | if (mPressedHandle != null) { 76 | drawRuleOfThirdsGuidelines(canvas); 77 | } 78 | } else if (mGuidelines == 0) { 79 | } 80 | } 81 | 82 | canvas.drawRect(Edge.LEFT.getCoordinate(), Edge.TOP.getCoordinate(), Edge.RIGHT.getCoordinate(), Edge.BOTTOM.getCoordinate(), mBorderPaint); 83 | drawCorners(canvas); 84 | } 85 | 86 | public boolean onTouchEvent(MotionEvent event) { 87 | if (!isEnabled()) { 88 | return false; 89 | } else { 90 | switch(event.getAction()) { 91 | case 0: 92 | onActionDown(event.getX(), event.getY()); 93 | return true; 94 | case 1: 95 | case 3: 96 | getParent().requestDisallowInterceptTouchEvent(false); 97 | onActionUp(); 98 | return true; 99 | case 2: 100 | onActionMove(event.getX(), event.getY()); 101 | getParent().requestDisallowInterceptTouchEvent(true); 102 | return true; 103 | default: 104 | return false; 105 | } 106 | } 107 | } 108 | 109 | public void setBitmapRect(Rect bitmapRect) { 110 | mBitmapRect = bitmapRect; 111 | initCropWindow(mBitmapRect); 112 | } 113 | 114 | public void resetCropOverlayView() { 115 | if (initializedCropWindow) { 116 | initCropWindow(mBitmapRect); 117 | invalidate(); 118 | } 119 | 120 | } 121 | 122 | public void setGuidelines(int guidelines) { 123 | if (guidelines >= 0 && guidelines <= 2) { 124 | mGuidelines = guidelines; 125 | if (initializedCropWindow) { 126 | initCropWindow(mBitmapRect); 127 | invalidate(); 128 | } 129 | 130 | } else { 131 | throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation."); 132 | } 133 | } 134 | 135 | public void setFixedAspectRatio(boolean fixAspectRatio) { 136 | mFixAspectRatio = fixAspectRatio; 137 | if (initializedCropWindow) { 138 | initCropWindow(mBitmapRect); 139 | invalidate(); 140 | } 141 | } 142 | 143 | public void setAspectRatioX(int aspectRatioX) { 144 | if (aspectRatioX <= 0) { 145 | throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0."); 146 | } else { 147 | mAspectRatioX = aspectRatioX; 148 | mTargetAspectRatio = (float)mAspectRatioX / (float)mAspectRatioY; 149 | if (initializedCropWindow) { 150 | initCropWindow(mBitmapRect); 151 | invalidate(); 152 | } 153 | 154 | } 155 | } 156 | 157 | public void setAspectRatioY(int aspectRatioY) { 158 | if (aspectRatioY <= 0) { 159 | throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0."); 160 | } else { 161 | mAspectRatioY = aspectRatioY; 162 | mTargetAspectRatio = (float)mAspectRatioX / (float)mAspectRatioY; 163 | if (initializedCropWindow) { 164 | initCropWindow(mBitmapRect); 165 | invalidate(); 166 | } 167 | 168 | } 169 | } 170 | 171 | public void setInitialAttributeValues(int guidelines, boolean fixAspectRatio, int aspectRatioX, int aspectRatioY) { 172 | if (guidelines >= 0 && guidelines <= 2) { 173 | mGuidelines = guidelines; 174 | mFixAspectRatio = fixAspectRatio; 175 | if (aspectRatioX <= 0) { 176 | throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0."); 177 | } else { 178 | mAspectRatioX = aspectRatioX; 179 | mTargetAspectRatio = (float)mAspectRatioX / (float)mAspectRatioY; 180 | if (aspectRatioY <= 0) { 181 | throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0."); 182 | } else { 183 | mAspectRatioY = aspectRatioY; 184 | mTargetAspectRatio = (float)mAspectRatioX / (float)mAspectRatioY; 185 | } 186 | } 187 | } else { 188 | throw new IllegalArgumentException("Guideline value must be set between 0 and 2. See documentation."); 189 | } 190 | } 191 | 192 | private void init(Context context) { 193 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 194 | mHandleRadius = HandleUtil.getTargetRadius(context); 195 | mSnapRadius = TypedValue.applyDimension(1, 6.0F, displayMetrics); 196 | mBorderPaint = PaintUtil.newBorderPaint(context); 197 | mGuidelinePaint = PaintUtil.newGuidelinePaint(); 198 | mBackgroundPaint = PaintUtil.newBackgroundPaint(context); 199 | mCornerPaint = PaintUtil.newCornerPaint(context); 200 | mCornerOffset = TypedValue.applyDimension(1, DEFAULT_CORNER_OFFSET_DP, displayMetrics); 201 | mCornerExtension = TypedValue.applyDimension(1, DEFAULT_CORNER_EXTENSION_DP, displayMetrics); 202 | mCornerLength = TypedValue.applyDimension(1, 20.0F, displayMetrics); 203 | mGuidelines = 1; 204 | } 205 | 206 | private void initCropWindow(Rect bitmapRect) { 207 | if (!initializedCropWindow) { 208 | initializedCropWindow = true; 209 | } 210 | 211 | float centerX; 212 | float cropWidth; 213 | if (mFixAspectRatio) { 214 | float halfCropWidth; 215 | if (AspectRatioUtil.calculateAspectRatio(bitmapRect) > mTargetAspectRatio) { 216 | Edge.TOP.setCoordinate((float)bitmapRect.top); 217 | Edge.BOTTOM.setCoordinate((float)bitmapRect.bottom); 218 | centerX = (float)getWidth() / 2.0F; 219 | cropWidth = Math.max(40.0F, AspectRatioUtil.calculateWidth(Edge.TOP.getCoordinate(), Edge.BOTTOM.getCoordinate(), mTargetAspectRatio)); 220 | if (cropWidth == 40.0F) { 221 | mTargetAspectRatio = 40.0F / (Edge.BOTTOM.getCoordinate() - Edge.TOP.getCoordinate()); 222 | } 223 | 224 | halfCropWidth = cropWidth / 2.0F; 225 | Edge.LEFT.setCoordinate(centerX - halfCropWidth); 226 | Edge.RIGHT.setCoordinate(centerX + halfCropWidth); 227 | } else { 228 | Edge.LEFT.setCoordinate((float)bitmapRect.left); 229 | Edge.RIGHT.setCoordinate((float)bitmapRect.right); 230 | centerX = (float)getHeight() / 2.0F; 231 | cropWidth = Math.max(40.0F, AspectRatioUtil.calculateHeight(Edge.LEFT.getCoordinate(), Edge.RIGHT.getCoordinate(), mTargetAspectRatio)); 232 | if (cropWidth == 40.0F) { 233 | mTargetAspectRatio = (Edge.RIGHT.getCoordinate() - Edge.LEFT.getCoordinate()) / 40.0F; 234 | } 235 | 236 | halfCropWidth = cropWidth / 2.0F; 237 | Edge.TOP.setCoordinate(centerX - halfCropWidth); 238 | Edge.BOTTOM.setCoordinate(centerX + halfCropWidth); 239 | } 240 | } else { 241 | centerX = 0.1F * (float)bitmapRect.width(); 242 | cropWidth = 0.1F * (float)bitmapRect.height(); 243 | Edge.LEFT.setCoordinate((float)bitmapRect.left + centerX); 244 | Edge.TOP.setCoordinate((float)bitmapRect.top + cropWidth); 245 | Edge.RIGHT.setCoordinate((float)bitmapRect.right - centerX); 246 | Edge.BOTTOM.setCoordinate((float)bitmapRect.bottom - cropWidth); 247 | } 248 | 249 | } 250 | 251 | public static boolean showGuidelines() { 252 | return Math.abs(Edge.LEFT.getCoordinate() - Edge.RIGHT.getCoordinate()) >= 100.0F && Math.abs(Edge.TOP.getCoordinate() - Edge.BOTTOM.getCoordinate()) >= 100.0F; 253 | } 254 | 255 | private void drawRuleOfThirdsGuidelines(Canvas canvas) { 256 | float left = Edge.LEFT.getCoordinate(); 257 | float top = Edge.TOP.getCoordinate(); 258 | float right = Edge.RIGHT.getCoordinate(); 259 | float bottom = Edge.BOTTOM.getCoordinate(); 260 | float oneThirdCropWidth = Edge.getWidth() / 3.0F; 261 | float x1 = left + oneThirdCropWidth; 262 | canvas.drawLine(x1, top, x1, bottom, mGuidelinePaint); 263 | float x2 = right - oneThirdCropWidth; 264 | canvas.drawLine(x2, top, x2, bottom, mGuidelinePaint); 265 | float oneThirdCropHeight = Edge.getHeight() / 3.0F; 266 | float y1 = top + oneThirdCropHeight; 267 | canvas.drawLine(left, y1, right, y1, mGuidelinePaint); 268 | float y2 = bottom - oneThirdCropHeight; 269 | canvas.drawLine(left, y2, right, y2, mGuidelinePaint); 270 | } 271 | 272 | private void drawBackground(Canvas canvas, Rect bitmapRect) { 273 | float left = Edge.LEFT.getCoordinate(); 274 | float top = Edge.TOP.getCoordinate(); 275 | float right = Edge.RIGHT.getCoordinate(); 276 | float bottom = Edge.BOTTOM.getCoordinate(); 277 | canvas.drawRect((float)bitmapRect.left, (float)bitmapRect.top, (float)bitmapRect.right, top, mBackgroundPaint); 278 | canvas.drawRect((float)bitmapRect.left, bottom, (float)bitmapRect.right, (float)bitmapRect.bottom, mBackgroundPaint); 279 | canvas.drawRect((float)bitmapRect.left, top, left, bottom, mBackgroundPaint); 280 | canvas.drawRect(right, top, (float)bitmapRect.right, bottom, mBackgroundPaint); 281 | } 282 | 283 | private void drawCorners(Canvas canvas) { 284 | float left = Edge.LEFT.getCoordinate(); 285 | float top = Edge.TOP.getCoordinate(); 286 | float right = Edge.RIGHT.getCoordinate(); 287 | float bottom = Edge.BOTTOM.getCoordinate(); 288 | canvas.drawLine(left - mCornerOffset, top - mCornerExtension, left - mCornerOffset, top + mCornerLength, mCornerPaint); 289 | canvas.drawLine(left, top - mCornerOffset, left + mCornerLength, top - mCornerOffset, mCornerPaint); 290 | canvas.drawLine(right + mCornerOffset, top - mCornerExtension, right + mCornerOffset, top + mCornerLength, mCornerPaint); 291 | canvas.drawLine(right, top - mCornerOffset, right - mCornerLength, top - mCornerOffset, mCornerPaint); 292 | canvas.drawLine(left - mCornerOffset, bottom + mCornerExtension, left - mCornerOffset, bottom - mCornerLength, mCornerPaint); 293 | canvas.drawLine(left, bottom + mCornerOffset, left + mCornerLength, bottom + mCornerOffset, mCornerPaint); 294 | canvas.drawLine(right + mCornerOffset, bottom + mCornerExtension, right + mCornerOffset, bottom - mCornerLength, mCornerPaint); 295 | canvas.drawLine(right, bottom + mCornerOffset, right - mCornerLength, bottom + mCornerOffset, mCornerPaint); 296 | } 297 | 298 | private void onActionDown(float x, float y) { 299 | float left = Edge.LEFT.getCoordinate(); 300 | float top = Edge.TOP.getCoordinate(); 301 | float right = Edge.RIGHT.getCoordinate(); 302 | float bottom = Edge.BOTTOM.getCoordinate(); 303 | mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius); 304 | if (mPressedHandle != null) { 305 | mTouchOffset = HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom); 306 | invalidate(); 307 | } 308 | } 309 | 310 | private void onActionUp() { 311 | if (mPressedHandle != null) { 312 | mPressedHandle = null; 313 | invalidate(); 314 | } 315 | } 316 | 317 | private void onActionMove(float x, float y) { 318 | if (mPressedHandle != null) { 319 | x += mTouchOffset.first; 320 | y += mTouchOffset.second; 321 | if (mFixAspectRatio) { 322 | mPressedHandle.updateCropWindow(x, y, mTargetAspectRatio, mBitmapRect, mSnapRadius); 323 | } else { 324 | mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius); 325 | } 326 | invalidate(); 327 | } 328 | } 329 | 330 | static { 331 | DEFAULT_CORNER_OFFSET_DP = DEFAULT_CORNER_THICKNESS_DP / 2.0F - DEFAULT_LINE_THICKNESS_DP / 2.0F; 332 | DEFAULT_CORNER_EXTENSION_DP = DEFAULT_CORNER_THICKNESS_DP / 2.0F + DEFAULT_CORNER_OFFSET_DP; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/edge/Edge.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.edge; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | 6 | import net.vrgsoft.videcrop.cropview.util.AspectRatioUtil; 7 | 8 | public enum Edge { 9 | LEFT, 10 | TOP, 11 | RIGHT, 12 | BOTTOM; 13 | 14 | public static final int MIN_CROP_LENGTH_PX = 40; 15 | private float mCoordinate; 16 | 17 | private Edge() { 18 | } 19 | 20 | public void setCoordinate(float coordinate) { 21 | this.mCoordinate = coordinate; 22 | } 23 | 24 | public void offset(float distance) { 25 | this.mCoordinate += distance; 26 | } 27 | 28 | public float getCoordinate() { 29 | return this.mCoordinate; 30 | } 31 | 32 | public void adjustCoordinate(float x, float y, Rect imageRect, float imageSnapRadius, float aspectRatio) { 33 | switch(this) { 34 | case LEFT: 35 | this.mCoordinate = adjustLeft(x, imageRect, imageSnapRadius, aspectRatio); 36 | break; 37 | case TOP: 38 | this.mCoordinate = adjustTop(y, imageRect, imageSnapRadius, aspectRatio); 39 | break; 40 | case RIGHT: 41 | this.mCoordinate = adjustRight(x, imageRect, imageSnapRadius, aspectRatio); 42 | break; 43 | case BOTTOM: 44 | this.mCoordinate = adjustBottom(y, imageRect, imageSnapRadius, aspectRatio); 45 | } 46 | 47 | } 48 | 49 | public void adjustCoordinate(float aspectRatio) { 50 | float left = LEFT.getCoordinate(); 51 | float top = TOP.getCoordinate(); 52 | float right = RIGHT.getCoordinate(); 53 | float bottom = BOTTOM.getCoordinate(); 54 | switch(this) { 55 | case LEFT: 56 | this.mCoordinate = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio); 57 | break; 58 | case TOP: 59 | this.mCoordinate = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio); 60 | break; 61 | case RIGHT: 62 | this.mCoordinate = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio); 63 | break; 64 | case BOTTOM: 65 | this.mCoordinate = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio); 66 | } 67 | 68 | } 69 | 70 | public boolean isNewRectangleOutOfBounds(Edge edge, Rect imageRect, float aspectRatio) { 71 | float offset = edge.snapOffset(imageRect); 72 | float right; 73 | float left; 74 | float top; 75 | float bottom; 76 | switch(this) { 77 | case LEFT: 78 | if (edge.equals(TOP)) { 79 | right = (float)imageRect.top; 80 | left = BOTTOM.getCoordinate() - offset; 81 | top = RIGHT.getCoordinate(); 82 | bottom = AspectRatioUtil.calculateLeft(right, top, left, aspectRatio); 83 | return this.isOutOfBounds(right, bottom, left, top, imageRect); 84 | } 85 | 86 | if (edge.equals(BOTTOM)) { 87 | right = (float)imageRect.bottom; 88 | left = TOP.getCoordinate() - offset; 89 | top = RIGHT.getCoordinate(); 90 | bottom = AspectRatioUtil.calculateLeft(left, top, right, aspectRatio); 91 | return this.isOutOfBounds(left, bottom, right, top, imageRect); 92 | } 93 | break; 94 | case TOP: 95 | if (edge.equals(LEFT)) { 96 | right = (float)imageRect.left; 97 | left = RIGHT.getCoordinate() - offset; 98 | top = BOTTOM.getCoordinate(); 99 | bottom = AspectRatioUtil.calculateTop(right, left, top, aspectRatio); 100 | return this.isOutOfBounds(bottom, right, top, left, imageRect); 101 | } 102 | 103 | if (edge.equals(RIGHT)) { 104 | right = (float)imageRect.right; 105 | left = LEFT.getCoordinate() - offset; 106 | top = BOTTOM.getCoordinate(); 107 | bottom = AspectRatioUtil.calculateTop(left, right, top, aspectRatio); 108 | return this.isOutOfBounds(bottom, left, top, right, imageRect); 109 | } 110 | break; 111 | case RIGHT: 112 | if (edge.equals(TOP)) { 113 | right = (float)imageRect.top; 114 | left = BOTTOM.getCoordinate() - offset; 115 | top = LEFT.getCoordinate(); 116 | bottom = AspectRatioUtil.calculateRight(top, right, left, aspectRatio); 117 | return this.isOutOfBounds(right, top, left, bottom, imageRect); 118 | } 119 | 120 | if (edge.equals(BOTTOM)) { 121 | right = (float)imageRect.bottom; 122 | left = TOP.getCoordinate() - offset; 123 | top = LEFT.getCoordinate(); 124 | bottom = AspectRatioUtil.calculateRight(top, left, right, aspectRatio); 125 | return this.isOutOfBounds(left, top, right, bottom, imageRect); 126 | } 127 | break; 128 | case BOTTOM: 129 | if (edge.equals(LEFT)) { 130 | right = (float)imageRect.left; 131 | left = RIGHT.getCoordinate() - offset; 132 | top = TOP.getCoordinate(); 133 | bottom = AspectRatioUtil.calculateBottom(right, top, left, aspectRatio); 134 | return this.isOutOfBounds(top, right, bottom, left, imageRect); 135 | } 136 | 137 | if (edge.equals(RIGHT)) { 138 | right = (float)imageRect.right; 139 | left = LEFT.getCoordinate() - offset; 140 | top = TOP.getCoordinate(); 141 | bottom = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio); 142 | return this.isOutOfBounds(top, left, bottom, right, imageRect); 143 | } 144 | } 145 | 146 | return true; 147 | } 148 | 149 | private boolean isOutOfBounds(float top, float left, float bottom, float right, Rect imageRect) { 150 | return top < (float)imageRect.top || left < (float)imageRect.left || bottom > (float)imageRect.bottom || right > (float)imageRect.right; 151 | } 152 | 153 | public float snapToRect(Rect imageRect) { 154 | float oldCoordinate = this.mCoordinate; 155 | switch(this) { 156 | case LEFT: 157 | this.mCoordinate = (float)imageRect.left; 158 | break; 159 | case TOP: 160 | this.mCoordinate = (float)imageRect.top; 161 | break; 162 | case RIGHT: 163 | this.mCoordinate = (float)imageRect.right; 164 | break; 165 | case BOTTOM: 166 | this.mCoordinate = (float)imageRect.bottom; 167 | } 168 | 169 | float offset = this.mCoordinate - oldCoordinate; 170 | return offset; 171 | } 172 | 173 | public float snapOffset(Rect imageRect) { 174 | float oldCoordinate = this.mCoordinate; 175 | float newCoordinate = oldCoordinate; 176 | switch(this) { 177 | case LEFT: 178 | newCoordinate = (float)imageRect.left; 179 | break; 180 | case TOP: 181 | newCoordinate = (float)imageRect.top; 182 | break; 183 | case RIGHT: 184 | newCoordinate = (float)imageRect.right; 185 | break; 186 | case BOTTOM: 187 | newCoordinate = (float)imageRect.bottom; 188 | } 189 | 190 | float offset = newCoordinate - oldCoordinate; 191 | return offset; 192 | } 193 | 194 | public void snapToView(View view) { 195 | switch(this) { 196 | case LEFT: 197 | this.mCoordinate = 0.0F; 198 | break; 199 | case TOP: 200 | this.mCoordinate = 0.0F; 201 | break; 202 | case RIGHT: 203 | this.mCoordinate = (float)view.getWidth(); 204 | break; 205 | case BOTTOM: 206 | this.mCoordinate = (float)view.getHeight(); 207 | } 208 | 209 | } 210 | 211 | public static float getWidth() { 212 | return RIGHT.getCoordinate() - LEFT.getCoordinate(); 213 | } 214 | 215 | public static float getHeight() { 216 | return BOTTOM.getCoordinate() - TOP.getCoordinate(); 217 | } 218 | 219 | public boolean isOutsideMargin(Rect rect, float margin) { 220 | boolean result = false; 221 | switch(this) { 222 | case LEFT: 223 | result = this.mCoordinate - (float)rect.left < margin; 224 | break; 225 | case TOP: 226 | result = this.mCoordinate - (float)rect.top < margin; 227 | break; 228 | case RIGHT: 229 | result = (float)rect.right - this.mCoordinate < margin; 230 | break; 231 | case BOTTOM: 232 | result = (float)rect.bottom - this.mCoordinate < margin; 233 | } 234 | 235 | return result; 236 | } 237 | 238 | public boolean isOutsideFrame(Rect rect) { 239 | double margin = 0.0D; 240 | boolean result = false; 241 | switch(this) { 242 | case LEFT: 243 | result = (double)(this.mCoordinate - (float)rect.left) < margin; 244 | break; 245 | case TOP: 246 | result = (double)(this.mCoordinate - (float)rect.top) < margin; 247 | break; 248 | case RIGHT: 249 | result = (double)((float)rect.right - this.mCoordinate) < margin; 250 | break; 251 | case BOTTOM: 252 | result = (double)((float)rect.bottom - this.mCoordinate) < margin; 253 | } 254 | 255 | return result; 256 | } 257 | 258 | private static float adjustLeft(float x, Rect imageRect, float imageSnapRadius, float aspectRatio) { 259 | float resultX; 260 | if (x - (float)imageRect.left < imageSnapRadius) { 261 | resultX = (float)imageRect.left; 262 | } else { 263 | float resultXHoriz = Float.POSITIVE_INFINITY; 264 | float resultXVert = Float.POSITIVE_INFINITY; 265 | if (x >= RIGHT.getCoordinate() - 40.0F) { 266 | resultXHoriz = RIGHT.getCoordinate() - 40.0F; 267 | } 268 | 269 | if ((RIGHT.getCoordinate() - x) / aspectRatio <= 40.0F) { 270 | resultXVert = RIGHT.getCoordinate() - 40.0F * aspectRatio; 271 | } 272 | 273 | resultX = Math.min(x, Math.min(resultXHoriz, resultXVert)); 274 | } 275 | 276 | return resultX; 277 | } 278 | 279 | private static float adjustRight(float x, Rect imageRect, float imageSnapRadius, float aspectRatio) { 280 | float resultX; 281 | if ((float)imageRect.right - x < imageSnapRadius) { 282 | resultX = (float)imageRect.right; 283 | } else { 284 | float resultXHoriz = Float.NEGATIVE_INFINITY; 285 | float resultXVert = Float.NEGATIVE_INFINITY; 286 | if (x <= LEFT.getCoordinate() + 40.0F) { 287 | resultXHoriz = LEFT.getCoordinate() + 40.0F; 288 | } 289 | 290 | if ((x - LEFT.getCoordinate()) / aspectRatio <= 40.0F) { 291 | resultXVert = LEFT.getCoordinate() + 40.0F * aspectRatio; 292 | } 293 | 294 | resultX = Math.max(x, Math.max(resultXHoriz, resultXVert)); 295 | } 296 | 297 | return resultX; 298 | } 299 | 300 | private static float adjustTop(float y, Rect imageRect, float imageSnapRadius, float aspectRatio) { 301 | float resultY; 302 | if (y - (float)imageRect.top < imageSnapRadius) { 303 | resultY = (float)imageRect.top; 304 | } else { 305 | float resultYVert = Float.POSITIVE_INFINITY; 306 | float resultYHoriz = Float.POSITIVE_INFINITY; 307 | if (y >= BOTTOM.getCoordinate() - 40.0F) { 308 | resultYHoriz = BOTTOM.getCoordinate() - 40.0F; 309 | } 310 | 311 | if ((BOTTOM.getCoordinate() - y) * aspectRatio <= 40.0F) { 312 | resultYVert = BOTTOM.getCoordinate() - 40.0F / aspectRatio; 313 | } 314 | 315 | resultY = Math.min(y, Math.min(resultYHoriz, resultYVert)); 316 | } 317 | 318 | return resultY; 319 | } 320 | 321 | private static float adjustBottom(float y, Rect imageRect, float imageSnapRadius, float aspectRatio) { 322 | float resultY; 323 | if ((float)imageRect.bottom - y < imageSnapRadius) { 324 | resultY = (float)imageRect.bottom; 325 | } else { 326 | float resultYVert = Float.NEGATIVE_INFINITY; 327 | float resultYHoriz = Float.NEGATIVE_INFINITY; 328 | if (y <= TOP.getCoordinate() + 40.0F) { 329 | resultYVert = TOP.getCoordinate() + 40.0F; 330 | } 331 | 332 | if ((y - TOP.getCoordinate()) * aspectRatio <= 40.0F) { 333 | resultYHoriz = TOP.getCoordinate() + 40.0F / aspectRatio; 334 | } 335 | 336 | resultY = Math.max(y, Math.max(resultYHoriz, resultYVert)); 337 | } 338 | 339 | return resultY; 340 | } 341 | } 342 | 343 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/edge/EdgePair.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.edge; 2 | 3 | public class EdgePair { 4 | public Edge primary; 5 | public Edge secondary; 6 | 7 | public EdgePair(Edge edge1, Edge edge2) { 8 | this.primary = edge1; 9 | this.secondary = edge2; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/handle/CenterHandleHelper.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.handle; 2 | 3 | import android.graphics.Rect; 4 | 5 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 6 | 7 | class CenterHandleHelper extends HandleHelper { 8 | CenterHandleHelper() { 9 | super(null, null); 10 | } 11 | 12 | void updateCropWindow(float x, float y, Rect imageRect, float snapRadius) { 13 | float left = Edge.LEFT.getCoordinate(); 14 | float top = Edge.TOP.getCoordinate(); 15 | float right = Edge.RIGHT.getCoordinate(); 16 | float bottom = Edge.BOTTOM.getCoordinate(); 17 | float currentCenterX = (left + right) / 2.0F; 18 | float currentCenterY = (top + bottom) / 2.0F; 19 | float offsetX = x - currentCenterX; 20 | float offsetY = y - currentCenterY; 21 | Edge.LEFT.offset(offsetX); 22 | Edge.TOP.offset(offsetY); 23 | Edge.RIGHT.offset(offsetX); 24 | Edge.BOTTOM.offset(offsetY); 25 | float offset; 26 | if (Edge.LEFT.isOutsideMargin(imageRect, snapRadius)) { 27 | offset = Edge.LEFT.snapToRect(imageRect); 28 | Edge.RIGHT.offset(offset); 29 | } else if (Edge.RIGHT.isOutsideMargin(imageRect, snapRadius)) { 30 | offset = Edge.RIGHT.snapToRect(imageRect); 31 | Edge.LEFT.offset(offset); 32 | } 33 | 34 | if (Edge.TOP.isOutsideMargin(imageRect, snapRadius)) { 35 | offset = Edge.TOP.snapToRect(imageRect); 36 | Edge.BOTTOM.offset(offset); 37 | } else if (Edge.BOTTOM.isOutsideMargin(imageRect, snapRadius)) { 38 | offset = Edge.BOTTOM.snapToRect(imageRect); 39 | Edge.TOP.offset(offset); 40 | } 41 | 42 | } 43 | 44 | void updateCropWindow(float x, float y, float targetAspectRatio, Rect imageRect, float snapRadius) { 45 | this.updateCropWindow(x, y, imageRect, snapRadius); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/handle/CornerHandleHelper.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.handle; 2 | 3 | import android.graphics.Rect; 4 | 5 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 6 | import net.vrgsoft.videcrop.cropview.window.edge.EdgePair; 7 | 8 | class CornerHandleHelper extends HandleHelper { 9 | CornerHandleHelper(Edge horizontalEdge, Edge verticalEdge) { 10 | super(horizontalEdge, verticalEdge); 11 | } 12 | 13 | void updateCropWindow(float x, float y, float targetAspectRatio, Rect imageRect, float snapRadius) { 14 | EdgePair activeEdges = this.getActiveEdges(x, y, targetAspectRatio); 15 | Edge primaryEdge = activeEdges.primary; 16 | Edge secondaryEdge = activeEdges.secondary; 17 | primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, targetAspectRatio); 18 | secondaryEdge.adjustCoordinate(targetAspectRatio); 19 | if (secondaryEdge.isOutsideMargin(imageRect, snapRadius)) { 20 | secondaryEdge.snapToRect(imageRect); 21 | primaryEdge.adjustCoordinate(targetAspectRatio); 22 | } 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/handle/Handle.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.handle; 2 | 3 | import android.graphics.Rect; 4 | 5 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 6 | 7 | public enum Handle { 8 | TOP_LEFT(new CornerHandleHelper(Edge.TOP, Edge.LEFT)), 9 | TOP_RIGHT(new CornerHandleHelper(Edge.TOP, Edge.RIGHT)), 10 | BOTTOM_LEFT(new CornerHandleHelper(Edge.BOTTOM, Edge.LEFT)), 11 | BOTTOM_RIGHT(new CornerHandleHelper(Edge.BOTTOM, Edge.RIGHT)), 12 | LEFT(new VerticalHandleHelper(Edge.LEFT)), 13 | TOP(new HorizontalHandleHelper(Edge.TOP)), 14 | RIGHT(new VerticalHandleHelper(Edge.RIGHT)), 15 | BOTTOM(new HorizontalHandleHelper(Edge.BOTTOM)), 16 | CENTER(new CenterHandleHelper()); 17 | 18 | private HandleHelper mHelper; 19 | 20 | private Handle(HandleHelper helper) { 21 | this.mHelper = helper; 22 | } 23 | 24 | public void updateCropWindow(float x, float y, Rect imageRect, float snapRadius) { 25 | this.mHelper.updateCropWindow(x, y, imageRect, snapRadius); 26 | } 27 | 28 | public void updateCropWindow(float x, float y, float targetAspectRatio, Rect imageRect, float snapRadius) { 29 | this.mHelper.updateCropWindow(x, y, targetAspectRatio, imageRect, snapRadius); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/handle/HandleHelper.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.handle; 2 | 3 | import android.graphics.Rect; 4 | 5 | import net.vrgsoft.videcrop.cropview.util.AspectRatioUtil; 6 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 7 | import net.vrgsoft.videcrop.cropview.window.edge.EdgePair; 8 | 9 | abstract class HandleHelper { 10 | private static final float UNFIXED_ASPECT_RATIO_CONSTANT = 1.0F; 11 | private Edge mHorizontalEdge; 12 | private Edge mVerticalEdge; 13 | private EdgePair mActiveEdges; 14 | 15 | HandleHelper(Edge horizontalEdge, Edge verticalEdge) { 16 | this.mHorizontalEdge = horizontalEdge; 17 | this.mVerticalEdge = verticalEdge; 18 | this.mActiveEdges = new EdgePair(this.mHorizontalEdge, this.mVerticalEdge); 19 | } 20 | 21 | void updateCropWindow(float x, float y, Rect imageRect, float snapRadius) { 22 | EdgePair activeEdges = this.getActiveEdges(); 23 | Edge primaryEdge = activeEdges.primary; 24 | Edge secondaryEdge = activeEdges.secondary; 25 | if (primaryEdge != null) { 26 | primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, 1.0F); 27 | } 28 | 29 | if (secondaryEdge != null) { 30 | secondaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, 1.0F); 31 | } 32 | 33 | } 34 | 35 | abstract void updateCropWindow(float var1, float var2, float var3, Rect var4, float var5); 36 | 37 | EdgePair getActiveEdges() { 38 | return this.mActiveEdges; 39 | } 40 | 41 | EdgePair getActiveEdges(float x, float y, float targetAspectRatio) { 42 | float potentialAspectRatio = this.getAspectRatio(x, y); 43 | if (potentialAspectRatio > targetAspectRatio) { 44 | this.mActiveEdges.primary = this.mVerticalEdge; 45 | this.mActiveEdges.secondary = this.mHorizontalEdge; 46 | } else { 47 | this.mActiveEdges.primary = this.mHorizontalEdge; 48 | this.mActiveEdges.secondary = this.mVerticalEdge; 49 | } 50 | 51 | return this.mActiveEdges; 52 | } 53 | 54 | private float getAspectRatio(float x, float y) { 55 | float left = this.mVerticalEdge == Edge.LEFT ? x : Edge.LEFT.getCoordinate(); 56 | float top = this.mHorizontalEdge == Edge.TOP ? y : Edge.TOP.getCoordinate(); 57 | float right = this.mVerticalEdge == Edge.RIGHT ? x : Edge.RIGHT.getCoordinate(); 58 | float bottom = this.mHorizontalEdge == Edge.BOTTOM ? y : Edge.BOTTOM.getCoordinate(); 59 | float aspectRatio = AspectRatioUtil.calculateAspectRatio(left, top, right, bottom); 60 | return aspectRatio; 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/handle/HorizontalHandleHelper.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.handle; 2 | 3 | import android.graphics.Rect; 4 | 5 | import net.vrgsoft.videcrop.cropview.util.AspectRatioUtil; 6 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 7 | 8 | class HorizontalHandleHelper extends HandleHelper { 9 | private Edge mEdge; 10 | 11 | HorizontalHandleHelper(Edge edge) { 12 | super(edge, null); 13 | this.mEdge = edge; 14 | } 15 | 16 | void updateCropWindow(float x, float y, float targetAspectRatio, Rect imageRect, float snapRadius) { 17 | this.mEdge.adjustCoordinate(x, y, imageRect, snapRadius, targetAspectRatio); 18 | float left = Edge.LEFT.getCoordinate(); 19 | float top = Edge.TOP.getCoordinate(); 20 | float right = Edge.RIGHT.getCoordinate(); 21 | float bottom = Edge.BOTTOM.getCoordinate(); 22 | float targetWidth = AspectRatioUtil.calculateWidth(top, bottom, targetAspectRatio); 23 | float currentWidth = right - left; 24 | float difference = targetWidth - currentWidth; 25 | float halfDifference = difference / 2.0F; 26 | left -= halfDifference; 27 | right += halfDifference; 28 | Edge.LEFT.setCoordinate(left); 29 | Edge.RIGHT.setCoordinate(right); 30 | float offset; 31 | if (Edge.LEFT.isOutsideMargin(imageRect, snapRadius) && !this.mEdge.isNewRectangleOutOfBounds(Edge.LEFT, imageRect, targetAspectRatio)) { 32 | offset = Edge.LEFT.snapToRect(imageRect); 33 | Edge.RIGHT.offset(-offset); 34 | this.mEdge.adjustCoordinate(targetAspectRatio); 35 | } 36 | 37 | if (Edge.RIGHT.isOutsideMargin(imageRect, snapRadius) && !this.mEdge.isNewRectangleOutOfBounds(Edge.RIGHT, imageRect, targetAspectRatio)) { 38 | offset = Edge.RIGHT.snapToRect(imageRect); 39 | Edge.LEFT.offset(-offset); 40 | this.mEdge.adjustCoordinate(targetAspectRatio); 41 | } 42 | 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/cropview/window/handle/VerticalHandleHelper.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.cropview.window.handle; 2 | 3 | import android.graphics.Rect; 4 | 5 | import net.vrgsoft.videcrop.cropview.util.AspectRatioUtil; 6 | import net.vrgsoft.videcrop.cropview.window.edge.Edge; 7 | 8 | class VerticalHandleHelper extends HandleHelper { 9 | private Edge mEdge; 10 | 11 | VerticalHandleHelper(Edge edge) { 12 | super(null, edge); 13 | this.mEdge = edge; 14 | } 15 | 16 | void updateCropWindow(float x, float y, float targetAspectRatio, Rect imageRect, float snapRadius) { 17 | this.mEdge.adjustCoordinate(x, y, imageRect, snapRadius, targetAspectRatio); 18 | float left = Edge.LEFT.getCoordinate(); 19 | float top = Edge.TOP.getCoordinate(); 20 | float right = Edge.RIGHT.getCoordinate(); 21 | float bottom = Edge.BOTTOM.getCoordinate(); 22 | float targetHeight = AspectRatioUtil.calculateHeight(left, right, targetAspectRatio); 23 | float currentHeight = bottom - top; 24 | float difference = targetHeight - currentHeight; 25 | float halfDifference = difference / 2.0F; 26 | top -= halfDifference; 27 | bottom += halfDifference; 28 | Edge.TOP.setCoordinate(top); 29 | Edge.BOTTOM.setCoordinate(bottom); 30 | float offset; 31 | if (Edge.TOP.isOutsideMargin(imageRect, snapRadius) && !this.mEdge.isNewRectangleOutOfBounds(Edge.TOP, imageRect, targetAspectRatio)) { 32 | offset = Edge.TOP.snapToRect(imageRect); 33 | Edge.BOTTOM.offset(-offset); 34 | this.mEdge.adjustCoordinate(targetAspectRatio); 35 | } 36 | 37 | if (Edge.BOTTOM.isOutsideMargin(imageRect, snapRadius) && !this.mEdge.isNewRectangleOutOfBounds(Edge.BOTTOM, imageRect, targetAspectRatio)) { 38 | offset = Edge.BOTTOM.snapToRect(imageRect); 39 | Edge.TOP.offset(-offset); 40 | this.mEdge.adjustCoordinate(targetAspectRatio); 41 | } 42 | 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/CommandResult.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | class CommandResult { 4 | final String output; 5 | final boolean success; 6 | 7 | CommandResult(boolean success, String output) { 8 | this.success = success; 9 | this.output = output; 10 | } 11 | 12 | static CommandResult getDummyFailureResponse() { 13 | return new CommandResult(false, ""); 14 | } 15 | 16 | static CommandResult getOutputFromProcess(Process process) { 17 | String output; 18 | if (success(process.exitValue())) { 19 | output = Util.convertInputStreamToString(process.getInputStream()); 20 | } else { 21 | output = Util.convertInputStreamToString(process.getErrorStream()); 22 | } 23 | return new CommandResult(success(process.exitValue()), output); 24 | } 25 | 26 | static boolean success(Integer exitValue) { 27 | return exitValue != null && exitValue == 0; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/CpuArch.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | public enum CpuArch { 4 | ARMv7, x86, NONE 5 | } 6 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/CpuArchHelper.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import android.os.Build; 4 | 5 | class CpuArchHelper { 6 | private static final String X86_CPU = "x86"; 7 | private static final String X86_64_CPU = "x86_64"; 8 | private static final String ARM_64_CPU = "arm64-v8a"; 9 | private static final String ARM_V7_CPU = "armeabi-v7a"; 10 | 11 | static CpuArch getCpuArch() { 12 | Log.d("Build.CPU_ABI : " + Build.CPU_ABI); 13 | 14 | switch (Build.CPU_ABI) { 15 | case X86_CPU: 16 | case X86_64_CPU: 17 | return CpuArch.x86; 18 | case ARM_64_CPU: 19 | case ARM_V7_CPU: 20 | return CpuArch.ARMv7; 21 | default: 22 | return CpuArch.NONE; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/ExecuteBinaryResponseHandler.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | public class ExecuteBinaryResponseHandler implements FFcommandExecuteResponseHandler { 4 | 5 | @Override 6 | public void onSuccess(String message) { 7 | 8 | } 9 | 10 | @Override 11 | public void onProgress(String message) { 12 | 13 | } 14 | 15 | @Override 16 | public void onFailure(String message) { 17 | 18 | } 19 | 20 | @Override 21 | public void onProgressPercent(float percent) { 22 | 23 | } 24 | 25 | @Override 26 | public void onStart() { 27 | 28 | } 29 | 30 | @Override 31 | public void onFinish() { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFbinaryContextProvider.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import android.content.Context; 4 | 5 | public interface FFbinaryContextProvider { 6 | 7 | Context provide(); 8 | } 9 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFbinaryInterface.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import java.util.Map; 4 | 5 | interface FFbinaryInterface { 6 | 7 | /** 8 | * Executes a command 9 | * 10 | * @param environmentVars Environment variables 11 | * @param cmd command to execute 12 | * @param ffcommandExecuteResponseHandler {@link FFcommandExecuteResponseHandler} 13 | * @return the task 14 | */ 15 | FFtask execute(Map environmentVars, String[] cmd, FFcommandExecuteResponseHandler ffcommandExecuteResponseHandler, float duration); 16 | 17 | /** 18 | * Executes a command 19 | * 20 | * @param cmd command to execute 21 | * @param ffcommandExecuteResponseHandler {@link FFcommandExecuteResponseHandler} 22 | * @return the task 23 | */ 24 | FFtask execute(String[] cmd, FFcommandExecuteResponseHandler ffcommandExecuteResponseHandler, float duration); 25 | 26 | /** 27 | * Checks if FF binary is supported on this device 28 | * 29 | * @return true if FF binary is supported on this device 30 | */ 31 | boolean isSupported(); 32 | 33 | /** 34 | * Checks if a command with given task is currently running 35 | * 36 | * @param task - the task that you want to check 37 | * @return true if a command is running 38 | */ 39 | boolean isCommandRunning(FFtask task); 40 | 41 | /** 42 | * Kill given running process 43 | * 44 | * @param task - the task to kill 45 | * @return true if process is killed successfully 46 | */ 47 | boolean killRunningProcesses(FFtask task); 48 | 49 | /** 50 | * Timeout for binary process, should be minimum of 10 seconds 51 | * 52 | * @param timeout in milliseconds 53 | */ 54 | void setTimeout(long timeout); 55 | } 56 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFbinaryObserver.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | public interface FFbinaryObserver extends Runnable { 4 | 5 | void cancel(); 6 | } 7 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFcommandExecuteAsyncTask.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.io.OutputStream; 9 | import java.util.Map; 10 | import java.util.concurrent.TimeoutException; 11 | 12 | class FFcommandExecuteAsyncTask extends AsyncTask implements FFtask { 13 | 14 | private final String[] cmd; 15 | private Map environment; 16 | private final FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler; 17 | private final ShellCommand shellCommand; 18 | private final long timeout; 19 | private long startTime; 20 | private Process process; 21 | private String output = ""; 22 | private float duration; 23 | private float outputFps; 24 | private boolean quitPending; 25 | 26 | FFcommandExecuteAsyncTask(String[] cmd, Map environment, long timeout, FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler, float duration) { 27 | this.cmd = cmd; 28 | this.timeout = timeout; 29 | this.environment = environment; 30 | this.ffmpegExecuteResponseHandler = ffmpegExecuteResponseHandler; 31 | this.shellCommand = new ShellCommand(); 32 | this.duration = duration; 33 | } 34 | 35 | @Override 36 | protected void onPreExecute() { 37 | startTime = System.currentTimeMillis(); 38 | if (ffmpegExecuteResponseHandler != null) { 39 | ffmpegExecuteResponseHandler.onStart(); 40 | } 41 | } 42 | 43 | @Override 44 | protected CommandResult doInBackground(Void... params) { 45 | try { 46 | process = shellCommand.run(cmd, environment); 47 | if (process == null) { 48 | return CommandResult.getDummyFailureResponse(); 49 | } 50 | Log.d("Running publishing updates method"); 51 | checkAndUpdateProcess(); 52 | return CommandResult.getOutputFromProcess(process); 53 | } catch (TimeoutException e) { 54 | Log.e("FFmpeg binary timed out", e); 55 | return new CommandResult(false, e.getMessage()); 56 | } catch (Exception e) { 57 | Log.e("Error running FFmpeg binary", e); 58 | } finally { 59 | Util.destroyProcess(process); 60 | } 61 | return CommandResult.getDummyFailureResponse(); 62 | } 63 | 64 | @Override 65 | protected void onProgressUpdate(String... values) { 66 | if (values != null && values[0] != null && ffmpegExecuteResponseHandler != null) { 67 | ffmpegExecuteResponseHandler.onProgress(values[0]); 68 | if (values[0].contains("Stream #") && values[0].contains("Video")) { 69 | outputFps = Util.getFpsFromCommandMessage(values[0]); 70 | } 71 | // if (values[0].contains("Duration:")) { 72 | // duration = Util.getDurationFromCommandMessage(values[0]); 73 | // } 74 | if (values[0].contains("frame=")) { 75 | float frameNumber = Util.getFramePositionFromCommandMessage(values[0]); 76 | ffmpegExecuteResponseHandler.onProgressPercent((frameNumber / (outputFps * duration)) * 100); 77 | } 78 | } 79 | } 80 | 81 | @Override 82 | protected void onPostExecute(CommandResult commandResult) { 83 | if (ffmpegExecuteResponseHandler != null) { 84 | output += commandResult.output; 85 | if (commandResult.success) { 86 | ffmpegExecuteResponseHandler.onSuccess(output); 87 | } else { 88 | ffmpegExecuteResponseHandler.onFailure(output); 89 | } 90 | ffmpegExecuteResponseHandler.onFinish(); 91 | } 92 | } 93 | 94 | private void checkAndUpdateProcess() throws TimeoutException, InterruptedException { 95 | while (!Util.isProcessCompleted(process)) { 96 | 97 | // checking if process is completed 98 | if (Util.isProcessCompleted(process)) { 99 | return; 100 | } 101 | 102 | // Handling timeout 103 | if (timeout != Long.MAX_VALUE && System.currentTimeMillis() > startTime + timeout) { 104 | throw new TimeoutException("FFmpeg binary timed out"); 105 | } 106 | 107 | try { 108 | String line; 109 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); 110 | while ((line = reader.readLine()) != null) { 111 | if (isCancelled()) { 112 | process.destroy(); 113 | process.waitFor(); 114 | return; 115 | } 116 | 117 | if (quitPending) { 118 | sendQ(); 119 | process = null; 120 | return; 121 | } 122 | 123 | output += line + "\n"; 124 | publishProgress(line); 125 | } 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | } 131 | 132 | public boolean isProcessCompleted() { 133 | return Util.isProcessCompleted(process); 134 | } 135 | 136 | @Override 137 | public boolean killRunningProcess() { 138 | return Util.killAsync(this); 139 | } 140 | 141 | @Override 142 | public void sendQuitSignal() { 143 | quitPending = true; 144 | } 145 | 146 | private void sendQ() { 147 | OutputStream outputStream = process.getOutputStream(); 148 | try { 149 | outputStream.write("q\n".getBytes()); 150 | outputStream.flush(); 151 | } catch (IOException e) { 152 | e.printStackTrace(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFcommandExecuteResponseHandler.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | public interface FFcommandExecuteResponseHandler extends ResponseHandler { 4 | 5 | /** 6 | * on Success 7 | * 8 | * @param message complete output of the binary command 9 | */ 10 | void onSuccess(String message); 11 | 12 | /** 13 | * on Progress 14 | * 15 | * @param message current output of binary command 16 | */ 17 | void onProgress(String message); 18 | 19 | /** 20 | * on Failure 21 | * 22 | * @param message complete output of the binary command 23 | */ 24 | void onFailure(String message); 25 | 26 | void onProgressPercent(float percent); 27 | } 28 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFmpeg.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.os.AsyncTask; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.lang.reflect.Array; 11 | import java.util.Map; 12 | 13 | public class FFmpeg implements FFbinaryInterface { 14 | private static final int VERSION = 17; // up this version when you add a new ffmpeg build 15 | private static final String KEY_PREF_VERSION = "ffmpeg_version"; 16 | 17 | private final FFbinaryContextProvider context; 18 | 19 | private static final long MINIMUM_TIMEOUT = 10 * 1000; 20 | private long timeout = Long.MAX_VALUE; 21 | 22 | private static FFmpeg instance = null; 23 | 24 | private FFmpeg(FFbinaryContextProvider context) { 25 | this.context = context; 26 | Log.setDebug(Util.isDebug(this.context.provide())); 27 | } 28 | 29 | public static FFmpeg getInstance(final Context context) { 30 | if (instance == null) { 31 | instance = new FFmpeg(new FFbinaryContextProvider() { 32 | @Override 33 | public Context provide() { 34 | return context; 35 | } 36 | }); 37 | } 38 | return instance; 39 | } 40 | 41 | @Override 42 | public boolean isSupported() { 43 | // check if arch is supported 44 | CpuArch cpuArch = CpuArchHelper.getCpuArch(); 45 | if (cpuArch == CpuArch.NONE) { 46 | Log.e("arch not supported"); 47 | return false; 48 | } 49 | 50 | // get ffmpeg file 51 | File ffmpeg = FileUtils.getFFmpeg(context.provide()); 52 | 53 | SharedPreferences settings = context.provide().getSharedPreferences("ffmpeg_prefs", Context.MODE_PRIVATE); 54 | int version = settings.getInt(KEY_PREF_VERSION, 0); 55 | 56 | // check if ffmpeg file exists 57 | if (!ffmpeg.exists() || version < VERSION) { 58 | String prefix = "arm/"; 59 | if (cpuArch == CpuArch.x86) { 60 | prefix = "x86/"; 61 | } 62 | Log.d("file does not exist, creating it..."); 63 | 64 | try { 65 | InputStream inputStream = context.provide().getAssets().open(prefix + "ffmpeg"); 66 | if (!FileUtils.inputStreamToFile(inputStream, ffmpeg)) { 67 | return false; 68 | } 69 | 70 | Log.d("successfully wrote ffmpeg file!"); 71 | 72 | settings.edit().putInt(KEY_PREF_VERSION, VERSION).apply(); 73 | } catch (IOException e) { 74 | Log.e("error while opening assets", e); 75 | return false; 76 | } 77 | } 78 | 79 | // check if ffmpeg can be executed 80 | if (!ffmpeg.canExecute()) { 81 | // try to make executable 82 | try { 83 | try { 84 | Runtime.getRuntime().exec("chmod -R 777 " + ffmpeg.getAbsolutePath()).waitFor(); 85 | } catch (InterruptedException e) { 86 | Log.e("interrupted exception", e); 87 | return false; 88 | } catch (IOException e) { 89 | Log.e("io exception", e); 90 | return false; 91 | } 92 | 93 | if (!ffmpeg.canExecute()) { 94 | // our last hope! 95 | if (!ffmpeg.setExecutable(true)) { 96 | Log.e("unable to make executable"); 97 | return false; 98 | } 99 | } 100 | } catch (SecurityException e) { 101 | Log.e("security exception", e); 102 | return false; 103 | } 104 | } 105 | 106 | Log.d("ffmpeg is ready!"); 107 | 108 | return true; 109 | } 110 | 111 | public boolean deleteFFmpegBin(){ 112 | File file = FileUtils.getFFmpeg(context.provide()); 113 | 114 | if(file.exists()){ 115 | return file.delete(); 116 | } 117 | return false; 118 | } 119 | 120 | @Override 121 | public FFtask execute(Map environvenmentVars, String[] cmd, FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler, float duration) { 122 | if (cmd.length != 0) { 123 | String[] ffmpegBinary = new String[]{FileUtils.getFFmpeg(context.provide()).getAbsolutePath()}; 124 | String[] command = concatenate(ffmpegBinary, cmd); 125 | FFcommandExecuteAsyncTask task = new FFcommandExecuteAsyncTask(command, environvenmentVars, timeout, ffmpegExecuteResponseHandler, duration); 126 | task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 127 | return task; 128 | } else { 129 | throw new IllegalArgumentException("shell command cannot be empty"); 130 | } 131 | } 132 | 133 | private static T[] concatenate(T[] a, T[] b) { 134 | int aLen = a.length; 135 | int bLen = b.length; 136 | 137 | @SuppressWarnings("unchecked") 138 | T[] c = (T[]) Array.newInstance(a.getClass().getComponentType(), aLen + bLen); 139 | System.arraycopy(a, 0, c, 0, aLen); 140 | System.arraycopy(b, 0, c, aLen, bLen); 141 | 142 | return c; 143 | } 144 | 145 | @Override 146 | public FFtask execute(String[] cmd, FFcommandExecuteResponseHandler ffmpegExecuteResponseHandler, float duration) { 147 | return execute(null, cmd, ffmpegExecuteResponseHandler, duration); 148 | } 149 | 150 | @Override 151 | public boolean isCommandRunning(FFtask task) { 152 | return task != null && !task.isProcessCompleted(); 153 | } 154 | 155 | @Override 156 | public boolean killRunningProcesses(FFtask task) { 157 | return task != null && task.killRunningProcess(); 158 | } 159 | 160 | @Override 161 | public void setTimeout(long timeout) { 162 | if (timeout >= MINIMUM_TIMEOUT) { 163 | this.timeout = timeout; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FFtask.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | public interface FFtask { 4 | 5 | /** 6 | * Sends 'q' to the ff binary running process asynchronously 7 | */ 8 | void sendQuitSignal(); 9 | 10 | /** 11 | * Checks if process is completed 12 | * @return true if a process is running 13 | */ 14 | boolean isProcessCompleted(); 15 | 16 | /** 17 | * Kill given running process 18 | * 19 | * @return true if process is killed successfully 20 | */ 21 | boolean killRunningProcess(); 22 | } 23 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/FileUtils.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | 12 | class FileUtils { 13 | private static final String FFMPEG_FILE_NAME = "ffmpeg"; 14 | 15 | static File getFFmpeg(Context context) { 16 | File folder = context.getFilesDir(); 17 | return new File(folder, FFMPEG_FILE_NAME); 18 | } 19 | 20 | static boolean inputStreamToFile(InputStream stream, File file) { 21 | try { 22 | InputStream input = new BufferedInputStream(stream); 23 | OutputStream output = new FileOutputStream(file); 24 | byte[] buffer = new byte[1024]; 25 | int bytesRead; 26 | while ((bytesRead = input.read(buffer, 0, buffer.length)) >= 0) { 27 | output.write(buffer, 0, bytesRead); 28 | } 29 | output.flush(); 30 | output.close(); 31 | input.close(); 32 | return true; 33 | } catch (IOException e) { 34 | Log.e("error while writing ff binary file", e); 35 | } 36 | return false; 37 | } 38 | } -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/Log.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | class Log { 4 | 5 | private static String TAG = FFmpeg.class.getSimpleName(); 6 | private static boolean DEBUG = false; 7 | 8 | public static void setDebug(boolean debug) { 9 | Log.DEBUG = debug; 10 | } 11 | 12 | public static void setTag(String tag) { 13 | Log.TAG = tag; 14 | } 15 | 16 | static void d(Object obj) { 17 | if (DEBUG) { 18 | android.util.Log.d(TAG, obj != null ? obj.toString() : ""); 19 | } 20 | } 21 | 22 | static void e(Object obj) { 23 | if (DEBUG) { 24 | android.util.Log.e(TAG, obj != null ? obj.toString() : ""); 25 | } 26 | } 27 | 28 | static void w(Object obj) { 29 | if (DEBUG) { 30 | android.util.Log.w(TAG, obj != null ? obj.toString() : ""); 31 | } 32 | } 33 | 34 | static void i(Object obj) { 35 | if (DEBUG) { 36 | android.util.Log.i(TAG, obj != null ? obj.toString() : ""); 37 | } 38 | } 39 | 40 | static void v(Object obj) { 41 | if (DEBUG) { 42 | android.util.Log.v(TAG, obj != null ? obj.toString() : ""); 43 | } 44 | } 45 | 46 | static void e(Object obj, Throwable throwable) { 47 | if (DEBUG) { 48 | android.util.Log.e(TAG, obj != null ? obj.toString() : "", throwable); 49 | } 50 | } 51 | 52 | static void e(Throwable throwable) { 53 | if (DEBUG) { 54 | android.util.Log.e(TAG, "", throwable); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | public interface ResponseHandler { 4 | 5 | /** 6 | * on Start 7 | */ 8 | void onStart(); 9 | 10 | /** 11 | * on Finish 12 | */ 13 | void onFinish(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/ShellCommand.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | 6 | class ShellCommand { 7 | 8 | Process run(String[] commandString, Map environment) { 9 | Process process = null; 10 | try { 11 | ProcessBuilder processBuilder = new ProcessBuilder(commandString); 12 | if (environment != null) { 13 | processBuilder.environment().putAll(environment); 14 | } 15 | process = processBuilder.start(); 16 | } catch (Throwable t) { 17 | Log.e("Exception while trying to run: " + Arrays.toString(commandString), t); 18 | } 19 | return process; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/ffmpeg/Util.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.ffmpeg; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.os.AsyncTask; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | 12 | class Util { 13 | 14 | static boolean isDebug(Context context) { 15 | return (context.getApplicationContext().getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE) != 0; 16 | } 17 | 18 | static String convertInputStreamToString(InputStream inputStream) { 19 | try { 20 | BufferedReader r = new BufferedReader(new InputStreamReader(inputStream)); 21 | String str; 22 | StringBuilder sb = new StringBuilder(); 23 | while ((str = r.readLine()) != null) { 24 | sb.append(str); 25 | } 26 | return sb.toString(); 27 | } catch (IOException e) { 28 | Log.e("error converting input stream to string", e); 29 | } 30 | return null; 31 | } 32 | 33 | static void destroyProcess(Process process) { 34 | if (process != null) { 35 | try { 36 | process.destroy(); 37 | } catch (Exception e) { 38 | Log.e("progress destroy error", e); 39 | } 40 | } 41 | } 42 | 43 | static boolean killAsync(AsyncTask asyncTask) { 44 | return asyncTask != null && !asyncTask.isCancelled() && asyncTask.cancel(true); 45 | } 46 | 47 | static boolean isProcessCompleted(Process process) { 48 | try { 49 | if (process == null) return true; 50 | process.exitValue(); 51 | return true; 52 | } catch (IllegalThreadStateException e) { 53 | // do nothing 54 | } 55 | return false; 56 | } 57 | 58 | public interface ObservePredicate { 59 | Boolean isReadyToProceed(); 60 | } 61 | 62 | static FFbinaryObserver observeOnce(final ObservePredicate predicate, final Runnable run, final int timeout) { 63 | final android.os.Handler observer = new android.os.Handler(); 64 | 65 | 66 | final FFbinaryObserver observeAction = new FFbinaryObserver() { 67 | private boolean canceled = false; 68 | private int timeElapsed = 0; 69 | 70 | @Override 71 | public void run() { 72 | if (timeElapsed + 40 > timeout) cancel(); 73 | timeElapsed += 40; 74 | 75 | if (canceled) return; 76 | 77 | boolean readyToProceed = false; 78 | try { 79 | readyToProceed = predicate.isReadyToProceed(); 80 | } catch (Exception e) { 81 | Log.v("Observing " + e.getMessage()); 82 | observer.postDelayed(this, 40); 83 | return; 84 | } 85 | 86 | if (readyToProceed) { 87 | Log.v("Observed"); 88 | run.run(); 89 | } else { 90 | Log.v("Observing"); 91 | observer.postDelayed(this, 40); 92 | } 93 | } 94 | 95 | @Override 96 | public void cancel() { 97 | canceled = true; 98 | } 99 | }; 100 | 101 | observer.post(observeAction); 102 | 103 | return observeAction; 104 | } 105 | 106 | static long getFramePositionFromCommandMessage(String message) { 107 | return Long.parseLong(message.split("frame=")[1].split("fps=")[0].trim()); 108 | } 109 | 110 | static float getFpsFromCommandMessage(String message) { 111 | String[] stream = message.split("Stream")[1].split("fps")[0].split(","); 112 | return Float.parseFloat(stream[stream.length - 1].trim()); 113 | } 114 | 115 | static float getDurationFromCommandMessage(String message){ 116 | String[] time = message.split("Duration:")[1].split(",")[0].trim().split(":"); 117 | return Integer.parseInt(time[0]) * 3600 + Integer.parseInt(time[1]) * 60 + Float.parseFloat(time[2]); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/player/VideoPlayer.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.player; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.os.Handler; 6 | 7 | import com.google.android.exoplayer2.DefaultLoadControl; 8 | import com.google.android.exoplayer2.DefaultRenderersFactory; 9 | import com.google.android.exoplayer2.ExoPlaybackException; 10 | import com.google.android.exoplayer2.ExoPlayer; 11 | import com.google.android.exoplayer2.ExoPlayerFactory; 12 | import com.google.android.exoplayer2.LoadControl; 13 | import com.google.android.exoplayer2.PlaybackParameters; 14 | import com.google.android.exoplayer2.Player; 15 | import com.google.android.exoplayer2.SimpleExoPlayer; 16 | import com.google.android.exoplayer2.Timeline; 17 | import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; 18 | import com.google.android.exoplayer2.extractor.ExtractorsFactory; 19 | import com.google.android.exoplayer2.source.ExtractorMediaSource; 20 | import com.google.android.exoplayer2.source.MediaSource; 21 | import com.google.android.exoplayer2.source.TrackGroupArray; 22 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; 23 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; 24 | import com.google.android.exoplayer2.trackselection.TrackSelection; 25 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 26 | import com.google.android.exoplayer2.trackselection.TrackSelector; 27 | import com.google.android.exoplayer2.ui.TimeBar; 28 | import com.google.android.exoplayer2.upstream.BandwidthMeter; 29 | import com.google.android.exoplayer2.upstream.DataSource; 30 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 31 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 32 | import com.google.android.exoplayer2.util.Util; 33 | import com.google.android.exoplayer2.video.VideoListener; 34 | 35 | import static com.google.android.exoplayer2.C.TIME_UNSET; 36 | 37 | public class VideoPlayer implements Player.EventListener, TimeBar.OnScrubListener, VideoListener { 38 | private SimpleExoPlayer player; 39 | private OnProgressUpdateListener mUpdateListener; 40 | private Handler progressHandler; 41 | private Runnable progressUpdater; 42 | 43 | public VideoPlayer(Context context) { 44 | BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); 45 | TrackSelection.Factory videoTrackSelectionFactory = 46 | new AdaptiveTrackSelection.Factory(bandwidthMeter); 47 | TrackSelector trackSelector = 48 | new DefaultTrackSelector(videoTrackSelectionFactory); 49 | LoadControl loadControl = new DefaultLoadControl(); 50 | player = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(context), trackSelector, loadControl); 51 | player.setRepeatMode(Player.REPEAT_MODE_ONE); 52 | player.addListener(this); 53 | progressHandler = new Handler(); 54 | } 55 | 56 | public void initMediaSource(Context context, String uri) { 57 | DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(context, "ExoPlayer")); 58 | ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); 59 | MediaSource videoSource = new ExtractorMediaSource(Uri.parse(uri), 60 | dataSourceFactory, extractorsFactory, null, null); 61 | 62 | player.prepare(videoSource); 63 | player.addVideoListener(this); 64 | } 65 | 66 | public SimpleExoPlayer getPlayer() { 67 | return player; 68 | } 69 | 70 | public void play(boolean play) { 71 | player.setPlayWhenReady(play); 72 | if (!play) { 73 | removeUpdater(); 74 | } 75 | } 76 | 77 | public void release() { 78 | player.release(); 79 | removeUpdater(); 80 | player = null; 81 | } 82 | 83 | public boolean isPlaying() { 84 | return player.getPlayWhenReady(); 85 | } 86 | 87 | @Override 88 | public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { 89 | updateProgress(); 90 | } 91 | 92 | @Override 93 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { 94 | 95 | } 96 | 97 | @Override 98 | public void onLoadingChanged(boolean isLoading) { 99 | 100 | } 101 | 102 | @Override 103 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { 104 | updateProgress(); 105 | } 106 | 107 | @Override 108 | public void onRepeatModeChanged(int repeatMode) { 109 | 110 | } 111 | 112 | @Override 113 | public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { 114 | 115 | } 116 | 117 | @Override 118 | public void onPlayerError(ExoPlaybackException error) { 119 | 120 | } 121 | 122 | @Override 123 | public void onPositionDiscontinuity(int reason) { 124 | updateProgress(); 125 | } 126 | 127 | @Override 128 | public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { 129 | 130 | } 131 | 132 | @Override 133 | public void onSeekProcessed() { 134 | 135 | } 136 | 137 | @Override 138 | public void onScrubStart(TimeBar timeBar, long position) { 139 | 140 | } 141 | 142 | @Override 143 | public void onScrubMove(TimeBar timeBar, long position) { 144 | seekTo(position); 145 | updateProgress(); 146 | } 147 | 148 | @Override 149 | public void onScrubStop(TimeBar timeBar, long position, boolean canceled) { 150 | seekTo(position); 151 | updateProgress(); 152 | } 153 | 154 | private void updateProgress() { 155 | if (mUpdateListener != null) { 156 | mUpdateListener.onProgressUpdate( 157 | player.getCurrentPosition(), 158 | player.getDuration() == TIME_UNSET ? 0L : player.getDuration(), 159 | player.getBufferedPosition()); 160 | } 161 | initUpdateTimer(); 162 | } 163 | 164 | private void initUpdateTimer() { 165 | long position = player.getCurrentPosition(); 166 | int playbackState = player.getPlaybackState(); 167 | long delayMs; 168 | if (playbackState != ExoPlayer.STATE_IDLE && playbackState != ExoPlayer.STATE_ENDED) { 169 | if (player.getPlayWhenReady() && playbackState == ExoPlayer.STATE_READY) { 170 | delayMs = 1000 - (position % 1000); 171 | if (delayMs < 200) { 172 | delayMs += 1000; 173 | } 174 | } else { 175 | delayMs = 1000; 176 | } 177 | 178 | removeUpdater(); 179 | progressUpdater = new Runnable() { 180 | @Override 181 | public void run() { 182 | updateProgress(); 183 | } 184 | }; 185 | 186 | progressHandler.postDelayed(progressUpdater, delayMs); 187 | } 188 | } 189 | 190 | private void removeUpdater() { 191 | if (progressUpdater != null) 192 | progressHandler.removeCallbacks(progressUpdater); 193 | } 194 | 195 | public void seekTo(long position) { 196 | player.seekTo(position); 197 | } 198 | 199 | public void setUpdateListener(OnProgressUpdateListener updateListener) { 200 | mUpdateListener = updateListener; 201 | } 202 | 203 | @Override 204 | public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { 205 | if(mUpdateListener != null){ 206 | mUpdateListener.onFirstTimeUpdate(player.getDuration(), player.getCurrentPosition()); 207 | } 208 | } 209 | 210 | @Override 211 | public void onRenderedFirstFrame() { 212 | 213 | } 214 | 215 | public interface OnProgressUpdateListener { 216 | void onProgressUpdate(long currentPosition, long duration, long bufferedPosition); 217 | 218 | void onFirstTimeUpdate(long duration, long currentPosition); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/util/Utils.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.util; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Build; 8 | import android.support.v4.content.ContextCompat; 9 | import android.support.v4.graphics.drawable.DrawableCompat; 10 | 11 | public final class Utils { 12 | private Utils () {} 13 | 14 | public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) { 15 | Drawable drawable = ContextCompat.getDrawable(context, drawableId); 16 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 17 | drawable = (DrawableCompat.wrap(drawable)).mutate(); 18 | } 19 | 20 | Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 21 | drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 22 | Canvas canvas = new Canvas(bitmap); 23 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 24 | drawable.draw(canvas); 25 | 26 | return bitmap; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/view/ProgressView.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RectF; 8 | import android.support.annotation.Nullable; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | 12 | import net.vrgsoft.videcrop.R; 13 | 14 | public class ProgressView extends View { 15 | private Paint mPaint; 16 | private int mCurrentProgress; 17 | private RectF mRectF; 18 | private float mPadding; 19 | 20 | public ProgressView(Context context) { 21 | this(context, null); 22 | } 23 | 24 | public ProgressView(Context context, @Nullable AttributeSet attrs) { 25 | this(context, attrs, 0); 26 | } 27 | 28 | public ProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 29 | super(context, attrs, defStyleAttr); 30 | init(); 31 | } 32 | 33 | private void init() { 34 | mPaint = new Paint(); 35 | mPaint.setAntiAlias(true); 36 | mPaint.setStyle(Paint.Style.STROKE); 37 | mPaint.setStrokeWidth(getContext().getResources().getDimension(R.dimen.progress_width)); 38 | mPadding = getContext().getResources().getDimension(R.dimen.padding); 39 | } 40 | 41 | @Override 42 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 43 | mRectF = new RectF(mPadding, mPadding, getWidth() - mPadding, getHeight() - mPadding); 44 | } 45 | 46 | @Override 47 | protected void onDraw(Canvas canvas) { 48 | mPaint.setColor(Color.WHITE); 49 | canvas.drawCircle(getWidth() * 1.0f / 2, getHeight() * 1.0f / 2, getWidth() * 1.0f / 2 - mPadding, mPaint); 50 | mPaint.setColor(Color.BLACK); 51 | canvas.drawArc(mRectF, 270, mCurrentProgress * 1.0f / 100 * 360, false, mPaint); 52 | } 53 | 54 | public void setProgress(int progress) { 55 | mCurrentProgress = progress; 56 | invalidate(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /videcrop/src/main/java/net/vrgsoft/videcrop/view/VideoSliceSeekBarH.java: -------------------------------------------------------------------------------- 1 | package net.vrgsoft.videcrop.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.Rect; 9 | import android.support.v7.widget.AppCompatImageView; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | 13 | import net.vrgsoft.videcrop.R; 14 | 15 | public class VideoSliceSeekBarH extends AppCompatImageView { 16 | private static final int SELECT_THUMB_NON = 0; 17 | private static final int SELECT_THUMB_LEFT = 1; 18 | private static final int SELECT_THUMB_RIGHT = 2; 19 | private boolean blocked; 20 | private boolean isVideoStatusDisplay; 21 | private long maxValue = 100; 22 | private Paint paint = new Paint(); 23 | private Paint paintThumb = new Paint(); 24 | private int progressBottom; 25 | private int progressColor = getResources().getColor(android.R.color.black); 26 | private int progressHalfHeight = 3; 27 | private int progressMinDiff = 15; 28 | private int progressMinDiffPixels; 29 | private int progressTop; 30 | private SeekBarChangeListener scl; 31 | private int secondaryProgressColor = getResources().getColor(R.color.txt_color); 32 | private int selectedThumb; 33 | private Bitmap thumbCurrentVideoPosition = BitmapFactory.decodeResource(getResources(), R.drawable.ic_thumb_3); 34 | private int thumbCurrentVideoPositionHalfWidth; 35 | private int thumbCurrentVideoPositionX; 36 | private int thumbCurrentVideoPositionY; 37 | private int thumbPadding = getResources().getDimensionPixelOffset(R.dimen.default_margin); 38 | private Bitmap thumbSlice = BitmapFactory.decodeResource(getResources(), R.drawable.ic_thumb_3); 39 | private int thumbSliceHalfWidth; 40 | private long thumbSliceLeftValue; 41 | private int thumbSliceLeftX; 42 | private long thumbSliceRightValue; 43 | private int thumbSliceRightX; 44 | private int thumbSliceY; 45 | private Bitmap thumbSlicer = BitmapFactory.decodeResource(getResources(), R.drawable.ic_thumb_3); 46 | 47 | public interface SeekBarChangeListener { 48 | void seekBarValueChanged(long leftThumb, long rightThumb); 49 | } 50 | 51 | public VideoSliceSeekBarH(Context context, AttributeSet attrs, int defStyle) { 52 | super(context, attrs, defStyle); 53 | } 54 | 55 | public VideoSliceSeekBarH(Context context, AttributeSet attrs) { 56 | super(context, attrs); 57 | } 58 | 59 | public VideoSliceSeekBarH(Context context) { 60 | super(context); 61 | } 62 | 63 | public void onWindowFocusChanged(boolean hasWindowFocus) { 64 | super.onWindowFocusChanged(hasWindowFocus); 65 | if (!isInEditMode()) { 66 | init(); 67 | } 68 | } 69 | 70 | private void init() { 71 | if (this.thumbSlice.getHeight() > getHeight()) { 72 | getLayoutParams().height = this.thumbSlice.getHeight(); 73 | } 74 | this.thumbSliceY = (getHeight() / 2) - (this.thumbSlice.getHeight() / 2); 75 | this.thumbCurrentVideoPositionY = (getHeight() / 2) - (this.thumbCurrentVideoPosition.getHeight() / 2); 76 | this.thumbSliceHalfWidth = this.thumbSlice.getWidth() / 2; 77 | this.thumbCurrentVideoPositionHalfWidth = this.thumbCurrentVideoPosition.getWidth() / 2; 78 | if (this.thumbSliceLeftX == 0 || this.thumbSliceRightX == 0) { 79 | this.thumbSliceLeftX = this.thumbPadding; 80 | this.thumbSliceRightX = getWidth() - this.thumbPadding; 81 | } 82 | this.progressMinDiffPixels = calculateCorrds(this.progressMinDiff) - (this.thumbPadding * 2); 83 | this.progressTop = (getHeight() / 2) - this.progressHalfHeight; 84 | this.progressBottom = (getHeight() / 2) + this.progressHalfHeight; 85 | invalidate(); 86 | } 87 | 88 | public void setSeekBarChangeListener(SeekBarChangeListener scl) { 89 | this.scl = scl; 90 | } 91 | 92 | protected void onDraw(Canvas canvas) { 93 | super.onDraw(canvas); 94 | this.paint.setColor(this.progressColor); 95 | canvas.drawRect(new Rect(this.thumbPadding, this.progressTop, this.thumbSliceLeftX, this.progressBottom), this.paint); 96 | canvas.drawRect(new Rect(this.thumbSliceRightX, this.progressTop, getWidth() - this.thumbPadding, this.progressBottom), this.paint); 97 | this.paint.setColor(this.secondaryProgressColor); 98 | canvas.drawRect(new Rect(this.thumbSliceLeftX, this.progressTop, this.thumbSliceRightX, this.progressBottom), this.paint); 99 | if (!this.blocked) { 100 | canvas.drawBitmap(this.thumbSlice, (float) (this.thumbSliceLeftX - this.thumbSliceHalfWidth), (float) this.thumbSliceY, this.paintThumb); 101 | canvas.drawBitmap(this.thumbSlicer, (float) (this.thumbSliceRightX - this.thumbSliceHalfWidth), (float) this.thumbSliceY, this.paintThumb); 102 | } 103 | if (this.isVideoStatusDisplay) { 104 | canvas.drawBitmap(this.thumbCurrentVideoPosition, (float) (this.thumbCurrentVideoPositionX - this.thumbCurrentVideoPositionHalfWidth), (float) this.thumbCurrentVideoPositionY, this.paintThumb); 105 | } 106 | } 107 | 108 | public boolean onTouchEvent(MotionEvent event) { 109 | if (!this.blocked) { 110 | int mx = (int) event.getX(); 111 | switch (event.getAction()) { 112 | case 0: 113 | if ((mx < this.thumbSliceLeftX - this.thumbSliceHalfWidth || mx > this.thumbSliceLeftX + this.thumbSliceHalfWidth) && mx >= this.thumbSliceLeftX - this.thumbSliceHalfWidth) { 114 | if ((mx < this.thumbSliceRightX - this.thumbSliceHalfWidth || mx > this.thumbSliceRightX + this.thumbSliceHalfWidth) && mx <= this.thumbSliceRightX + this.thumbSliceHalfWidth) { 115 | if ((mx - this.thumbSliceLeftX) + this.thumbSliceHalfWidth >= (this.thumbSliceRightX - this.thumbSliceHalfWidth) - mx) { 116 | if ((mx - this.thumbSliceLeftX) + this.thumbSliceHalfWidth > (this.thumbSliceRightX - this.thumbSliceHalfWidth) - mx) { 117 | this.selectedThumb = 2; 118 | break; 119 | } 120 | } 121 | this.selectedThumb = 1; 122 | break; 123 | } 124 | this.selectedThumb = 2; 125 | break; 126 | } 127 | this.selectedThumb = 1; 128 | break; 129 | case 1: 130 | this.selectedThumb = 0; 131 | break; 132 | case 2: 133 | if ((mx <= (this.thumbSliceLeftX + this.thumbSliceHalfWidth) + 0 && this.selectedThumb == 2) || (mx >= (this.thumbSliceRightX - this.thumbSliceHalfWidth) + 0 && this.selectedThumb == 1)) { 134 | this.selectedThumb = 0; 135 | } 136 | if (this.selectedThumb != 1) { 137 | if (this.selectedThumb == 2) { 138 | this.thumbSliceRightX = mx; 139 | break; 140 | } 141 | } 142 | this.thumbSliceLeftX = mx; 143 | break; 144 | } 145 | notifySeekBarValueChanged(); 146 | } 147 | return true; 148 | } 149 | 150 | private void notifySeekBarValueChanged() { 151 | if (this.thumbSliceLeftX < this.thumbPadding) { 152 | this.thumbSliceLeftX = this.thumbPadding; 153 | } 154 | if (this.thumbSliceRightX < this.thumbPadding) { 155 | this.thumbSliceRightX = this.thumbPadding; 156 | } 157 | if (this.thumbSliceLeftX > getWidth() - this.thumbPadding) { 158 | this.thumbSliceLeftX = getWidth() - this.thumbPadding; 159 | } 160 | if (this.thumbSliceRightX > getWidth() - this.thumbPadding) { 161 | this.thumbSliceRightX = getWidth() - this.thumbPadding; 162 | } 163 | invalidate(); 164 | if (this.scl != null) { 165 | calculateThumbValue(); 166 | this.scl.seekBarValueChanged(this.thumbSliceLeftValue, this.thumbSliceRightValue); 167 | } 168 | } 169 | 170 | private void calculateThumbValue() { 171 | this.thumbSliceLeftValue = (this.maxValue * (this.thumbSliceLeftX - this.thumbPadding)) / (getWidth() - (this.thumbPadding * 2)); 172 | this.thumbSliceRightValue = (this.maxValue * (this.thumbSliceRightX - this.thumbPadding)) / (getWidth() - (this.thumbPadding * 2)); 173 | } 174 | 175 | private int calculateCorrds(long progress) { 176 | return ((int) (((((double) getWidth()) - (2.0d * ((double) this.thumbPadding))) / ((double) this.maxValue)) * ((double) progress))) + this.thumbPadding; 177 | } 178 | 179 | public void setLeftProgress(long progress) { 180 | if (progress < this.thumbSliceRightValue - this.progressMinDiff) { 181 | this.thumbSliceLeftX = calculateCorrds(progress); 182 | } 183 | notifySeekBarValueChanged(); 184 | } 185 | 186 | public void setRightProgress(long progress) { 187 | if (progress > this.thumbSliceLeftValue + this.progressMinDiff) { 188 | this.thumbSliceRightX = calculateCorrds(progress); 189 | } 190 | notifySeekBarValueChanged(); 191 | } 192 | 193 | public int getSelectedThumb() { 194 | return this.selectedThumb; 195 | } 196 | 197 | public long getLeftProgress() { 198 | return this.thumbSliceLeftValue; 199 | } 200 | 201 | public long getRightProgress() { 202 | return this.thumbSliceRightValue; 203 | } 204 | 205 | public void setProgress(int leftProgress, int rightProgress) { 206 | if (rightProgress - leftProgress > this.progressMinDiff) { 207 | this.thumbSliceLeftX = calculateCorrds(leftProgress); 208 | this.thumbSliceRightX = calculateCorrds(rightProgress); 209 | } 210 | notifySeekBarValueChanged(); 211 | } 212 | 213 | public void videoPlayingProgress(long progress) { 214 | this.isVideoStatusDisplay = true; 215 | this.thumbCurrentVideoPositionX = calculateCorrds(progress); 216 | invalidate(); 217 | } 218 | 219 | public void removeVideoStatusThumb() { 220 | this.isVideoStatusDisplay = false; 221 | invalidate(); 222 | } 223 | 224 | public void setSliceBlocked(boolean isBLock) { 225 | this.blocked = isBLock; 226 | invalidate(); 227 | } 228 | 229 | public void setMaxValue(long maxValue) { 230 | this.maxValue = maxValue; 231 | } 232 | 233 | public void setProgressMinDiff(int progressMinDiff) { 234 | this.progressMinDiff = progressMinDiff; 235 | this.progressMinDiffPixels = calculateCorrds(progressMinDiff); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable-nodpi/ic_thumb_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/videcrop/src/main/res/drawable-nodpi/ic_thumb_3.png -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/bg_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/cutter_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/videcrop/src/main/res/drawable/cutter_dot.png -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_aspect_ratio.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop_16_9.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop_4_3.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop_custom.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop_landscape.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop_portrait.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_crop_square.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_thumb.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/ic_thumb_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/videcrop/src/main/res/drawable/ic_thumb_2.png -------------------------------------------------------------------------------- /videcrop/src/main/res/drawable/progress_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/videcrop/src/main/res/drawable/progress_thumb.png -------------------------------------------------------------------------------- /videcrop/src/main/res/layout/activity_crop.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 27 | 28 | 40 | 41 | 50 | 51 | 60 | 61 | 70 | 71 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 124 | 125 | 135 | 136 | 147 | 148 | 159 | 160 | 161 | 169 | -------------------------------------------------------------------------------- /videcrop/src/main/res/layout/view_aspect_ratio_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 33 | 34 | 48 | 49 | 58 | 59 | 73 | 74 | 83 | 84 | 98 | 99 | 108 | 109 | 123 | 124 | 133 | 134 | 148 | 149 | 158 | -------------------------------------------------------------------------------- /videcrop/src/main/res/layout/view_crop.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 19 | -------------------------------------------------------------------------------- /videcrop/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /videcrop/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #AAFFFFFF 4 | #B0000000 5 | 6 | @color/white_translucent 7 | @color/white_translucent 8 | @android:color/white 9 | @color/black_translucent 10 | #eeeeee 11 | #3b3b3b 12 | -------------------------------------------------------------------------------- /videcrop/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3dp 4 | 5dp 5 | 1px 6 | 7 | 24dp 8 | 3dp 9 | 20dp 10 | 20dp 11 | 10dp 12 | 10dp 13 | -------------------------------------------------------------------------------- /videcrop/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | videcrop 3 | 00:00 4 | Custom 5 | Square 6 | Portrait 7 | Landscape 8 | 4:3 9 | 16:9 10 | 11 | -------------------------------------------------------------------------------- /videcrop/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |