├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
4 |
--------------------------------------------------------------------------------
/video.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VRGsoftUA/VideoCrop/0d1bb24f9ef4af04a2031e855d31f11e64b1e743/video.gif
--------------------------------------------------------------------------------