├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── k4l-video-trimmer
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── life
│ │ └── knowledge4
│ │ └── videotrimmer
│ │ └── ExampleInstrumentationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── life
│ │ │ └── knowledge4
│ │ │ └── videotrimmer
│ │ │ ├── K4LVideoTrimmer.java
│ │ │ ├── interfaces
│ │ │ ├── OnK4LVideoListener.java
│ │ │ ├── OnProgressVideoListener.java
│ │ │ ├── OnRangeSeekBarListener.java
│ │ │ └── OnTrimVideoListener.java
│ │ │ ├── utils
│ │ │ ├── BackgroundExecutor.java
│ │ │ ├── FileUtils.java
│ │ │ ├── TrimVideoUtils.java
│ │ │ └── UiThreadExecutor.java
│ │ │ └── view
│ │ │ ├── ProgressBarView.java
│ │ │ ├── RangeSeekBarView.java
│ │ │ ├── Thumb.java
│ │ │ └── TimeLineView.java
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── apptheme_text_select_handle_left.png
│ │ ├── apptheme_text_select_handle_middle.png
│ │ ├── apptheme_text_select_handle_right.png
│ │ └── icon_video_play.png
│ │ ├── drawable-v21
│ │ ├── black_button_background.xml
│ │ └── play_button.xml
│ │ ├── drawable
│ │ ├── black_button_background.xml
│ │ └── play_button.xml
│ │ ├── layout
│ │ └── view_time_line.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-pt-rBR
│ │ └── strings.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── life
│ └── knowledge4
│ └── videotrimmer
│ └── ExampleUnitTest.java
├── mavenpush.gradle
├── sample-app
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── life
│ │ └── knowledge4
│ │ └── videotrimmersample
│ │ └── ExampleInstrumentationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── life
│ │ │ └── knowledge4
│ │ │ └── videotrimmersample
│ │ │ ├── MainActivity.java
│ │ │ └── TrimmerActivity.java
│ └── res
│ │ ├── drawable-v21
│ │ └── background_button.xml
│ │ ├── drawable
│ │ ├── background_button.xml
│ │ ├── ic_photo_size_select_actual_black_24dp.xml
│ │ └── ic_videocam_black_24dp.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── activity_trimmer.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── raw
│ │ └── intro.mp4
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── life
│ └── knowledge4
│ └── videotrimmersample
│ └── ExampleUnitTest.java
├── screenshot
└── screenshot.png
└── settings.gradle
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | - [ ] I have verified there are no duplicate active or recent bugs, questions, or requests
2 |
3 | ###### Include the following:
4 | - k4l-video-trimmer version: `1.0.3`
5 | - Device OS version: `6.0.1`
6 | - Devide Manufacturer: `Motorola`
7 | - Device Name: `G4 Plus`
8 |
9 | ###### Reproduction Steps
10 | 1.
11 | 2.
12 | 3.
13 |
14 | ###### Expected Result
15 |
16 | ###### Actual Result
17 |
18 | ### Tell us what could be improved:
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ###### Fixes issue #.
2 | - [ ] This pull request follows the coding standards
3 |
4 | ###### This PR changes:
5 | -
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/target
2 | **/.settings
3 | **/bin
4 | **/.classpath
5 | **/.project
6 | .DS_Store
7 |
8 | # Gradle
9 | .gradle
10 | gradle.properties
11 |
12 | # Built application files
13 | *.apk
14 | *.ap_
15 |
16 | # Files for the dex VM
17 | *.dex
18 |
19 | # Java class files
20 | *.class
21 |
22 | # Generated files
23 | bin/
24 | gen/
25 |
26 | # Local configuration file (sdk path, etc)
27 | local.properties
28 |
29 | # Windows thumbnail db
30 | Thumbs.db
31 |
32 | # OSX files
33 | .DS_Store
34 |
35 | # Eclipse project files
36 | .classpath
37 | .project
38 |
39 | # Android Studio
40 | .idea
41 | *.iml
42 | build/
43 |
44 | /*/out
45 | /*/*/build
46 | /*/*/production
47 | *.iws
48 | *.ipr
49 | *~
50 | *.swp
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Knowledge, education for life.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://raw.githubusercontent.com/hyperium/hyper/master/LICENSE)
2 | [](https://github.com/knowledge4life/k4l-video-trimmer/stargazers)
3 | [](https://github.com/knowledge4life/k4l-video-trimmer/network)
4 | [](https://android-arsenal.com/details/1/3714)
5 |
6 | # VideoTrimmer
7 |
8 | #### This project aims to provide an ultimate and flexible video trimmer experience.
9 |
10 |
11 |
12 | ## [Watch a DEMO here](http://gfycat.com/UnnaturalConsiderateFiddlercrab)
13 |
14 | # Usage
15 |
16 | *For a working implementation, please have a look at the Sample Project - sample*
17 |
18 | 1. Include the library as local library project.
19 |
20 | ``` compile 'life.knowledge4:k4l-video-trimmer:1.0' ```
21 |
22 | 2. Add K4LVideoTrimmer component into your layout.
23 |
24 | ```
25 |
29 | ```
30 |
31 | 3. Set the K4LVideoTrimmer selected video Uri.
32 |
33 | ```java
34 | K4LVideoTrimmer videoTrimmer = ((K4LVideoTrimmer) findViewById(R.id.timeLine));
35 | if (videoTrimmer != null) {
36 | videoTrimmer.setVideoURI(Uri.parse(path));
37 | }
38 | ```
39 |
40 | # Default destination folder
41 | Environment.getExternalStorageDirectory()
42 |
43 | # Here is an example of a listener implementation.
44 |
45 | 1. Implements `OnTrimVideoListener` methods
46 |
47 | ```java
48 | @Override
49 | public void getResult(final Uri uri) {
50 | // handle K4LVideoTrimmer result.
51 | }
52 |
53 | @Override
54 | public void cancelAction() {
55 | // handle K4LVideoTrimmer cancel action
56 | }
57 | ```
58 |
59 | # Customization
60 |
61 | * Custom destination folder
62 | ```java
63 | videoTrimmer.setDestinationPath("/storage/emulated/0/DCIM/CameraCustom/");
64 | ```
65 |
66 | * Set maximum video time interval
67 | ```java
68 | videoTrimmer.setMaxDuration(10);
69 | ```
70 |
71 | # Incoming improvements
72 |
73 | - Customize K4LVideoTrimmer colors
74 | - Customize K4LVideoTrimmer drawables
75 | - Add support for `setMinDuration`
76 | - Add tests
77 |
78 | # Known issues and limitations
79 | - Thumbnails are only added to the timeline once all of them are created in a background thread
80 | - As for now there is no way of personalising the component
81 | - We only support MP4 files
82 | - Methods count: 5768 from Isoparser + 237 from K4l-video-trimmer
83 |
84 | # Compatibility
85 |
86 | * Library - Android ICS 4.1+ (API 16)
87 | * Sample - Android ICS 4.1+ (API 16)
88 |
89 | # Using SNAPSHOTS
90 |
91 | Add the sonatype snapshots repository.
92 | ```
93 | 'https://oss.sonatype.org/content/repositories/snapshots/'
94 | ```
95 | Example:
96 | ```
97 | repositories{
98 | flatDir{
99 | dirs 'libs'
100 | }
101 | maven {
102 | url = 'https://oss.sonatype.org/content/repositories/snapshots/'
103 | }
104 | }
105 | ```
106 | Then:
107 | ```
108 | compile 'life.knowledge4:k4l-video-trimmer:1.1.3-SNAPSHOT'
109 | ```
110 |
111 | ## Collaboration
112 | There are many ways of improving and adding more features, so feel free to collaborate with ideas, issues and/or pull requests.
113 |
114 | ### Let us know!
115 |
116 | We’d be really happy if you sent us links to your projects where you use our component. Just create an issue and let us know if you have any questions or suggestion regarding the library.
117 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.0-alpha7'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | project.ext{
16 | VERSION_NAME='1.1.3-SNAPSHOT'
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/titansgroup/k4l-video-trimmer/29601369245a34af57fe9f14b51475c46d31fb5b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Aug 07 15:14:15 BRT 2016
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-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply from: '../mavenpush.gradle'
3 |
4 | android {
5 | compileSdkVersion 24
6 | buildToolsVersion "23.0.3"
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 24
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support:appcompat-v7:24.0.0'
26 | compile 'com.googlecode.mp4parser:isoparser:1.1.20'
27 | testCompile 'junit:junit:4.12'
28 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
29 | androidTestCompile 'com.android.support.test:runner:0.5'
30 | androidTestCompile 'com.android.support:support-annotations:24.0.0'
31 | }
32 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/dogo/Development/sdks/android-sdk-macosx/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/androidTest/java/life/knowledge4/videotrimmer/ExampleInstrumentationTest.java:
--------------------------------------------------------------------------------
1 | package life.knowledge4.videotrimmer;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.filters.MediumTest;
6 | import android.support.test.runner.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 |
12 | import static org.junit.Assert.*;
13 |
14 | /**
15 | * Instrumentation test, which will execute on an Android device.
16 | *
17 | * @see Testing documentation
18 | */
19 | @MediumTest
20 | @RunWith(AndroidJUnit4.class)
21 | public class ExampleInstrumentationTest {
22 | @Test
23 | public void useAppContext() throws Exception {
24 | // Context of the app under test.
25 | Context appContext = InstrumentationRegistry.getTargetContext();
26 |
27 | assertEquals("life.knowledge4.videotrimmer", appContext.getPackageName());
28 | }
29 | }
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/K4LVideoTrimmer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer;
25 |
26 | import android.content.Context;
27 | import android.media.MediaMetadataRetriever;
28 | import android.media.MediaPlayer;
29 | import android.net.Uri;
30 | import android.os.Environment;
31 | import android.os.Handler;
32 | import android.os.Message;
33 | import android.support.annotation.NonNull;
34 | import android.util.AttributeSet;
35 | import android.util.Log;
36 | import android.view.GestureDetector;
37 | import android.view.LayoutInflater;
38 | import android.view.MotionEvent;
39 | import android.view.View;
40 | import android.view.ViewGroup;
41 | import android.widget.FrameLayout;
42 | import android.widget.ImageView;
43 | import android.widget.RelativeLayout;
44 | import android.widget.SeekBar;
45 | import android.widget.TextView;
46 | import android.widget.VideoView;
47 |
48 | import java.io.File;
49 | import java.lang.ref.WeakReference;
50 | import java.util.ArrayList;
51 | import java.util.List;
52 |
53 | import life.knowledge4.videotrimmer.interfaces.OnK4LVideoListener;
54 | import life.knowledge4.videotrimmer.interfaces.OnProgressVideoListener;
55 | import life.knowledge4.videotrimmer.interfaces.OnRangeSeekBarListener;
56 | import life.knowledge4.videotrimmer.interfaces.OnTrimVideoListener;
57 | import life.knowledge4.videotrimmer.utils.BackgroundExecutor;
58 | import life.knowledge4.videotrimmer.utils.TrimVideoUtils;
59 | import life.knowledge4.videotrimmer.utils.UiThreadExecutor;
60 | import life.knowledge4.videotrimmer.view.ProgressBarView;
61 | import life.knowledge4.videotrimmer.view.RangeSeekBarView;
62 | import life.knowledge4.videotrimmer.view.Thumb;
63 | import life.knowledge4.videotrimmer.view.TimeLineView;
64 |
65 | import static life.knowledge4.videotrimmer.utils.TrimVideoUtils.stringForTime;
66 |
67 | public class K4LVideoTrimmer extends FrameLayout {
68 |
69 | private static final String TAG = K4LVideoTrimmer.class.getSimpleName();
70 | private static final int MIN_TIME_FRAME = 1000;
71 | private static final int SHOW_PROGRESS = 2;
72 |
73 | private SeekBar mHolderTopView;
74 | private RangeSeekBarView mRangeSeekBarView;
75 | private RelativeLayout mLinearVideo;
76 | private View mTimeInfoContainer;
77 | private VideoView mVideoView;
78 | private ImageView mPlayView;
79 | private TextView mTextSize;
80 | private TextView mTextTimeFrame;
81 | private TextView mTextTime;
82 | private TimeLineView mTimeLineView;
83 |
84 | private ProgressBarView mVideoProgressIndicator;
85 | private Uri mSrc;
86 | private String mFinalPath;
87 |
88 | private int mMaxDuration;
89 | private List mListeners;
90 |
91 | private OnTrimVideoListener mOnTrimVideoListener;
92 | private OnK4LVideoListener mOnK4LVideoListener;
93 |
94 | private int mDuration = 0;
95 | private int mTimeVideo = 0;
96 | private int mStartPosition = 0;
97 | private int mEndPosition = 0;
98 |
99 | private long mOriginSizeFile;
100 | private boolean mResetSeekBar = true;
101 | private final MessageHandler mMessageHandler = new MessageHandler(this);
102 |
103 | public K4LVideoTrimmer(@NonNull Context context, AttributeSet attrs) {
104 | this(context, attrs, 0);
105 | }
106 |
107 | public K4LVideoTrimmer(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
108 | super(context, attrs, defStyleAttr);
109 | init(context);
110 | }
111 |
112 | private void init(Context context) {
113 | LayoutInflater.from(context).inflate(R.layout.view_time_line, this, true);
114 |
115 | mHolderTopView = ((SeekBar) findViewById(R.id.handlerTop));
116 | mVideoProgressIndicator = ((ProgressBarView) findViewById(R.id.timeVideoView));
117 | mRangeSeekBarView = ((RangeSeekBarView) findViewById(R.id.timeLineBar));
118 | mLinearVideo = ((RelativeLayout) findViewById(R.id.layout_surface_view));
119 | mVideoView = ((VideoView) findViewById(R.id.video_loader));
120 | mPlayView = ((ImageView) findViewById(R.id.icon_video_play));
121 | mTimeInfoContainer = findViewById(R.id.timeText);
122 | mTextSize = ((TextView) findViewById(R.id.textSize));
123 | mTextTimeFrame = ((TextView) findViewById(R.id.textTimeSelection));
124 | mTextTime = ((TextView) findViewById(R.id.textTime));
125 | mTimeLineView = ((TimeLineView) findViewById(R.id.timeLineView));
126 |
127 | setUpListeners();
128 | setUpMargins();
129 | }
130 |
131 | private void setUpListeners() {
132 | mListeners = new ArrayList<>();
133 | mListeners.add(new OnProgressVideoListener() {
134 | @Override
135 | public void updateProgress(int time, int max, float scale) {
136 | updateVideoProgress(time);
137 | }
138 | });
139 | mListeners.add(mVideoProgressIndicator);
140 |
141 | findViewById(R.id.btCancel)
142 | .setOnClickListener(
143 | new OnClickListener() {
144 | @Override
145 | public void onClick(View view) {
146 | onCancelClicked();
147 | }
148 | }
149 | );
150 |
151 | findViewById(R.id.btSave)
152 | .setOnClickListener(
153 | new OnClickListener() {
154 | @Override
155 | public void onClick(View view) {
156 | onSaveClicked();
157 | }
158 | }
159 | );
160 |
161 | final GestureDetector gestureDetector = new
162 | GestureDetector(getContext(),
163 | new GestureDetector.SimpleOnGestureListener() {
164 | @Override
165 | public boolean onSingleTapConfirmed(MotionEvent e) {
166 | onClickVideoPlayPause();
167 | return true;
168 | }
169 | }
170 | );
171 |
172 | mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
173 | @Override
174 | public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
175 | if (mOnTrimVideoListener != null)
176 | mOnTrimVideoListener.onError("Something went wrong reason : " + what);
177 | return false;
178 | }
179 | });
180 |
181 | mVideoView.setOnTouchListener(new View.OnTouchListener() {
182 | @Override
183 | public boolean onTouch(View v, @NonNull MotionEvent event) {
184 | gestureDetector.onTouchEvent(event);
185 | return true;
186 | }
187 | });
188 |
189 | mRangeSeekBarView.addOnRangeSeekBarListener(new OnRangeSeekBarListener() {
190 | @Override
191 | public void onCreate(RangeSeekBarView rangeSeekBarView, int index, float value) {
192 | // Do nothing
193 | }
194 |
195 | @Override
196 | public void onSeek(RangeSeekBarView rangeSeekBarView, int index, float value) {
197 | onSeekThumbs(index, value);
198 | }
199 |
200 | @Override
201 | public void onSeekStart(RangeSeekBarView rangeSeekBarView, int index, float value) {
202 | // Do nothing
203 | }
204 |
205 | @Override
206 | public void onSeekStop(RangeSeekBarView rangeSeekBarView, int index, float value) {
207 | onStopSeekThumbs();
208 | }
209 | });
210 | mRangeSeekBarView.addOnRangeSeekBarListener(mVideoProgressIndicator);
211 |
212 | mHolderTopView.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
213 | @Override
214 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
215 | onPlayerIndicatorSeekChanged(progress, fromUser);
216 | }
217 |
218 | @Override
219 | public void onStartTrackingTouch(SeekBar seekBar) {
220 | onPlayerIndicatorSeekStart();
221 | }
222 |
223 | @Override
224 | public void onStopTrackingTouch(SeekBar seekBar) {
225 | onPlayerIndicatorSeekStop(seekBar);
226 | }
227 | });
228 |
229 | mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
230 | @Override
231 | public void onPrepared(MediaPlayer mp) {
232 | onVideoPrepared(mp);
233 | }
234 | });
235 |
236 | mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
237 | @Override
238 | public void onCompletion(MediaPlayer mp) {
239 | onVideoCompleted();
240 | }
241 | });
242 | }
243 |
244 | private void setUpMargins() {
245 | int marge = mRangeSeekBarView.getThumbs().get(0).getWidthBitmap();
246 | int widthSeek = mHolderTopView.getThumb().getMinimumWidth() / 2;
247 |
248 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mHolderTopView.getLayoutParams();
249 | lp.setMargins(marge - widthSeek, 0, marge - widthSeek, 0);
250 | mHolderTopView.setLayoutParams(lp);
251 |
252 | lp = (RelativeLayout.LayoutParams) mTimeLineView.getLayoutParams();
253 | lp.setMargins(marge, 0, marge, 0);
254 | mTimeLineView.setLayoutParams(lp);
255 |
256 | lp = (RelativeLayout.LayoutParams) mVideoProgressIndicator.getLayoutParams();
257 | lp.setMargins(marge, 0, marge, 0);
258 | mVideoProgressIndicator.setLayoutParams(lp);
259 | }
260 |
261 | private void onSaveClicked() {
262 | if (mStartPosition <= 0 && mEndPosition >= mDuration) {
263 | if (mOnTrimVideoListener != null)
264 | mOnTrimVideoListener.getResult(mSrc);
265 | } else {
266 | mPlayView.setVisibility(View.VISIBLE);
267 | mVideoView.pause();
268 |
269 | MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
270 | mediaMetadataRetriever.setDataSource(getContext(), mSrc);
271 | long METADATA_KEY_DURATION = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
272 |
273 | final File file = new File(mSrc.getPath());
274 |
275 | if (mTimeVideo < MIN_TIME_FRAME) {
276 |
277 | if ((METADATA_KEY_DURATION - mEndPosition) > (MIN_TIME_FRAME - mTimeVideo)) {
278 | mEndPosition += (MIN_TIME_FRAME - mTimeVideo);
279 | } else if (mStartPosition > (MIN_TIME_FRAME - mTimeVideo)) {
280 | mStartPosition -= (MIN_TIME_FRAME - mTimeVideo);
281 | }
282 | }
283 |
284 | //notify that video trimming started
285 | if (mOnTrimVideoListener != null)
286 | mOnTrimVideoListener.onTrimStarted();
287 |
288 | BackgroundExecutor.execute(
289 | new BackgroundExecutor.Task("", 0L, "") {
290 | @Override
291 | public void execute() {
292 | try {
293 | TrimVideoUtils.startTrim(file, getDestinationPath(), mStartPosition, mEndPosition, mOnTrimVideoListener);
294 | } catch (final Throwable e) {
295 | Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
296 | }
297 | }
298 | }
299 | );
300 | }
301 | }
302 |
303 | private void onClickVideoPlayPause() {
304 | if (mVideoView.isPlaying()) {
305 | mPlayView.setVisibility(View.VISIBLE);
306 | mMessageHandler.removeMessages(SHOW_PROGRESS);
307 | mVideoView.pause();
308 | } else {
309 | mPlayView.setVisibility(View.GONE);
310 |
311 | if (mResetSeekBar) {
312 | mResetSeekBar = false;
313 | mVideoView.seekTo(mStartPosition);
314 | }
315 |
316 | mMessageHandler.sendEmptyMessage(SHOW_PROGRESS);
317 | mVideoView.start();
318 | }
319 | }
320 |
321 | private void onCancelClicked() {
322 | mVideoView.stopPlayback();
323 | if (mOnTrimVideoListener != null) {
324 | mOnTrimVideoListener.cancelAction();
325 | }
326 | }
327 |
328 | private String getDestinationPath() {
329 | if (mFinalPath == null) {
330 | File folder = Environment.getExternalStorageDirectory();
331 | mFinalPath = folder.getPath() + File.separator;
332 | Log.d(TAG, "Using default path " + mFinalPath);
333 | }
334 | return mFinalPath;
335 | }
336 |
337 | private void onPlayerIndicatorSeekChanged(int progress, boolean fromUser) {
338 |
339 | int duration = (int) ((mDuration * progress) / 1000L);
340 |
341 | if (fromUser) {
342 | if (duration < mStartPosition) {
343 | setProgressBarPosition(mStartPosition);
344 | duration = mStartPosition;
345 | } else if (duration > mEndPosition) {
346 | setProgressBarPosition(mEndPosition);
347 | duration = mEndPosition;
348 | }
349 | setTimeVideo(duration);
350 | }
351 | }
352 |
353 | private void onPlayerIndicatorSeekStart() {
354 | mMessageHandler.removeMessages(SHOW_PROGRESS);
355 | mVideoView.pause();
356 | mPlayView.setVisibility(View.VISIBLE);
357 | notifyProgressUpdate(false);
358 | }
359 |
360 | private void onPlayerIndicatorSeekStop(@NonNull SeekBar seekBar) {
361 | mMessageHandler.removeMessages(SHOW_PROGRESS);
362 | mVideoView.pause();
363 | mPlayView.setVisibility(View.VISIBLE);
364 |
365 | int duration = (int) ((mDuration * seekBar.getProgress()) / 1000L);
366 | mVideoView.seekTo(duration);
367 | setTimeVideo(duration);
368 | notifyProgressUpdate(false);
369 | }
370 |
371 | private void onVideoPrepared(@NonNull MediaPlayer mp) {
372 | // Adjust the size of the video
373 | // so it fits on the screen
374 | int videoWidth = mp.getVideoWidth();
375 | int videoHeight = mp.getVideoHeight();
376 | float videoProportion = (float) videoWidth / (float) videoHeight;
377 | int screenWidth = mLinearVideo.getWidth();
378 | int screenHeight = mLinearVideo.getHeight();
379 | float screenProportion = (float) screenWidth / (float) screenHeight;
380 | ViewGroup.LayoutParams lp = mVideoView.getLayoutParams();
381 |
382 | if (videoProportion > screenProportion) {
383 | lp.width = screenWidth;
384 | lp.height = (int) ((float) screenWidth / videoProportion);
385 | } else {
386 | lp.width = (int) (videoProportion * (float) screenHeight);
387 | lp.height = screenHeight;
388 | }
389 | mVideoView.setLayoutParams(lp);
390 |
391 | mPlayView.setVisibility(View.VISIBLE);
392 |
393 | mDuration = mVideoView.getDuration();
394 | setSeekBarPosition();
395 |
396 | setTimeFrames();
397 | setTimeVideo(0);
398 |
399 | if (mOnK4LVideoListener != null) {
400 | mOnK4LVideoListener.onVideoPrepared();
401 | }
402 | }
403 |
404 | private void setSeekBarPosition() {
405 |
406 | if (mDuration >= mMaxDuration) {
407 | mStartPosition = mDuration / 2 - mMaxDuration / 2;
408 | mEndPosition = mDuration / 2 + mMaxDuration / 2;
409 |
410 | mRangeSeekBarView.setThumbValue(0, (mStartPosition * 100) / mDuration);
411 | mRangeSeekBarView.setThumbValue(1, (mEndPosition * 100) / mDuration);
412 |
413 | } else {
414 | mStartPosition = 0;
415 | mEndPosition = mDuration;
416 | }
417 |
418 | setProgressBarPosition(mStartPosition);
419 | mVideoView.seekTo(mStartPosition);
420 |
421 | mTimeVideo = mDuration;
422 | mRangeSeekBarView.initMaxWidth();
423 | }
424 |
425 | private void setTimeFrames() {
426 | String seconds = getContext().getString(R.string.short_seconds);
427 | mTextTimeFrame.setText(String.format("%s %s - %s %s", stringForTime(mStartPosition), seconds, stringForTime(mEndPosition), seconds));
428 | }
429 |
430 | private void setTimeVideo(int position) {
431 | String seconds = getContext().getString(R.string.short_seconds);
432 | mTextTime.setText(String.format("%s %s", stringForTime(position), seconds));
433 | }
434 |
435 | private void onSeekThumbs(int index, float value) {
436 | switch (index) {
437 | case Thumb.LEFT: {
438 | mStartPosition = (int) ((mDuration * value) / 100L);
439 | mVideoView.seekTo(mStartPosition);
440 | break;
441 | }
442 | case Thumb.RIGHT: {
443 | mEndPosition = (int) ((mDuration * value) / 100L);
444 | break;
445 | }
446 | }
447 | setProgressBarPosition(mStartPosition);
448 |
449 | setTimeFrames();
450 | mTimeVideo = mEndPosition - mStartPosition;
451 | }
452 |
453 | private void onStopSeekThumbs() {
454 | mMessageHandler.removeMessages(SHOW_PROGRESS);
455 | mVideoView.pause();
456 | mPlayView.setVisibility(View.VISIBLE);
457 | }
458 |
459 | private void onVideoCompleted() {
460 | mVideoView.seekTo(mStartPosition);
461 | }
462 |
463 | private void notifyProgressUpdate(boolean all) {
464 | if (mDuration == 0) return;
465 |
466 | int position = mVideoView.getCurrentPosition();
467 | if (all) {
468 | for (OnProgressVideoListener item : mListeners) {
469 | item.updateProgress(position, mDuration, ((position * 100) / mDuration));
470 | }
471 | } else {
472 | mListeners.get(1).updateProgress(position, mDuration, ((position * 100) / mDuration));
473 | }
474 | }
475 |
476 | private void updateVideoProgress(int time) {
477 | if (mVideoView == null) {
478 | return;
479 | }
480 |
481 | if (time >= mEndPosition) {
482 | mMessageHandler.removeMessages(SHOW_PROGRESS);
483 | mVideoView.pause();
484 | mPlayView.setVisibility(View.VISIBLE);
485 | mResetSeekBar = true;
486 | return;
487 | }
488 |
489 | if (mHolderTopView != null) {
490 | // use long to avoid overflow
491 | setProgressBarPosition(time);
492 | }
493 | setTimeVideo(time);
494 | }
495 |
496 | private void setProgressBarPosition(int position) {
497 | if (mDuration > 0) {
498 | long pos = 1000L * position / mDuration;
499 | mHolderTopView.setProgress((int) pos);
500 | }
501 | }
502 |
503 | /**
504 | * Set video information visibility.
505 | * For now this is for debugging
506 | *
507 | * @param visible whether or not the videoInformation will be visible
508 | */
509 | public void setVideoInformationVisibility(boolean visible) {
510 | mTimeInfoContainer.setVisibility(visible ? VISIBLE : GONE);
511 | }
512 |
513 | /**
514 | * Listener for events such as trimming operation success and cancel
515 | *
516 | * @param onTrimVideoListener interface for events
517 | */
518 | @SuppressWarnings("unused")
519 | public void setOnTrimVideoListener(OnTrimVideoListener onTrimVideoListener) {
520 | mOnTrimVideoListener = onTrimVideoListener;
521 | }
522 |
523 | /**
524 | * Listener for some {@link VideoView} events
525 | *
526 | * @param onK4LVideoListener interface for events
527 | */
528 | @SuppressWarnings("unused")
529 | public void setOnK4LVideoListener(OnK4LVideoListener onK4LVideoListener) {
530 | mOnK4LVideoListener = onK4LVideoListener;
531 | }
532 |
533 | /**
534 | * Sets the path where the trimmed video will be saved
535 | * Ex: /storage/emulated/0/MyAppFolder/
536 | *
537 | * @param finalPath the full path
538 | */
539 | @SuppressWarnings("unused")
540 | public void setDestinationPath(final String finalPath) {
541 | mFinalPath = finalPath;
542 | Log.d(TAG, "Setting custom path " + mFinalPath);
543 | }
544 |
545 | /**
546 | * Cancel all current operations
547 | */
548 | public void destroy() {
549 | BackgroundExecutor.cancelAll("", true);
550 | UiThreadExecutor.cancelAll("");
551 | }
552 |
553 | /**
554 | * Set the maximum duration of the trimmed video.
555 | * The trimmer interface wont allow the user to set duration longer than maxDuration
556 | *
557 | * @param maxDuration the maximum duration of the trimmed video in seconds
558 | */
559 | @SuppressWarnings("unused")
560 | public void setMaxDuration(int maxDuration) {
561 | mMaxDuration = maxDuration * 1000;
562 | }
563 |
564 | /**
565 | * Sets the uri of the video to be trimmer
566 | *
567 | * @param videoURI Uri of the video
568 | */
569 | @SuppressWarnings("unused")
570 | public void setVideoURI(final Uri videoURI) {
571 | mSrc = videoURI;
572 |
573 | if (mOriginSizeFile == 0) {
574 | File file = new File(mSrc.getPath());
575 |
576 | mOriginSizeFile = file.length();
577 | long fileSizeInKB = mOriginSizeFile / 1024;
578 |
579 | if (fileSizeInKB > 1000) {
580 | long fileSizeInMB = fileSizeInKB / 1024;
581 | mTextSize.setText(String.format("%s %s", fileSizeInMB, getContext().getString(R.string.megabyte)));
582 | } else {
583 | mTextSize.setText(String.format("%s %s", fileSizeInKB, getContext().getString(R.string.kilobyte)));
584 | }
585 | }
586 |
587 | mVideoView.setVideoURI(mSrc);
588 | mVideoView.requestFocus();
589 |
590 | mTimeLineView.setVideo(mSrc);
591 | }
592 |
593 | private static class MessageHandler extends Handler {
594 |
595 | @NonNull
596 | private final WeakReference mView;
597 |
598 | MessageHandler(K4LVideoTrimmer view) {
599 | mView = new WeakReference<>(view);
600 | }
601 |
602 | @Override
603 | public void handleMessage(Message msg) {
604 | K4LVideoTrimmer view = mView.get();
605 | if (view == null || view.mVideoView == null) {
606 | return;
607 | }
608 |
609 | view.notifyProgressUpdate(true);
610 | if (view.mVideoView.isPlaying()) {
611 | sendEmptyMessageDelayed(0, 10);
612 | }
613 | }
614 | }
615 | }
616 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/interfaces/OnK4LVideoListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer.interfaces;
25 |
26 | public interface OnK4LVideoListener {
27 |
28 | void onVideoPrepared();
29 | }
30 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/interfaces/OnProgressVideoListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer.interfaces;
25 |
26 | public interface OnProgressVideoListener {
27 |
28 | void updateProgress(int time, int max, float scale);
29 | }
30 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/interfaces/OnRangeSeekBarListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer.interfaces;
25 |
26 | import life.knowledge4.videotrimmer.view.RangeSeekBarView;
27 |
28 | public interface OnRangeSeekBarListener {
29 | void onCreate(RangeSeekBarView rangeSeekBarView, int index, float value);
30 |
31 | void onSeek(RangeSeekBarView rangeSeekBarView, int index, float value);
32 |
33 | void onSeekStart(RangeSeekBarView rangeSeekBarView, int index, float value);
34 |
35 | void onSeekStop(RangeSeekBarView rangeSeekBarView, int index, float value);
36 | }
37 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/interfaces/OnTrimVideoListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer.interfaces;
25 |
26 | import android.net.Uri;
27 |
28 | public interface OnTrimVideoListener {
29 |
30 | void onTrimStarted();
31 |
32 | void getResult(final Uri uri);
33 |
34 | void cancelAction();
35 |
36 | void onError(final String message);
37 | }
38 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/utils/BackgroundExecutor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
10 | * Unless required by applicable law or agreed To in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 | package life.knowledge4.videotrimmer.utils;
17 |
18 | import android.util.Log;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import java.util.concurrent.Executor;
23 | import java.util.concurrent.ExecutorService;
24 | import java.util.concurrent.Executors;
25 | import java.util.concurrent.Future;
26 | import java.util.concurrent.ScheduledExecutorService;
27 | import java.util.concurrent.TimeUnit;
28 | import java.util.concurrent.atomic.AtomicBoolean;
29 |
30 | public final class BackgroundExecutor {
31 |
32 | private static final String TAG = "BackgroundExecutor";
33 |
34 | public static final Executor DEFAULT_EXECUTOR = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
35 | private static Executor executor = DEFAULT_EXECUTOR;
36 | private static final List TASKS = new ArrayList<>();
37 | private static final ThreadLocal CURRENT_SERIAL = new ThreadLocal<>();
38 |
39 | private BackgroundExecutor() {
40 | }
41 |
42 | /**
43 | * Execute a runnable after the given delay.
44 | *
45 | * @param runnable the task to execute
46 | * @param delay the time from now to delay execution, in milliseconds
47 | *
48 | * if delay is strictly positive and the current
49 | * executor does not support scheduling (if
50 | * Executor has been called with such an
51 | * executor)
52 | * @return Future associated to the running task
53 | * @throws IllegalArgumentException if the current executor set by Executor
54 | * does not support scheduling
55 | */
56 | private static Future> directExecute(Runnable runnable, long delay) {
57 | Future> future = null;
58 | if (delay > 0) {
59 | /* no serial, but a delay: schedule the task */
60 | if (!(executor instanceof ScheduledExecutorService)) {
61 | throw new IllegalArgumentException("The executor set does not support scheduling");
62 | }
63 | ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executor;
64 | future = scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS);
65 | } else {
66 | if (executor instanceof ExecutorService) {
67 | ExecutorService executorService = (ExecutorService) executor;
68 | future = executorService.submit(runnable);
69 | } else {
70 | /* non-cancellable task */
71 | executor.execute(runnable);
72 | }
73 | }
74 | return future;
75 | }
76 |
77 | /**
78 | * Execute a task after (at least) its delay and after all
79 | * tasks added with the same non-null serial (if any) have
80 | * completed execution.
81 | *
82 | * @param task the task to execute
83 | * @throws IllegalArgumentException if task.delay is strictly positive and the
84 | * current executor does not support scheduling (if
85 | * Executor has been called with such an
86 | * executor)
87 | */
88 | public static synchronized void execute(Task task) {
89 | Future> future = null;
90 | if (task.serial == null || !hasSerialRunning(task.serial)) {
91 | task.executionAsked = true;
92 | future = directExecute(task, task.remainingDelay);
93 | }
94 | if ((task.id != null || task.serial != null) && !task.managed.get()) {
95 | /* keep task */
96 | task.future = future;
97 | TASKS.add(task);
98 | }
99 | }
100 |
101 | /**
102 | * Indicates whether a task with the specified serial has been
103 | * submitted to the executor.
104 | *
105 | * @param serial the serial queue
106 | * @return true if such a task has been submitted,
107 | * false otherwise
108 | */
109 | private static boolean hasSerialRunning(String serial) {
110 | for (Task task : TASKS) {
111 | if (task.executionAsked && serial.equals(task.serial)) {
112 | return true;
113 | }
114 | }
115 | return false;
116 | }
117 |
118 | /**
119 | * Retrieve and remove the first task having the specified
120 | * serial (if any).
121 | *
122 | * @param serial the serial queue
123 | * @return task if found, null otherwise
124 | */
125 | private static Task take(String serial) {
126 | int len = TASKS.size();
127 | for (int i = 0; i < len; i++) {
128 | if (serial.equals(TASKS.get(i).serial)) {
129 | return TASKS.remove(i);
130 | }
131 | }
132 | return null;
133 | }
134 |
135 | /**
136 | * Cancel all tasks having the specified id.
137 | *
138 | * @param id the cancellation identifier
139 | * @param mayInterruptIfRunning true if the thread executing this task should be
140 | * interrupted; otherwise, in-progress tasks are allowed to
141 | * complete
142 | */
143 | public static synchronized void cancelAll(String id, boolean mayInterruptIfRunning) {
144 | for (int i = TASKS.size() - 1; i >= 0; i--) {
145 | Task task = TASKS.get(i);
146 | if (id.equals(task.id)) {
147 | if (task.future != null) {
148 | task.future.cancel(mayInterruptIfRunning);
149 | if (!task.managed.getAndSet(true)) {
150 | /*
151 | * the task has been submitted to the executor, but its
152 | * execution has not started yet, so that its run()
153 | * method will never call postExecute()
154 | */
155 | task.postExecute();
156 | }
157 | } else if (task.executionAsked) {
158 | Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)");
159 | } else {
160 | /* this task has not been submitted to the executor */
161 | TASKS.remove(i);
162 | }
163 | }
164 | }
165 | }
166 |
167 | public static abstract class Task implements Runnable {
168 |
169 | private String id;
170 | private long remainingDelay;
171 | private long targetTimeMillis; /* since epoch */
172 | private String serial;
173 | private boolean executionAsked;
174 | private Future> future;
175 |
176 | /*
177 | * A task can be cancelled after it has been submitted to the executor
178 | * but before its run() method is called. In that case, run() will never
179 | * be called, hence neither will postExecute(): the tasks with the same
180 | * serial identifier (if any) will never be submitted.
181 | *
182 | * Therefore, cancelAll() *must* call postExecute() if run() is not
183 | * started.
184 | *
185 | * This flag guarantees that either cancelAll() or run() manages this
186 | * task post execution, but not both.
187 | */
188 | private AtomicBoolean managed = new AtomicBoolean();
189 |
190 | public Task(String id, long delay, String serial) {
191 | if (!"".equals(id)) {
192 | this.id = id;
193 | }
194 | if (delay > 0) {
195 | remainingDelay = delay;
196 | targetTimeMillis = System.currentTimeMillis() + delay;
197 | }
198 | if (!"".equals(serial)) {
199 | this.serial = serial;
200 | }
201 | }
202 |
203 | @Override
204 | public void run() {
205 | if (managed.getAndSet(true)) {
206 | /* cancelled and postExecute() already called */
207 | return;
208 | }
209 |
210 | try {
211 | CURRENT_SERIAL.set(serial);
212 | execute();
213 | } finally {
214 | /* handle next tasks */
215 | postExecute();
216 | }
217 | }
218 |
219 | public abstract void execute();
220 |
221 | private void postExecute() {
222 | if (id == null && serial == null) {
223 | /* nothing to do */
224 | return;
225 | }
226 | CURRENT_SERIAL.set(null);
227 | synchronized (BackgroundExecutor.class) {
228 | /* execution complete */
229 | TASKS.remove(this);
230 |
231 | if (serial != null) {
232 | Task next = take(serial);
233 | if (next != null) {
234 | if (next.remainingDelay != 0) {
235 | /* the delay may not have elapsed yet */
236 | next.remainingDelay = Math.max(0L, targetTimeMillis - System.currentTimeMillis());
237 | }
238 | /* a task having the same serial was queued, execute it */
239 | BackgroundExecutor.execute(next);
240 | }
241 | }
242 | }
243 | }
244 | }
245 | }
246 |
247 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer.utils;
25 |
26 | import android.annotation.SuppressLint;
27 | import android.content.ContentUris;
28 | import android.content.Context;
29 | import android.database.Cursor;
30 | import android.net.Uri;
31 | import android.os.Build;
32 | import android.os.Environment;
33 | import android.provider.DocumentsContract;
34 | import android.provider.MediaStore;
35 | import android.support.annotation.NonNull;
36 |
37 | public class FileUtils {
38 |
39 | /**
40 | * Get a file path from a Uri. This will get the the path for Storage Access
41 | * Framework Documents, as well as the _data field for the MediaStore and
42 | * other file-based ContentProviders.
43 | *
44 | * Callers should check whether the path is local before assuming it
45 | * represents a local file.
46 | *
47 | * @param context The context.
48 | * @param uri The Uri to query.
49 | * @author paulburke
50 | */
51 | @SuppressLint("NewApi")
52 | public static String getPath(final Context context, final Uri uri) {
53 |
54 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
55 |
56 | // DocumentProvider
57 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
58 | if (isExternalStorageDocument(uri)) {
59 | final String docId = DocumentsContract.getDocumentId(uri);
60 | final String[] split = docId.split(":");
61 | final String type = split[0];
62 |
63 | if ("primary".equalsIgnoreCase(type)) {
64 | return Environment.getExternalStorageDirectory() + "/" + split[1];
65 | }
66 |
67 | // TODO handle non-primary volumes
68 | }
69 | // DownloadsProvider
70 | else if (isDownloadsDocument(uri)) {
71 |
72 | final String id = DocumentsContract.getDocumentId(uri);
73 | final Uri contentUri = ContentUris.withAppendedId(
74 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
75 |
76 | return getDataColumn(context, contentUri, null, null);
77 | }
78 | // MediaProvider
79 | else if (isMediaDocument(uri)) {
80 | final String docId = DocumentsContract.getDocumentId(uri);
81 | final String[] split = docId.split(":");
82 | final String type = split[0];
83 |
84 | Uri contentUri = null;
85 | if ("image".equals(type)) {
86 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
87 | } else if ("video".equals(type)) {
88 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
89 | } else if ("audio".equals(type)) {
90 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
91 | }
92 |
93 | final String selection = "_id=?";
94 | final String[] selectionArgs = new String[]{
95 | split[1]
96 | };
97 |
98 | return getDataColumn(context, contentUri, selection, selectionArgs);
99 | }
100 | }
101 | // MediaStore (and general)
102 | else if ("content".equalsIgnoreCase(uri.getScheme())) {
103 |
104 | // Return the remote address
105 | if (isGooglePhotosUri(uri))
106 | return uri.getLastPathSegment();
107 |
108 | return getDataColumn(context, uri, null, null);
109 | }
110 | // File
111 | else if ("file".equalsIgnoreCase(uri.getScheme())) {
112 | return uri.getPath();
113 | }
114 |
115 | return null;
116 | }
117 |
118 | /**
119 | * Get the value of the data column for this Uri. This is useful for
120 | * MediaStore Uris, and other file-based ContentProviders.
121 | *
122 | * @param context The context.
123 | * @param uri The Uri to query.
124 | * @param selection (Optional) Filter used in the query.
125 | * @param selectionArgs (Optional) Selection arguments used in the query.
126 | * @return The value of the _data column, which is typically a file path.
127 | * @author paulburke
128 | */
129 | private static String getDataColumn(@NonNull Context context, Uri uri, String selection, String[] selectionArgs) {
130 |
131 | Cursor cursor = null;
132 | final String column = "_data";
133 | final String[] projection = {column};
134 |
135 | try {
136 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
137 | if (cursor != null && cursor.moveToFirst()) {
138 | final int column_index = cursor.getColumnIndexOrThrow(column);
139 | return cursor.getString(column_index);
140 | }
141 | } finally {
142 | if (cursor != null)
143 | cursor.close();
144 | }
145 | return null;
146 | }
147 |
148 | /**
149 | * @param uri The Uri to check.
150 | * @return Whether the Uri authority is Google Photos.
151 | */
152 | private static boolean isGooglePhotosUri(Uri uri) {
153 | return "com.google.android.apps.photos.content".equals(uri.getAuthority());
154 | }
155 |
156 | /**
157 | * @param uri The Uri to check.
158 | * @return Whether the Uri authority is ExternalStorageProvider.
159 | */
160 | private static boolean isExternalStorageDocument(@NonNull Uri uri) {
161 | return "com.android.externalstorage.documents".equals(uri.getAuthority());
162 | }
163 |
164 | /**
165 | * @param uri The Uri to check.
166 | * @return Whether the Uri authority is DownloadsProvider.
167 | */
168 | private static boolean isDownloadsDocument(@NonNull Uri uri) {
169 | return "com.android.providers.downloads.documents".equals(uri.getAuthority());
170 | }
171 |
172 | /**
173 | * @param uri The Uri to check.
174 | * @return Whether the Uri authority is MediaProvider.
175 | */
176 | private static boolean isMediaDocument(@NonNull Uri uri) {
177 | return "com.android.providers.media.documents".equals(uri.getAuthority());
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/k4l-video-trimmer/src/main/java/life/knowledge4/videotrimmer/utils/TrimVideoUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2016 Knowledge, education for life.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package life.knowledge4.videotrimmer.utils;
25 |
26 | import android.net.Uri;
27 | import android.support.annotation.NonNull;
28 | import android.util.Log;
29 |
30 | import com.coremedia.iso.boxes.Container;
31 | import com.googlecode.mp4parser.FileDataSourceViaHeapImpl;
32 | import com.googlecode.mp4parser.authoring.Movie;
33 | import com.googlecode.mp4parser.authoring.Track;
34 | import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
35 | import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
36 | import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
37 | import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
38 |
39 | import java.io.File;
40 | import java.io.FileOutputStream;
41 | import java.io.IOException;
42 | import java.nio.channels.FileChannel;
43 | import java.text.SimpleDateFormat;
44 | import java.util.Arrays;
45 | import java.util.Date;
46 | import java.util.Formatter;
47 | import java.util.LinkedList;
48 | import java.util.List;
49 | import java.util.Locale;
50 |
51 | import life.knowledge4.videotrimmer.interfaces.OnTrimVideoListener;
52 |
53 | public class TrimVideoUtils {
54 |
55 | private static final String TAG = TrimVideoUtils.class.getSimpleName();
56 |
57 | public static void startTrim(@NonNull File src, @NonNull String dst, long startMs, long endMs, @NonNull OnTrimVideoListener callback) throws IOException {
58 | final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
59 | final String fileName = "MP4_" + timeStamp + ".mp4";
60 | final String filePath = dst + fileName;
61 |
62 | File file = new File(filePath);
63 | file.getParentFile().mkdirs();
64 | Log.d(TAG, "Generated file path " + filePath);
65 | genVideoUsingMp4Parser(src, file, startMs, endMs, callback);
66 | }
67 |
68 | private static void genVideoUsingMp4Parser(@NonNull File src, @NonNull File dst, long startMs, long endMs, @NonNull OnTrimVideoListener callback) throws IOException {
69 | // NOTE: Switched to using FileDataSourceViaHeapImpl since it does not use memory mapping (VM).
70 | // Otherwise we get OOM with large movie files.
71 | Movie movie = MovieCreator.build(new FileDataSourceViaHeapImpl(src.getAbsolutePath()));
72 |
73 | List