├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── README.md
├── VideoTrimmer.iml
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── es
│ │ └── anthorlop
│ │ └── videotrimmer
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── es
│ │ └── anthorlop
│ │ └── videotrimmer
│ │ ├── MainActivity.java
│ │ ├── VideoRecorderedPlayerActivity.java
│ │ ├── custom
│ │ ├── RangeSeekBar.java
│ │ └── VideoControllerView.java
│ │ └── utils
│ │ └── Screens.java
│ └── res
│ ├── drawable
│ ├── ic_camara_menu_switch.png
│ ├── ic_flash_off_white.png
│ ├── ic_flash_on_white.png
│ ├── ic_media_fullscreen_shrink.png
│ ├── ic_media_fullscreen_stretch.png
│ ├── ic_media_pause.png
│ ├── ic_media_play.png
│ ├── ic_player_big_pause.png
│ ├── ic_player_big_play.png
│ └── seek_thumb.png
│ ├── layout
│ ├── activity_main.xml
│ ├── activity_video_player.xml
│ └── media_controller.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screen_landscape.png
├── screen_portrait.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | VideoTrimmer
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VideoTrimmer
2 |
3 | Select, preview and edit a video file
4 |
5 | Apache License 2.0
6 | Native Android app for edit video files
7 |
8 | IDE: Latest version of Android Studio 1.2.2 for MAC OS X
9 | Other tools:
10 | Adobe Illustrator
11 | Charles web debugging proxy
12 |
13 |
14 | Devices tested:
15 | Sony Xperia Z2
16 | Samsung Galaxy S4
17 | Samsung Galaxy S3
18 | Nexus 10
19 | Nexus 7
20 |
21 |
22 | Warnings: 0
23 | Fragmentation: minSDKversion = 14 (Android 4.0 ICE_CREAM_SANDWICH), so can run in around 99.5 % of existing Android devices, like the Android documentation show (https://developer.android.com/about/dashboards/index.html)
24 |
25 |
26 |
27 |
28 | Screenshots
29 | Mobile portrait
30 |
31 |
32 |
33 | Mobile landscape
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/VideoTrimmer.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
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 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "es.anthorlop.videotrimmer"
9 | minSdkVersion 14
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 |
25 | compile 'com.android.support:appcompat-v7:22.2.0'
26 |
27 | compile 'com.jakewharton:butterknife:6.1.0'
28 |
29 | compile 'com.googlecode.mp4parser:isoparser:1.+'
30 |
31 | compile 'org.apache.commons:commons-io:1.3.2'
32 | }
33 |
--------------------------------------------------------------------------------
/app/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/antoniohormigo/Library/Android/sdk/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/es/anthorlop/videotrimmer/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package es.anthorlop.videotrimmer;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/es/anthorlop/videotrimmer/MainActivity.java:
--------------------------------------------------------------------------------
1 | package es.anthorlop.videotrimmer;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.database.Cursor;
7 | import android.graphics.Bitmap;
8 | import android.graphics.BitmapFactory;
9 | import android.net.Uri;
10 | import android.os.Environment;
11 | import android.os.Bundle;
12 | import android.provider.MediaStore;
13 | import android.util.Log;
14 | import android.view.Menu;
15 | import android.view.MenuItem;
16 | import android.view.View;
17 | import android.widget.Button;
18 | import android.widget.Toast;
19 |
20 | import org.apache.commons.io.FileUtils;
21 |
22 | import java.io.File;
23 | import java.io.FileNotFoundException;
24 | import java.io.FileOutputStream;
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.util.Date;
28 |
29 | import butterknife.ButterKnife;
30 | import butterknife.InjectView;
31 |
32 |
33 | public class MainActivity extends Activity {
34 |
35 | @InjectView(R.id.buttonSelect)
36 | Button buttonSelect;
37 |
38 | private static final int SELECT_PHOTO = 567843;
39 |
40 | private static final int VIDEO_RECORDERED = 452684;
41 |
42 | private String url_file;
43 |
44 | @Override
45 | protected void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 | setContentView(R.layout.activity_main);
48 |
49 | ButterKnife.inject(this);
50 |
51 | buttonSelect.setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | openGalleryToSendFiles(v);
55 | }
56 | });
57 |
58 | }
59 |
60 | private void openGalleryToSendFiles(View view) {
61 | // create example file to save to OneDrive
62 | Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
63 | photoPickerIntent.setType("video/*");
64 | startActivityForResult(photoPickerIntent, SELECT_PHOTO);
65 | }
66 |
67 | @Override
68 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
69 |
70 | if (requestCode == SELECT_PHOTO) {
71 | if (resultCode == RESULT_OK) {
72 |
73 | Uri selectedImage = data.getData();
74 |
75 | String filepath = getRealPathFromURI(selectedImage, this);
76 |
77 | if (filepath != null) {
78 |
79 | File file = new File(Environment.getExternalStorageDirectory().getPath() + "/anthorlopCamera");
80 | if (!file.exists()) {
81 | file.mkdirs();
82 | }
83 |
84 | Date d = new Date();
85 | String timestamp = String.valueOf(d.getTime());
86 |
87 | url_file = Environment.getExternalStorageDirectory().getPath() + "/anthorlopCamera/preview_" + timestamp + ".mp4";
88 |
89 | File original = new File(filepath);
90 | File destino = new File(url_file);
91 |
92 | try {
93 | FileUtils.copyFile(original, destino);
94 | } catch (IOException e) {
95 | e.printStackTrace();
96 | }
97 |
98 | goToPreviewVideo();
99 | } else {
100 | Toast.makeText(MainActivity.this, "Se ha producido en error", Toast.LENGTH_LONG).show();
101 | }
102 | }
103 |
104 | } else if (requestCode == VIDEO_RECORDERED) {
105 | if (resultCode == RESULT_OK) {
106 |
107 | String path = data.getExtras().getString(VideoRecorderedPlayerActivity.VIDEO_PATH);
108 |
109 |
110 | Toast.makeText(MainActivity.this, "El vídeo se ha editado correctamente '" + path + "'", Toast.LENGTH_LONG).show();
111 |
112 | } else if (resultCode == RESULT_CANCELED && data != null && data.getExtras() != null
113 | && data.getExtras().containsKey(VideoRecorderedPlayerActivity.VIDEO_PATH)) {
114 |
115 | // el video ha sido editado y se vuelve a mostrar
116 |
117 | String path = data.getExtras().getString(VideoRecorderedPlayerActivity.VIDEO_PATH);
118 |
119 | url_file = path.replace("_edited.mp4", ".mp4");
120 |
121 | // eliminamos el video inicial
122 | File orig = new File(url_file);
123 | if (orig.exists() && orig.isFile()) {
124 | orig.delete();
125 | }
126 |
127 | // cambiamos el nombre del video editado por el inicial
128 | File nuevo = new File(path);
129 | nuevo.renameTo(new File(path.replace("_edited.mp4", ".mp4")));
130 |
131 | goToPreviewVideo();
132 | } else if (resultCode == RESULT_CANCELED) {
133 |
134 | Toast.makeText(MainActivity.this, "Se ha cancelado la edición del video", Toast.LENGTH_LONG).show();
135 |
136 | }
137 | }
138 | super.onActivityResult(requestCode, resultCode, data);
139 | }
140 |
141 | private void goToPreviewVideo() {
142 | Intent intent = new Intent(this, VideoRecorderedPlayerActivity.class);
143 | Bundle extras = new Bundle();
144 | extras.putString(VideoRecorderedPlayerActivity.VIDEO_PATH, url_file);
145 | intent.putExtras(extras);
146 | startActivityForResult(intent, VIDEO_RECORDERED);
147 | //finish();
148 | }
149 |
150 | @Override
151 | public boolean onCreateOptionsMenu(Menu menu) {
152 | // Inflate the menu; this adds items to the action bar if it is present.
153 | getMenuInflater().inflate(R.menu.menu_main, menu);
154 | return true;
155 | }
156 |
157 | @Override
158 | public boolean onOptionsItemSelected(MenuItem item) {
159 | // Handle action bar item clicks here. The action bar will
160 | // automatically handle clicks on the Home/Up button, so long
161 | // as you specify a parent activity in AndroidManifest.xml.
162 | int id = item.getItemId();
163 |
164 | //noinspection SimplifiableIfStatement
165 | if (id == R.id.action_settings) {
166 | return true;
167 | }
168 |
169 | return super.onOptionsItemSelected(item);
170 | }
171 |
172 | @Override
173 | protected void onDestroy() {
174 | super.onDestroy();
175 | ButterKnife.reset(this);
176 | }
177 |
178 | public String getRealPathFromURI(Uri contentUri, Context context) {
179 | String videoPath = contentUri.getPath();
180 | try{
181 | String[] proj = { MediaStore.Video.Media.DATA };
182 | //Cursor cursor = activity.managedQuery(contentUri, proj, null, null,
183 | //null);
184 | Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
185 | Log.i("cursor", "upload-->" + cursor);
186 | Log.i("contentUri", "upload-->" + contentUri);
187 | Log.i("proj", "upload-->" + proj);
188 | int position=0;
189 |
190 | if (cursor !=null && cursor.moveToPosition(position)) {
191 | int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
192 |
193 | Log.i("column_index", "Upload-->" + column_index);
194 | videoPath = cursor.getString(column_index); //I got a null pointer exception here.(But cursor hreturns saome value)
195 | Log.i("videoPath", "Upload-->" + videoPath);
196 | cursor.close();
197 |
198 | }
199 |
200 | } catch(Exception e){
201 | return contentUri.getPath();
202 | }
203 |
204 | return videoPath;
205 |
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/app/src/main/java/es/anthorlop/videotrimmer/VideoRecorderedPlayerActivity.java:
--------------------------------------------------------------------------------
1 | package es.anthorlop.videotrimmer;
2 |
3 | import android.app.Activity;
4 | import android.app.ProgressDialog;
5 | import android.content.Intent;
6 | import android.content.pm.ActivityInfo;
7 | import android.media.AudioManager;
8 | import android.media.MediaPlayer;
9 | import android.net.Uri;
10 | import android.os.AsyncTask;
11 | import android.os.Bundle;
12 | import android.util.DisplayMetrics;
13 | import android.util.Log;
14 | import android.view.SurfaceHolder;
15 | import android.view.SurfaceView;
16 | import android.view.View;
17 | import android.view.Window;
18 | import android.view.WindowManager;
19 | import android.widget.Button;
20 | import android.widget.FrameLayout;
21 | import android.widget.RelativeLayout;
22 | import android.widget.Toast;
23 |
24 | import com.coremedia.iso.boxes.Container;
25 | import com.googlecode.mp4parser.FileDataSourceImpl;
26 | import com.googlecode.mp4parser.authoring.Movie;
27 | import com.googlecode.mp4parser.authoring.Track;
28 | import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
29 | import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
30 | import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
31 |
32 | import java.io.File;
33 | import java.io.FileNotFoundException;
34 | import java.io.FileOutputStream;
35 | import java.io.IOException;
36 | import java.nio.channels.WritableByteChannel;
37 | import java.util.LinkedList;
38 | import java.util.List;
39 |
40 | import butterknife.ButterKnife;
41 | import butterknife.InjectView;
42 | import es.anthorlop.videotrimmer.custom.RangeSeekBar;
43 | import es.anthorlop.videotrimmer.custom.VideoControllerView;
44 |
45 | public class VideoRecorderedPlayerActivity extends Activity
46 | implements SurfaceHolder.Callback, MediaPlayer.OnPreparedListener, VideoControllerView.MediaPlayerControl {
47 |
48 | public static String TAG = "VideoRecorderedPlayerActivity";
49 |
50 | public static String VIDEO_PATH = "VideoPath";
51 |
52 | private MediaPlayer player;
53 | private VideoControllerView controller;
54 |
55 | RangeSeekBar seekBar;
56 |
57 | @InjectView(R.id.videoSurfaceContainer)
58 | FrameLayout videoSurfaceContainer;
59 |
60 | @InjectView(R.id.videoSurface)
61 | SurfaceView videoSurface;
62 |
63 | @InjectView(R.id.cancelButton)
64 | Button cancelButton;
65 |
66 | @InjectView(R.id.continueButton)
67 | Button continueButton;
68 |
69 | @InjectView(R.id.rangeLayout)
70 | RelativeLayout rangeLayout;
71 |
72 | private ProgressDialog mProgressDialog;
73 |
74 | private String path;
75 | private String finalPath;
76 |
77 | public void onCreate(Bundle icicle) {
78 | super.onCreate(icicle);
79 |
80 | requestWindowFeature(Window.FEATURE_NO_TITLE);
81 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
82 | WindowManager.LayoutParams.FLAG_FULLSCREEN);
83 |
84 | setContentView(R.layout.activity_video_player);
85 |
86 | ButterKnife.inject(this);
87 |
88 | if (getIntent().getExtras() != null) {
89 | path = getIntent().getExtras().getString(VIDEO_PATH);
90 | init();
91 | }
92 |
93 |
94 |
95 | cancelButton.setOnClickListener(new View.OnClickListener() {
96 | @Override
97 | public void onClick(View v) {
98 | deleteVideo();
99 | finish();
100 | }
101 | });
102 |
103 | continueButton.setOnClickListener(new View.OnClickListener() {
104 | @Override
105 | public void onClick(View v) {
106 |
107 | // comprobamos si se debe editar el video
108 | if (controller.getMinPosition() > 0 || controller.getMaxPosition() < getDuration()) {
109 |
110 | mProgressDialog = new ProgressDialog(VideoRecorderedPlayerActivity.this);
111 | mProgressDialog.setMessage("Preparando vídeo ...");
112 | mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
113 | mProgressDialog.setCancelable(false);
114 | mProgressDialog.show();
115 |
116 | new PostTrimAsynkTask(path).execute("");
117 |
118 | } else {
119 |
120 | goBackAndSendVideo();
121 |
122 | }
123 | }
124 | });
125 |
126 | videoSurfaceContainer.setOnClickListener(new View.OnClickListener() {
127 | @Override
128 | public void onClick(View v) {
129 | controller.onclick();
130 | }
131 | });
132 | }
133 |
134 | private void goBackAndReopenVideo() {
135 |
136 | Intent resultIntent = new Intent();
137 | Bundle bundle = new Bundle();
138 | bundle.putString(VIDEO_PATH, finalPath);
139 | resultIntent.putExtras(bundle);
140 | setResult(RESULT_CANCELED, resultIntent);
141 | VideoRecorderedPlayerActivity.this.finish();
142 |
143 | }
144 |
145 | private void goBackAndSendVideo() {
146 |
147 | Intent resultIntent = new Intent();
148 | Bundle bundle = new Bundle();
149 | bundle.putString(VIDEO_PATH, path);
150 | resultIntent.putExtras(bundle);
151 | setResult(RESULT_OK, resultIntent);
152 | VideoRecorderedPlayerActivity.this.finish();
153 |
154 | }
155 |
156 | public void onPause() {
157 | super.onPause();
158 | }
159 |
160 | private void init() {
161 | SurfaceHolder videoHolder = videoSurface.getHolder();
162 | videoHolder.addCallback(this);
163 |
164 | player = new MediaPlayer();
165 | controller = new VideoControllerView(this);
166 |
167 | try {
168 | player.setAudioStreamType(AudioManager.STREAM_MUSIC);
169 | player.setDataSource(this, Uri.parse(path));
170 | player.setOnPreparedListener(this);
171 | } catch (IllegalArgumentException e) {
172 | e.printStackTrace();
173 | } catch (SecurityException e) {
174 | e.printStackTrace();
175 | } catch (IllegalStateException e) {
176 | e.printStackTrace();
177 | } catch (IOException e) {
178 | e.printStackTrace();
179 | }
180 |
181 | }
182 |
183 | @Override
184 | public void surfaceCreated(SurfaceHolder holder) {
185 |
186 | try {
187 | player.setDisplay(holder);
188 | player.prepareAsync();
189 | } catch (IllegalStateException e) {
190 | e.printStackTrace();
191 | }
192 | }
193 |
194 | @Override
195 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
196 |
197 | }
198 |
199 | @Override
200 | public void surfaceDestroyed(SurfaceHolder holder) {
201 | /*player.reset();
202 | player.release();*/
203 | }
204 |
205 | // Implement MediaPlayer.OnPreparedListener
206 | @Override
207 | public void onPrepared(MediaPlayer mp) {
208 | controller.setMediaPlayer(this);
209 |
210 | controller.setMinPosition(0);
211 | controller.setMaxPosition(getDuration());
212 |
213 | seekTo(1);
214 |
215 | seekBar = new RangeSeekBar(0, getDuration(), this);
216 | seekBar.setOnRangeSeekBarChangeListener(new RangeSeekBar.OnRangeSeekBarChangeListener() {
217 | @Override
218 | public void onRangeSeekBarValuesChanged(RangeSeekBar> bar, Integer minValue, Integer maxValue) {
219 | controller.setMinPosition(minValue);
220 | controller.setMaxPosition(maxValue);
221 |
222 | if (getCurrentPosition() < minValue) {
223 | seekTo(minValue);
224 | }
225 |
226 | if (getCurrentPosition() > maxValue) {
227 | seekTo(maxValue);
228 | }
229 |
230 | controller.show();
231 | }
232 | });
233 |
234 | rangeLayout.addView(seekBar);
235 |
236 | if (player.getVideoWidth() > player.getVideoHeight()) {
237 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
238 | } else {
239 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
240 | }
241 |
242 | controller.setAnchorView(videoSurfaceContainer, player.getVideoWidth(), player.getVideoHeight());
243 |
244 | controller.show();
245 |
246 | //player.start();
247 | }
248 | // End MediaPlayer.OnPreparedListener
249 |
250 | // Implement VideoMediaController.MediaPlayerControl
251 | @Override
252 | public boolean canPause() {
253 | return true;
254 | }
255 |
256 | @Override
257 | public boolean canSeekBackward() {
258 | return true;
259 | }
260 |
261 | @Override
262 | public boolean canSeekForward() {
263 | return true;
264 | }
265 |
266 | @Override
267 | public int getBufferPercentage() {
268 | return 0;
269 | }
270 |
271 | @Override
272 | public int getCurrentPosition() {
273 | return player.getCurrentPosition();
274 | }
275 |
276 | @Override
277 | public int getDuration() {
278 | return player.getDuration();
279 | }
280 |
281 | @Override
282 | public boolean isPlaying() {
283 | return player.isPlaying();
284 | }
285 |
286 | @Override
287 | public void pause() {
288 | player.pause();
289 | }
290 |
291 | @Override
292 | public void seekTo(int i) {
293 | player.seekTo(i);
294 | }
295 |
296 | @Override
297 | public void start() {
298 | player.start();
299 | }
300 |
301 | @Override
302 | public boolean isFullScreen() {
303 | return false;
304 | }
305 |
306 | @Override
307 | public void toggleFullScreen() {
308 | //обрабатываем нажатие на кнопку увеличения видео в весь экран
309 | DisplayMetrics metrics = new DisplayMetrics();
310 | getWindowManager().getDefaultDisplay().getMetrics(metrics);
311 | RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) videoSurfaceContainer .getLayoutParams();
312 | params.width = metrics.widthPixels;
313 | params.height = metrics.heightPixels;
314 | params.leftMargin = 0;
315 | videoSurfaceContainer.setLayoutParams(params);
316 | }
317 | // End VideoMediaController.MediaPlayerControl
318 |
319 | @Override
320 | protected void onDestroy() {
321 | super.onDestroy();
322 | ButterKnife.reset(this);
323 |
324 | if (mProgressDialog != null && mProgressDialog.isShowing()) {
325 | mProgressDialog.dismiss();
326 | }
327 |
328 | }
329 |
330 | private FileDataSourceImpl file = null;
331 | private FileOutputStream fos = null;
332 | private WritableByteChannel fc = null;
333 |
334 | private void trimVideo(String path, String finalPath, double startTime, double endTime) {
335 |
336 | Movie movie = null;
337 | try {
338 | file = new FileDataSourceImpl(path);
339 |
340 |
341 | movie = new MovieCreator()
342 | .build(file);
343 |
344 | } catch (FileNotFoundException e) {
345 | e.printStackTrace();
346 | } catch (IOException e) {
347 | e.printStackTrace();
348 | }
349 |
350 |
351 |
352 | List tracks = movie.getTracks();
353 | movie.setTracks(new LinkedList());
354 | // remove all tracks we will create new tracks from the old
355 |
356 | boolean timeCorrected = false;
357 |
358 | // Here we try to find a track that has sync samples. Since we can only
359 | // start decoding
360 | // at such a sample we SHOULD make sure that the start of the new
361 | // fragment is exactly
362 | // such a frame
363 | for (Track track : tracks) {
364 | if (track.getSyncSamples() != null
365 | && track.getSyncSamples().length > 0) {
366 | if (timeCorrected) {
367 | // This exception here could be a false positive in case we
368 | // have multiple tracks
369 | // with sync samples at exactly the same positions. E.g. a
370 | // single movie containing
371 | // multiple qualities of the same video (Microsoft Smooth
372 | // Streaming file)
373 |
374 | throw new RuntimeException(
375 | "The startTime has already been corrected by another track with SyncSample. Not Supported.");
376 |
377 | }
378 | }
379 | }
380 |
381 | for (Track track : tracks) {
382 | long currentSample = 0;
383 | double currentTime = 0;
384 | long startSample = -1;
385 | long endSample = -1;
386 |
387 | for (int i = 0; i < track.getSampleDurations().length; i++) {
388 | if (currentTime <= startTime) {
389 |
390 | // current sample is still before the new starttime
391 | startSample = currentSample;
392 | }
393 | if (currentTime <= endTime) {
394 | // current sample is after the new start time and still before the new endtime
395 | endSample = currentSample;
396 | } else {
397 | // current sample is after the end of the cropped video
398 | break;
399 | }
400 | currentTime += (double) track.getSampleDurations()[i] / (double) track.getTrackMetaData().getTimescale();
401 | currentSample++;
402 | }
403 |
404 | movie.addTrack(new CroppedTrack(track, startSample, endSample));
405 |
406 | Container out = new DefaultMp4Builder().build(movie);
407 | //MovieHeaderBox mvhd = Path.getPath(out, "moov/mvhd");
408 | //mvhd.setMatrix(Matrix.ROTATE_180);
409 |
410 | File dst = new File(finalPath);
411 |
412 | try {
413 | fos = new FileOutputStream(dst);
414 |
415 | fc = fos.getChannel();
416 |
417 | try {
418 | out.writeContainer(fc);
419 |
420 | } catch (IOException e) {
421 | e.printStackTrace();
422 | }
423 |
424 | } catch (FileNotFoundException e) {
425 | e.printStackTrace();
426 | }
427 | }
428 | }
429 |
430 | @Override
431 | public void onBackPressed() {
432 | super.onBackPressed();
433 |
434 | deleteVideo();
435 |
436 | }
437 |
438 | private void deleteVideo() {
439 | File mp4 = new File(path);
440 | if (mp4.exists() && mp4.isFile()) {
441 | mp4.delete();
442 | }
443 | }
444 |
445 |
446 | private class PostTrimAsynkTask extends AsyncTask {
447 |
448 | private String mPath;
449 |
450 | public PostTrimAsynkTask(String path) {
451 | this.mPath = path;
452 | }
453 |
454 | @Override
455 | protected Boolean doInBackground(String... params) {
456 |
457 | try {
458 |
459 | finalPath = mPath.replace(".mp4", "_edited.mp4");
460 |
461 | trimVideo(mPath, finalPath, controller.getMinPosition()/1000L, controller.getMaxPosition()/1000L);
462 |
463 | return true;
464 |
465 | } catch (Exception e) {
466 | Log.e(TAG, e.toString());
467 | }
468 |
469 | return false;
470 | }
471 |
472 | @Override
473 | protected void onPostExecute(Boolean result){
474 |
475 | try {
476 | fc.close();
477 | fos.close();
478 | file.close();
479 | } catch (IOException e2) {
480 | e2.printStackTrace();
481 | }
482 |
483 | if (result) {
484 | goBackAndReopenVideo();
485 | Toast.makeText(VideoRecorderedPlayerActivity.this, "Video editado correctamente",
486 | Toast.LENGTH_LONG).show();
487 | } else {
488 | Toast.makeText(VideoRecorderedPlayerActivity.this, "Error al editar el vídeo",
489 | Toast.LENGTH_LONG).show();
490 | }
491 |
492 | mProgressDialog.dismiss();
493 |
494 | }
495 | }
496 |
497 |
498 |
499 | }
--------------------------------------------------------------------------------
/app/src/main/java/es/anthorlop/videotrimmer/custom/RangeSeekBar.java:
--------------------------------------------------------------------------------
1 | package es.anthorlop.videotrimmer.custom;
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.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Paint.Style;
10 | import android.graphics.RectF;
11 | import android.os.Bundle;
12 | import android.os.Parcelable;
13 | import android.view.MotionEvent;
14 | import android.view.ViewConfiguration;
15 | import android.widget.ImageView;
16 |
17 | import java.math.BigDecimal;
18 |
19 | import es.anthorlop.videotrimmer.R;
20 |
21 | /**
22 | * @author anthorlop
23 | *
24 | */
25 | public class RangeSeekBar extends ImageView {
26 | private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
27 | private final Bitmap thumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb);
28 | private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb);
29 | private final float thumbWidth = thumbImage.getWidth();
30 | private final float thumbHalfWidth = 0.5f * thumbWidth;
31 | private final float thumbHalfHeight = 0.5f * thumbImage.getHeight();
32 | private final float lineHeight = 0.3f * thumbHalfHeight;
33 | private final float padding = thumbHalfWidth;
34 | private final T absoluteMinValue, absoluteMaxValue;
35 | private final NumberType numberType;
36 | private final double absoluteMinValuePrim, absoluteMaxValuePrim;
37 | private double normalizedMinValue = 0d;
38 | private double normalizedMaxValue = 1d;
39 | private Thumb pressedThumb = null;
40 | private boolean notifyWhileDragging = false;
41 | private OnRangeSeekBarChangeListener listener;
42 |
43 | /**
44 | * An invalid pointer id.
45 | */
46 | public static final int INVALID_POINTER_ID = 255;
47 |
48 | // Localized constants from MotionEvent for compatibility
49 | // with API < 8 "Froyo".
50 | public static final int ACTION_POINTER_UP = 0x6, ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
51 |
52 | private float mDownMotionX;
53 | private int mActivePointerId = INVALID_POINTER_ID;
54 |
55 | /**
56 | * On touch, this offset plus the scaled value from the position of the touch will form the progress value. Usually 0.
57 | */
58 | float mTouchProgressOffset;
59 |
60 | private int mScaledTouchSlop;
61 | private boolean mIsDragging;
62 |
63 | /**
64 | * Creates a new RangeSeekBar.
65 | *
66 | * @param absoluteMinValue
67 | * The minimum value of the selectable range.
68 | * @param absoluteMaxValue
69 | * The maximum value of the selectable range.
70 | * @param context
71 | * @throws IllegalArgumentException
72 | * Will be thrown if min/max value type is not one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
73 | */
74 | public RangeSeekBar(T absoluteMinValue, T absoluteMaxValue, Context context) throws IllegalArgumentException {
75 | super(context);
76 | this.absoluteMinValue = absoluteMinValue;
77 | this.absoluteMaxValue = absoluteMaxValue;
78 | absoluteMinValuePrim = absoluteMinValue.doubleValue();
79 | absoluteMaxValuePrim = absoluteMaxValue.doubleValue();
80 | numberType = NumberType.fromNumber(absoluteMinValue);
81 |
82 | // make RangeSeekBar focusable. This solves focus handling issues in case EditText widgets are being used along with the RangeSeekBar within ScollViews.
83 | setFocusable(true);
84 | setFocusableInTouchMode(true);
85 | init();
86 | }
87 |
88 | private final void init() {
89 | mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
90 | }
91 |
92 | public boolean isNotifyWhileDragging() {
93 | return notifyWhileDragging;
94 | }
95 |
96 | /**
97 | * Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
98 | *
99 | * @param flag
100 | */
101 | public void setNotifyWhileDragging(boolean flag) {
102 | this.notifyWhileDragging = flag;
103 | }
104 |
105 | /**
106 | * Returns the absolute minimum value of the range that has been set at construction time.
107 | *
108 | * @return The absolute minimum value of the range.
109 | */
110 | public T getAbsoluteMinValue() {
111 | return absoluteMinValue;
112 | }
113 |
114 | /**
115 | * Returns the absolute maximum value of the range that has been set at construction time.
116 | *
117 | * @return The absolute maximum value of the range.
118 | */
119 | public T getAbsoluteMaxValue() {
120 | return absoluteMaxValue;
121 | }
122 |
123 | /**
124 | * Returns the currently selected min value.
125 | *
126 | * @return The currently selected min value.
127 | */
128 | public T getSelectedMinValue() {
129 | return normalizedToValue(normalizedMinValue);
130 | }
131 |
132 | /**
133 | * Sets the currently selected minimum value. The widget will be invalidated and redrawn.
134 | *
135 | * @param value
136 | * The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
137 | */
138 | public void setSelectedMinValue(T value) {
139 | // in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
140 | if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
141 | setNormalizedMinValue(0d);
142 | }
143 | else {
144 | setNormalizedMinValue(valueToNormalized(value));
145 | }
146 | }
147 |
148 | /**
149 | * Returns the currently selected max value.
150 | *
151 | * @return The currently selected max value.
152 | */
153 | public T getSelectedMaxValue() {
154 | return normalizedToValue(normalizedMaxValue);
155 | }
156 |
157 | /**
158 | * Sets the currently selected maximum value. The widget will be invalidated and redrawn.
159 | *
160 | * @param value
161 | * The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
162 | */
163 | public void setSelectedMaxValue(T value) {
164 | // in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
165 | if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
166 | setNormalizedMaxValue(1d);
167 | }
168 | else {
169 | setNormalizedMaxValue(valueToNormalized(value));
170 | }
171 | }
172 |
173 | /**
174 | * Registers given listener callback to notify about changed selected values.
175 | *
176 | * @param listener
177 | * The listener to notify about changed selected values.
178 | */
179 | public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener listener) {
180 | this.listener = listener;
181 | }
182 |
183 | /**
184 | * Handles thumb selection and movement. Notifies listener callback on certain events.
185 | */
186 | @Override
187 | public boolean onTouchEvent(MotionEvent event) {
188 |
189 | if (!isEnabled())
190 | return false;
191 |
192 | int pointerIndex;
193 |
194 | final int action = event.getAction();
195 | switch (action & MotionEvent.ACTION_MASK) {
196 |
197 | case MotionEvent.ACTION_DOWN:
198 | // Remember where the motion event started
199 | mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
200 | pointerIndex = event.findPointerIndex(mActivePointerId);
201 | mDownMotionX = event.getX(pointerIndex);
202 |
203 | pressedThumb = evalPressedThumb(mDownMotionX);
204 |
205 | // Only handle thumb presses.
206 | if (pressedThumb == null)
207 | return super.onTouchEvent(event);
208 |
209 | setPressed(true);
210 | invalidate();
211 | onStartTrackingTouch();
212 | trackTouchEvent(event);
213 | attemptClaimDrag();
214 |
215 | break;
216 | case MotionEvent.ACTION_MOVE:
217 | if (pressedThumb != null) {
218 |
219 | if (mIsDragging) {
220 | trackTouchEvent(event);
221 | }
222 | else {
223 | // Scroll to follow the motion event
224 | pointerIndex = event.findPointerIndex(mActivePointerId);
225 | final float x = event.getX(pointerIndex);
226 |
227 | if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
228 | setPressed(true);
229 | invalidate();
230 | onStartTrackingTouch();
231 | trackTouchEvent(event);
232 | attemptClaimDrag();
233 | }
234 | }
235 |
236 | if (notifyWhileDragging && listener != null) {
237 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
238 | }
239 | }
240 | break;
241 | case MotionEvent.ACTION_UP:
242 | if (mIsDragging) {
243 | trackTouchEvent(event);
244 | onStopTrackingTouch();
245 | setPressed(false);
246 | }
247 | else {
248 | // Touch up when we never crossed the touch slop threshold
249 | // should be interpreted as a tap-seek to that location.
250 | onStartTrackingTouch();
251 | trackTouchEvent(event);
252 | onStopTrackingTouch();
253 | }
254 |
255 | pressedThumb = null;
256 | invalidate();
257 | if (listener != null) {
258 | listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
259 | }
260 | break;
261 | case MotionEvent.ACTION_POINTER_DOWN: {
262 | final int index = event.getPointerCount() - 1;
263 | // final int index = ev.getActionIndex();
264 | mDownMotionX = event.getX(index);
265 | mActivePointerId = event.getPointerId(index);
266 | invalidate();
267 | break;
268 | }
269 | case MotionEvent.ACTION_POINTER_UP:
270 | onSecondaryPointerUp(event);
271 | invalidate();
272 | break;
273 | case MotionEvent.ACTION_CANCEL:
274 | if (mIsDragging) {
275 | onStopTrackingTouch();
276 | setPressed(false);
277 | }
278 | invalidate(); // see above explanation
279 | break;
280 | }
281 | return true;
282 | }
283 |
284 | private final void onSecondaryPointerUp(MotionEvent ev) {
285 | final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
286 |
287 | final int pointerId = ev.getPointerId(pointerIndex);
288 | if (pointerId == mActivePointerId) {
289 | // This was our active pointer going up. Choose
290 | // a new active pointer and adjust accordingly.
291 | // TODO: Make this decision more intelligent.
292 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
293 | mDownMotionX = ev.getX(newPointerIndex);
294 | mActivePointerId = ev.getPointerId(newPointerIndex);
295 | }
296 | }
297 |
298 | private final void trackTouchEvent(MotionEvent event) {
299 | final int pointerIndex = event.findPointerIndex(mActivePointerId);
300 | final float x = event.getX(pointerIndex);
301 |
302 | if (Thumb.MIN.equals(pressedThumb)) {
303 | setNormalizedMinValue(screenToNormalized(x));
304 | }
305 | else if (Thumb.MAX.equals(pressedThumb)) {
306 | setNormalizedMaxValue(screenToNormalized(x));
307 | }
308 | }
309 |
310 | /**
311 | * Tries to claim the user's drag motion, and requests disallowing any ancestors from stealing events in the drag.
312 | */
313 | private void attemptClaimDrag() {
314 | if (getParent() != null) {
315 | getParent().requestDisallowInterceptTouchEvent(true);
316 | }
317 | }
318 |
319 | /**
320 | * This is called when the user has started touching this widget.
321 | */
322 | void onStartTrackingTouch() {
323 | mIsDragging = true;
324 | }
325 |
326 | /**
327 | * This is called when the user either releases his touch or the touch is canceled.
328 | */
329 | void onStopTrackingTouch() {
330 | mIsDragging = false;
331 | }
332 |
333 | /**
334 | * Ensures correct size of the widget.
335 | */
336 | @Override
337 | protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
338 | int width = 200;
339 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
340 | width = MeasureSpec.getSize(widthMeasureSpec);
341 | }
342 | int height = thumbImage.getHeight();
343 | if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
344 | height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
345 | }
346 | setMeasuredDimension(width, height);
347 | }
348 |
349 | /**
350 | * Draws the widget on the given canvas.
351 | */
352 | @Override
353 | protected synchronized void onDraw(Canvas canvas) {
354 | super.onDraw(canvas);
355 |
356 | // draw seek bar background line
357 | final RectF rect = new RectF(padding, 0.5f * (getHeight() - lineHeight), getWidth() - padding, 0.5f * (getHeight() + lineHeight));
358 | paint.setStyle(Style.FILL);
359 | paint.setColor(Color.GRAY);
360 | paint.setAntiAlias(true);
361 | canvas.drawRect(rect, paint);
362 |
363 | // draw seek bar active range line
364 | rect.left = normalizedToScreen(normalizedMinValue);
365 | rect.right = normalizedToScreen(normalizedMaxValue);
366 |
367 | // orange color
368 | paint.setColor(getResources().getColor(android.R.color.holo_blue_light));
369 | canvas.drawRect(rect, paint);
370 |
371 | // draw minimum thumb
372 | drawThumb(normalizedToScreen(normalizedMinValue), Thumb.MIN.equals(pressedThumb), canvas);
373 |
374 | // draw maximum thumb
375 | drawThumb(normalizedToScreen(normalizedMaxValue), Thumb.MAX.equals(pressedThumb), canvas);
376 | }
377 |
378 | /**
379 | * Overridden to save instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {@link #setId(int)} method. Other members of this class than the normalized min and max values don't need to be saved.
380 | */
381 | @Override
382 | protected Parcelable onSaveInstanceState() {
383 | final Bundle bundle = new Bundle();
384 | bundle.putParcelable("SUPER", super.onSaveInstanceState());
385 | bundle.putDouble("MIN", normalizedMinValue);
386 | bundle.putDouble("MAX", normalizedMaxValue);
387 | return bundle;
388 | }
389 |
390 | /**
391 | * Overridden to restore instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {@link #setId(int)} method.
392 | */
393 | @Override
394 | protected void onRestoreInstanceState(Parcelable parcel) {
395 | final Bundle bundle = (Bundle) parcel;
396 | super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
397 | normalizedMinValue = bundle.getDouble("MIN");
398 | normalizedMaxValue = bundle.getDouble("MAX");
399 | }
400 |
401 | /**
402 | * Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
403 | *
404 | * @param screenCoord
405 | * The x-coordinate in screen space where to draw the image.
406 | * @param pressed
407 | * Is the thumb currently in "pressed" state?
408 | * @param canvas
409 | * The canvas to draw upon.
410 | */
411 | private void drawThumb(float screenCoord, boolean pressed, Canvas canvas) {
412 | canvas.drawBitmap(pressed ? thumbPressedImage : thumbImage, screenCoord - thumbHalfWidth, (float) ((0.5f * getHeight()) - thumbHalfHeight), paint);
413 | }
414 |
415 | /**
416 | * Decides which (if any) thumb is touched by the given x-coordinate.
417 | *
418 | * @param touchX
419 | * The x-coordinate of a touch event in screen space.
420 | * @return The pressed thumb or null if none has been touched.
421 | */
422 | private Thumb evalPressedThumb(float touchX) {
423 | Thumb result = null;
424 | boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
425 | boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
426 | if (minThumbPressed && maxThumbPressed) {
427 | // if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
428 | result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
429 | }
430 | else if (minThumbPressed) {
431 | result = Thumb.MIN;
432 | }
433 | else if (maxThumbPressed) {
434 | result = Thumb.MAX;
435 | }
436 | return result;
437 | }
438 |
439 | /**
440 | * Decides if given x-coordinate in screen space needs to be interpreted as "within" the normalized thumb x-coordinate.
441 | *
442 | * @param touchX
443 | * The x-coordinate in screen space to check.
444 | * @param normalizedThumbValue
445 | * The normalized x-coordinate of the thumb to check.
446 | * @return true if x-coordinate is in thumb range, false otherwise.
447 | */
448 | private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
449 | return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth;
450 | }
451 |
452 | /**
453 | * Sets normalized min value to value so that 0 <= value <= normalized max value <= 1. The View will get invalidated when calling this method.
454 | *
455 | * @param value
456 | * The new normalized min value to set.
457 | */
458 | public void setNormalizedMinValue(double value) {
459 | normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
460 | invalidate();
461 | }
462 |
463 | /**
464 | * Sets normalized max value to value so that 0 <= normalized min value <= value <= 1. The View will get invalidated when calling this method.
465 | *
466 | * @param value
467 | * The new normalized max value to set.
468 | */
469 | public void setNormalizedMaxValue(double value) {
470 | normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
471 | invalidate();
472 | }
473 |
474 | /**
475 | * Converts a normalized value to a Number object in the value space between absolute minimum and maximum.
476 | *
477 | * @param normalized
478 | * @return
479 | */
480 | @SuppressWarnings("unchecked")
481 | private T normalizedToValue(double normalized) {
482 | return (T) numberType.toNumber(absoluteMinValuePrim + normalized * (absoluteMaxValuePrim - absoluteMinValuePrim));
483 | }
484 |
485 | /**
486 | * Converts the given Number value to a normalized double.
487 | *
488 | * @param value
489 | * The Number value to normalize.
490 | * @return The normalized double.
491 | */
492 | private double valueToNormalized(T value) {
493 | if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
494 | // prevent division by zero, simply return 0.
495 | return 0d;
496 | }
497 | return (value.doubleValue() - absoluteMinValuePrim) / (absoluteMaxValuePrim - absoluteMinValuePrim);
498 | }
499 |
500 | /**
501 | * Converts a normalized value into screen space.
502 | *
503 | * @param normalizedCoord
504 | * The normalized value to convert.
505 | * @return The converted value in screen space.
506 | */
507 | private float normalizedToScreen(double normalizedCoord) {
508 | return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));
509 | }
510 |
511 | /**
512 | * Converts screen space x-coordinates into normalized values.
513 | *
514 | * @param screenCoord
515 | * The x-coordinate in screen space to convert.
516 | * @return The normalized value.
517 | */
518 | private double screenToNormalized(float screenCoord) {
519 | int width = getWidth();
520 | if (width <= 2 * padding) {
521 | // prevent division by zero, simply return 0.
522 | return 0d;
523 | }
524 | else {
525 | double result = (screenCoord - padding) / (width - 2 * padding);
526 | return Math.min(1d, Math.max(0d, result));
527 | }
528 | }
529 |
530 | /**
531 | * Callback listener interface to notify about changed range values.
532 | *
533 | * @param
534 | * The Number type the RangeSeekBar has been declared with.
535 | */
536 | public interface OnRangeSeekBarChangeListener {
537 | public void onRangeSeekBarValuesChanged(RangeSeekBar> bar, T minValue, T maxValue);
538 | }
539 |
540 | /**
541 | * Thumb constants (min and max).
542 | */
543 | private static enum Thumb {
544 | MIN, MAX
545 | };
546 |
547 | /**
548 | * Utility enumaration used to convert between Numbers and doubles.
549 | *
550 | *
551 | */
552 | private static enum NumberType {
553 | LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;
554 |
555 | public static NumberType fromNumber(E value) throws IllegalArgumentException {
556 | if (value instanceof Long) {
557 | return LONG;
558 | }
559 | if (value instanceof Double) {
560 | return DOUBLE;
561 | }
562 | if (value instanceof Integer) {
563 | return INTEGER;
564 | }
565 | if (value instanceof Float) {
566 | return FLOAT;
567 | }
568 | if (value instanceof Short) {
569 | return SHORT;
570 | }
571 | if (value instanceof Byte) {
572 | return BYTE;
573 | }
574 | if (value instanceof BigDecimal) {
575 | return BIG_DECIMAL;
576 | }
577 | throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
578 | }
579 |
580 | public Number toNumber(double value) {
581 | switch (this) {
582 | case LONG:
583 | return new Long((long) value);
584 | case DOUBLE:
585 | return value;
586 | case INTEGER:
587 | return new Integer((int) value);
588 | case FLOAT:
589 | return new Float(value);
590 | case SHORT:
591 | return new Short((short) value);
592 | case BYTE:
593 | return new Byte((byte) value);
594 | case BIG_DECIMAL:
595 | return new BigDecimal(value);
596 | }
597 | throw new InstantiationError("can't convert " + this + " to a Number object");
598 | }
599 | }
600 | }
601 |
--------------------------------------------------------------------------------
/app/src/main/java/es/anthorlop/videotrimmer/custom/VideoControllerView.java:
--------------------------------------------------------------------------------
1 | package es.anthorlop.videotrimmer.custom;
2 |
3 | import android.content.Context;
4 | import android.content.res.Configuration;
5 | import android.graphics.PorterDuff;
6 | import android.graphics.PorterDuffColorFilter;
7 | import android.os.Handler;
8 | import android.os.Message;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.Gravity;
12 | import android.view.KeyEvent;
13 | import android.view.LayoutInflater;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.widget.FrameLayout;
18 | import android.widget.ImageButton;
19 | import android.widget.ImageView;
20 | import android.widget.ProgressBar;
21 | import android.widget.SeekBar;
22 | import android.widget.SeekBar.OnSeekBarChangeListener;
23 | import android.widget.TextView;
24 |
25 | import java.lang.ref.WeakReference;
26 | import java.util.Formatter;
27 | import java.util.Locale;
28 |
29 | import es.anthorlop.videotrimmer.R;
30 | import es.anthorlop.videotrimmer.utils.Screens;
31 |
32 | public class VideoControllerView extends FrameLayout {
33 |
34 | private static final String TAG = "VideoControllerView";
35 |
36 | private MediaPlayerControl mPlayer;
37 | private Context mContext;
38 | private ViewGroup mAnchor;
39 | private View mRoot;
40 | private ProgressBar mProgress;
41 | private TextView mEndTime, mCurrentTime;
42 | private boolean mShowing;
43 | private boolean mDragging;
44 | private static final int sDefaultTimeout = 3000;
45 | private static final int FADE_OUT = 1;
46 | private static final int SHOW_PROGRESS = 2;
47 | private boolean mUseFastForward;
48 | private boolean mFromXml;
49 | private boolean mListenersSet;
50 | private OnClickListener mNextListener, mPrevListener;
51 | StringBuilder mFormatBuilder;
52 | Formatter mFormatter;
53 |
54 | private ImageButton mPauseButton;
55 |
56 | private ImageView mPauseImg;
57 |
58 | private ImageButton mFfwdButton;
59 | private ImageButton mRewButton;
60 | private ImageButton mNextButton;
61 | private ImageButton mPrevButton;
62 | private ImageButton mFullscreenButton;
63 | private Handler mHandler = new MessageHandler(this);
64 |
65 | private int minPosition = 0;
66 | private int maxPosition = 0;
67 |
68 | public VideoControllerView(Context context, AttributeSet attrs) {
69 | super(context, attrs);
70 | mRoot = null;
71 | mContext = context;
72 | mUseFastForward = true;
73 | mFromXml = true;
74 |
75 | Log.i(TAG, TAG);
76 | }
77 |
78 | public VideoControllerView(Context context, boolean useFastForward) {
79 | super(context);
80 | mContext = context;
81 | mUseFastForward = useFastForward;
82 |
83 | Log.i(TAG, TAG);
84 | }
85 |
86 | public VideoControllerView(Context context) {
87 | this(context, true);
88 |
89 | Log.i(TAG, TAG);
90 | }
91 |
92 | @Override
93 | public void onFinishInflate() {
94 | if (mRoot != null)
95 | initControllerView(mRoot);
96 | }
97 |
98 | public void setMediaPlayer(MediaPlayerControl player) {
99 | mPlayer = player;
100 | updatePausePlay();
101 | updateFullScreen();
102 | }
103 |
104 | public void setAnchorView(ViewGroup view, int ratioWidth, int ratioHeight) {
105 | mAnchor = view;
106 |
107 | int h = view.getHeight();
108 | int w = Screens.getScreenWidth(mContext);
109 |
110 | if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
111 | h = w*ratioHeight/ratioWidth;
112 | } else {
113 | w = h*ratioWidth/ratioHeight;
114 | }
115 |
116 | LayoutParams frameParams = new LayoutParams(
117 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
118 | );
119 |
120 | view.getLayoutParams().width = w;
121 | view.getLayoutParams().height = h;
122 |
123 | removeAllViews();
124 | View v = makeControllerView();
125 | addView(v, frameParams);
126 | }
127 |
128 | protected View makeControllerView() {
129 | LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
130 | mRoot = inflate.inflate(R.layout.media_controller, null);
131 |
132 | initControllerView(mRoot);
133 |
134 | return mRoot;
135 | }
136 |
137 | private void initControllerView(View v) {
138 | mPauseButton = (ImageButton) v.findViewById(R.id.pause);
139 | if (mPauseButton != null) {
140 | mPauseButton.requestFocus();
141 | mPauseButton.setOnClickListener(mPauseListener);
142 | }
143 |
144 | mPauseImg = (ImageView) v.findViewById(R.id.pausePlay);
145 | if (mPauseImg != null) {
146 | mPauseImg.requestFocus();
147 | mPauseImg.setOnClickListener(mPauseListener);
148 | }
149 |
150 | mFullscreenButton = (ImageButton) v.findViewById(R.id.fullscreen);
151 | if (mFullscreenButton != null) {
152 | mFullscreenButton.requestFocus();
153 | mFullscreenButton.setOnClickListener(mFullscreenListener);
154 | }
155 |
156 | mFfwdButton = (ImageButton) v.findViewById(R.id.ffwd);
157 | if (mFfwdButton != null) {
158 | mFfwdButton.setOnClickListener(mFfwdListener);
159 | if (!mFromXml) {
160 | mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
161 | }
162 | }
163 |
164 | mRewButton = (ImageButton) v.findViewById(R.id.rew);
165 | if (mRewButton != null) {
166 | mRewButton.setOnClickListener(mRewListener);
167 | if (!mFromXml) {
168 | mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
169 | }
170 | }
171 |
172 | // По дефолту вьюха будет спрятана, и показываться будет после события onTouch()
173 | mNextButton = (ImageButton) v.findViewById(R.id.next);
174 | if (mNextButton != null && !mFromXml && !mListenersSet) {
175 | mNextButton.setVisibility(View.GONE);
176 | }
177 | mPrevButton = (ImageButton) v.findViewById(R.id.prev);
178 | if (mPrevButton != null && !mFromXml && !mListenersSet) {
179 | mPrevButton.setVisibility(View.GONE);
180 | }
181 |
182 | mProgress = (SeekBar) v.findViewById(R.id.mediacontroller_progress);
183 | if (mProgress != null) {
184 | if (mProgress instanceof SeekBar) {
185 |
186 | mProgress.getProgressDrawable().setColorFilter(new PorterDuffColorFilter(getResources().getColor(android.R.color.holo_blue_light), PorterDuff.Mode.SRC_IN));
187 |
188 | SeekBar seeker = (SeekBar) mProgress;
189 | seeker.setOnSeekBarChangeListener(mSeekListener);
190 | }
191 | mProgress.setMax(1000);
192 | }
193 |
194 | mEndTime = (TextView) v.findViewById(R.id.time);
195 | mCurrentTime = (TextView) v.findViewById(R.id.time_current);
196 | mFormatBuilder = new StringBuilder();
197 | mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
198 |
199 | installPrevNextListeners();
200 | }
201 |
202 |
203 | public void show() {
204 | show(sDefaultTimeout);
205 | }
206 |
207 | public void onclick() {
208 | if (mPauseImg.getVisibility() == INVISIBLE) {
209 | show();
210 | } else {
211 | mPauseImg.setVisibility(INVISIBLE);
212 | }
213 | }
214 |
215 | private void disableUnsupportedButtons() {
216 | if (mPlayer == null) {
217 | return;
218 | }
219 |
220 | try {
221 | if (mPauseButton != null && !mPlayer.canPause()) {
222 | mPauseButton.setEnabled(false);
223 | }
224 | if (mRewButton != null && !mPlayer.canSeekBackward()) {
225 | mRewButton.setEnabled(false);
226 | }
227 | if (mFfwdButton != null && !mPlayer.canSeekForward()) {
228 | mFfwdButton.setEnabled(false);
229 | }
230 | } catch (IncompatibleClassChangeError ex) {
231 | //выводите в лог что хотите из ex
232 | }
233 | }
234 |
235 | public void show(int timeout) {
236 |
237 | mPauseImg.setVisibility(VISIBLE);
238 |
239 | if (!mShowing && mAnchor != null) {
240 |
241 | setProgress();
242 | if (mPauseButton != null) {
243 | mPauseButton.requestFocus();
244 | }
245 | disableUnsupportedButtons();
246 |
247 | LayoutParams tlp = new LayoutParams(
248 | ViewGroup.LayoutParams.MATCH_PARENT,
249 | ViewGroup.LayoutParams.WRAP_CONTENT,
250 | Gravity.BOTTOM
251 | );
252 |
253 | mAnchor.addView(this, tlp);
254 | mShowing = true;
255 | }
256 | updatePausePlay();
257 | updateFullScreen();
258 |
259 | mHandler.sendEmptyMessage(SHOW_PROGRESS);
260 |
261 | Message msg = mHandler.obtainMessage(FADE_OUT);
262 | if (timeout != 0) {
263 | mHandler.removeMessages(FADE_OUT);
264 | mHandler.sendMessageDelayed(msg, timeout);
265 | }
266 | }
267 |
268 | public boolean isShowing() {
269 | return mShowing;
270 | }
271 |
272 | public void hide() {
273 | if (mAnchor == null) {
274 | return;
275 | }
276 |
277 | try {
278 | mAnchor.removeView(this);
279 | mHandler.removeMessages(SHOW_PROGRESS);
280 | } catch (IllegalArgumentException ex) {
281 | Log.w("MediaController", "already removed");
282 | }
283 | mShowing = false;
284 | }
285 |
286 | private String stringForTime(int timeMs) {
287 | int totalSeconds = timeMs / 1000;
288 |
289 | int seconds = totalSeconds % 60;
290 | int minutes = (totalSeconds / 60) % 60;
291 | int hours = totalSeconds / 3600;
292 |
293 | mFormatBuilder.setLength(0);
294 | if (hours > 0) {
295 | return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
296 | } else {
297 | return mFormatter.format("%02d:%02d", minutes, seconds).toString();
298 | }
299 | }
300 |
301 | private int setProgress() {
302 | if (mPlayer == null || mDragging) {
303 | return 0;
304 | }
305 |
306 | int position = mPlayer.getCurrentPosition();
307 | int duration = mPlayer.getDuration();
308 | if (mProgress != null) {
309 | if (duration > 0) {
310 | // use long to avoid overflow
311 | long pos = 1000L * position / duration;
312 | mProgress.setProgress( (int) pos);
313 | }
314 | int percent = mPlayer.getBufferPercentage();
315 | mProgress.setSecondaryProgress(percent * 10);
316 | }
317 |
318 | if (mEndTime != null)
319 | mEndTime.setText(stringForTime(duration));
320 | if (mCurrentTime != null)
321 | mCurrentTime.setText(stringForTime(position));
322 |
323 | /*if (position > maxPosition) {
324 | mPlayer.pause();
325 | updatePausePlay();
326 | }
327 |
328 | if (position < minPosition) {
329 | mPlayer.seekTo(minPosition);
330 | }*/
331 |
332 | return position;
333 | }
334 |
335 | @Override
336 | public boolean onTouchEvent(MotionEvent event) {
337 | show(sDefaultTimeout);
338 | return true;
339 | }
340 |
341 | @Override
342 | public boolean onTrackballEvent(MotionEvent ev) {
343 | show(sDefaultTimeout);
344 | return false;
345 | }
346 |
347 | @Override
348 | public boolean dispatchKeyEvent(KeyEvent event) {
349 | if (mPlayer == null) {
350 | return true;
351 | }
352 |
353 | int keyCode = event.getKeyCode();
354 | final boolean uniqueDown = event.getRepeatCount() == 0
355 | && event.getAction() == KeyEvent.ACTION_DOWN;
356 | if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
357 | || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
358 | || keyCode == KeyEvent.KEYCODE_SPACE) {
359 | if (uniqueDown) {
360 | doPauseResume();
361 | show(sDefaultTimeout);
362 | if (mPauseButton != null) {
363 | mPauseButton.requestFocus();
364 | }
365 | }
366 | return true;
367 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
368 | if (uniqueDown && !mPlayer.isPlaying()) {
369 | mPlayer.start();
370 | updatePausePlay();
371 | show(sDefaultTimeout);
372 | }
373 | return true;
374 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
375 | || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
376 | if (uniqueDown && mPlayer.isPlaying()) {
377 | mPlayer.pause();
378 | updatePausePlay();
379 | show(sDefaultTimeout);
380 | }
381 | return true;
382 | } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
383 | || keyCode == KeyEvent.KEYCODE_VOLUME_UP
384 | || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
385 | return super.dispatchKeyEvent(event);
386 | } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
387 | if (uniqueDown) {
388 | hide();
389 | }
390 | return true;
391 | }
392 |
393 | show(sDefaultTimeout);
394 | return super.dispatchKeyEvent(event);
395 | }
396 |
397 | private OnClickListener mPauseListener = new OnClickListener() {
398 | public void onClick(View v) {
399 | doPauseResume();
400 | show(sDefaultTimeout);
401 | }
402 | };
403 |
404 | private OnClickListener mFullscreenListener = new OnClickListener() {
405 | public void onClick(View v) {
406 | doToggleFullscreen();
407 | show(sDefaultTimeout);
408 | }
409 | };
410 |
411 | public void updatePausePlay() {
412 | if (mRoot == null || mPauseButton == null || mPlayer == null) {
413 | return;
414 | }
415 |
416 | if (mPlayer.isPlaying()) {
417 | mPauseButton.setImageResource(R.drawable.ic_media_pause);
418 | mPauseImg.setImageResource(R.drawable.ic_player_big_pause);
419 | } else {
420 | mPauseButton.setImageResource(R.drawable.ic_media_play);
421 | mPauseImg.setImageResource(R.drawable.ic_player_big_play);
422 | }
423 | }
424 |
425 | public void updateFullScreen() {
426 | if (mRoot == null || mFullscreenButton == null || mPlayer == null) {
427 | return;
428 | }
429 |
430 | if (mPlayer.isFullScreen()) {
431 | mFullscreenButton.setImageResource(R.drawable.ic_media_fullscreen_shrink);
432 | }
433 | else {
434 | mFullscreenButton.setImageResource(R.drawable.ic_media_fullscreen_stretch);
435 | }
436 | }
437 |
438 | private void doPauseResume() {
439 | if (mPlayer == null) {
440 | return;
441 | }
442 |
443 | if (mPlayer.isPlaying()) {
444 | mPlayer.pause();
445 | } else {
446 | mPlayer.start();
447 |
448 | if (mPlayer.getCurrentPosition() > maxPosition) {
449 | mPlayer.seekTo(minPosition);
450 | }
451 |
452 | }
453 | updatePausePlay();
454 | }
455 |
456 | private void doToggleFullscreen() {
457 | if (mPlayer == null) {
458 | return;
459 | }
460 |
461 | mPlayer.toggleFullScreen();
462 | }
463 |
464 | private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
465 | public void onStartTrackingTouch(SeekBar bar) {
466 | show(3600000);
467 |
468 | mDragging = true;
469 | mHandler.removeMessages(SHOW_PROGRESS);
470 | }
471 |
472 | public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
473 | if (mPlayer == null) {
474 | return;
475 | }
476 |
477 | if (!fromuser) {
478 |
479 | long duration = mPlayer.getDuration();
480 | long newposition = (duration * progress) / 1000L;
481 |
482 | if (newposition > maxPosition) {
483 | if (mPlayer.isPlaying()) {
484 | mPauseButton.performClick();
485 | }
486 | }
487 |
488 | return;
489 | }
490 |
491 | long duration = mPlayer.getDuration();
492 | long newposition = (duration * progress) / 1000L;
493 |
494 | if (newposition > maxPosition) {
495 | newposition = maxPosition;
496 | if (mPlayer.isPlaying()) {
497 | mPauseButton.performClick();
498 | }
499 | }
500 |
501 | if (newposition < minPosition) {
502 | newposition = minPosition;
503 | }
504 |
505 | mPlayer.seekTo( (int) newposition);
506 |
507 | if (mCurrentTime != null)
508 | mCurrentTime.setText(stringForTime( (int) newposition));
509 |
510 | }
511 |
512 | public void onStopTrackingTouch(SeekBar bar) {
513 | mDragging = false;
514 | setProgress();
515 | updatePausePlay();
516 | show(sDefaultTimeout);
517 |
518 | mHandler.sendEmptyMessage(SHOW_PROGRESS);
519 | }
520 | };
521 |
522 | @Override
523 | public void setEnabled(boolean enabled) {
524 | if (mPauseButton != null) {
525 | mPauseButton.setEnabled(enabled);
526 | }
527 | if (mFfwdButton != null) {
528 | mFfwdButton.setEnabled(enabled);
529 | }
530 | if (mRewButton != null) {
531 | mRewButton.setEnabled(enabled);
532 | }
533 | if (mNextButton != null) {
534 | mNextButton.setEnabled(enabled && mNextListener != null);
535 | }
536 | if (mPrevButton != null) {
537 | mPrevButton.setEnabled(enabled && mPrevListener != null);
538 | }
539 | if (mProgress != null) {
540 | mProgress.setEnabled(enabled);
541 | }
542 | disableUnsupportedButtons();
543 | super.setEnabled(enabled);
544 | }
545 |
546 | private OnClickListener mRewListener = new OnClickListener() {
547 | public void onClick(View v) {
548 | if (mPlayer == null) {
549 | return;
550 | }
551 |
552 | int pos = mPlayer.getCurrentPosition();
553 | pos -= 5000; // милисекунд
554 | mPlayer.seekTo(pos);
555 | setProgress();
556 |
557 | show(sDefaultTimeout);
558 | }
559 | };
560 |
561 | private OnClickListener mFfwdListener = new OnClickListener() {
562 | public void onClick(View v) {
563 | if (mPlayer == null) {
564 | return;
565 | }
566 |
567 | int pos = mPlayer.getCurrentPosition();
568 | pos += 15000; // милисекунд
569 | mPlayer.seekTo(pos);
570 | setProgress();
571 |
572 | show(sDefaultTimeout);
573 | }
574 | };
575 |
576 | private void installPrevNextListeners() {
577 | if (mNextButton != null) {
578 | mNextButton.setOnClickListener(mNextListener);
579 | mNextButton.setEnabled(mNextListener != null);
580 | }
581 |
582 | if (mPrevButton != null) {
583 | mPrevButton.setOnClickListener(mPrevListener);
584 | mPrevButton.setEnabled(mPrevListener != null);
585 | }
586 | }
587 |
588 | public void setPrevNextListeners(OnClickListener next, OnClickListener prev) {
589 | mNextListener = next;
590 | mPrevListener = prev;
591 | mListenersSet = true;
592 |
593 | if (mRoot != null) {
594 | installPrevNextListeners();
595 |
596 | if (mNextButton != null && !mFromXml) {
597 | mNextButton.setVisibility(View.VISIBLE);
598 | }
599 | if (mPrevButton != null && !mFromXml) {
600 | mPrevButton.setVisibility(View.VISIBLE);
601 | }
602 | }
603 | }
604 |
605 | public interface MediaPlayerControl {
606 | void start();
607 | void pause();
608 | int getDuration();
609 | int getCurrentPosition();
610 | void seekTo(int pos);
611 | boolean isPlaying();
612 | int getBufferPercentage();
613 | boolean canPause();
614 | boolean canSeekBackward();
615 | boolean canSeekForward();
616 | boolean isFullScreen();
617 | void toggleFullScreen();
618 | }
619 |
620 | private static class MessageHandler extends Handler {
621 | private final WeakReference mView;
622 |
623 | MessageHandler(VideoControllerView view) {
624 | mView = new WeakReference(view);
625 | }
626 | @Override
627 | public void handleMessage(Message msg) {
628 | VideoControllerView view = mView.get();
629 | if (view == null || view.mPlayer == null) {
630 | return;
631 | }
632 |
633 | int pos;
634 | switch (msg.what) {
635 | case FADE_OUT:
636 | //view.hide();
637 | view.mPauseImg.setVisibility(INVISIBLE);
638 |
639 | break;
640 | case SHOW_PROGRESS:
641 | pos = view.setProgress();
642 | if (!view.mDragging && view.mShowing && view.mPlayer.isPlaying()) {
643 | msg = obtainMessage(SHOW_PROGRESS);
644 | sendMessageDelayed(msg, 1000 - (pos % 1000));
645 | }
646 | break;
647 | }
648 | }
649 | }
650 |
651 | public int getMinPosition() {
652 | return minPosition;
653 | }
654 |
655 | public void setMinPosition(int minPosition) {
656 | this.minPosition = minPosition;
657 | }
658 |
659 | public int getMaxPosition() {
660 | return maxPosition;
661 | }
662 |
663 | public void setMaxPosition(int maxPosition) {
664 | this.maxPosition = maxPosition;
665 | }
666 | }
--------------------------------------------------------------------------------
/app/src/main/java/es/anthorlop/videotrimmer/utils/Screens.java:
--------------------------------------------------------------------------------
1 | package es.anthorlop.videotrimmer.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import android.content.res.Resources;
7 | import android.graphics.Point;
8 | import android.util.DisplayMetrics;
9 | import android.view.Display;
10 | import android.view.WindowManager;
11 |
12 |
13 | public class Screens {
14 |
15 | @SuppressWarnings("deprecation")
16 | /**
17 | * M�todo que devuelve el alto de la pantalla
18 | *
19 | * @param context Context de la aplicaci�n
20 | *
21 | * @return int con la altura
22 | */
23 | public static int getScreenHeight(Context context) {
24 |
25 | WindowManager wm = (WindowManager) context
26 | .getSystemService(Context.WINDOW_SERVICE);
27 | Display display = wm.getDefaultDisplay();
28 | return display.getHeight();
29 |
30 | }
31 |
32 | @SuppressWarnings("deprecation")
33 | /**
34 | * M�todo que devuelve el ancho de la pantalla
35 | *
36 | * @param context Context de la aplicaci�n
37 | *
38 | * @return int con la ancho
39 | */
40 | public static int getScreenWidth(Context context) {
41 |
42 | WindowManager wm = (WindowManager) context
43 | .getSystemService(Context.WINDOW_SERVICE);
44 | Display display = wm.getDefaultDisplay();
45 | return display.getWidth();
46 |
47 | }
48 |
49 | /**
50 | * This method converts dp unit to equivalent pixels, depending on device
51 | * density.
52 | *
53 | * @param dp
54 | * A value in dp (density independent pixels) unit. Which we need
55 | * to convert into pixels
56 | * @param context
57 | * Context to get resources and device specific display metrics
58 | * @return A float value to represent px equivalent to dp depending on
59 | * device density
60 | */
61 | public static float convertDpToPixel(float dp, Context context) {
62 | Resources resources = context.getResources();
63 | DisplayMetrics metrics = resources.getDisplayMetrics();
64 | float px = dp * (metrics.densityDpi / 160f);
65 | return px;
66 | }
67 |
68 | /**
69 | * This method converts device specific pixels to density independent
70 | * pixels.
71 | *
72 | * @param px
73 | * A value in px (pixels) unit. Which we need to convert into db
74 | * @param context
75 | * Context to get resources and device specific display metrics
76 | * @return A float value to represent dp equivalent to px value
77 | */
78 | public static float convertPixelsToDp(float px, Context context) {
79 | Resources resources = context.getResources();
80 | DisplayMetrics metrics = resources.getDisplayMetrics();
81 | float dp = px / (metrics.densityDpi / 160f);
82 | return dp;
83 | }
84 |
85 | /**
86 | * Valida si el dispositivo es una tableta
87 | *
88 | * @param context Context de la aplicaci�n
89 | * @return true si es una tablet
90 | * false si es un m�vil
91 | */
92 | public static Boolean isTableta(Context context) {
93 | if ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE
94 | || ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE)) {
95 | return true;
96 | } else {
97 | return false;
98 | }
99 | }
100 |
101 | /**
102 | * Valida si el dispositivo es una tableta de tama�o large (is at least approximately 480x640 dp units)
103 | *
104 | * @param context Context de la aplicaci�n
105 | * @return true si es una tablet de tama�o Large
106 | * false si es no lo es
107 | */
108 | public static Boolean isTabletaLarge(Context context) {
109 |
110 | if ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE) {
111 | return true;
112 | } else {
113 | return false;
114 | }
115 | }
116 |
117 | /**
118 | * Valida si el dispositivo es una tableta de tama�o large (is at least approximately 720x960 dp units)
119 | *
120 | * @param context Context de la aplicaci�n
121 | * @return true si es una tablet de tama�o Large
122 | * false si es no lo es
123 | */
124 | public static Boolean isTabletXLarge(Context context) {
125 | if ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
126 | return true;
127 | } else {
128 | return false;
129 | }
130 | }
131 |
132 | /**
133 | * Valida si el dispositivo tiene un tama�o de pantalla standard (is at least approximately 320x470 dp units)
134 | *
135 | * @param context Context de la aplicaci�n
136 | * @return true si el tama�o de pantalla es standard
137 | * false si es no lo es
138 | */
139 |
140 | public static Boolean isNormal(Context context) {
141 | if ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_NORMAL) {
142 | return true;
143 | } else {
144 | return false;
145 | }
146 | }
147 |
148 | /**
149 | * Valida si el dispositivo es una tableta de tama�o peque�o (is at least approximately 320x426 dp units)
150 | *
151 | * @param context Context de la aplicaci�n
152 | * @return true si es m�vil peque�o
153 | * false si es no lo es
154 | */
155 | public static Boolean isSmall(Context context) {
156 | if ((context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL) {
157 | return true;
158 | } else {
159 | return false;
160 | }
161 | }
162 |
163 | /**
164 | * Valida la orientaci�n del dispositivo
165 | *
166 | * @param ctx Context de la aplicaci�n
167 | *
168 | * @return true si la pantalla est� en vertical
169 | * false si est� en horizontal
170 | */
171 | public static boolean isVertical(Context ctx) {
172 | if (ctx.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
173 | return true;
174 | else
175 | return false;
176 | }
177 |
178 | /**
179 | * Tama�o de la pantalla
180 | *
181 | * @param display display
182 | *
183 | * @return Point tama�o de la pantalla
184 | */
185 | public static Point getDisplaySize(final Display display) {
186 | final Point point = new Point();
187 | try {
188 | display.getSize(point);
189 | } catch (NoSuchMethodError ignore) { // Older device
190 | point.x = display.getWidth();
191 | point.y = display.getHeight();
192 | }
193 | return point;
194 | }
195 |
196 | // navigation bar (at the bottom of the screen on a Nexus device)
197 | public static int getNavigationBarHeight(Context ctx) {
198 | Resources resources = ctx.getResources();
199 | int resourceId = resources.getIdentifier("navigation_bar_height",
200 | "dimen", "android");
201 | if (resourceId > 0) {
202 | return resources.getDimensionPixelSize(resourceId);
203 | }
204 | return 0;
205 | }
206 |
207 | // navigation bar (at the bottom of the screen on a Nexus device)
208 | public static int getStatusBarHeight(Context ctx) {
209 | Resources resources = ctx.getResources();
210 | int resourceId = resources.getIdentifier("status_bar_height", "dimen",
211 | "android");
212 | if (resourceId > 0) {
213 | return resources.getDimensionPixelSize(resourceId);
214 | }
215 | return 0;
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_camara_menu_switch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_camara_menu_switch.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_flash_off_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_flash_off_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_flash_on_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_flash_on_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_media_fullscreen_shrink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_media_fullscreen_shrink.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_media_fullscreen_stretch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_media_fullscreen_stretch.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_media_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_media_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_media_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_media_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_player_big_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_player_big_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_player_big_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/ic_player_big_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seek_thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/drawable/seek_thumb.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_video_player.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
25 |
26 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
60 |
61 |
72 |
73 |
74 |
75 |
80 |
81 |
87 |
88 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
104 |
112 |
118 |
119 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/media_controller.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
21 |
22 |
25 |
26 |
28 |
29 |
31 |
32 |
34 |
35 |
37 |
38 |
39 |
40 |
44 |
45 |
55 |
56 |
61 |
62 |
72 |
73 |
84 |
85 |
86 |
87 |
88 |
92 |
93 |
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | VideoTrimmer
3 |
4 | Select a video file
5 | Settings
6 |
7 | VideoPlayerActivity
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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:1.2.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
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.2.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 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/screen_landscape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/screen_landscape.png
--------------------------------------------------------------------------------
/screen_portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anthorlop/VideoTrimmer/c41e16ea31ff7df4d696c64be6b5b682ed650bcd/screen_portrait.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------